Source code

Revision control

Other Tools

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