Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef vm_RegExpStatics_h
8 : #define vm_RegExpStatics_h
9 :
10 : #include "jscntxt.h"
11 :
12 : #include "gc/Marking.h"
13 : #include "vm/MatchPairs.h"
14 : #include "vm/RegExpShared.h"
15 : #include "vm/Runtime.h"
16 :
17 : namespace js {
18 :
19 : class GlobalObject;
20 : class RegExpStaticsObject;
21 :
22 0 : class RegExpStatics
23 : {
24 : /* The latest RegExp output, set after execution. */
25 : VectorMatchPairs matches;
26 : HeapPtr<JSLinearString*> matchesInput;
27 :
28 : /*
29 : * The previous RegExp input, used to resolve lazy state.
30 : * A raw RegExpShared cannot be stored because it may be in
31 : * a different compartment via evalcx().
32 : */
33 : HeapPtr<JSAtom*> lazySource;
34 : RegExpFlag lazyFlags;
35 : size_t lazyIndex;
36 :
37 : /* The latest RegExp input, set before execution. */
38 : HeapPtr<JSString*> pendingInput;
39 :
40 : /*
41 : * If non-zero, |matchesInput| and the |lazy*| fields may be used
42 : * to replay the last executed RegExp, and |matches| is invalid.
43 : */
44 : int32_t pendingLazyEvaluation;
45 :
46 : public:
47 13 : RegExpStatics() { clear(); }
48 : static RegExpStaticsObject* create(JSContext* cx, Handle<GlobalObject*> parent);
49 :
50 : private:
51 : bool executeLazy(JSContext* cx);
52 :
53 : inline void checkInvariants();
54 :
55 : /*
56 : * Check whether a match for pair |pairNum| occurred. If so, allocate and
57 : * store the match string in |*out|; otherwise place |undefined| in |*out|.
58 : */
59 : bool makeMatch(JSContext* cx, size_t pairNum, MutableHandleValue out);
60 : bool createDependent(JSContext* cx, size_t start, size_t end, MutableHandleValue out);
61 :
62 : struct InitBuffer {};
63 : explicit RegExpStatics(InitBuffer) {}
64 :
65 : public:
66 : /* Mutators. */
67 : inline void updateLazily(JSContext* cx, JSLinearString* input,
68 : RegExpShared* shared, size_t lastIndex);
69 : inline bool updateFromMatchPairs(JSContext* cx, JSLinearString* input, MatchPairs& newPairs);
70 :
71 : inline void clear();
72 :
73 : /* Corresponds to JSAPI functionality to set the pending RegExp input. */
74 0 : void reset(JSContext* cx, JSString* newInput) {
75 0 : clear();
76 0 : pendingInput = newInput;
77 0 : checkInvariants();
78 0 : }
79 :
80 : inline void setPendingInput(JSString* newInput);
81 :
82 : public:
83 : /* Default match accessor. */
84 : const MatchPairs& getMatches() const {
85 : /* Safe: only used by String methods, which do not set lazy mode. */
86 : MOZ_ASSERT(!pendingLazyEvaluation);
87 : return matches;
88 : }
89 :
90 : JSString* getPendingInput() const { return pendingInput; }
91 :
92 0 : void trace(JSTracer* trc) {
93 : /*
94 : * Changes to this function must also be reflected in
95 : * RegExpStatics::AutoRooter::trace().
96 : */
97 0 : TraceNullableEdge(trc, &matchesInput, "res->matchesInput");
98 0 : TraceNullableEdge(trc, &lazySource, "res->lazySource");
99 0 : TraceNullableEdge(trc, &pendingInput, "res->pendingInput");
100 0 : }
101 :
102 : /* Value creators. */
103 :
104 : bool createPendingInput(JSContext* cx, MutableHandleValue out);
105 : bool createLastMatch(JSContext* cx, MutableHandleValue out);
106 : bool createLastParen(JSContext* cx, MutableHandleValue out);
107 : bool createParen(JSContext* cx, size_t pairNum, MutableHandleValue out);
108 : bool createLeftContext(JSContext* cx, MutableHandleValue out);
109 : bool createRightContext(JSContext* cx, MutableHandleValue out);
110 :
111 : /* Infallible substring creators. */
112 :
113 : void getParen(size_t pairNum, JSSubString* out) const;
114 : void getLastMatch(JSSubString* out) const;
115 : void getLastParen(JSSubString* out) const;
116 : void getLeftContext(JSSubString* out) const;
117 : void getRightContext(JSSubString* out) const;
118 :
119 0 : static size_t offsetOfPendingInput() {
120 0 : return offsetof(RegExpStatics, pendingInput);
121 : }
122 :
123 0 : static size_t offsetOfMatchesInput() {
124 0 : return offsetof(RegExpStatics, matchesInput);
125 : }
126 :
127 0 : static size_t offsetOfLazySource() {
128 0 : return offsetof(RegExpStatics, lazySource);
129 : }
130 :
131 0 : static size_t offsetOfLazyFlags() {
132 0 : return offsetof(RegExpStatics, lazyFlags);
133 : }
134 :
135 0 : static size_t offsetOfLazyIndex() {
136 0 : return offsetof(RegExpStatics, lazyIndex);
137 : }
138 :
139 0 : static size_t offsetOfPendingLazyEvaluation() {
140 0 : return offsetof(RegExpStatics, pendingLazyEvaluation);
141 : }
142 : };
143 :
144 : inline bool
145 6 : RegExpStatics::createDependent(JSContext* cx, size_t start, size_t end, MutableHandleValue out)
146 : {
147 : /* Private function: caller must perform lazy evaluation. */
148 6 : MOZ_ASSERT(!pendingLazyEvaluation);
149 :
150 6 : MOZ_ASSERT(start <= end);
151 6 : MOZ_ASSERT(end <= matchesInput->length());
152 6 : JSString* str = NewDependentString(cx, matchesInput, start, end - start);
153 6 : if (!str)
154 0 : return false;
155 6 : out.setString(str);
156 6 : return true;
157 : }
158 :
159 : inline bool
160 0 : RegExpStatics::createPendingInput(JSContext* cx, MutableHandleValue out)
161 : {
162 : /* Lazy evaluation need not be resolved to return the input. */
163 0 : out.setString(pendingInput ? pendingInput.get() : cx->runtime()->emptyString.ref());
164 0 : return true;
165 : }
166 :
167 : inline bool
168 0 : RegExpStatics::makeMatch(JSContext* cx, size_t pairNum, MutableHandleValue out)
169 : {
170 : /* Private function: caller must perform lazy evaluation. */
171 0 : MOZ_ASSERT(!pendingLazyEvaluation);
172 :
173 0 : if (matches.empty() || pairNum >= matches.pairCount() || matches[pairNum].isUndefined()) {
174 0 : out.setUndefined();
175 0 : return true;
176 : }
177 :
178 0 : const MatchPair& pair = matches[pairNum];
179 0 : return createDependent(cx, pair.start, pair.limit, out);
180 : }
181 :
182 : inline bool
183 0 : RegExpStatics::createLastMatch(JSContext* cx, MutableHandleValue out)
184 : {
185 0 : if (!executeLazy(cx))
186 0 : return false;
187 0 : return makeMatch(cx, 0, out);
188 : }
189 :
190 : inline bool
191 0 : RegExpStatics::createLastParen(JSContext* cx, MutableHandleValue out)
192 : {
193 0 : if (!executeLazy(cx))
194 0 : return false;
195 :
196 0 : if (matches.empty() || matches.pairCount() == 1) {
197 0 : out.setString(cx->runtime()->emptyString);
198 0 : return true;
199 : }
200 0 : const MatchPair& pair = matches[matches.pairCount() - 1];
201 0 : if (pair.start == -1) {
202 0 : out.setString(cx->runtime()->emptyString);
203 0 : return true;
204 : }
205 0 : MOZ_ASSERT(pair.start >= 0 && pair.limit >= 0);
206 0 : MOZ_ASSERT(pair.limit >= pair.start);
207 0 : return createDependent(cx, pair.start, pair.limit, out);
208 : }
209 :
210 : inline bool
211 0 : RegExpStatics::createParen(JSContext* cx, size_t pairNum, MutableHandleValue out)
212 : {
213 0 : MOZ_ASSERT(pairNum >= 1);
214 0 : if (!executeLazy(cx))
215 0 : return false;
216 :
217 0 : if (matches.empty() || pairNum >= matches.pairCount()) {
218 0 : out.setString(cx->runtime()->emptyString);
219 0 : return true;
220 : }
221 0 : return makeMatch(cx, pairNum, out);
222 : }
223 :
224 : inline bool
225 0 : RegExpStatics::createLeftContext(JSContext* cx, MutableHandleValue out)
226 : {
227 0 : if (!executeLazy(cx))
228 0 : return false;
229 :
230 0 : if (matches.empty()) {
231 0 : out.setString(cx->runtime()->emptyString);
232 0 : return true;
233 : }
234 0 : if (matches[0].start < 0) {
235 0 : out.setUndefined();
236 0 : return true;
237 : }
238 0 : return createDependent(cx, 0, matches[0].start, out);
239 : }
240 :
241 : inline bool
242 6 : RegExpStatics::createRightContext(JSContext* cx, MutableHandleValue out)
243 : {
244 6 : if (!executeLazy(cx))
245 0 : return false;
246 :
247 6 : if (matches.empty()) {
248 0 : out.setString(cx->runtime()->emptyString);
249 0 : return true;
250 : }
251 6 : if (matches[0].limit < 0) {
252 0 : out.setUndefined();
253 0 : return true;
254 : }
255 6 : return createDependent(cx, matches[0].limit, matchesInput->length(), out);
256 : }
257 :
258 : inline void
259 : RegExpStatics::getParen(size_t pairNum, JSSubString* out) const
260 : {
261 : MOZ_ASSERT(!pendingLazyEvaluation);
262 :
263 : MOZ_ASSERT(pairNum >= 1 && pairNum < matches.pairCount());
264 : const MatchPair& pair = matches[pairNum];
265 : if (pair.isUndefined()) {
266 : out->initEmpty(matchesInput);
267 : return;
268 : }
269 : out->init(matchesInput, pair.start, pair.length());
270 : }
271 :
272 : inline void
273 : RegExpStatics::getLastMatch(JSSubString* out) const
274 : {
275 : MOZ_ASSERT(!pendingLazyEvaluation);
276 :
277 : if (matches.empty()) {
278 : out->initEmpty(matchesInput);
279 : return;
280 : }
281 : MOZ_ASSERT(matchesInput);
282 : MOZ_ASSERT(matches[0].limit >= matches[0].start);
283 : out->init(matchesInput, matches[0].start, matches[0].length());
284 : }
285 :
286 : inline void
287 : RegExpStatics::getLastParen(JSSubString* out) const
288 : {
289 : MOZ_ASSERT(!pendingLazyEvaluation);
290 :
291 : /* Note: the first pair is the whole match. */
292 : if (matches.empty() || matches.pairCount() == 1) {
293 : out->initEmpty(matchesInput);
294 : return;
295 : }
296 : getParen(matches.parenCount(), out);
297 : }
298 :
299 : inline void
300 : RegExpStatics::getLeftContext(JSSubString* out) const
301 : {
302 : MOZ_ASSERT(!pendingLazyEvaluation);
303 :
304 : if (matches.empty()) {
305 : out->initEmpty(matchesInput);
306 : return;
307 : }
308 : out->init(matchesInput, 0, matches[0].start);
309 : }
310 :
311 : inline void
312 : RegExpStatics::getRightContext(JSSubString* out) const
313 : {
314 : MOZ_ASSERT(!pendingLazyEvaluation);
315 :
316 : if (matches.empty()) {
317 : out->initEmpty(matchesInput);
318 : return;
319 : }
320 : MOZ_ASSERT(matches[0].limit <= int(matchesInput->length()));
321 : size_t length = matchesInput->length() - matches[0].limit;
322 : out->init(matchesInput, matches[0].limit, length);
323 : }
324 :
325 : inline void
326 62 : RegExpStatics::updateLazily(JSContext* cx, JSLinearString* input,
327 : RegExpShared* shared, size_t lastIndex)
328 : {
329 62 : MOZ_ASSERT(input && shared);
330 :
331 62 : BarrieredSetPair<JSString, JSLinearString>(cx->zone(),
332 : pendingInput, input,
333 62 : matchesInput, input);
334 :
335 62 : lazySource = shared->source;
336 62 : lazyFlags = shared->flags;
337 62 : lazyIndex = lastIndex;
338 62 : pendingLazyEvaluation = 1;
339 62 : }
340 :
341 : inline bool
342 60 : RegExpStatics::updateFromMatchPairs(JSContext* cx, JSLinearString* input, MatchPairs& newPairs)
343 : {
344 60 : MOZ_ASSERT(input);
345 :
346 : /* Unset all lazy state. */
347 60 : pendingLazyEvaluation = false;
348 60 : this->lazySource = nullptr;
349 60 : this->lazyIndex = size_t(-1);
350 :
351 60 : BarrieredSetPair<JSString, JSLinearString>(cx->zone(),
352 : pendingInput, input,
353 60 : matchesInput, input);
354 :
355 60 : if (!matches.initArrayFrom(newPairs)) {
356 0 : ReportOutOfMemory(cx);
357 0 : return false;
358 : }
359 :
360 60 : return true;
361 : }
362 :
363 : inline void
364 13 : RegExpStatics::clear()
365 : {
366 13 : matches.forgetArray();
367 13 : matchesInput = nullptr;
368 13 : lazySource = nullptr;
369 13 : lazyFlags = RegExpFlag(0);
370 13 : lazyIndex = size_t(-1);
371 13 : pendingInput = nullptr;
372 13 : pendingLazyEvaluation = false;
373 13 : }
374 :
375 : inline void
376 0 : RegExpStatics::setPendingInput(JSString* newInput)
377 : {
378 0 : pendingInput = newInput;
379 0 : }
380 :
381 : inline void
382 0 : RegExpStatics::checkInvariants()
383 : {
384 : #ifdef DEBUG
385 0 : if (pendingLazyEvaluation) {
386 0 : MOZ_ASSERT(lazySource);
387 0 : MOZ_ASSERT(matchesInput);
388 0 : MOZ_ASSERT(lazyIndex != size_t(-1));
389 0 : return;
390 : }
391 :
392 0 : if (matches.empty()) {
393 0 : MOZ_ASSERT(!matchesInput);
394 0 : return;
395 : }
396 :
397 : /* Pair count is non-zero, so there must be match pairs input. */
398 0 : MOZ_ASSERT(matchesInput);
399 0 : size_t mpiLen = matchesInput->length();
400 :
401 : /* Both members of the first pair must be non-negative. */
402 0 : MOZ_ASSERT(!matches[0].isUndefined());
403 0 : MOZ_ASSERT(matches[0].limit >= 0);
404 :
405 : /* Present pairs must be valid. */
406 0 : for (size_t i = 0; i < matches.pairCount(); i++) {
407 0 : if (matches[i].isUndefined())
408 0 : continue;
409 0 : const MatchPair& pair = matches[i];
410 0 : MOZ_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && pair.start >= 0);
411 : }
412 : #endif /* DEBUG */
413 : }
414 :
415 : } /* namespace js */
416 :
417 : #endif /* vm_RegExpStatics_h */
|