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 : #include "builtin/RegExp.h"
8 :
9 : #include "mozilla/CheckedInt.h"
10 : #include "mozilla/TypeTraits.h"
11 :
12 : #include "jscntxt.h"
13 :
14 : #include "frontend/TokenStream.h"
15 : #include "irregexp/RegExpParser.h"
16 : #include "jit/InlinableNatives.h"
17 : #include "vm/RegExpStatics.h"
18 : #include "vm/SelfHosting.h"
19 : #include "vm/StringBuffer.h"
20 : #include "vm/Unicode.h"
21 :
22 : #include "jsobjinlines.h"
23 :
24 : #include "vm/NativeObject-inl.h"
25 :
26 : using namespace js;
27 : using namespace js::unicode;
28 :
29 : using mozilla::ArrayLength;
30 : using mozilla::CheckedInt;
31 : using mozilla::Maybe;
32 :
33 : /*
34 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
35 : * steps 3, 16-25.
36 : */
37 : bool
38 57 : js::CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches,
39 : MutableHandleValue rval)
40 : {
41 57 : MOZ_ASSERT(input);
42 :
43 : /*
44 : * Create the (slow) result array for a match.
45 : *
46 : * Array contents:
47 : * 0: matched string
48 : * 1..pairCount-1: paren matches
49 : * input: input string
50 : * index: start index for the match
51 : */
52 :
53 : /* Get the templateObject that defines the shape and type of the output object */
54 57 : JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
55 57 : if (!templateObject)
56 0 : return false;
57 :
58 57 : size_t numPairs = matches.length();
59 57 : MOZ_ASSERT(numPairs > 0);
60 :
61 : /* Step 17. */
62 114 : RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject));
63 57 : if (!arr)
64 0 : return false;
65 :
66 : /* Steps 22-24.
67 : * Store a Value for each pair. */
68 189 : for (size_t i = 0; i < numPairs; i++) {
69 132 : const MatchPair& pair = matches[i];
70 :
71 132 : if (pair.isUndefined()) {
72 14 : MOZ_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
73 14 : arr->setDenseInitializedLength(i + 1);
74 14 : arr->initDenseElement(i, UndefinedValue());
75 : } else {
76 118 : JSLinearString* str = NewDependentString(cx, input, pair.start, pair.length());
77 118 : if (!str)
78 0 : return false;
79 118 : arr->setDenseInitializedLength(i + 1);
80 118 : arr->initDenseElement(i, StringValue(str));
81 : }
82 : }
83 :
84 : /* Step 20 (reordered).
85 : * Set the |index| property. (TemplateObject positions it in slot 0) */
86 57 : arr->setSlot(0, Int32Value(matches[0].start));
87 :
88 : /* Step 21 (reordered).
89 : * Set the |input| property. (TemplateObject positions it in slot 1) */
90 57 : arr->setSlot(1, StringValue(input));
91 :
92 : #ifdef DEBUG
93 114 : RootedValue test(cx);
94 114 : RootedId id(cx, NameToId(cx->names().index));
95 57 : if (!NativeGetProperty(cx, arr, id, &test))
96 0 : return false;
97 57 : MOZ_ASSERT(test == arr->getSlot(0));
98 57 : id = NameToId(cx->names().input);
99 57 : if (!NativeGetProperty(cx, arr, id, &test))
100 0 : return false;
101 57 : MOZ_ASSERT(test == arr->getSlot(1));
102 : #endif
103 :
104 : /* Step 25. */
105 57 : rval.setObject(*arr);
106 57 : return true;
107 : }
108 :
109 : static int32_t
110 3 : CreateRegExpSearchResult(JSContext* cx, const MatchPairs& matches)
111 : {
112 : /* Fit the start and limit of match into a int32_t. */
113 3 : uint32_t position = matches[0].start;
114 3 : uint32_t lastIndex = matches[0].limit;
115 3 : MOZ_ASSERT(position < 0x8000);
116 3 : MOZ_ASSERT(lastIndex < 0x8000);
117 3 : return position | (lastIndex << 15);
118 : }
119 :
120 : /*
121 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
122 : * steps 3, 9-14, except 12.a.i, 12.c.i.1.
123 : */
124 : static RegExpRunStatus
125 258 : ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, MutableHandleRegExpShared re,
126 : HandleLinearString input, size_t searchIndex, MatchPairs* matches,
127 : size_t* endIndex)
128 : {
129 258 : RegExpRunStatus status = RegExpShared::execute(cx, re, input, searchIndex, matches, endIndex);
130 :
131 : /* Out of spec: Update RegExpStatics. */
132 258 : if (status == RegExpRunStatus_Success && res) {
133 122 : if (matches) {
134 60 : if (!res->updateFromMatchPairs(cx, input, *matches))
135 0 : return RegExpRunStatus_Error;
136 : } else {
137 62 : res->updateLazily(cx, input, re, searchIndex);
138 : }
139 : }
140 258 : return status;
141 : }
142 :
143 : /* Legacy ExecuteRegExp behavior is baked into the JSAPI. */
144 : bool
145 0 : js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, Handle<RegExpObject*> reobj,
146 : HandleLinearString input, size_t* lastIndex, bool test,
147 : MutableHandleValue rval)
148 : {
149 0 : RootedRegExpShared shared(cx, RegExpObject::getShared(cx, reobj));
150 0 : if (!shared)
151 0 : return false;
152 :
153 0 : ScopedMatchPairs matches(&cx->tempLifoAlloc());
154 :
155 0 : RegExpRunStatus status = ExecuteRegExpImpl(cx, res, &shared, input, *lastIndex,
156 0 : &matches, nullptr);
157 0 : if (status == RegExpRunStatus_Error)
158 0 : return false;
159 :
160 0 : if (status == RegExpRunStatus_Success_NotFound) {
161 : /* ExecuteRegExp() previously returned an array or null. */
162 0 : rval.setNull();
163 0 : return true;
164 : }
165 :
166 0 : *lastIndex = matches[0].limit;
167 :
168 0 : if (test) {
169 : /* Forbid an array, as an optimization. */
170 0 : rval.setBoolean(true);
171 0 : return true;
172 : }
173 :
174 0 : return CreateRegExpMatchResult(cx, input, matches, rval);
175 : }
176 :
177 : static bool
178 2 : CheckPatternSyntax(JSContext* cx, HandleAtom pattern, RegExpFlag flags)
179 : {
180 4 : CompileOptions options(cx);
181 4 : frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
182 4 : return irregexp::ParsePatternSyntax(dummyTokenStream, cx->tempLifoAlloc(), pattern,
183 8 : flags & UnicodeFlag);
184 : }
185 :
186 : enum RegExpSharedUse {
187 : UseRegExpShared,
188 : DontUseRegExpShared
189 : };
190 :
191 : /*
192 : * ES 2016 draft Mar 25, 2016 21.2.3.2.2.
193 : *
194 : * Steps 14-15 set |obj|'s "lastIndex" property to zero. Some of
195 : * RegExpInitialize's callers have a fresh RegExp not yet exposed to script:
196 : * in these cases zeroing "lastIndex" is infallible. But others have a RegExp
197 : * whose "lastIndex" property might have been made non-writable: here, zeroing
198 : * "lastIndex" can fail. We efficiently solve this problem by completely
199 : * removing "lastIndex" zeroing from the provided function.
200 : *
201 : * CALLERS MUST HANDLE "lastIndex" ZEROING THEMSELVES!
202 : *
203 : * Because this function only ever returns a user-provided |obj| in the spec,
204 : * we omit it and just return the usual success/failure.
205 : */
206 : static bool
207 2 : RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle<RegExpObject*> obj,
208 : HandleValue patternValue, HandleValue flagsValue,
209 : RegExpSharedUse sharedUse = DontUseRegExpShared)
210 : {
211 4 : RootedAtom pattern(cx);
212 2 : if (patternValue.isUndefined()) {
213 : /* Step 1. */
214 0 : pattern = cx->names().empty;
215 : } else {
216 : /* Step 2. */
217 2 : pattern = ToAtom<CanGC>(cx, patternValue);
218 2 : if (!pattern)
219 0 : return false;
220 : }
221 :
222 : /* Step 3. */
223 2 : RegExpFlag flags = RegExpFlag(0);
224 2 : if (!flagsValue.isUndefined()) {
225 : /* Step 4. */
226 2 : RootedString flagStr(cx, ToString<CanGC>(cx, flagsValue));
227 1 : if (!flagStr)
228 0 : return false;
229 :
230 : /* Step 5. */
231 1 : if (!ParseRegExpFlags(cx, flagStr, &flags))
232 0 : return false;
233 : }
234 :
235 2 : if (sharedUse == UseRegExpShared) {
236 : /* Steps 7-8. */
237 0 : RegExpShared* re = cx->zone()->regExps.get(cx, pattern, flags);
238 0 : if (!re)
239 0 : return false;
240 :
241 : /* Steps 9-12. */
242 0 : obj->initIgnoringLastIndex(pattern, flags);
243 :
244 0 : obj->setShared(*re);
245 : } else {
246 : /* Steps 7-8. */
247 2 : if (!CheckPatternSyntax(cx, pattern, flags))
248 0 : return false;
249 :
250 : /* Steps 9-12. */
251 2 : obj->initIgnoringLastIndex(pattern, flags);
252 : }
253 :
254 2 : return true;
255 : }
256 :
257 : /* ES 2016 draft Mar 25, 2016 21.2.3.2.3. */
258 : bool
259 0 : js::RegExpCreate(JSContext* cx, HandleValue patternValue, HandleValue flagsValue,
260 : MutableHandleValue rval)
261 : {
262 : /* Step 1. */
263 0 : Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject));
264 0 : if (!regexp)
265 0 : return false;
266 :
267 : /* Step 2. */
268 0 : if (!RegExpInitializeIgnoringLastIndex(cx, regexp, patternValue, flagsValue, UseRegExpShared))
269 0 : return false;
270 0 : regexp->zeroLastIndex(cx);
271 :
272 0 : rval.setObject(*regexp);
273 0 : return true;
274 : }
275 :
276 : MOZ_ALWAYS_INLINE bool
277 258 : IsRegExpObject(HandleValue v)
278 : {
279 258 : return v.isObject() && v.toObject().is<RegExpObject>();
280 : }
281 :
282 : /* ES6 draft rc3 7.2.8. */
283 : bool
284 570 : js::IsRegExp(JSContext* cx, HandleValue value, bool* result)
285 : {
286 : /* Step 1. */
287 570 : if (!value.isObject()) {
288 570 : *result = false;
289 570 : return true;
290 : }
291 0 : RootedObject obj(cx, &value.toObject());
292 :
293 : /* Steps 2-3. */
294 0 : RootedValue isRegExp(cx);
295 0 : RootedId matchId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().match));
296 0 : if (!GetProperty(cx, obj, obj, matchId, &isRegExp))
297 0 : return false;
298 :
299 : /* Step 4. */
300 0 : if (!isRegExp.isUndefined()) {
301 0 : *result = ToBoolean(isRegExp);
302 0 : return true;
303 : }
304 :
305 : /* Steps 5-6. */
306 : ESClass cls;
307 0 : if (!GetClassOfValue(cx, value, &cls))
308 0 : return false;
309 :
310 0 : *result = cls == ESClass::RegExp;
311 0 : return true;
312 : }
313 :
314 : /* ES6 B.2.5.1. */
315 : MOZ_ALWAYS_INLINE bool
316 0 : regexp_compile_impl(JSContext* cx, const CallArgs& args)
317 : {
318 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
319 :
320 0 : Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>());
321 :
322 : // Step 3.
323 0 : RootedValue patternValue(cx, args.get(0));
324 : ESClass cls;
325 0 : if (!GetClassOfValue(cx, patternValue, &cls))
326 0 : return false;
327 0 : if (cls == ESClass::RegExp) {
328 : // Step 3a.
329 0 : if (args.hasDefined(1)) {
330 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
331 0 : return false;
332 : }
333 :
334 : // Beware! |patternObj| might be a proxy into another compartment, so
335 : // don't assume |patternObj.is<RegExpObject>()|. For the same reason,
336 : // don't reuse the RegExpShared below.
337 0 : RootedObject patternObj(cx, &patternValue.toObject());
338 :
339 0 : RootedAtom sourceAtom(cx);
340 : RegExpFlag flags;
341 : {
342 : // Step 3b.
343 0 : RegExpShared* shared = RegExpToShared(cx, patternObj);
344 0 : if (!shared)
345 0 : return false;
346 :
347 0 : sourceAtom = shared->getSource();
348 0 : flags = shared->getFlags();
349 : }
350 :
351 : // Step 5, minus lastIndex zeroing.
352 0 : regexp->initIgnoringLastIndex(sourceAtom, flags);
353 : } else {
354 : // Step 4.
355 0 : RootedValue P(cx, patternValue);
356 0 : RootedValue F(cx, args.get(1));
357 :
358 : // Step 5, minus lastIndex zeroing.
359 0 : if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F))
360 0 : return false;
361 : }
362 :
363 : // The final niggling bit of step 5.
364 : //
365 : // |regexp| is user-exposed, but if its "lastIndex" property hasn't been
366 : // made non-writable, we can still use a fast path to zero it.
367 0 : if (regexp->lookupPure(cx->names().lastIndex)->writable()) {
368 0 : regexp->zeroLastIndex(cx);
369 : } else {
370 0 : RootedValue zero(cx, Int32Value(0));
371 0 : if (!SetProperty(cx, regexp, cx->names().lastIndex, zero))
372 0 : return false;
373 : }
374 :
375 0 : args.rval().setObject(*regexp);
376 0 : return true;
377 : }
378 :
379 : static bool
380 0 : regexp_compile(JSContext* cx, unsigned argc, Value* vp)
381 : {
382 0 : CallArgs args = CallArgsFromVp(argc, vp);
383 :
384 : /* Steps 1-2. */
385 0 : return CallNonGenericMethod<IsRegExpObject, regexp_compile_impl>(cx, args);
386 : }
387 :
388 : /*
389 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.3.1.
390 : */
391 : bool
392 2 : js::regexp_construct(JSContext* cx, unsigned argc, Value* vp)
393 : {
394 2 : CallArgs args = CallArgsFromVp(argc, vp);
395 :
396 : // Steps 1.
397 : bool patternIsRegExp;
398 2 : if (!IsRegExp(cx, args.get(0), &patternIsRegExp))
399 0 : return false;
400 :
401 : // We can delay step 3 and step 4a until later, during
402 : // GetPrototypeFromBuiltinConstructor calls. Accessing the new.target
403 : // and the callee from the stack is unobservable.
404 2 : if (!args.isConstructing()) {
405 : // Step 3.b.
406 0 : if (patternIsRegExp && !args.hasDefined(1)) {
407 0 : RootedObject patternObj(cx, &args[0].toObject());
408 :
409 : // Step 3.b.i.
410 0 : RootedValue patternConstructor(cx);
411 0 : if (!GetProperty(cx, patternObj, patternObj, cx->names().constructor, &patternConstructor))
412 0 : return false;
413 :
414 : // Step 3.b.ii.
415 0 : if (patternConstructor.isObject() && patternConstructor.toObject() == args.callee()) {
416 0 : args.rval().set(args[0]);
417 0 : return true;
418 : }
419 : }
420 : }
421 :
422 4 : RootedValue patternValue(cx, args.get(0));
423 :
424 : // Step 4.
425 : ESClass cls;
426 2 : if (!GetClassOfValue(cx, patternValue, &cls))
427 0 : return false;
428 2 : if (cls == ESClass::RegExp) {
429 : // Beware! |patternObj| might be a proxy into another compartment, so
430 : // don't assume |patternObj.is<RegExpObject>()|. For the same reason,
431 : // don't reuse the RegExpShared below.
432 0 : RootedObject patternObj(cx, &patternValue.toObject());
433 :
434 0 : RootedAtom sourceAtom(cx);
435 : RegExpFlag flags;
436 : {
437 : // Step 4.a.
438 0 : RegExpShared* shared = RegExpToShared(cx, patternObj);
439 0 : if (!shared)
440 0 : return false;
441 0 : sourceAtom = shared->getSource();
442 :
443 : // Step 4.b.
444 : // Get original flags in all cases, to compare with passed flags.
445 0 : flags = shared->getFlags();
446 : }
447 :
448 : // Step 7.
449 0 : RootedObject proto(cx);
450 0 : if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
451 0 : return false;
452 :
453 0 : Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
454 0 : if (!regexp)
455 0 : return false;
456 :
457 : // Step 8.
458 0 : if (args.hasDefined(1)) {
459 : // Step 4.c / 21.2.3.2.2 RegExpInitialize step 4.
460 0 : RegExpFlag flagsArg = RegExpFlag(0);
461 0 : RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
462 0 : if (!flagStr)
463 0 : return false;
464 0 : if (!ParseRegExpFlags(cx, flagStr, &flagsArg))
465 0 : return false;
466 :
467 0 : if (!(flags & UnicodeFlag) && flagsArg & UnicodeFlag) {
468 : // Have to check syntax again when adding 'u' flag.
469 :
470 : // ES 2017 draft rev 9b49a888e9dfe2667008a01b2754c3662059ae56
471 : // 21.2.3.2.2 step 7.
472 0 : if (!CheckPatternSyntax(cx, sourceAtom, flagsArg))
473 0 : return false;
474 : }
475 0 : flags = flagsArg;
476 : }
477 :
478 0 : regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
479 :
480 0 : args.rval().setObject(*regexp);
481 0 : return true;
482 : }
483 :
484 4 : RootedValue P(cx);
485 4 : RootedValue F(cx);
486 :
487 : // Step 5.
488 2 : if (patternIsRegExp) {
489 0 : RootedObject patternObj(cx, &patternValue.toObject());
490 :
491 : // Step 5.a.
492 0 : if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P))
493 0 : return false;
494 :
495 : // Step 5.b.
496 0 : F = args.get(1);
497 0 : if (F.isUndefined()) {
498 0 : if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F))
499 0 : return false;
500 : }
501 : } else {
502 : // Steps 6.a-b.
503 2 : P = patternValue;
504 2 : F = args.get(1);
505 : }
506 :
507 : // Step 7.
508 4 : RootedObject proto(cx);
509 2 : if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
510 0 : return false;
511 :
512 4 : Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
513 2 : if (!regexp)
514 0 : return false;
515 :
516 : // Step 8.
517 2 : if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F))
518 0 : return false;
519 2 : regexp->zeroLastIndex(cx);
520 :
521 2 : args.rval().setObject(*regexp);
522 2 : return true;
523 : }
524 :
525 : /*
526 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.3.1
527 : * steps 4, 7-8.
528 : */
529 : bool
530 0 : js::regexp_construct_raw_flags(JSContext* cx, unsigned argc, Value* vp)
531 : {
532 0 : CallArgs args = CallArgsFromVp(argc, vp);
533 0 : MOZ_ASSERT(args.length() == 2);
534 0 : MOZ_ASSERT(!args.isConstructing());
535 :
536 : // Step 4.a.
537 0 : RootedAtom sourceAtom(cx, AtomizeString(cx, args[0].toString()));
538 0 : if (!sourceAtom)
539 0 : return false;
540 :
541 : // Step 4.c.
542 0 : int32_t flags = int32_t(args[1].toNumber());
543 :
544 : // Step 7.
545 0 : RegExpObject* regexp = RegExpAlloc(cx, GenericObject);
546 0 : if (!regexp)
547 0 : return false;
548 :
549 : // Step 8.
550 0 : regexp->initAndZeroLastIndex(sourceAtom, RegExpFlag(flags), cx);
551 0 : args.rval().setObject(*regexp);
552 0 : return true;
553 : }
554 :
555 : MOZ_ALWAYS_INLINE bool
556 0 : IsRegExpPrototype(HandleValue v)
557 : {
558 0 : if (IsRegExpObject(v) || !v.isObject())
559 0 : return false;
560 :
561 : // Note: The prototype shares its JSClass with instances.
562 0 : return StandardProtoKeyOrNull(&v.toObject()) == JSProto_RegExp;
563 : }
564 :
565 : // ES 2017 draft 21.2.5.4.
566 : MOZ_ALWAYS_INLINE bool
567 0 : regexp_global_impl(JSContext* cx, const CallArgs& args)
568 : {
569 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
570 :
571 : // Steps 4-6.
572 0 : RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
573 0 : args.rval().setBoolean(reObj->global());
574 0 : return true;
575 : }
576 :
577 : bool
578 0 : js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp)
579 : {
580 0 : CallArgs args = CallArgsFromVp(argc, vp);
581 :
582 : // Step 3.a.
583 0 : if (IsRegExpPrototype(args.thisv())) {
584 0 : args.rval().setUndefined();
585 0 : return true;
586 : }
587 :
588 : // Steps 1-3.
589 0 : return CallNonGenericMethod<IsRegExpObject, regexp_global_impl>(cx, args);
590 : }
591 :
592 : // ES 2017 draft 21.2.5.5.
593 : MOZ_ALWAYS_INLINE bool
594 0 : regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args)
595 : {
596 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
597 :
598 : // Steps 4-6.
599 0 : RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
600 0 : args.rval().setBoolean(reObj->ignoreCase());
601 0 : return true;
602 : }
603 :
604 : bool
605 0 : js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp)
606 : {
607 0 : CallArgs args = CallArgsFromVp(argc, vp);
608 :
609 : // Step 3.a.
610 0 : if (IsRegExpPrototype(args.thisv())) {
611 0 : args.rval().setUndefined();
612 0 : return true;
613 : }
614 :
615 : // Steps 1-3.
616 0 : return CallNonGenericMethod<IsRegExpObject, regexp_ignoreCase_impl>(cx, args);
617 : }
618 :
619 : // ES 2017 draft 21.2.5.7.
620 : MOZ_ALWAYS_INLINE bool
621 0 : regexp_multiline_impl(JSContext* cx, const CallArgs& args)
622 : {
623 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
624 :
625 : // Steps 4-6.
626 0 : RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
627 0 : args.rval().setBoolean(reObj->multiline());
628 0 : return true;
629 : }
630 :
631 : bool
632 0 : js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp)
633 : {
634 0 : CallArgs args = CallArgsFromVp(argc, vp);
635 :
636 : // Step 3.a.
637 0 : if (IsRegExpPrototype(args.thisv())) {
638 0 : args.rval().setUndefined();
639 0 : return true;
640 : }
641 :
642 : // Steps 1-3.
643 0 : return CallNonGenericMethod<IsRegExpObject, regexp_multiline_impl>(cx, args);
644 : }
645 :
646 : // ES 2017 draft 21.2.5.10.
647 : MOZ_ALWAYS_INLINE bool
648 0 : regexp_source_impl(JSContext* cx, const CallArgs& args)
649 : {
650 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
651 :
652 : // Step 5.
653 0 : RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
654 0 : RootedAtom src(cx, reObj->getSource());
655 0 : if (!src)
656 0 : return false;
657 :
658 : // Step 7.
659 0 : JSString* str = EscapeRegExpPattern(cx, src);
660 0 : if (!str)
661 0 : return false;
662 :
663 0 : args.rval().setString(str);
664 0 : return true;
665 : }
666 :
667 : static bool
668 0 : regexp_source(JSContext* cx, unsigned argc, JS::Value* vp)
669 : {
670 0 : CallArgs args = CallArgsFromVp(argc, vp);
671 :
672 : // Step 3.a.
673 0 : if (IsRegExpPrototype(args.thisv())) {
674 0 : args.rval().setString(cx->names().emptyRegExp);
675 0 : return true;
676 : }
677 :
678 : // Steps 1-4.
679 0 : return CallNonGenericMethod<IsRegExpObject, regexp_source_impl>(cx, args);
680 : }
681 :
682 : // ES 2017 draft 21.2.5.12.
683 : MOZ_ALWAYS_INLINE bool
684 0 : regexp_sticky_impl(JSContext* cx, const CallArgs& args)
685 : {
686 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
687 :
688 : // Steps 4-6.
689 0 : RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
690 0 : args.rval().setBoolean(reObj->sticky());
691 0 : return true;
692 : }
693 :
694 : bool
695 0 : js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp)
696 : {
697 0 : CallArgs args = CallArgsFromVp(argc, vp);
698 :
699 : // Step 3.a.
700 0 : if (IsRegExpPrototype(args.thisv())) {
701 0 : args.rval().setUndefined();
702 0 : return true;
703 : }
704 :
705 : // Steps 1-3.
706 0 : return CallNonGenericMethod<IsRegExpObject, regexp_sticky_impl>(cx, args);
707 : }
708 :
709 : // ES 2017 draft 21.2.5.15.
710 : MOZ_ALWAYS_INLINE bool
711 0 : regexp_unicode_impl(JSContext* cx, const CallArgs& args)
712 : {
713 0 : MOZ_ASSERT(IsRegExpObject(args.thisv()));
714 :
715 : // Steps 4-6.
716 0 : RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
717 0 : args.rval().setBoolean(reObj->unicode());
718 0 : return true;
719 : }
720 :
721 : bool
722 0 : js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp)
723 : {
724 0 : CallArgs args = CallArgsFromVp(argc, vp);
725 :
726 : // Step 3.a.
727 0 : if (IsRegExpPrototype(args.thisv())) {
728 0 : args.rval().setUndefined();
729 0 : return true;
730 : }
731 :
732 : // Steps 1-3.
733 0 : return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args);
734 : }
735 :
736 : const JSPropertySpec js::regexp_properties[] = {
737 : JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
738 : JS_PSG("global", regexp_global, 0),
739 : JS_PSG("ignoreCase", regexp_ignoreCase, 0),
740 : JS_PSG("multiline", regexp_multiline, 0),
741 : JS_PSG("source", regexp_source, 0),
742 : JS_PSG("sticky", regexp_sticky, 0),
743 : JS_PSG("unicode", regexp_unicode, 0),
744 : JS_PS_END
745 : };
746 :
747 : const JSFunctionSpec js::regexp_methods[] = {
748 : #if JS_HAS_TOSOURCE
749 : JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
750 : #endif
751 : JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
752 : JS_FN("compile", regexp_compile, 2,0),
753 : JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1,0),
754 : JS_SELF_HOSTED_FN("test", "RegExpTest" , 1,0),
755 : JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1,0),
756 : JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2,0),
757 : JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1,0),
758 : JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2,0),
759 : JS_FS_END
760 : };
761 :
762 : #define STATIC_PAREN_GETTER_CODE(parenNum) \
763 : if (!res->createParen(cx, parenNum, args.rval())) \
764 : return false; \
765 : if (args.rval().isUndefined()) \
766 : args.rval().setString(cx->runtime()->emptyString); \
767 : return true
768 :
769 : /*
770 : * RegExp static properties.
771 : *
772 : * RegExp class static properties and their Perl counterparts:
773 : *
774 : * RegExp.input $_
775 : * RegExp.lastMatch $&
776 : * RegExp.lastParen $+
777 : * RegExp.leftContext $`
778 : * RegExp.rightContext $'
779 : */
780 :
781 : #define DEFINE_STATIC_GETTER(name, code) \
782 : static bool \
783 : name(JSContext* cx, unsigned argc, Value* vp) \
784 : { \
785 : CallArgs args = CallArgsFromVp(argc, vp); \
786 : RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \
787 : if (!res) \
788 : return false; \
789 : code; \
790 : }
791 :
792 0 : DEFINE_STATIC_GETTER(static_input_getter, return res->createPendingInput(cx, args.rval()))
793 0 : DEFINE_STATIC_GETTER(static_lastMatch_getter, return res->createLastMatch(cx, args.rval()))
794 0 : DEFINE_STATIC_GETTER(static_lastParen_getter, return res->createLastParen(cx, args.rval()))
795 0 : DEFINE_STATIC_GETTER(static_leftContext_getter, return res->createLeftContext(cx, args.rval()))
796 6 : DEFINE_STATIC_GETTER(static_rightContext_getter, return res->createRightContext(cx, args.rval()))
797 :
798 0 : DEFINE_STATIC_GETTER(static_paren1_getter, STATIC_PAREN_GETTER_CODE(1))
799 0 : DEFINE_STATIC_GETTER(static_paren2_getter, STATIC_PAREN_GETTER_CODE(2))
800 0 : DEFINE_STATIC_GETTER(static_paren3_getter, STATIC_PAREN_GETTER_CODE(3))
801 0 : DEFINE_STATIC_GETTER(static_paren4_getter, STATIC_PAREN_GETTER_CODE(4))
802 0 : DEFINE_STATIC_GETTER(static_paren5_getter, STATIC_PAREN_GETTER_CODE(5))
803 0 : DEFINE_STATIC_GETTER(static_paren6_getter, STATIC_PAREN_GETTER_CODE(6))
804 0 : DEFINE_STATIC_GETTER(static_paren7_getter, STATIC_PAREN_GETTER_CODE(7))
805 0 : DEFINE_STATIC_GETTER(static_paren8_getter, STATIC_PAREN_GETTER_CODE(8))
806 0 : DEFINE_STATIC_GETTER(static_paren9_getter, STATIC_PAREN_GETTER_CODE(9))
807 :
808 : #define DEFINE_STATIC_SETTER(name, code) \
809 : static bool \
810 : name(JSContext* cx, unsigned argc, Value* vp) \
811 : { \
812 : RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \
813 : if (!res) \
814 : return false; \
815 : code; \
816 : return true; \
817 : }
818 :
819 : static bool
820 0 : static_input_setter(JSContext* cx, unsigned argc, Value* vp)
821 : {
822 0 : CallArgs args = CallArgsFromVp(argc, vp);
823 0 : RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global());
824 0 : if (!res)
825 0 : return false;
826 :
827 0 : RootedString str(cx, ToString<CanGC>(cx, args.get(0)));
828 0 : if (!str)
829 0 : return false;
830 :
831 0 : res->setPendingInput(str);
832 0 : args.rval().setString(str);
833 0 : return true;
834 : }
835 :
836 : const JSPropertySpec js::regexp_static_props[] = {
837 : JS_PSGS("input", static_input_getter, static_input_setter,
838 : JSPROP_PERMANENT | JSPROP_ENUMERATE),
839 : JS_PSG("lastMatch", static_lastMatch_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
840 : JS_PSG("lastParen", static_lastParen_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
841 : JS_PSG("leftContext", static_leftContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
842 : JS_PSG("rightContext", static_rightContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
843 : JS_PSG("$1", static_paren1_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
844 : JS_PSG("$2", static_paren2_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
845 : JS_PSG("$3", static_paren3_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
846 : JS_PSG("$4", static_paren4_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
847 : JS_PSG("$5", static_paren5_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
848 : JS_PSG("$6", static_paren6_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
849 : JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
850 : JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
851 : JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
852 : JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT),
853 : JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT),
854 : JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT),
855 : JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT),
856 : JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT),
857 : JS_SELF_HOSTED_SYM_GET(species, "RegExpSpecies", 0),
858 : JS_PS_END
859 : };
860 :
861 : template <typename CharT>
862 : static bool
863 0 : IsTrailSurrogateWithLeadSurrogateImpl(JSContext* cx, HandleLinearString input, size_t index)
864 : {
865 0 : JS::AutoCheckCannotGC nogc;
866 0 : MOZ_ASSERT(index > 0 && index < input->length());
867 0 : const CharT* inputChars = input->chars<CharT>(nogc);
868 :
869 0 : return unicode::IsTrailSurrogate(inputChars[index]) &&
870 0 : unicode::IsLeadSurrogate(inputChars[index - 1]);
871 : }
872 :
873 : static bool
874 0 : IsTrailSurrogateWithLeadSurrogate(JSContext* cx, HandleLinearString input, int32_t index)
875 : {
876 0 : if (index <= 0 || size_t(index) >= input->length())
877 0 : return false;
878 :
879 0 : return input->hasLatin1Chars()
880 0 : ? IsTrailSurrogateWithLeadSurrogateImpl<Latin1Char>(cx, input, index)
881 0 : : IsTrailSurrogateWithLeadSurrogateImpl<char16_t>(cx, input, index);
882 : }
883 :
884 : /*
885 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
886 : * steps 3, 9-14, except 12.a.i, 12.c.i.1.
887 : */
888 : static RegExpRunStatus
889 258 : ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
890 : int32_t lastIndex,
891 : MatchPairs* matches, size_t* endIndex, RegExpStaticsUpdate staticsUpdate)
892 : {
893 : /*
894 : * WARNING: Despite the presence of spec step comment numbers, this
895 : * algorithm isn't consistent with any ES6 version, draft or
896 : * otherwise. YOU HAVE BEEN WARNED.
897 : */
898 :
899 : /* Steps 1-2 performed by the caller. */
900 516 : Rooted<RegExpObject*> reobj(cx, ®exp->as<RegExpObject>());
901 :
902 516 : RootedRegExpShared re(cx, RegExpObject::getShared(cx, reobj));
903 258 : if (!re)
904 0 : return RegExpRunStatus_Error;
905 :
906 : RegExpStatics* res;
907 258 : if (staticsUpdate == UpdateRegExpStatics) {
908 258 : res = GlobalObject::getRegExpStatics(cx, cx->global());
909 258 : if (!res)
910 0 : return RegExpRunStatus_Error;
911 : } else {
912 0 : res = nullptr;
913 : }
914 :
915 516 : RootedLinearString input(cx, string->ensureLinear(cx));
916 258 : if (!input)
917 0 : return RegExpRunStatus_Error;
918 :
919 : /* Handled by caller */
920 258 : MOZ_ASSERT(lastIndex >= 0 && size_t(lastIndex) <= input->length());
921 :
922 : /* Steps 4-8 performed by the caller. */
923 :
924 : /* Step 10. */
925 258 : if (reobj->unicode()) {
926 : /*
927 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad
928 : * 21.2.2.2 step 2.
929 : * Let listIndex be the index into Input of the character that was
930 : * obtained from element index of str.
931 : *
932 : * In the spec, pattern match is performed with decoded Unicode code
933 : * points, but our implementation performs it with UTF-16 encoded
934 : * string. In step 2, we should decrement lastIndex (index) if it
935 : * points the trail surrogate that has corresponding lead surrogate.
936 : *
937 : * var r = /\uD83D\uDC38/ug;
938 : * r.lastIndex = 1;
939 : * var str = "\uD83D\uDC38";
940 : * var result = r.exec(str); // pattern match starts from index 0
941 : * print(result.index); // prints 0
942 : *
943 : * Note: this doesn't match the current spec text and result in
944 : * different values for `result.index` under certain conditions.
945 : * However, the spec will change to match our implementation's
946 : * behavior. See https://github.com/tc39/ecma262/issues/128.
947 : */
948 0 : if (IsTrailSurrogateWithLeadSurrogate(cx, input, lastIndex))
949 0 : lastIndex--;
950 : }
951 :
952 : /* Steps 3, 11-14, except 12.a.i, 12.c.i.1. */
953 258 : RegExpRunStatus status = ExecuteRegExpImpl(cx, res, &re, input, lastIndex, matches, endIndex);
954 258 : if (status == RegExpRunStatus_Error)
955 0 : return RegExpRunStatus_Error;
956 :
957 : /* Steps 12.a.i, 12.c.i.i, 15 are done by Self-hosted function. */
958 :
959 258 : return status;
960 : }
961 :
962 : /*
963 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
964 : * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
965 : */
966 : static bool
967 100 : RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
968 : int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval)
969 : {
970 : /* Execute regular expression and gather matches. */
971 200 : ScopedMatchPairs matches(&cx->tempLifoAlloc());
972 :
973 : /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
974 : RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
975 100 : &matches, nullptr, staticsUpdate);
976 100 : if (status == RegExpRunStatus_Error)
977 0 : return false;
978 :
979 : /* Steps 12.a, 12.c. */
980 100 : if (status == RegExpRunStatus_Success_NotFound) {
981 43 : rval.setNull();
982 43 : return true;
983 : }
984 :
985 : /* Steps 16-25 */
986 57 : return CreateRegExpMatchResult(cx, string, matches, rval);
987 : }
988 :
989 : /*
990 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
991 : * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
992 : */
993 : bool
994 100 : js::RegExpMatcher(JSContext* cx, unsigned argc, Value* vp)
995 : {
996 100 : CallArgs args = CallArgsFromVp(argc, vp);
997 100 : MOZ_ASSERT(args.length() == 3);
998 100 : MOZ_ASSERT(IsRegExpObject(args[0]));
999 100 : MOZ_ASSERT(args[1].isString());
1000 100 : MOZ_ASSERT(args[2].isNumber());
1001 :
1002 200 : RootedObject regexp(cx, &args[0].toObject());
1003 200 : RootedString string(cx, args[1].toString());
1004 :
1005 : int32_t lastIndex;
1006 100 : MOZ_ALWAYS_TRUE(ToInt32(cx, args[2], &lastIndex));
1007 :
1008 : /* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
1009 200 : return RegExpMatcherImpl(cx, regexp, string, lastIndex,
1010 200 : UpdateRegExpStatics, args.rval());
1011 : }
1012 :
1013 : /*
1014 : * Separate interface for use by IonMonkey.
1015 : * This code cannot re-enter Ion code.
1016 : */
1017 : bool
1018 0 : js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
1019 : int32_t lastIndex,
1020 : MatchPairs* maybeMatches, MutableHandleValue output)
1021 : {
1022 0 : MOZ_ASSERT(lastIndex >= 0);
1023 :
1024 : // The MatchPairs will always be passed in, but RegExp execution was
1025 : // successful only if the pairs have actually been filled in.
1026 0 : if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0)
1027 0 : return CreateRegExpMatchResult(cx, input, *maybeMatches, output);
1028 : return RegExpMatcherImpl(cx, regexp, input, lastIndex,
1029 0 : UpdateRegExpStatics, output);
1030 : }
1031 :
1032 : /*
1033 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
1034 : * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
1035 : * This code is inlined in CodeGenerator.cpp generateRegExpSearcherStub,
1036 : * changes to this code need to get reflected in there too.
1037 : */
1038 : static bool
1039 24 : RegExpSearcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
1040 : int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, int32_t* result)
1041 : {
1042 : /* Execute regular expression and gather matches. */
1043 48 : ScopedMatchPairs matches(&cx->tempLifoAlloc());
1044 :
1045 : /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
1046 : RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
1047 24 : &matches, nullptr, staticsUpdate);
1048 24 : if (status == RegExpRunStatus_Error)
1049 0 : return false;
1050 :
1051 : /* Steps 12.a, 12.c. */
1052 24 : if (status == RegExpRunStatus_Success_NotFound) {
1053 21 : *result = -1;
1054 21 : return true;
1055 : }
1056 :
1057 : /* Steps 16-25 */
1058 3 : *result = CreateRegExpSearchResult(cx, matches);
1059 3 : return true;
1060 : }
1061 :
1062 : /*
1063 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
1064 : * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
1065 : */
1066 : bool
1067 24 : js::RegExpSearcher(JSContext* cx, unsigned argc, Value* vp)
1068 : {
1069 24 : CallArgs args = CallArgsFromVp(argc, vp);
1070 24 : MOZ_ASSERT(args.length() == 3);
1071 24 : MOZ_ASSERT(IsRegExpObject(args[0]));
1072 24 : MOZ_ASSERT(args[1].isString());
1073 24 : MOZ_ASSERT(args[2].isNumber());
1074 :
1075 48 : RootedObject regexp(cx, &args[0].toObject());
1076 48 : RootedString string(cx, args[1].toString());
1077 :
1078 : int32_t lastIndex;
1079 24 : MOZ_ALWAYS_TRUE(ToInt32(cx, args[2], &lastIndex));
1080 :
1081 : /* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
1082 24 : int32_t result = 0;
1083 24 : if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, UpdateRegExpStatics, &result))
1084 0 : return false;
1085 :
1086 24 : args.rval().setInt32(result);
1087 24 : return true;
1088 : }
1089 :
1090 : /*
1091 : * Separate interface for use by IonMonkey.
1092 : * This code cannot re-enter Ion code.
1093 : */
1094 : bool
1095 0 : js::RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
1096 : int32_t lastIndex, MatchPairs* maybeMatches, int32_t* result)
1097 : {
1098 0 : MOZ_ASSERT(lastIndex >= 0);
1099 :
1100 : // The MatchPairs will always be passed in, but RegExp execution was
1101 : // successful only if the pairs have actually been filled in.
1102 0 : if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0) {
1103 0 : *result = CreateRegExpSearchResult(cx, *maybeMatches);
1104 0 : return true;
1105 : }
1106 : return RegExpSearcherImpl(cx, regexp, input, lastIndex,
1107 0 : UpdateRegExpStatics, result);
1108 : }
1109 :
1110 : bool
1111 0 : js::regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp)
1112 : {
1113 0 : CallArgs args = CallArgsFromVp(argc, vp);
1114 0 : MOZ_ASSERT(args.length() == 2);
1115 0 : MOZ_ASSERT(IsRegExpObject(args[0]));
1116 0 : MOZ_ASSERT(args[1].isString());
1117 :
1118 0 : RootedObject regexp(cx, &args[0].toObject());
1119 0 : RootedString string(cx, args[1].toString());
1120 :
1121 0 : return RegExpMatcherImpl(cx, regexp, string, 0,
1122 0 : DontUpdateRegExpStatics, args.rval());
1123 : }
1124 :
1125 : /*
1126 : * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
1127 : * steps 3, 9-14, except 12.a.i, 12.c.i.1.
1128 : */
1129 : bool
1130 134 : js::RegExpTester(JSContext* cx, unsigned argc, Value* vp)
1131 : {
1132 134 : CallArgs args = CallArgsFromVp(argc, vp);
1133 134 : MOZ_ASSERT(args.length() == 3);
1134 134 : MOZ_ASSERT(IsRegExpObject(args[0]));
1135 134 : MOZ_ASSERT(args[1].isString());
1136 134 : MOZ_ASSERT(args[2].isNumber());
1137 :
1138 268 : RootedObject regexp(cx, &args[0].toObject());
1139 268 : RootedString string(cx, args[1].toString());
1140 :
1141 : int32_t lastIndex;
1142 134 : MOZ_ALWAYS_TRUE(ToInt32(cx, args[2], &lastIndex));
1143 :
1144 : /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
1145 134 : size_t endIndex = 0;
1146 268 : RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
1147 134 : nullptr, &endIndex, UpdateRegExpStatics);
1148 :
1149 134 : if (status == RegExpRunStatus_Error)
1150 0 : return false;
1151 :
1152 134 : if (status == RegExpRunStatus_Success) {
1153 62 : MOZ_ASSERT(endIndex <= INT32_MAX);
1154 62 : args.rval().setInt32(int32_t(endIndex));
1155 : } else {
1156 72 : args.rval().setInt32(-1);
1157 : }
1158 134 : return true;
1159 : }
1160 :
1161 : /*
1162 : * Separate interface for use by IonMonkey.
1163 : * This code cannot re-enter Ion code.
1164 : */
1165 : bool
1166 0 : js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
1167 : int32_t lastIndex, int32_t* endIndex)
1168 : {
1169 0 : MOZ_ASSERT(lastIndex >= 0);
1170 :
1171 0 : size_t endIndexTmp = 0;
1172 : RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex,
1173 0 : nullptr, &endIndexTmp, UpdateRegExpStatics);
1174 :
1175 0 : if (status == RegExpRunStatus_Success) {
1176 0 : MOZ_ASSERT(endIndexTmp <= INT32_MAX);
1177 0 : *endIndex = int32_t(endIndexTmp);
1178 0 : return true;
1179 : }
1180 0 : if (status == RegExpRunStatus_Success_NotFound) {
1181 0 : *endIndex = -1;
1182 0 : return true;
1183 : }
1184 :
1185 0 : return false;
1186 : }
1187 :
1188 : bool
1189 0 : js::regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp)
1190 : {
1191 0 : CallArgs args = CallArgsFromVp(argc, vp);
1192 0 : MOZ_ASSERT(args.length() == 2);
1193 0 : MOZ_ASSERT(IsRegExpObject(args[0]));
1194 0 : MOZ_ASSERT(args[1].isString());
1195 :
1196 0 : RootedObject regexp(cx, &args[0].toObject());
1197 0 : RootedString string(cx, args[1].toString());
1198 :
1199 0 : size_t ignored = 0;
1200 0 : RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, 0,
1201 0 : nullptr, &ignored, DontUpdateRegExpStatics);
1202 0 : args.rval().setBoolean(status == RegExpRunStatus_Success);
1203 0 : return status != RegExpRunStatus_Error;
1204 : }
1205 :
1206 : static void
1207 0 : GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out)
1208 : {
1209 0 : if (capture.isUndefined()) {
1210 0 : out->initEmpty(matched);
1211 0 : return;
1212 : }
1213 0 : JSLinearString& captureLinear = capture.toString()->asLinear();
1214 0 : out->init(&captureLinear, 0, captureLinear.length());
1215 : }
1216 :
1217 : template <typename CharT>
1218 : static bool
1219 0 : InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position, size_t tailPos,
1220 : MutableHandle<GCVector<Value>> captures, JSLinearString* replacement,
1221 : const CharT* replacementBegin, const CharT* currentDollar,
1222 : const CharT* replacementEnd,
1223 : JSSubString* out, size_t* skip)
1224 : {
1225 0 : MOZ_ASSERT(*currentDollar == '$');
1226 :
1227 : /* If there is only a dollar, bail now. */
1228 0 : if (currentDollar + 1 >= replacementEnd)
1229 0 : return false;
1230 :
1231 : /* ES 2016 draft Mar 25, 2016 Table 46. */
1232 0 : char16_t c = currentDollar[1];
1233 0 : if (JS7_ISDEC(c)) {
1234 : /* $n, $nn */
1235 0 : unsigned num = JS7_UNDEC(c);
1236 0 : if (num > captures.length()) {
1237 : // The result is implementation-defined, do not substitute.
1238 0 : return false;
1239 : }
1240 :
1241 0 : const CharT* currentChar = currentDollar + 2;
1242 0 : if (currentChar < replacementEnd) {
1243 0 : c = *currentChar;
1244 0 : if (JS7_ISDEC(c)) {
1245 0 : unsigned tmpNum = 10 * num + JS7_UNDEC(c);
1246 : // If num > captures.length(), the result is implementation-defined.
1247 : // Consume next character only if num <= captures.length().
1248 0 : if (tmpNum <= captures.length()) {
1249 0 : currentChar++;
1250 0 : num = tmpNum;
1251 : }
1252 : }
1253 : }
1254 :
1255 0 : if (num == 0) {
1256 : // The result is implementation-defined.
1257 : // Do not substitute.
1258 0 : return false;
1259 : }
1260 :
1261 0 : *skip = currentChar - currentDollar;
1262 :
1263 0 : MOZ_ASSERT(num <= captures.length());
1264 :
1265 0 : GetParen(matched, captures[num -1], out);
1266 0 : return true;
1267 : }
1268 :
1269 0 : *skip = 2;
1270 0 : switch (c) {
1271 : default:
1272 0 : return false;
1273 : case '$':
1274 0 : out->init(replacement, currentDollar - replacementBegin, 1);
1275 0 : break;
1276 : case '&':
1277 0 : out->init(matched, 0, matched->length());
1278 0 : break;
1279 : case '+':
1280 : // SpiderMonkey extension
1281 0 : if (captures.length() == 0)
1282 0 : out->initEmpty(matched);
1283 : else
1284 0 : GetParen(matched, captures[captures.length() - 1], out);
1285 0 : break;
1286 : case '`':
1287 0 : out->init(string, 0, position);
1288 0 : break;
1289 : case '\'':
1290 0 : out->init(string, tailPos, string->length() - tailPos);
1291 0 : break;
1292 : }
1293 0 : return true;
1294 : }
1295 :
1296 : template <typename CharT>
1297 : static bool
1298 0 : FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearString string,
1299 : size_t position, size_t tailPos, MutableHandle<GCVector<Value>> captures,
1300 : HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep)
1301 : {
1302 0 : CheckedInt<uint32_t> replen = replacement->length();
1303 :
1304 0 : JS::AutoCheckCannotGC nogc;
1305 0 : MOZ_ASSERT(firstDollarIndex < replacement->length());
1306 0 : const CharT* replacementBegin = replacement->chars<CharT>(nogc);
1307 0 : const CharT* currentDollar = replacementBegin + firstDollarIndex;
1308 0 : const CharT* replacementEnd = replacementBegin + replacement->length();
1309 0 : do {
1310 0 : JSSubString sub;
1311 : size_t skip;
1312 0 : if (InterpretDollar(matched, string, position, tailPos, captures, replacement,
1313 : replacementBegin, currentDollar, replacementEnd, &sub, &skip))
1314 : {
1315 0 : if (sub.length > skip)
1316 0 : replen += sub.length - skip;
1317 : else
1318 0 : replen -= skip - sub.length;
1319 0 : currentDollar += skip;
1320 : } else {
1321 0 : currentDollar++;
1322 : }
1323 :
1324 0 : currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
1325 : } while (currentDollar);
1326 :
1327 0 : if (!replen.isValid()) {
1328 0 : ReportAllocationOverflow(cx);
1329 0 : return false;
1330 : }
1331 :
1332 0 : *sizep = replen.value();
1333 0 : return true;
1334 : }
1335 :
1336 : static bool
1337 0 : FindReplaceLength(JSContext* cx, HandleLinearString matched, HandleLinearString string,
1338 : size_t position, size_t tailPos, MutableHandle<GCVector<Value>> captures,
1339 : HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep)
1340 : {
1341 0 : return replacement->hasLatin1Chars()
1342 0 : ? FindReplaceLengthString<Latin1Char>(cx, matched, string, position, tailPos, captures,
1343 : replacement, firstDollarIndex, sizep)
1344 : : FindReplaceLengthString<char16_t>(cx, matched, string, position, tailPos, captures,
1345 0 : replacement, firstDollarIndex, sizep);
1346 : }
1347 :
1348 : /*
1349 : * Precondition: |sb| already has necessary growth space reserved (as
1350 : * derived from FindReplaceLength), and has been inflated to TwoByte if
1351 : * necessary.
1352 : */
1353 : template <typename CharT>
1354 : static void
1355 0 : DoReplace(HandleLinearString matched, HandleLinearString string,
1356 : size_t position, size_t tailPos, MutableHandle<GCVector<Value>> captures,
1357 : HandleLinearString replacement, size_t firstDollarIndex, StringBuffer &sb)
1358 : {
1359 0 : JS::AutoCheckCannotGC nogc;
1360 0 : const CharT* replacementBegin = replacement->chars<CharT>(nogc);
1361 0 : const CharT* currentChar = replacementBegin;
1362 :
1363 0 : MOZ_ASSERT(firstDollarIndex < replacement->length());
1364 0 : const CharT* currentDollar = replacementBegin + firstDollarIndex;
1365 0 : const CharT* replacementEnd = replacementBegin + replacement->length();
1366 0 : do {
1367 : /* Move one of the constant portions of the replacement value. */
1368 0 : size_t len = currentDollar - currentChar;
1369 0 : sb.infallibleAppend(currentChar, len);
1370 0 : currentChar = currentDollar;
1371 :
1372 0 : JSSubString sub;
1373 : size_t skip;
1374 0 : if (InterpretDollar(matched, string, position, tailPos, captures, replacement,
1375 : replacementBegin, currentDollar, replacementEnd, &sub, &skip))
1376 : {
1377 0 : sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length);
1378 0 : currentChar += skip;
1379 0 : currentDollar += skip;
1380 : } else {
1381 0 : currentDollar++;
1382 : }
1383 :
1384 0 : currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
1385 : } while (currentDollar);
1386 0 : sb.infallibleAppend(currentChar, replacement->length() - (currentChar - replacementBegin));
1387 0 : }
1388 :
1389 : static bool
1390 0 : NeedTwoBytes(HandleLinearString string, HandleLinearString replacement,
1391 : HandleLinearString matched, Handle<GCVector<Value>> captures)
1392 : {
1393 0 : if (string->hasTwoByteChars())
1394 0 : return true;
1395 0 : if (replacement->hasTwoByteChars())
1396 0 : return true;
1397 0 : if (matched->hasTwoByteChars())
1398 0 : return true;
1399 :
1400 0 : for (size_t i = 0, len = captures.length(); i < len; i++) {
1401 0 : Value capture = captures[i];
1402 0 : if (capture.isUndefined())
1403 0 : continue;
1404 0 : if (capture.toString()->hasTwoByteChars())
1405 0 : return true;
1406 : }
1407 :
1408 0 : return false;
1409 : }
1410 :
1411 : /* ES 2016 draft Mar 25, 2016 21.1.3.14.1. */
1412 : bool
1413 0 : js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string,
1414 : size_t position, HandleObject capturesObj, HandleLinearString replacement,
1415 : size_t firstDollarIndex, MutableHandleValue rval)
1416 : {
1417 0 : MOZ_ASSERT(firstDollarIndex < replacement->length());
1418 :
1419 : // Step 1 (skipped).
1420 :
1421 : // Step 2.
1422 0 : size_t matchLength = matched->length();
1423 :
1424 : // Steps 3-5 (skipped).
1425 :
1426 : // Step 6.
1427 0 : MOZ_ASSERT(position <= string->length());
1428 :
1429 : // Step 10 (reordered).
1430 : uint32_t nCaptures;
1431 0 : if (!GetLengthProperty(cx, capturesObj, &nCaptures))
1432 0 : return false;
1433 :
1434 0 : Rooted<GCVector<Value>> captures(cx, GCVector<Value>(cx));
1435 0 : if (!captures.reserve(nCaptures))
1436 0 : return false;
1437 :
1438 : // Step 7.
1439 0 : RootedValue capture(cx);
1440 0 : for (uint32_t i = 0; i < nCaptures; i++) {
1441 0 : if (!GetElement(cx, capturesObj, capturesObj, i, &capture))
1442 0 : return false;
1443 :
1444 0 : if (capture.isUndefined()) {
1445 0 : captures.infallibleAppend(capture);
1446 0 : continue;
1447 : }
1448 :
1449 0 : MOZ_ASSERT(capture.isString());
1450 0 : RootedLinearString captureLinear(cx, capture.toString()->ensureLinear(cx));
1451 0 : if (!captureLinear)
1452 0 : return false;
1453 0 : captures.infallibleAppend(StringValue(captureLinear));
1454 : }
1455 :
1456 : // Step 8 (skipped).
1457 :
1458 : // Step 9.
1459 0 : CheckedInt<uint32_t> checkedTailPos(0);
1460 0 : checkedTailPos += position;
1461 0 : checkedTailPos += matchLength;
1462 0 : if (!checkedTailPos.isValid()) {
1463 0 : ReportAllocationOverflow(cx);
1464 0 : return false;
1465 : }
1466 0 : uint32_t tailPos = checkedTailPos.value();
1467 :
1468 : // Step 11.
1469 : size_t reserveLength;
1470 0 : if (!FindReplaceLength(cx, matched, string, position, tailPos, &captures, replacement,
1471 : firstDollarIndex, &reserveLength))
1472 : {
1473 0 : return false;
1474 : }
1475 :
1476 0 : StringBuffer result(cx);
1477 0 : if (NeedTwoBytes(string, replacement, matched, captures)) {
1478 0 : if (!result.ensureTwoByteChars())
1479 0 : return false;
1480 : }
1481 :
1482 0 : if (!result.reserve(reserveLength))
1483 0 : return false;
1484 :
1485 0 : if (replacement->hasLatin1Chars()) {
1486 0 : DoReplace<Latin1Char>(matched, string, position, tailPos, &captures,
1487 0 : replacement, firstDollarIndex, result);
1488 : } else {
1489 0 : DoReplace<char16_t>(matched, string, position, tailPos, &captures,
1490 0 : replacement, firstDollarIndex, result);
1491 : }
1492 :
1493 : // Step 12.
1494 0 : JSString* resultString = result.finishString();
1495 0 : if (!resultString)
1496 0 : return false;
1497 :
1498 0 : rval.setString(resultString);
1499 0 : return true;
1500 : }
1501 :
1502 : bool
1503 30 : js::GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp)
1504 : {
1505 30 : CallArgs args = CallArgsFromVp(argc, vp);
1506 30 : MOZ_ASSERT(args.length() == 1);
1507 60 : RootedString str(cx, args[0].toString());
1508 :
1509 : // Should be handled in different path.
1510 30 : MOZ_ASSERT(str->length() != 0);
1511 :
1512 30 : int32_t index = -1;
1513 30 : if (!GetFirstDollarIndexRaw(cx, str, &index))
1514 0 : return false;
1515 :
1516 30 : args.rval().setInt32(index);
1517 30 : return true;
1518 : }
1519 :
1520 : template <typename TextChar>
1521 : static MOZ_ALWAYS_INLINE int
1522 30 : GetFirstDollarIndexImpl(const TextChar* text, uint32_t textLen)
1523 : {
1524 30 : const TextChar* end = text + textLen;
1525 453 : for (const TextChar* c = text; c != end; ++c) {
1526 435 : if (*c == '$')
1527 12 : return c - text;
1528 : }
1529 18 : return -1;
1530 : }
1531 :
1532 : int32_t
1533 30 : js::GetFirstDollarIndexRawFlat(JSLinearString* text)
1534 : {
1535 30 : uint32_t len = text->length();
1536 :
1537 60 : JS::AutoCheckCannotGC nogc;
1538 30 : if (text->hasLatin1Chars())
1539 27 : return GetFirstDollarIndexImpl(text->latin1Chars(nogc), len);
1540 :
1541 3 : return GetFirstDollarIndexImpl(text->twoByteChars(nogc), len);
1542 : }
1543 :
1544 : bool
1545 30 : js::GetFirstDollarIndexRaw(JSContext* cx, HandleString str, int32_t* index)
1546 : {
1547 30 : JSLinearString* text = str->ensureLinear(cx);
1548 30 : if (!text)
1549 0 : return false;
1550 :
1551 30 : *index = GetFirstDollarIndexRawFlat(text);
1552 30 : return true;
1553 : }
1554 :
1555 : bool
1556 109 : js::RegExpPrototypeOptimizable(JSContext* cx, unsigned argc, Value* vp)
1557 : {
1558 : // This can only be called from self-hosted code.
1559 109 : CallArgs args = CallArgsFromVp(argc, vp);
1560 109 : MOZ_ASSERT(args.length() == 1);
1561 :
1562 109 : args.rval().setBoolean(RegExpPrototypeOptimizableRaw(cx, &args[0].toObject()));
1563 109 : return true;
1564 : }
1565 :
1566 : bool
1567 109 : js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto)
1568 : {
1569 218 : JS::AutoCheckCannotGC nogc;
1570 218 : AutoAssertNoPendingException aanpe(cx);
1571 109 : if (!proto->isNative())
1572 0 : return false;
1573 :
1574 109 : NativeObject* nproto = static_cast<NativeObject*>(proto);
1575 :
1576 109 : Shape* shape = cx->compartment()->regExps.getOptimizableRegExpPrototypeShape();
1577 109 : if (shape == nproto->lastProperty())
1578 101 : return true;
1579 :
1580 : JSFunction* flagsGetter;
1581 8 : if (!GetOwnGetterPure(cx, proto, NameToId(cx->names().flags), &flagsGetter))
1582 0 : return false;
1583 :
1584 8 : if (!flagsGetter)
1585 0 : return false;
1586 :
1587 8 : if (!IsSelfHostedFunctionWithName(flagsGetter, cx->names().RegExpFlagsGetter))
1588 0 : return false;
1589 :
1590 : JSNative globalGetter;
1591 8 : if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().global), &globalGetter))
1592 0 : return false;
1593 :
1594 8 : if (globalGetter != regexp_global)
1595 0 : return false;
1596 :
1597 : JSNative ignoreCaseGetter;
1598 8 : if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().ignoreCase), &ignoreCaseGetter))
1599 0 : return false;
1600 :
1601 8 : if (ignoreCaseGetter != regexp_ignoreCase)
1602 0 : return false;
1603 :
1604 : JSNative multilineGetter;
1605 8 : if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().multiline), &multilineGetter))
1606 0 : return false;
1607 :
1608 8 : if (multilineGetter != regexp_multiline)
1609 0 : return false;
1610 :
1611 : JSNative stickyGetter;
1612 8 : if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().sticky), &stickyGetter))
1613 0 : return false;
1614 :
1615 8 : if (stickyGetter != regexp_sticky)
1616 0 : return false;
1617 :
1618 : JSNative unicodeGetter;
1619 8 : if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().unicode), &unicodeGetter))
1620 0 : return false;
1621 :
1622 8 : if (unicodeGetter != regexp_unicode)
1623 0 : return false;
1624 :
1625 : // Check if @@match, @@search, and exec are own data properties,
1626 : // those values should be tested in selfhosted JS.
1627 8 : bool has = false;
1628 8 : if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().match), &has))
1629 0 : return false;
1630 8 : if (!has)
1631 0 : return false;
1632 :
1633 8 : if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().search), &has))
1634 0 : return false;
1635 8 : if (!has)
1636 0 : return false;
1637 :
1638 8 : if (!HasOwnDataPropertyPure(cx, proto, NameToId(cx->names().exec), &has))
1639 0 : return false;
1640 8 : if (!has)
1641 0 : return false;
1642 :
1643 8 : cx->compartment()->regExps.setOptimizableRegExpPrototypeShape(nproto->lastProperty());
1644 8 : return true;
1645 : }
1646 :
1647 : bool
1648 93 : js::RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp)
1649 : {
1650 : // This can only be called from self-hosted code.
1651 93 : CallArgs args = CallArgsFromVp(argc, vp);
1652 93 : MOZ_ASSERT(args.length() == 2);
1653 :
1654 186 : args.rval().setBoolean(RegExpInstanceOptimizableRaw(cx, &args[0].toObject(),
1655 279 : &args[1].toObject()));
1656 93 : return true;
1657 : }
1658 :
1659 : bool
1660 93 : js::RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto)
1661 : {
1662 186 : JS::AutoCheckCannotGC nogc;
1663 186 : AutoAssertNoPendingException aanpe(cx);
1664 :
1665 93 : RegExpObject* rx = &obj->as<RegExpObject>();
1666 :
1667 93 : Shape* shape = cx->compartment()->regExps.getOptimizableRegExpInstanceShape();
1668 93 : if (shape == rx->lastProperty())
1669 86 : return true;
1670 :
1671 7 : if (!rx->hasStaticPrototype())
1672 0 : return false;
1673 :
1674 7 : if (rx->staticPrototype() != proto)
1675 0 : return false;
1676 :
1677 7 : if (!RegExpObject::isInitialShape(rx))
1678 0 : return false;
1679 :
1680 7 : cx->compartment()->regExps.setOptimizableRegExpInstanceShape(rx->lastProperty());
1681 7 : return true;
1682 : }
1683 :
1684 : /*
1685 : * Pattern match the script to check if it is is indexing into a particular
1686 : * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
1687 : * such cases, which are used by javascript packers (particularly the popular
1688 : * Dean Edwards packer) to efficiently encode large scripts. We only handle the
1689 : * code patterns generated by such packers here.
1690 : */
1691 : bool
1692 14 : js::intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp)
1693 : {
1694 : // This can only be called from self-hosted code.
1695 14 : CallArgs args = CallArgsFromVp(argc, vp);
1696 14 : MOZ_ASSERT(args.length() == 1);
1697 :
1698 14 : JSObject& lambda = args[0].toObject();
1699 14 : args.rval().setUndefined();
1700 :
1701 14 : if (!lambda.is<JSFunction>())
1702 0 : return true;
1703 :
1704 28 : RootedFunction fun(cx, &lambda.as<JSFunction>());
1705 14 : if (!fun->isInterpreted() || fun->isClassConstructor())
1706 4 : return true;
1707 :
1708 10 : JSScript* script = JSFunction::getOrCreateScript(cx, fun);
1709 10 : if (!script)
1710 0 : return false;
1711 :
1712 10 : jsbytecode* pc = script->code();
1713 :
1714 : /*
1715 : * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'.
1716 : * Rule out the (unlikely) possibility of a function with environment
1717 : * objects since it would make our environment walk off.
1718 : */
1719 10 : if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsSomeEnvironmentObject())
1720 10 : return true;
1721 0 : EnvironmentCoordinate ec(pc);
1722 0 : EnvironmentObject* env = &fun->environment()->as<EnvironmentObject>();
1723 0 : for (unsigned i = 0; i < ec.hops(); ++i)
1724 0 : env = &env->enclosingEnvironment().as<EnvironmentObject>();
1725 0 : Value b = env->aliasedBinding(ec);
1726 0 : pc += JSOP_GETALIASEDVAR_LENGTH;
1727 :
1728 : /* Look for 'a' to be the lambda's first argument. */
1729 0 : if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0)
1730 0 : return true;
1731 0 : pc += JSOP_GETARG_LENGTH;
1732 :
1733 : /* 'b[a]' */
1734 0 : if (JSOp(*pc) != JSOP_GETELEM)
1735 0 : return true;
1736 0 : pc += JSOP_GETELEM_LENGTH;
1737 :
1738 : /* 'return b[a]' */
1739 0 : if (JSOp(*pc) != JSOP_RETURN)
1740 0 : return true;
1741 :
1742 : /* 'b' must behave like a normal object. */
1743 0 : if (!b.isObject())
1744 0 : return true;
1745 :
1746 0 : JSObject& bobj = b.toObject();
1747 0 : const Class* clasp = bobj.getClass();
1748 0 : if (!clasp->isNative() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty())
1749 0 : return true;
1750 :
1751 0 : args.rval().setObject(bobj);
1752 0 : return true;
1753 : }
1754 :
1755 : /*
1756 : * Emulates `b[a]` property access, that is detected in GetElemBaseForLambda.
1757 : * It returns the property value only if the property is data property and the
1758 : * propety value is a string. Otherwise it returns undefined.
1759 : */
1760 : bool
1761 0 : js::intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp)
1762 : {
1763 0 : CallArgs args = CallArgsFromVp(argc, vp);
1764 0 : MOZ_ASSERT(args.length() == 2);
1765 :
1766 0 : RootedObject obj(cx, &args[0].toObject());
1767 0 : if (!obj->isNative()) {
1768 : // The object is already checked to be native in GetElemBaseForLambda,
1769 : // but it can be swapped to the other class that is non-native.
1770 : // Return undefined to mark failure to get the property.
1771 0 : args.rval().setUndefined();
1772 0 : return true;
1773 : }
1774 :
1775 0 : RootedNativeObject nobj(cx, &obj->as<NativeObject>());
1776 0 : RootedString name(cx, args[1].toString());
1777 :
1778 0 : RootedAtom atom(cx, AtomizeString(cx, name));
1779 0 : if (!atom)
1780 0 : return false;
1781 :
1782 0 : RootedValue v(cx);
1783 0 : if (GetPropertyPure(cx, nobj, AtomToId(atom), v.address()) && v.isString())
1784 0 : args.rval().set(v);
1785 : else
1786 0 : args.rval().setUndefined();
1787 :
1788 0 : return true;
1789 : }
|