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