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 #include "builtin/RegExp.h"
8 
9 #include "mozilla/TypeTraits.h"
10 
11 #include "jscntxt.h"
12 
13 #include "irregexp/RegExpParser.h"
14 #include "jit/InlinableNatives.h"
15 #include "vm/RegExpStatics.h"
16 #include "vm/StringBuffer.h"
17 
18 #include "jsobjinlines.h"
19 
20 #include "vm/NativeObject-inl.h"
21 
22 using namespace js;
23 
24 using mozilla::ArrayLength;
25 using mozilla::Maybe;
26 
27 bool
CreateRegExpMatchResult(JSContext * cx,HandleString input,const MatchPairs & matches,MutableHandleValue rval)28 js::CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches,
29                             MutableHandleValue rval)
30 {
31     MOZ_ASSERT(input);
32 
33     /*
34      * Create the (slow) result array for a match.
35      *
36      * Array contents:
37      *  0:              matched string
38      *  1..pairCount-1: paren matches
39      *  input:          input string
40      *  index:          start index for the match
41      */
42 
43     /* Get the templateObject that defines the shape and type of the output object */
44     JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
45     if (!templateObject)
46         return false;
47 
48     size_t numPairs = matches.length();
49     MOZ_ASSERT(numPairs > 0);
50 
51     RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject));
52     if (!arr)
53         return false;
54 
55     /* Store a Value for each pair. */
56     for (size_t i = 0; i < numPairs; i++) {
57         const MatchPair& pair = matches[i];
58 
59         if (pair.isUndefined()) {
60             MOZ_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
61             arr->setDenseInitializedLength(i + 1);
62             arr->initDenseElement(i, UndefinedValue());
63         } else {
64             JSLinearString* str = NewDependentString(cx, input, pair.start, pair.length());
65             if (!str)
66                 return false;
67             arr->setDenseInitializedLength(i + 1);
68             arr->initDenseElement(i, StringValue(str));
69         }
70     }
71 
72     /* Set the |index| property. (TemplateObject positions it in slot 0) */
73     arr->setSlot(0, Int32Value(matches[0].start));
74 
75     /* Set the |input| property. (TemplateObject positions it in slot 1) */
76     arr->setSlot(1, StringValue(input));
77 
78 #ifdef DEBUG
79     RootedValue test(cx);
80     RootedId id(cx, NameToId(cx->names().index));
81     if (!NativeGetProperty(cx, arr, id, &test))
82         return false;
83     MOZ_ASSERT(test == arr->getSlot(0));
84     id = NameToId(cx->names().input);
85     if (!NativeGetProperty(cx, arr, id, &test))
86         return false;
87     MOZ_ASSERT(test == arr->getSlot(1));
88 #endif
89 
90     rval.setObject(*arr);
91     return true;
92 }
93 
94 static RegExpRunStatus
ExecuteRegExpImpl(JSContext * cx,RegExpStatics * res,RegExpShared & re,HandleLinearString input,size_t searchIndex,MatchPairs * matches)95 ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, RegExpShared& re, HandleLinearString input,
96                   size_t searchIndex, MatchPairs* matches)
97 {
98     RegExpRunStatus status = re.execute(cx, input, searchIndex, matches);
99     if (status == RegExpRunStatus_Success && res) {
100         if (matches) {
101             if (!res->updateFromMatchPairs(cx, input, *matches))
102                 return RegExpRunStatus_Error;
103         } else {
104             res->updateLazily(cx, input, &re, searchIndex);
105         }
106     }
107     return status;
108 }
109 
110 /* Legacy ExecuteRegExp behavior is baked into the JSAPI. */
111 bool
ExecuteRegExpLegacy(JSContext * cx,RegExpStatics * res,RegExpObject & reobj,HandleLinearString input,size_t * lastIndex,bool test,MutableHandleValue rval)112 js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, RegExpObject& reobj,
113                         HandleLinearString input, size_t* lastIndex, bool test,
114                         MutableHandleValue rval)
115 {
116     RegExpGuard shared(cx);
117     if (!reobj.getShared(cx, &shared))
118         return false;
119 
120     ScopedMatchPairs matches(&cx->tempLifoAlloc());
121 
122     RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *shared, input, *lastIndex, &matches);
123     if (status == RegExpRunStatus_Error)
124         return false;
125 
126     if (status == RegExpRunStatus_Success_NotFound) {
127         /* ExecuteRegExp() previously returned an array or null. */
128         rval.setNull();
129         return true;
130     }
131 
132     *lastIndex = matches[0].limit;
133 
134     if (test) {
135         /* Forbid an array, as an optimization. */
136         rval.setBoolean(true);
137         return true;
138     }
139 
140     return CreateRegExpMatchResult(cx, input, matches, rval);
141 }
142 
143 /*
144  * ES6 21.2.3.2.2.  Because this function only ever returns |obj| in the spec,
145  * provided by the user, we omit it and just return the usual success/failure.
146  */
147 static bool
RegExpInitializeIgnoringLastIndex(JSContext * cx,Handle<RegExpObject * > obj,HandleValue patternValue,HandleValue flagsValue,RegExpStaticsUse staticsUse)148 RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle<RegExpObject*> obj,
149                                   HandleValue patternValue, HandleValue flagsValue,
150                                   RegExpStaticsUse staticsUse)
151 {
152     RootedAtom pattern(cx);
153     if (patternValue.isUndefined()) {
154         /* Step 1. */
155         pattern = cx->names().empty;
156     } else {
157         /* Steps 2-3. */
158         pattern = ToAtom<CanGC>(cx, patternValue);
159         if (!pattern)
160             return false;
161     }
162 
163     /* Step 4. */
164     RegExpFlag flags = RegExpFlag(0);
165     if (!flagsValue.isUndefined()) {
166         /* Steps 5-6. */
167         RootedString flagStr(cx, ToString<CanGC>(cx, flagsValue));
168         if (!flagStr)
169             return false;
170 
171         /* Step 7. */
172         if (!ParseRegExpFlags(cx, flagStr, &flags))
173             return false;
174     }
175 
176     /* Steps 8-10. */
177     CompileOptions options(cx);
178     frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
179     if (!irregexp::ParsePatternSyntax(dummyTokenStream, cx->tempLifoAlloc(), pattern))
180         return false;
181 
182     if (staticsUse == UseRegExpStatics) {
183         RegExpStatics* res = cx->global()->getRegExpStatics(cx);
184         if (!res)
185             return false;
186         flags = RegExpFlag(flags | res->getFlags());
187     }
188 
189     /* Steps 11-13. */
190     obj->initIgnoringLastIndex(pattern, flags);
191     return true;
192 }
193 
194 MOZ_ALWAYS_INLINE bool
IsRegExpObject(HandleValue v)195 IsRegExpObject(HandleValue v)
196 {
197     return v.isObject() && v.toObject().is<RegExpObject>();
198 }
199 
200 /* ES6 draft rc3 7.2.8. */
201 bool
IsRegExp(JSContext * cx,HandleValue value,bool * result)202 js::IsRegExp(JSContext* cx, HandleValue value, bool* result)
203 {
204     /* Step 1. */
205     if (!value.isObject()) {
206         *result = false;
207         return true;
208     }
209     RootedObject obj(cx, &value.toObject());
210 
211     /* Steps 2-3. */
212     RootedValue isRegExp(cx);
213     RootedId matchId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().match));
214     if (!GetProperty(cx, obj, obj, matchId, &isRegExp))
215         return false;
216 
217     /* Step 4. */
218     if (!isRegExp.isUndefined()) {
219         *result = ToBoolean(isRegExp);
220         return true;
221     }
222 
223     /* Steps 5-6. */
224     ESClassValue cls;
225     if (!GetClassOfValue(cx, value, &cls))
226         return false;
227 
228     *result = cls == ESClass_RegExp;
229     return true;
230 }
231 
232 /* ES6 B.2.5.1. */
233 MOZ_ALWAYS_INLINE bool
regexp_compile_impl(JSContext * cx,const CallArgs & args)234 regexp_compile_impl(JSContext* cx, const CallArgs& args)
235 {
236     MOZ_ASSERT(IsRegExpObject(args.thisv()));
237 
238     Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>());
239 
240     // Step 3.
241     RootedValue patternValue(cx, args.get(0));
242     ESClassValue cls;
243     if (!GetClassOfValue(cx, patternValue, &cls))
244         return false;
245     if (cls == ESClass_RegExp) {
246         // Step 3a.
247         if (args.hasDefined(1)) {
248             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
249             return false;
250         }
251 
252         // Beware!  |patternObj| might be a proxy into another compartment, so
253         // don't assume |patternObj.is<RegExpObject>()|.  For the same reason,
254         // don't reuse the RegExpShared below.
255         RootedObject patternObj(cx, &patternValue.toObject());
256 
257         RootedAtom sourceAtom(cx);
258         RegExpFlag flags;
259         {
260             // Step 3b.
261             RegExpGuard g(cx);
262             if (!RegExpToShared(cx, patternObj, &g))
263                 return false;
264 
265             sourceAtom = g->getSource();
266             flags = g->getFlags();
267         }
268 
269         // Step 5, minus lastIndex zeroing.
270         regexp->initIgnoringLastIndex(sourceAtom, flags);
271     } else {
272         // Step 4.
273         RootedValue P(cx, patternValue);
274         RootedValue F(cx, args.get(1));
275 
276         // Step 5, minus lastIndex zeroing.
277         if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F, UseRegExpStatics))
278             return false;
279     }
280 
281     if (regexp->lookupPure(cx->names().lastIndex)->writable()) {
282         regexp->zeroLastIndex(cx);
283     } else {
284         RootedValue zero(cx, Int32Value(0));
285         if (!SetProperty(cx, regexp, cx->names().lastIndex, zero))
286             return false;
287     }
288 
289     args.rval().setObject(*regexp);
290     return true;
291 }
292 
293 static bool
regexp_compile(JSContext * cx,unsigned argc,Value * vp)294 regexp_compile(JSContext* cx, unsigned argc, Value* vp)
295 {
296     CallArgs args = CallArgsFromVp(argc, vp);
297 
298     /* Steps 1-2. */
299     return CallNonGenericMethod<IsRegExpObject, regexp_compile_impl>(cx, args);
300 }
301 
302 /* ES6 21.2.3.1. */
303 bool
regexp_construct(JSContext * cx,unsigned argc,Value * vp)304 js::regexp_construct(JSContext* cx, unsigned argc, Value* vp)
305 {
306     CallArgs args = CallArgsFromVp(argc, vp);
307 
308     // Steps 1-2.
309     bool patternIsRegExp;
310     if (!IsRegExp(cx, args.get(0), &patternIsRegExp))
311         return false;
312 
313 
314     // We can delay step 3 and step 4a until later, during
315     // GetPrototypeFromCallableConstructor calls. Accessing the new.target
316     // and the callee from the stack is unobservable.
317     if (!args.isConstructing()) {
318         // Step 4b.
319         if (patternIsRegExp && !args.hasDefined(1)) {
320             RootedObject patternObj(cx, &args[0].toObject());
321 
322             // Steps 4b.i-ii.
323             RootedValue patternConstructor(cx);
324             if (!GetProperty(cx, patternObj, patternObj, cx->names().constructor, &patternConstructor))
325                 return false;
326 
327             // Step 4b.iii.
328             if (patternConstructor.isObject() && patternConstructor.toObject() == args.callee()) {
329                 args.rval().set(args[0]);
330                 return true;
331             }
332         }
333     }
334 
335     RootedValue patternValue(cx, args.get(0));
336 
337     // Step 5.
338     ESClassValue cls;
339     if (!GetClassOfValue(cx, patternValue, &cls))
340         return false;
341     if (cls == ESClass_RegExp) {
342         // Beware!  |patternObj| might be a proxy into another compartment, so
343         // don't assume |patternObj.is<RegExpObject>()|.  For the same reason,
344         // don't reuse the RegExpShared below.
345         RootedObject patternObj(cx, &patternValue.toObject());
346 
347         // Step 5
348         RootedAtom sourceAtom(cx);
349         RegExpFlag flags;
350         {
351             // Step 5.a.
352             RegExpGuard g(cx);
353             if (!RegExpToShared(cx, patternObj, &g))
354                 return false;
355             sourceAtom = g->getSource();
356 
357             if (!args.hasDefined(1)) {
358                 // Step 5b.
359                 flags = g->getFlags();
360             }
361         }
362 
363         // Steps 8-9.
364         RootedObject proto(cx);
365         if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
366             return false;
367 
368         Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, proto));
369         if (!regexp)
370             return false;
371 
372         // Step 10.
373         if (args.hasDefined(1)) {
374             // Step 5c / 21.2.3.2.2 RegExpInitialize step 5.
375             flags = RegExpFlag(0);
376             RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
377             if (!flagStr)
378                 return false;
379             if (!ParseRegExpFlags(cx, flagStr, &flags))
380                 return false;
381         }
382 
383         regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
384 
385         args.rval().setObject(*regexp);
386         return true;
387     }
388 
389     RootedValue P(cx);
390     RootedValue F(cx);
391 
392     // Step 6.
393     if (patternIsRegExp) {
394         RootedObject patternObj(cx, &patternValue.toObject());
395 
396         // Steps 6a-b.
397         if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P))
398             return false;
399 
400         // Steps 6c-d.
401         F = args.get(1);
402         if (F.isUndefined()) {
403             if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F))
404                 return false;
405         }
406     } else {
407         // Steps 7a-b.
408         P = patternValue;
409         F = args.get(1);
410     }
411 
412     // Steps 8-9.
413     RootedObject proto(cx);
414     if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
415         return false;
416 
417     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, proto));
418     if (!regexp)
419         return false;
420 
421     // Step 10.
422     if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F, UseRegExpStatics))
423         return false;
424     regexp->zeroLastIndex(cx);
425 
426     args.rval().setObject(*regexp);
427     return true;
428 }
429 
430 bool
regexp_construct_no_statics(JSContext * cx,unsigned argc,Value * vp)431 js::regexp_construct_no_statics(JSContext* cx, unsigned argc, Value* vp)
432 {
433     CallArgs args = CallArgsFromVp(argc, vp);
434 
435     MOZ_ASSERT(args.length() == 1 || args.length() == 2);
436     MOZ_ASSERT(args[0].isString());
437     MOZ_ASSERT_IF(args.length() == 2, args[1].isString());
438     MOZ_ASSERT(!args.isConstructing());
439 
440     /* Steps 1-6 are not required since pattern is always string. */
441 
442     /* Steps 7-10. */
443     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
444     if (!regexp)
445         return false;
446 
447     if (!RegExpInitializeIgnoringLastIndex(cx, regexp, args[0], args.get(1), DontUseRegExpStatics))
448         return false;
449     regexp->zeroLastIndex(cx);
450 
451     args.rval().setObject(*regexp);
452     return true;
453 }
454 
455 /* ES6 draft rev32 21.2.5.4. */
456 MOZ_ALWAYS_INLINE bool
regexp_global_impl(JSContext * cx,const CallArgs & args)457 regexp_global_impl(JSContext* cx, const CallArgs& args)
458 {
459     MOZ_ASSERT(IsRegExpObject(args.thisv()));
460     Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
461 
462     /* Steps 4-6. */
463     args.rval().setBoolean(reObj->global());
464     return true;
465 }
466 
467 static bool
regexp_global(JSContext * cx,unsigned argc,JS::Value * vp)468 regexp_global(JSContext* cx, unsigned argc, JS::Value* vp)
469 {
470     /* Steps 1-3. */
471     CallArgs args = CallArgsFromVp(argc, vp);
472     return CallNonGenericMethod<IsRegExpObject, regexp_global_impl>(cx, args);
473 }
474 
475 /* ES6 draft rev32 21.2.5.5. */
476 MOZ_ALWAYS_INLINE bool
regexp_ignoreCase_impl(JSContext * cx,const CallArgs & args)477 regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args)
478 {
479     MOZ_ASSERT(IsRegExpObject(args.thisv()));
480     Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
481 
482     /* Steps 4-6. */
483     args.rval().setBoolean(reObj->ignoreCase());
484     return true;
485 }
486 
487 static bool
regexp_ignoreCase(JSContext * cx,unsigned argc,JS::Value * vp)488 regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp)
489 {
490     /* Steps 1-3. */
491     CallArgs args = CallArgsFromVp(argc, vp);
492     return CallNonGenericMethod<IsRegExpObject, regexp_ignoreCase_impl>(cx, args);
493 }
494 
495 /* ES6 draft rev32 21.2.5.7. */
496 MOZ_ALWAYS_INLINE bool
regexp_multiline_impl(JSContext * cx,const CallArgs & args)497 regexp_multiline_impl(JSContext* cx, const CallArgs& args)
498 {
499     MOZ_ASSERT(IsRegExpObject(args.thisv()));
500     Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
501 
502     /* Steps 4-6. */
503     args.rval().setBoolean(reObj->multiline());
504     return true;
505 }
506 
507 static bool
regexp_multiline(JSContext * cx,unsigned argc,JS::Value * vp)508 regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp)
509 {
510     /* Steps 1-3. */
511     CallArgs args = CallArgsFromVp(argc, vp);
512     return CallNonGenericMethod<IsRegExpObject, regexp_multiline_impl>(cx, args);
513 }
514 
515 /* ES6 draft rev32 21.2.5.10. */
516 MOZ_ALWAYS_INLINE bool
regexp_source_impl(JSContext * cx,const CallArgs & args)517 regexp_source_impl(JSContext* cx, const CallArgs& args)
518 {
519     MOZ_ASSERT(IsRegExpObject(args.thisv()));
520     Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
521 
522     /* Step 5. */
523     RootedAtom src(cx, reObj->getSource());
524     if (!src)
525         return false;
526 
527     /* Step 7. */
528     RootedString str(cx, EscapeRegExpPattern(cx, src));
529     if (!str)
530         return false;
531 
532     args.rval().setString(str);
533     return true;
534 }
535 
536 static bool
regexp_source(JSContext * cx,unsigned argc,JS::Value * vp)537 regexp_source(JSContext* cx, unsigned argc, JS::Value* vp)
538 {
539     /* Steps 1-4. */
540     CallArgs args = CallArgsFromVp(argc, vp);
541     return CallNonGenericMethod<IsRegExpObject, regexp_source_impl>(cx, args);
542 }
543 
544 /* ES6 draft rev32 21.2.5.12. */
545 MOZ_ALWAYS_INLINE bool
regexp_sticky_impl(JSContext * cx,const CallArgs & args)546 regexp_sticky_impl(JSContext* cx, const CallArgs& args)
547 {
548     MOZ_ASSERT(IsRegExpObject(args.thisv()));
549     Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
550 
551     /* Steps 4-6. */
552     args.rval().setBoolean(reObj->sticky());
553     return true;
554 }
555 
556 static bool
regexp_sticky(JSContext * cx,unsigned argc,JS::Value * vp)557 regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp)
558 {
559     /* Steps 1-3. */
560     CallArgs args = CallArgsFromVp(argc, vp);
561     return CallNonGenericMethod<IsRegExpObject, regexp_sticky_impl>(cx, args);
562 }
563 
564 const JSPropertySpec js::regexp_properties[] = {
565     JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
566     JS_PSG("global", regexp_global, 0),
567     JS_PSG("ignoreCase", regexp_ignoreCase, 0),
568     JS_PSG("multiline", regexp_multiline, 0),
569     JS_PSG("source", regexp_source, 0),
570     JS_PSG("sticky", regexp_sticky, 0),
571     JS_PS_END
572 };
573 
574 const JSFunctionSpec js::regexp_methods[] = {
575 #if JS_HAS_TOSOURCE
576     JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
577 #endif
578     JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
579     JS_FN("compile",        regexp_compile,     2,0),
580     JS_INLINABLE_FN("exec", regexp_exec,        1,0, RegExpExec),
581     JS_INLINABLE_FN("test", regexp_test,        1,0, RegExpTest),
582     JS_FS_END
583 };
584 
585 #define STATIC_PAREN_GETTER_CODE(parenNum)                                      \
586     if (!res->createParen(cx, parenNum, args.rval()))                           \
587         return false;                                                           \
588     if (args.rval().isUndefined())                                              \
589         args.rval().setString(cx->runtime()->emptyString);                      \
590     return true
591 
592 /*
593  * RegExp static properties.
594  *
595  * RegExp class static properties and their Perl counterparts:
596  *
597  *  RegExp.input                $_
598  *  RegExp.multiline            $*
599  *  RegExp.lastMatch            $&
600  *  RegExp.lastParen            $+
601  *  RegExp.leftContext          $`
602  *  RegExp.rightContext         $'
603  */
604 
605 #define DEFINE_STATIC_GETTER(name, code)                                        \
606     static bool                                                                 \
607     name(JSContext* cx, unsigned argc, Value* vp)                               \
608     {                                                                           \
609         CallArgs args = CallArgsFromVp(argc, vp);                               \
610         RegExpStatics* res = cx->global()->getRegExpStatics(cx);                \
611         if (!res)                                                               \
612             return false;                                                       \
613         code;                                                                   \
614     }
615 
616 DEFINE_STATIC_GETTER(static_input_getter,        return res->createPendingInput(cx, args.rval()))
617 DEFINE_STATIC_GETTER(static_multiline_getter,    args.rval().setBoolean(res->multiline());
618                                                  return true)
619 DEFINE_STATIC_GETTER(static_lastMatch_getter,    return res->createLastMatch(cx, args.rval()))
620 DEFINE_STATIC_GETTER(static_lastParen_getter,    return res->createLastParen(cx, args.rval()))
621 DEFINE_STATIC_GETTER(static_leftContext_getter,  return res->createLeftContext(cx, args.rval()))
622 DEFINE_STATIC_GETTER(static_rightContext_getter, return res->createRightContext(cx, args.rval()))
623 
624 DEFINE_STATIC_GETTER(static_paren1_getter,       STATIC_PAREN_GETTER_CODE(1))
625 DEFINE_STATIC_GETTER(static_paren2_getter,       STATIC_PAREN_GETTER_CODE(2))
626 DEFINE_STATIC_GETTER(static_paren3_getter,       STATIC_PAREN_GETTER_CODE(3))
627 DEFINE_STATIC_GETTER(static_paren4_getter,       STATIC_PAREN_GETTER_CODE(4))
628 DEFINE_STATIC_GETTER(static_paren5_getter,       STATIC_PAREN_GETTER_CODE(5))
629 DEFINE_STATIC_GETTER(static_paren6_getter,       STATIC_PAREN_GETTER_CODE(6))
630 DEFINE_STATIC_GETTER(static_paren7_getter,       STATIC_PAREN_GETTER_CODE(7))
631 DEFINE_STATIC_GETTER(static_paren8_getter,       STATIC_PAREN_GETTER_CODE(8))
632 DEFINE_STATIC_GETTER(static_paren9_getter,       STATIC_PAREN_GETTER_CODE(9))
633 
634 #define DEFINE_STATIC_SETTER(name, code)                                        \
635     static bool                                                                 \
636     name(JSContext* cx, unsigned argc, Value* vp)                               \
637     {                                                                           \
638         RegExpStatics* res = cx->global()->getRegExpStatics(cx);                \
639         if (!res)                                                               \
640             return false;                                                       \
641         code;                                                                   \
642         return true;                                                            \
643     }
644 
645 static bool
static_input_setter(JSContext * cx,unsigned argc,Value * vp)646 static_input_setter(JSContext* cx, unsigned argc, Value* vp)
647 {
648     CallArgs args = CallArgsFromVp(argc, vp);
649     RegExpStatics* res = cx->global()->getRegExpStatics(cx);
650     if (!res)
651         return false;
652 
653     RootedString str(cx, ToString<CanGC>(cx, args.get(0)));
654     if (!str)
655         return false;
656 
657     res->setPendingInput(str);
658     args.rval().setString(str);
659     return true;
660 }
661 
662 static bool
static_multiline_setter(JSContext * cx,unsigned argc,Value * vp)663 static_multiline_setter(JSContext* cx, unsigned argc, Value* vp)
664 {
665     CallArgs args = CallArgsFromVp(argc, vp);
666     RegExpStatics* res = cx->global()->getRegExpStatics(cx);
667     if (!res)
668         return false;
669 
670     bool b = ToBoolean(args.get(0));
671     res->setMultiline(cx, b);
672     args.rval().setBoolean(b);
673     return true;
674 }
675 
676 const JSPropertySpec js::regexp_static_props[] = {
677     JS_PSGS("input", static_input_getter, static_input_setter,
678             JSPROP_PERMANENT | JSPROP_ENUMERATE),
679     JS_PSGS("multiline", static_multiline_getter, static_multiline_setter,
680             JSPROP_PERMANENT | JSPROP_ENUMERATE),
681     JS_PSG("lastMatch", static_lastMatch_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
682     JS_PSG("lastParen", static_lastParen_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
683     JS_PSG("leftContext",  static_leftContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
684     JS_PSG("rightContext", static_rightContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
685     JS_PSG("$1", static_paren1_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
686     JS_PSG("$2", static_paren2_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
687     JS_PSG("$3", static_paren3_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
688     JS_PSG("$4", static_paren4_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
689     JS_PSG("$5", static_paren5_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
690     JS_PSG("$6", static_paren6_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
691     JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
692     JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
693     JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
694     JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT),
695     JS_PSGS("$*", static_multiline_getter, static_multiline_setter, JSPROP_PERMANENT),
696     JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT),
697     JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT),
698     JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT),
699     JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT),
700     JS_PS_END
701 };
702 
703 JSObject*
CreateRegExpPrototype(JSContext * cx,JSProtoKey key)704 js::CreateRegExpPrototype(JSContext* cx, JSProtoKey key)
705 {
706     MOZ_ASSERT(key == JSProto_RegExp);
707 
708     Rooted<RegExpObject*> proto(cx, cx->global()->createBlankPrototype<RegExpObject>(cx));
709     if (!proto)
710         return nullptr;
711     proto->NativeObject::setPrivate(nullptr);
712 
713     if (!RegExpObject::assignInitialShape(cx, proto))
714         return nullptr;
715 
716     RootedAtom source(cx, cx->names().empty);
717     proto->initAndZeroLastIndex(source, RegExpFlag(0), cx);
718 
719     return proto;
720 }
721 
722 static bool
ReportLastIndexNonwritable(JSContext * cx)723 ReportLastIndexNonwritable(JSContext* cx)
724 {
725     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_READ_ONLY, "\"lastIndex\"");
726     return false;
727 }
728 
729 static bool
SetLastIndex(JSContext * cx,Handle<RegExpObject * > reobj,double lastIndex)730 SetLastIndex(JSContext* cx, Handle<RegExpObject*> reobj, double lastIndex)
731 {
732     if (!reobj->lookup(cx, cx->names().lastIndex)->writable())
733         return ReportLastIndexNonwritable(cx);
734 
735     reobj->setLastIndex(lastIndex);
736     return true;
737 }
738 
739 /* ES6 final draft 21.2.5.2.2. */
740 RegExpRunStatus
ExecuteRegExp(JSContext * cx,HandleObject regexp,HandleString string,MatchPairs * matches,RegExpStaticsUpdate staticsUpdate)741 js::ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
742                   MatchPairs* matches, RegExpStaticsUpdate staticsUpdate)
743 {
744     /*
745      * WARNING: Despite the presence of spec step comment numbers, this
746      *          algorithm isn't consistent with any ES6 version, draft or
747      *          otherwise.  YOU HAVE BEEN WARNED.
748      */
749 
750     /* Steps 1-2 performed by the caller. */
751     Rooted<RegExpObject*> reobj(cx, &regexp->as<RegExpObject>());
752 
753     RegExpGuard re(cx);
754     if (!reobj->getShared(cx, &re))
755         return RegExpRunStatus_Error;
756 
757     RegExpStatics* res;
758     if (staticsUpdate == UpdateRegExpStatics) {
759         res = cx->global()->getRegExpStatics(cx);
760         if (!res)
761             return RegExpRunStatus_Error;
762     } else {
763         res = nullptr;
764     }
765 
766     RootedLinearString input(cx, string->ensureLinear(cx));
767     if (!input)
768         return RegExpRunStatus_Error;
769 
770     /* Step 3. */
771     size_t length = input->length();
772 
773     /* Steps 4-5. */
774     RootedValue lastIndex(cx, reobj->getLastIndex());
775     int searchIndex;
776     if (lastIndex.isInt32()) {
777         /* Aggressively avoid doubles. */
778         searchIndex = lastIndex.toInt32();
779     } else {
780         double d;
781         if (!ToInteger(cx, lastIndex, &d))
782             return RegExpRunStatus_Error;
783 
784         /* Inlined steps 6-10, 15.a with doubles to detect failure case. */
785         if (reobj->needUpdateLastIndex() && (d < 0 || d > length)) {
786             /* Steps 15.a.i-ii. */
787             if (!SetLastIndex(cx, reobj, 0))
788                 return RegExpRunStatus_Error;
789 
790             /* Step 15.a.iii. */
791             return RegExpRunStatus_Success_NotFound;
792         }
793 
794         searchIndex = int(d);
795     }
796 
797     /*
798      * Steps 6-10.
799      *
800      * Also make sure that we have a MatchPairs for regexps which update their
801      * last index, as we won't compute the last index otherwise.
802      */
803     Maybe<ScopedMatchPairs> alternateMatches;
804     if (!reobj->needUpdateLastIndex()) {
805         searchIndex = 0;
806     } else if (!matches) {
807         alternateMatches.emplace(&cx->tempLifoAlloc());
808         matches = &alternateMatches.ref();
809     }
810 
811     /* Step 15.a. */
812     if (searchIndex < 0 || size_t(searchIndex) > length) {
813         /* Steps 15.a.i-ii. */
814         if (!SetLastIndex(cx, reobj, 0))
815             return RegExpRunStatus_Error;
816 
817         /* Step 15.a.iii. */
818         return RegExpRunStatus_Success_NotFound;
819     }
820 
821     /* Step 14-29. */
822     RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *re, input, searchIndex, matches);
823     if (status == RegExpRunStatus_Error)
824         return RegExpRunStatus_Error;
825 
826     if (status == RegExpRunStatus_Success_NotFound) {
827         /* Steps 15.a.i-ii. */
828         if (!SetLastIndex(cx, reobj, 0))
829             return RegExpRunStatus_Error;
830     } else if (reobj->needUpdateLastIndex()) {
831         /* Steps 18.a-b. */
832         MOZ_ASSERT(matches && !matches->empty());
833         if (!SetLastIndex(cx, reobj, (*matches)[0].limit))
834             return RegExpRunStatus_Error;
835     }
836 
837     return status;
838 }
839 
840 /* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). */
841 static RegExpRunStatus
ExecuteRegExp(JSContext * cx,const CallArgs & args,MatchPairs * matches)842 ExecuteRegExp(JSContext* cx, const CallArgs& args, MatchPairs* matches)
843 {
844     /* Step 1 (a) was performed by CallNonGenericMethod. */
845     RootedObject regexp(cx, &args.thisv().toObject());
846 
847     /* Step 2. */
848     RootedString string(cx, ToString<CanGC>(cx, args.get(0)));
849     if (!string)
850         return RegExpRunStatus_Error;
851 
852     return ExecuteRegExp(cx, regexp, string, matches, UpdateRegExpStatics);
853 }
854 
855 /* ES5 15.10.6.2. */
856 static bool
regexp_exec_impl(JSContext * cx,HandleObject regexp,HandleString string,RegExpStaticsUpdate staticsUpdate,MutableHandleValue rval)857 regexp_exec_impl(JSContext* cx, HandleObject regexp, HandleString string,
858                  RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval)
859 {
860     /* Execute regular expression and gather matches. */
861     ScopedMatchPairs matches(&cx->tempLifoAlloc());
862 
863     RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, &matches, staticsUpdate);
864     if (status == RegExpRunStatus_Error)
865         return false;
866 
867     if (status == RegExpRunStatus_Success_NotFound) {
868         rval.setNull();
869         return true;
870     }
871 
872     return CreateRegExpMatchResult(cx, string, matches, rval);
873 }
874 
875 static bool
regexp_exec_impl(JSContext * cx,const CallArgs & args)876 regexp_exec_impl(JSContext* cx, const CallArgs& args)
877 {
878     RootedObject regexp(cx, &args.thisv().toObject());
879     RootedString string(cx, ToString<CanGC>(cx, args.get(0)));
880     if (!string)
881         return false;
882 
883     return regexp_exec_impl(cx, regexp, string, UpdateRegExpStatics, args.rval());
884 }
885 
886 bool
regexp_exec(JSContext * cx,unsigned argc,Value * vp)887 js::regexp_exec(JSContext* cx, unsigned argc, Value* vp)
888 {
889     CallArgs args = CallArgsFromVp(argc, vp);
890     return CallNonGenericMethod(cx, IsRegExpObject, regexp_exec_impl, args);
891 }
892 
893 /* Separate interface for use by IonMonkey. */
894 bool
regexp_exec_raw(JSContext * cx,HandleObject regexp,HandleString input,MatchPairs * maybeMatches,MutableHandleValue output)895 js::regexp_exec_raw(JSContext* cx, HandleObject regexp, HandleString input,
896                     MatchPairs* maybeMatches, MutableHandleValue output)
897 {
898     // The MatchPairs will always be passed in, but RegExp execution was
899     // successful only if the pairs have actually been filled in.
900     if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0)
901         return CreateRegExpMatchResult(cx, input, *maybeMatches, output);
902     return regexp_exec_impl(cx, regexp, input, UpdateRegExpStatics, output);
903 }
904 
905 bool
regexp_exec_no_statics(JSContext * cx,unsigned argc,Value * vp)906 js::regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp)
907 {
908     CallArgs args = CallArgsFromVp(argc, vp);
909     MOZ_ASSERT(args.length() == 2);
910     MOZ_ASSERT(IsRegExpObject(args[0]));
911     MOZ_ASSERT(args[1].isString());
912 
913     RootedObject regexp(cx, &args[0].toObject());
914     RootedString string(cx, args[1].toString());
915 
916     return regexp_exec_impl(cx, regexp, string, DontUpdateRegExpStatics, args.rval());
917 }
918 
919 /* ES5 15.10.6.3. */
920 static bool
regexp_test_impl(JSContext * cx,const CallArgs & args)921 regexp_test_impl(JSContext* cx, const CallArgs& args)
922 {
923     RegExpRunStatus status = ExecuteRegExp(cx, args, nullptr);
924     args.rval().setBoolean(status == RegExpRunStatus_Success);
925     return status != RegExpRunStatus_Error;
926 }
927 
928 /* Separate interface for use by IonMonkey. */
929 bool
regexp_test_raw(JSContext * cx,HandleObject regexp,HandleString input,bool * result)930 js::regexp_test_raw(JSContext* cx, HandleObject regexp, HandleString input, bool* result)
931 {
932     RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, nullptr, UpdateRegExpStatics);
933     *result = (status == RegExpRunStatus_Success);
934     return status != RegExpRunStatus_Error;
935 }
936 
937 bool
regexp_test(JSContext * cx,unsigned argc,Value * vp)938 js::regexp_test(JSContext* cx, unsigned argc, Value* vp)
939 {
940     CallArgs args = CallArgsFromVp(argc, vp);
941     return CallNonGenericMethod(cx, IsRegExpObject, regexp_test_impl, args);
942 }
943 
944 bool
regexp_test_no_statics(JSContext * cx,unsigned argc,Value * vp)945 js::regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp)
946 {
947     CallArgs args = CallArgsFromVp(argc, vp);
948     MOZ_ASSERT(args.length() == 2);
949     MOZ_ASSERT(IsRegExpObject(args[0]));
950     MOZ_ASSERT(args[1].isString());
951 
952     RootedObject regexp(cx, &args[0].toObject());
953     RootedString string(cx, args[1].toString());
954 
955     RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, nullptr, DontUpdateRegExpStatics);
956     args.rval().setBoolean(status == RegExpRunStatus_Success);
957     return status != RegExpRunStatus_Error;
958 }
959