1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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 "gc/Marking.h"
11 #include "vm/JSContext.h"
12 #include "vm/MatchPairs.h"
13 #include "vm/RegExpShared.h"
14 #include "vm/Runtime.h"
15
16 namespace js {
17
18 class GlobalObject;
19 class RegExpStaticsObject;
20
21 class RegExpStatics {
22 /* The latest RegExp output, set after execution. */
23 VectorMatchPairs matches;
24 HeapPtr<JSLinearString*> matchesInput;
25
26 /*
27 * The previous RegExp input, used to resolve lazy state.
28 * A raw RegExpShared cannot be stored because it may be in
29 * a different compartment via evalcx().
30 */
31 HeapPtr<JSAtom*> lazySource;
32 JS::RegExpFlags lazyFlags;
33 size_t lazyIndex;
34
35 /* The latest RegExp input, set before execution. */
36 HeapPtr<JSString*> pendingInput;
37
38 /*
39 * If non-zero, |matchesInput| and the |lazy*| fields may be used
40 * to replay the last executed RegExp, and |matches| is invalid.
41 */
42 int32_t pendingLazyEvaluation;
43
44 public:
RegExpStatics()45 RegExpStatics() { clear(); }
46 static RegExpStaticsObject* create(JSContext* cx);
47
48 private:
49 bool executeLazy(JSContext* cx);
50
51 inline void checkInvariants();
52
53 /*
54 * Check whether a match for pair |pairNum| occurred. If so, allocate and
55 * store the match string in |*out|; otherwise place |undefined| in |*out|.
56 */
57 bool makeMatch(JSContext* cx, size_t pairNum, MutableHandleValue out);
58 bool createDependent(JSContext* cx, size_t start, size_t end,
59 MutableHandleValue out);
60
61 public:
62 /* Mutators. */
63 inline bool updateFromMatchPairs(JSContext* cx, JSLinearString* input,
64 VectorMatchPairs& newPairs);
65
66 inline void clear();
67
68 /* Corresponds to JSAPI functionality to set the pending RegExp input. */
reset(JSString * newInput)69 void reset(JSString* newInput) {
70 clear();
71 pendingInput = newInput;
72 checkInvariants();
73 }
74
75 inline void setPendingInput(JSString* newInput);
76
77 public:
trace(JSTracer * trc)78 void trace(JSTracer* trc) {
79 /*
80 * Changes to this function must also be reflected in
81 * RegExpStatics::AutoRooter::trace().
82 */
83 TraceNullableEdge(trc, &matchesInput, "res->matchesInput");
84 TraceNullableEdge(trc, &lazySource, "res->lazySource");
85 TraceNullableEdge(trc, &pendingInput, "res->pendingInput");
86 }
87
88 /* Value creators. */
89
90 bool createPendingInput(JSContext* cx, MutableHandleValue out);
91 bool createLastMatch(JSContext* cx, MutableHandleValue out);
92 bool createLastParen(JSContext* cx, MutableHandleValue out);
93 bool createParen(JSContext* cx, size_t pairNum, MutableHandleValue out);
94 bool createLeftContext(JSContext* cx, MutableHandleValue out);
95 bool createRightContext(JSContext* cx, MutableHandleValue out);
96
offsetOfPendingInput()97 static size_t offsetOfPendingInput() {
98 return offsetof(RegExpStatics, pendingInput);
99 }
100
offsetOfMatchesInput()101 static size_t offsetOfMatchesInput() {
102 return offsetof(RegExpStatics, matchesInput);
103 }
104
offsetOfLazySource()105 static size_t offsetOfLazySource() {
106 return offsetof(RegExpStatics, lazySource);
107 }
108
offsetOfLazyFlags()109 static size_t offsetOfLazyFlags() {
110 return offsetof(RegExpStatics, lazyFlags);
111 }
112
offsetOfLazyIndex()113 static size_t offsetOfLazyIndex() {
114 return offsetof(RegExpStatics, lazyIndex);
115 }
116
offsetOfPendingLazyEvaluation()117 static size_t offsetOfPendingLazyEvaluation() {
118 return offsetof(RegExpStatics, pendingLazyEvaluation);
119 }
120 };
121
createDependent(JSContext * cx,size_t start,size_t end,MutableHandleValue out)122 inline bool RegExpStatics::createDependent(JSContext* cx, size_t start,
123 size_t end, MutableHandleValue out) {
124 /* Private function: caller must perform lazy evaluation. */
125 MOZ_ASSERT(!pendingLazyEvaluation);
126
127 MOZ_ASSERT(start <= end);
128 MOZ_ASSERT(end <= matchesInput->length());
129 JSString* str = NewDependentString(cx, matchesInput, start, end - start);
130 if (!str) {
131 return false;
132 }
133 out.setString(str);
134 return true;
135 }
136
createPendingInput(JSContext * cx,MutableHandleValue out)137 inline bool RegExpStatics::createPendingInput(JSContext* cx,
138 MutableHandleValue out) {
139 /* Lazy evaluation need not be resolved to return the input. */
140 out.setString(pendingInput ? pendingInput.get()
141 : cx->runtime()->emptyString.ref());
142 return true;
143 }
144
makeMatch(JSContext * cx,size_t pairNum,MutableHandleValue out)145 inline bool RegExpStatics::makeMatch(JSContext* cx, size_t pairNum,
146 MutableHandleValue out) {
147 /* Private function: caller must perform lazy evaluation. */
148 MOZ_ASSERT(!pendingLazyEvaluation);
149
150 if (matches.empty() || pairNum >= matches.pairCount() ||
151 matches[pairNum].isUndefined()) {
152 out.setUndefined();
153 return true;
154 }
155
156 const MatchPair& pair = matches[pairNum];
157 return createDependent(cx, pair.start, pair.limit, out);
158 }
159
createLastMatch(JSContext * cx,MutableHandleValue out)160 inline bool RegExpStatics::createLastMatch(JSContext* cx,
161 MutableHandleValue out) {
162 if (!executeLazy(cx)) {
163 return false;
164 }
165 return makeMatch(cx, 0, out);
166 }
167
createLastParen(JSContext * cx,MutableHandleValue out)168 inline bool RegExpStatics::createLastParen(JSContext* cx,
169 MutableHandleValue out) {
170 if (!executeLazy(cx)) {
171 return false;
172 }
173
174 if (matches.empty() || matches.pairCount() == 1) {
175 out.setString(cx->runtime()->emptyString);
176 return true;
177 }
178 const MatchPair& pair = matches[matches.pairCount() - 1];
179 if (pair.start == -1) {
180 out.setString(cx->runtime()->emptyString);
181 return true;
182 }
183 MOZ_ASSERT(pair.start >= 0 && pair.limit >= 0);
184 MOZ_ASSERT(pair.limit >= pair.start);
185 return createDependent(cx, pair.start, pair.limit, out);
186 }
187
createParen(JSContext * cx,size_t pairNum,MutableHandleValue out)188 inline bool RegExpStatics::createParen(JSContext* cx, size_t pairNum,
189 MutableHandleValue out) {
190 MOZ_ASSERT(pairNum >= 1);
191 if (!executeLazy(cx)) {
192 return false;
193 }
194
195 if (matches.empty() || pairNum >= matches.pairCount()) {
196 out.setString(cx->runtime()->emptyString);
197 return true;
198 }
199 return makeMatch(cx, pairNum, out);
200 }
201
createLeftContext(JSContext * cx,MutableHandleValue out)202 inline bool RegExpStatics::createLeftContext(JSContext* cx,
203 MutableHandleValue out) {
204 if (!executeLazy(cx)) {
205 return false;
206 }
207
208 if (matches.empty()) {
209 out.setString(cx->runtime()->emptyString);
210 return true;
211 }
212 if (matches[0].start < 0) {
213 out.setUndefined();
214 return true;
215 }
216 return createDependent(cx, 0, matches[0].start, out);
217 }
218
createRightContext(JSContext * cx,MutableHandleValue out)219 inline bool RegExpStatics::createRightContext(JSContext* cx,
220 MutableHandleValue out) {
221 if (!executeLazy(cx)) {
222 return false;
223 }
224
225 if (matches.empty()) {
226 out.setString(cx->runtime()->emptyString);
227 return true;
228 }
229 if (matches[0].limit < 0) {
230 out.setUndefined();
231 return true;
232 }
233 return createDependent(cx, matches[0].limit, matchesInput->length(), out);
234 }
235
updateFromMatchPairs(JSContext * cx,JSLinearString * input,VectorMatchPairs & newPairs)236 inline bool RegExpStatics::updateFromMatchPairs(JSContext* cx,
237 JSLinearString* input,
238 VectorMatchPairs& newPairs) {
239 MOZ_ASSERT(input);
240
241 /* Unset all lazy state. */
242 pendingLazyEvaluation = false;
243 this->lazySource = nullptr;
244 this->lazyIndex = size_t(-1);
245
246 BarrieredSetPair<JSString, JSLinearString>(cx->zone(), pendingInput, input,
247 matchesInput, input);
248
249 if (!matches.initArrayFrom(newPairs)) {
250 ReportOutOfMemory(cx);
251 return false;
252 }
253
254 return true;
255 }
256
clear()257 inline void RegExpStatics::clear() {
258 matches.forgetArray();
259 matchesInput = nullptr;
260 lazySource = nullptr;
261 lazyFlags = JS::RegExpFlag::NoFlags;
262 lazyIndex = size_t(-1);
263 pendingInput = nullptr;
264 pendingLazyEvaluation = false;
265 }
266
setPendingInput(JSString * newInput)267 inline void RegExpStatics::setPendingInput(JSString* newInput) {
268 pendingInput = newInput;
269 }
270
checkInvariants()271 inline void RegExpStatics::checkInvariants() {
272 #ifdef DEBUG
273 if (pendingLazyEvaluation) {
274 MOZ_ASSERT(lazySource);
275 MOZ_ASSERT(matchesInput);
276 MOZ_ASSERT(lazyIndex != size_t(-1));
277 return;
278 }
279
280 if (matches.empty()) {
281 MOZ_ASSERT(!matchesInput);
282 return;
283 }
284
285 /* Pair count is non-zero, so there must be match pairs input. */
286 MOZ_ASSERT(matchesInput);
287 size_t mpiLen = matchesInput->length();
288
289 /* Both members of the first pair must be non-negative. */
290 MOZ_ASSERT(!matches[0].isUndefined());
291 MOZ_ASSERT(matches[0].limit >= 0);
292
293 /* Present pairs must be valid. */
294 for (size_t i = 0; i < matches.pairCount(); i++) {
295 if (matches[i].isUndefined()) {
296 continue;
297 }
298 const MatchPair& pair = matches[i];
299 MOZ_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start &&
300 pair.start >= 0);
301 }
302 #endif /* DEBUG */
303 }
304
305 } /* namespace js */
306
307 #endif /* vm_RegExpStatics_h */
308