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, ®exp->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