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 "vm/RegExpObject.h"
8 :
9 : #include "mozilla/MemoryReporting.h"
10 : #include "mozilla/PodOperations.h"
11 :
12 : #include "jshashutil.h"
13 : #include "jsstr.h"
14 : #ifdef DEBUG
15 : #include "jsutil.h"
16 : #endif
17 :
18 : #include "builtin/RegExp.h"
19 : #include "frontend/TokenStream.h"
20 : #ifdef DEBUG
21 : #include "irregexp/RegExpBytecode.h"
22 : #endif
23 : #include "irregexp/RegExpParser.h"
24 : #include "vm/MatchPairs.h"
25 : #include "vm/RegExpStatics.h"
26 : #include "vm/StringBuffer.h"
27 : #include "vm/TraceLogging.h"
28 : #ifdef DEBUG
29 : #include "vm/Unicode.h"
30 : #endif
31 : #include "vm/Xdr.h"
32 :
33 : #include "jsobjinlines.h"
34 :
35 : #include "vm/NativeObject-inl.h"
36 : #include "vm/Shape-inl.h"
37 :
38 : using namespace js;
39 :
40 : using mozilla::ArrayLength;
41 : using mozilla::DebugOnly;
42 : using mozilla::Maybe;
43 : using mozilla::PodCopy;
44 : using js::frontend::TokenStream;
45 :
46 : using JS::AutoCheckCannotGC;
47 :
48 : JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
49 : JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
50 : JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
51 : JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
52 : JS_STATIC_ASSERT(UnicodeFlag == JSREG_UNICODE);
53 :
54 : RegExpObject*
55 677 : js::RegExpAlloc(JSContext* cx, NewObjectKind newKind, HandleObject proto /* = nullptr */)
56 : {
57 1353 : Rooted<RegExpObject*> regexp(cx, NewObjectWithClassProto<RegExpObject>(cx, proto, newKind));
58 676 : if (!regexp)
59 0 : return nullptr;
60 :
61 676 : regexp->initPrivate(nullptr);
62 :
63 676 : if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, regexp))
64 0 : return nullptr;
65 :
66 676 : MOZ_ASSERT(regexp->lookupPure(cx->names().lastIndex)->slot() ==
67 : RegExpObject::lastIndexSlot());
68 :
69 676 : return regexp;
70 : }
71 :
72 : /* MatchPairs */
73 :
74 : bool
75 60 : MatchPairs::initArrayFrom(MatchPairs& copyFrom)
76 : {
77 60 : MOZ_ASSERT(copyFrom.pairCount() > 0);
78 :
79 60 : if (!allocOrExpandArray(copyFrom.pairCount()))
80 0 : return false;
81 :
82 60 : PodCopy(pairs_, copyFrom.pairs_, pairCount_);
83 :
84 60 : return true;
85 : }
86 :
87 : bool
88 124 : ScopedMatchPairs::allocOrExpandArray(size_t pairCount)
89 : {
90 : /* Array expansion is forbidden, but array reuse is acceptable. */
91 124 : if (pairCount_) {
92 0 : MOZ_ASSERT(pairs_);
93 0 : MOZ_ASSERT(pairCount_ == pairCount);
94 0 : return true;
95 : }
96 :
97 124 : MOZ_ASSERT(!pairs_);
98 124 : pairs_ = (MatchPair*)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount);
99 124 : if (!pairs_)
100 0 : return false;
101 :
102 124 : pairCount_ = pairCount;
103 124 : return true;
104 : }
105 :
106 : bool
107 66 : VectorMatchPairs::allocOrExpandArray(size_t pairCount)
108 : {
109 66 : if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
110 0 : return false;
111 :
112 66 : pairs_ = &vec_[0];
113 66 : pairCount_ = pairCount;
114 66 : return true;
115 : }
116 :
117 : /* RegExpObject */
118 :
119 : /* static */ RegExpShared*
120 481 : RegExpObject::getShared(JSContext* cx, Handle<RegExpObject*> regexp)
121 : {
122 481 : if (regexp->hasShared())
123 408 : return regexp->sharedRef();
124 :
125 73 : return createShared(cx, regexp);
126 : }
127 :
128 : /* static */ bool
129 0 : RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask)
130 : {
131 0 : if (native == regexp_global) {
132 0 : *mask = GlobalFlag;
133 0 : return true;
134 : }
135 0 : if (native == regexp_ignoreCase) {
136 0 : *mask = IgnoreCaseFlag;
137 0 : return true;
138 : }
139 0 : if (native == regexp_multiline) {
140 0 : *mask = MultilineFlag;
141 0 : return true;
142 : }
143 0 : if (native == regexp_sticky) {
144 0 : *mask = StickyFlag;
145 0 : return true;
146 : }
147 0 : if (native == regexp_unicode) {
148 0 : *mask = UnicodeFlag;
149 0 : return true;
150 : }
151 :
152 0 : return false;
153 : }
154 :
155 : /* static */ void
156 33 : RegExpObject::trace(JSTracer* trc, JSObject* obj)
157 : {
158 33 : obj->as<RegExpObject>().trace(trc);
159 33 : }
160 :
161 : static inline bool
162 0 : IsMarkingTrace(JSTracer* trc)
163 : {
164 : // Determine whether tracing is happening during normal marking. We need to
165 : // test all the following conditions, since:
166 : //
167 : // 1. During TraceRuntime, CurrentThreadIsHeapBusy() is true, but the
168 : // tracer might not be a marking tracer.
169 : // 2. When a write barrier executes, IsMarkingTracer is true, but
170 : // CurrentThreadIsHeapBusy() will be false.
171 :
172 0 : return JS::CurrentThreadIsHeapCollecting() && trc->isMarkingTracer();
173 : }
174 :
175 : void
176 33 : RegExpObject::trace(JSTracer* trc)
177 : {
178 33 : TraceNullableEdge(trc, &sharedRef(), "RegExpObject shared");
179 33 : }
180 :
181 : static JSObject*
182 83 : CreateRegExpPrototype(JSContext* cx, JSProtoKey key)
183 : {
184 83 : return GlobalObject::createBlankPrototype(cx, cx->global(), &RegExpObject::protoClass_);
185 : }
186 :
187 : static const ClassOps RegExpObjectClassOps = {
188 : nullptr, /* addProperty */
189 : nullptr, /* delProperty */
190 : nullptr, /* getProperty */
191 : nullptr, /* setProperty */
192 : nullptr, /* enumerate */
193 : nullptr, /* newEnumerate */
194 : nullptr, /* resolve */
195 : nullptr, /* mayResolve */
196 : nullptr, /* finalize */
197 : nullptr, /* call */
198 : nullptr, /* hasInstance */
199 : nullptr, /* construct */
200 : RegExpObject::trace,
201 : };
202 :
203 : static const ClassSpec RegExpObjectClassSpec = {
204 : GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>,
205 : CreateRegExpPrototype,
206 : nullptr,
207 : js::regexp_static_props,
208 : js::regexp_methods,
209 : js::regexp_properties
210 : };
211 :
212 : const Class RegExpObject::class_ = {
213 : js_RegExp_str,
214 : JSCLASS_HAS_PRIVATE |
215 : JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
216 : JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
217 : &RegExpObjectClassOps,
218 : &RegExpObjectClassSpec
219 : };
220 :
221 : const Class RegExpObject::protoClass_ = {
222 : js_Object_str,
223 : JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
224 : JS_NULL_CLASS_OPS,
225 : &RegExpObjectClassSpec
226 : };
227 :
228 : RegExpObject*
229 169 : RegExpObject::create(JSContext* cx, const char16_t* chars, size_t length, RegExpFlag flags,
230 : const ReadOnlyCompileOptions* options, TokenStream* tokenStream,
231 : LifoAlloc& alloc, NewObjectKind newKind)
232 : {
233 338 : RootedAtom source(cx, AtomizeChars(cx, chars, length));
234 169 : if (!source)
235 0 : return nullptr;
236 :
237 169 : return create(cx, source, flags, options, tokenStream, alloc, newKind);
238 : }
239 :
240 : RegExpObject*
241 675 : RegExpObject::create(JSContext* cx, HandleAtom source, RegExpFlag flags,
242 : const ReadOnlyCompileOptions* options, TokenStream* tokenStream,
243 : LifoAlloc& alloc, NewObjectKind newKind)
244 : {
245 1349 : Maybe<CompileOptions> dummyOptions;
246 675 : if (!tokenStream && !options) {
247 350 : dummyOptions.emplace(cx);
248 350 : options = dummyOptions.ptr();
249 : }
250 1349 : Maybe<TokenStream> dummyTokenStream;
251 675 : if (!tokenStream) {
252 1012 : dummyTokenStream.emplace(cx, *options,
253 : (const char16_t*) nullptr, 0,
254 506 : (frontend::StrictModeGetter*) nullptr);
255 506 : tokenStream = dummyTokenStream.ptr();
256 : }
257 :
258 675 : if (!irregexp::ParsePatternSyntax(*tokenStream, alloc, source, flags & UnicodeFlag))
259 0 : return nullptr;
260 :
261 1349 : Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, newKind));
262 674 : if (!regexp)
263 0 : return nullptr;
264 :
265 674 : regexp->initAndZeroLastIndex(source, flags, cx);
266 :
267 674 : return regexp;
268 : }
269 :
270 : /* static */ RegExpShared*
271 73 : RegExpObject::createShared(JSContext* cx, Handle<RegExpObject*> regexp)
272 : {
273 73 : MOZ_ASSERT(!regexp->hasShared());
274 146 : RootedAtom source(cx, regexp->getSource());
275 73 : RegExpShared* shared = cx->zone()->regExps.get(cx, source, regexp->getFlags());
276 73 : if (!shared)
277 0 : return nullptr;
278 :
279 73 : regexp->setShared(*shared);
280 73 : return shared;
281 : }
282 :
283 : Shape*
284 13 : RegExpObject::assignInitialShape(JSContext* cx, Handle<RegExpObject*> self)
285 : {
286 13 : MOZ_ASSERT(self->empty());
287 :
288 : JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
289 :
290 : /* The lastIndex property alone is writable but non-configurable. */
291 26 : return NativeObject::addDataProperty(cx, self, cx->names().lastIndex, LAST_INDEX_SLOT,
292 26 : JSPROP_PERMANENT);
293 : }
294 :
295 : void
296 899 : RegExpObject::initIgnoringLastIndex(JSAtom* source, RegExpFlag flags)
297 : {
298 : // If this is a re-initialization with an existing RegExpShared, 'flags'
299 : // may not match getShared()->flags, so forget the RegExpShared.
300 899 : sharedRef() = nullptr;
301 :
302 899 : setSource(source);
303 899 : setFlags(flags);
304 899 : }
305 :
306 : void
307 897 : RegExpObject::initAndZeroLastIndex(JSAtom* source, RegExpFlag flags, JSContext* cx)
308 : {
309 897 : initIgnoringLastIndex(source, flags);
310 897 : zeroLastIndex(cx);
311 897 : }
312 :
313 : static MOZ_ALWAYS_INLINE bool
314 0 : IsLineTerminator(const JS::Latin1Char c)
315 : {
316 0 : return c == '\n' || c == '\r';
317 : }
318 :
319 : static MOZ_ALWAYS_INLINE bool
320 0 : IsLineTerminator(const char16_t c)
321 : {
322 0 : return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029;
323 : }
324 :
325 : static MOZ_ALWAYS_INLINE bool
326 0 : AppendEscapedLineTerminator(StringBuffer& sb, const JS::Latin1Char c)
327 : {
328 0 : switch (c) {
329 : case '\n':
330 0 : if (!sb.append('n'))
331 0 : return false;
332 0 : break;
333 : case '\r':
334 0 : if (!sb.append('r'))
335 0 : return false;
336 0 : break;
337 : default:
338 0 : MOZ_CRASH("Bad LineTerminator");
339 : }
340 0 : return true;
341 : }
342 :
343 : static MOZ_ALWAYS_INLINE bool
344 0 : AppendEscapedLineTerminator(StringBuffer& sb, const char16_t c)
345 : {
346 0 : switch (c) {
347 : case '\n':
348 0 : if (!sb.append('n'))
349 0 : return false;
350 0 : break;
351 : case '\r':
352 0 : if (!sb.append('r'))
353 0 : return false;
354 0 : break;
355 : case 0x2028:
356 0 : if (!sb.append("u2028"))
357 0 : return false;
358 0 : break;
359 : case 0x2029:
360 0 : if (!sb.append("u2029"))
361 0 : return false;
362 0 : break;
363 : default:
364 0 : MOZ_CRASH("Bad LineTerminator");
365 : }
366 0 : return true;
367 : }
368 :
369 : template <typename CharT>
370 : static MOZ_ALWAYS_INLINE bool
371 0 : SetupBuffer(StringBuffer& sb, const CharT* oldChars, size_t oldLen, const CharT* it)
372 : {
373 0 : if (mozilla::IsSame<CharT, char16_t>::value && !sb.ensureTwoByteChars())
374 0 : return false;
375 :
376 0 : if (!sb.reserve(oldLen + 1))
377 0 : return false;
378 :
379 0 : sb.infallibleAppend(oldChars, size_t(it - oldChars));
380 0 : return true;
381 : }
382 :
383 : // Note: leaves the string buffer empty if no escaping need be performed.
384 : template <typename CharT>
385 : static bool
386 0 : EscapeRegExpPattern(StringBuffer& sb, const CharT* oldChars, size_t oldLen)
387 : {
388 0 : bool inBrackets = false;
389 0 : bool previousCharacterWasBackslash = false;
390 :
391 0 : for (const CharT* it = oldChars; it < oldChars + oldLen; ++it) {
392 0 : CharT ch = *it;
393 0 : if (!previousCharacterWasBackslash) {
394 0 : if (inBrackets) {
395 0 : if (ch == ']')
396 0 : inBrackets = false;
397 0 : } else if (ch == '/') {
398 : // There's a forward slash that needs escaping.
399 0 : if (sb.empty()) {
400 : // This is the first char we've seen that needs escaping,
401 : // copy everything up to this point.
402 0 : if (!SetupBuffer(sb, oldChars, oldLen, it))
403 0 : return false;
404 : }
405 0 : if (!sb.append('\\'))
406 0 : return false;
407 0 : } else if (ch == '[') {
408 0 : inBrackets = true;
409 : }
410 : }
411 :
412 0 : if (IsLineTerminator(ch)) {
413 : // There's LineTerminator that needs escaping.
414 0 : if (sb.empty()) {
415 : // This is the first char we've seen that needs escaping,
416 : // copy everything up to this point.
417 0 : if (!SetupBuffer(sb, oldChars, oldLen, it))
418 0 : return false;
419 : }
420 0 : if (!previousCharacterWasBackslash) {
421 0 : if (!sb.append('\\'))
422 0 : return false;
423 : }
424 0 : if (!AppendEscapedLineTerminator(sb, ch))
425 0 : return false;
426 0 : } else if (!sb.empty()) {
427 0 : if (!sb.append(ch))
428 0 : return false;
429 : }
430 :
431 0 : if (previousCharacterWasBackslash)
432 0 : previousCharacterWasBackslash = false;
433 0 : else if (ch == '\\')
434 0 : previousCharacterWasBackslash = true;
435 : }
436 :
437 0 : return true;
438 : }
439 :
440 : // ES6 draft rev32 21.2.3.2.4.
441 : JSAtom*
442 0 : js::EscapeRegExpPattern(JSContext* cx, HandleAtom src)
443 : {
444 : // Step 2.
445 0 : if (src->length() == 0)
446 0 : return cx->names().emptyRegExp;
447 :
448 : // We may never need to use |sb|. Start using it lazily.
449 0 : StringBuffer sb(cx);
450 :
451 0 : if (src->hasLatin1Chars()) {
452 0 : JS::AutoCheckCannotGC nogc;
453 0 : if (!::EscapeRegExpPattern(sb, src->latin1Chars(nogc), src->length()))
454 0 : return nullptr;
455 : } else {
456 0 : JS::AutoCheckCannotGC nogc;
457 0 : if (!::EscapeRegExpPattern(sb, src->twoByteChars(nogc), src->length()))
458 0 : return nullptr;
459 : }
460 :
461 : // Step 3.
462 0 : return sb.empty() ? src : sb.finishAtom();
463 : }
464 :
465 : // ES6 draft rev32 21.2.5.14. Optimized for RegExpObject.
466 : JSFlatString*
467 0 : RegExpObject::toString(JSContext* cx) const
468 : {
469 : // Steps 3-4.
470 0 : RootedAtom src(cx, getSource());
471 0 : if (!src)
472 0 : return nullptr;
473 0 : RootedAtom escapedSrc(cx, EscapeRegExpPattern(cx, src));
474 :
475 : // Step 7.
476 0 : StringBuffer sb(cx);
477 0 : size_t len = escapedSrc->length();
478 0 : if (!sb.reserve(len + 2))
479 0 : return nullptr;
480 0 : sb.infallibleAppend('/');
481 0 : if (!sb.append(escapedSrc))
482 0 : return nullptr;
483 0 : sb.infallibleAppend('/');
484 :
485 : // Steps 5-7.
486 0 : if (global() && !sb.append('g'))
487 0 : return nullptr;
488 0 : if (ignoreCase() && !sb.append('i'))
489 0 : return nullptr;
490 0 : if (multiline() && !sb.append('m'))
491 0 : return nullptr;
492 0 : if (unicode() && !sb.append('u'))
493 0 : return nullptr;
494 0 : if (sticky() && !sb.append('y'))
495 0 : return nullptr;
496 :
497 0 : return sb.finishString();
498 : }
499 :
500 : #ifdef DEBUG
501 : /* static */ bool
502 0 : RegExpShared::dumpBytecode(JSContext* cx, MutableHandleRegExpShared re, bool match_only,
503 : HandleLinearString input)
504 : {
505 0 : CompilationMode mode = match_only ? MatchOnly : Normal;
506 0 : if (!RegExpShared::compileIfNecessary(cx, re, input, mode, ForceByteCode))
507 0 : return false;
508 :
509 0 : const uint8_t* byteCode = re->compilation(mode, input->hasLatin1Chars()).byteCode;
510 0 : const uint8_t* pc = byteCode;
511 :
512 0 : auto Load32Aligned = [](const uint8_t* pc) -> int32_t {
513 0 : MOZ_ASSERT((reinterpret_cast<uintptr_t>(pc) & 3) == 0);
514 0 : return *reinterpret_cast<const int32_t*>(pc);
515 : };
516 :
517 0 : auto Load16Aligned = [](const uint8_t* pc) -> int32_t {
518 0 : MOZ_ASSERT((reinterpret_cast<uintptr_t>(pc) & 1) == 0);
519 0 : return *reinterpret_cast<const uint16_t*>(pc);
520 : };
521 :
522 0 : int32_t numRegisters = Load32Aligned(pc);
523 0 : fprintf(stderr, "numRegisters: %d\n", numRegisters);
524 0 : pc += 4;
525 :
526 0 : fprintf(stderr, "loc op\n");
527 0 : fprintf(stderr, "----- --\n");
528 :
529 0 : auto DumpLower = [](const char* text) {
530 0 : while (*text) {
531 0 : fprintf(stderr, "%c", unicode::ToLowerCase(*text));
532 0 : text++;
533 : }
534 0 : };
535 :
536 : #define BYTECODE(NAME) \
537 : case irregexp::BC_##NAME: \
538 : DumpLower(#NAME);
539 : #define ADVANCE(NAME) \
540 : fprintf(stderr, "\n"); \
541 : pc += irregexp::BC_##NAME##_LENGTH; \
542 : maxPc = js::Max(maxPc, pc); \
543 : break;
544 : #define STOP(NAME) \
545 : fprintf(stderr, "\n"); \
546 : pc += irregexp::BC_##NAME##_LENGTH; \
547 : break;
548 : #define JUMP(NAME, OFFSET) \
549 : fprintf(stderr, "\n"); \
550 : maxPc = js::Max(maxPc, byteCode + OFFSET); \
551 : pc += irregexp::BC_##NAME##_LENGTH; \
552 : break;
553 : #define BRANCH(NAME, OFFSET) \
554 : fprintf(stderr, "\n"); \
555 : pc += irregexp::BC_##NAME##_LENGTH; \
556 : maxPc = js::Max(maxPc, js::Max(pc, byteCode + OFFSET)); \
557 : break;
558 :
559 : // Bytecode has no end marker, we need to calculate the bytecode length by
560 : // tracing jumps and branches.
561 0 : const uint8_t* maxPc = pc;
562 0 : while (pc <= maxPc) {
563 0 : fprintf(stderr, "%05d: ", int32_t(pc - byteCode));
564 0 : int32_t insn = Load32Aligned(pc);
565 0 : switch (insn & irregexp::BYTECODE_MASK) {
566 0 : BYTECODE(BREAK) {
567 0 : STOP(BREAK);
568 : }
569 0 : BYTECODE(PUSH_CP) {
570 0 : ADVANCE(PUSH_CP);
571 : }
572 0 : BYTECODE(PUSH_BT) {
573 0 : int32_t offset = Load32Aligned(pc + 4);
574 : fprintf(stderr, " %d",
575 0 : offset);
576 : // Pushed value is used by POP_BT for jumping.
577 : // Resolve maxPc here.
578 0 : BRANCH(PUSH_BT, offset);
579 : }
580 0 : BYTECODE(PUSH_REGISTER) {
581 0 : fprintf(stderr, " reg[%d]",
582 0 : insn >> irregexp::BYTECODE_SHIFT);
583 0 : ADVANCE(PUSH_REGISTER);
584 : }
585 0 : BYTECODE(SET_REGISTER) {
586 0 : fprintf(stderr, " reg[%d], %d",
587 : insn >> irregexp::BYTECODE_SHIFT,
588 0 : Load32Aligned(pc + 4));
589 0 : ADVANCE(SET_REGISTER);
590 : }
591 0 : BYTECODE(ADVANCE_REGISTER) {
592 0 : fprintf(stderr, " reg[%d], %d",
593 : insn >> irregexp::BYTECODE_SHIFT,
594 0 : Load32Aligned(pc + 4));
595 0 : ADVANCE(ADVANCE_REGISTER);
596 : }
597 0 : BYTECODE(SET_REGISTER_TO_CP) {
598 0 : fprintf(stderr, " reg[%d], %d",
599 : insn >> irregexp::BYTECODE_SHIFT,
600 0 : Load32Aligned(pc + 4));
601 0 : ADVANCE(SET_REGISTER_TO_CP);
602 : }
603 0 : BYTECODE(SET_CP_TO_REGISTER) {
604 0 : fprintf(stderr, " reg[%d]",
605 0 : insn >> irregexp::BYTECODE_SHIFT);
606 0 : ADVANCE(SET_CP_TO_REGISTER);
607 : }
608 0 : BYTECODE(SET_REGISTER_TO_SP) {
609 0 : fprintf(stderr, " reg[%d]",
610 0 : insn >> irregexp::BYTECODE_SHIFT);
611 0 : ADVANCE(SET_REGISTER_TO_SP);
612 : }
613 0 : BYTECODE(SET_SP_TO_REGISTER) {
614 0 : fprintf(stderr, " reg[%d]",
615 0 : insn >> irregexp::BYTECODE_SHIFT);
616 0 : ADVANCE(SET_SP_TO_REGISTER);
617 : }
618 0 : BYTECODE(POP_CP) {
619 0 : ADVANCE(POP_CP);
620 : }
621 0 : BYTECODE(POP_BT) {
622 : // Jump is already resolved in PUSH_BT.
623 0 : STOP(POP_BT);
624 : }
625 0 : BYTECODE(POP_REGISTER) {
626 0 : fprintf(stderr, " reg[%d]",
627 0 : insn >> irregexp::BYTECODE_SHIFT);
628 0 : ADVANCE(POP_REGISTER);
629 : }
630 0 : BYTECODE(FAIL) {
631 0 : ADVANCE(FAIL);
632 : }
633 0 : BYTECODE(SUCCEED) {
634 0 : ADVANCE(SUCCEED);
635 : }
636 0 : BYTECODE(ADVANCE_CP) {
637 0 : fprintf(stderr, " %d",
638 0 : insn >> irregexp::BYTECODE_SHIFT);
639 0 : ADVANCE(ADVANCE_CP);
640 : }
641 0 : BYTECODE(GOTO) {
642 0 : int32_t offset = Load32Aligned(pc + 4);
643 : fprintf(stderr, " %d",
644 0 : offset);
645 0 : JUMP(GOTO, offset);
646 : }
647 0 : BYTECODE(ADVANCE_CP_AND_GOTO) {
648 0 : int32_t offset = Load32Aligned(pc + 4);
649 0 : fprintf(stderr, " %d, %d",
650 : insn >> irregexp::BYTECODE_SHIFT,
651 0 : offset);
652 0 : JUMP(ADVANCE_CP_AND_GOTO, offset);
653 : }
654 0 : BYTECODE(CHECK_GREEDY) {
655 0 : int32_t offset = Load32Aligned(pc + 4);
656 : fprintf(stderr, " %d",
657 0 : offset);
658 0 : BRANCH(CHECK_GREEDY, offset);
659 : }
660 0 : BYTECODE(LOAD_CURRENT_CHAR) {
661 0 : int32_t offset = Load32Aligned(pc + 4);
662 0 : fprintf(stderr, " %d, %d",
663 : insn >> irregexp::BYTECODE_SHIFT,
664 0 : offset);
665 0 : BRANCH(LOAD_CURRENT_CHAR, offset);
666 : }
667 0 : BYTECODE(LOAD_CURRENT_CHAR_UNCHECKED) {
668 0 : fprintf(stderr, " %d",
669 0 : insn >> irregexp::BYTECODE_SHIFT);
670 0 : ADVANCE(LOAD_CURRENT_CHAR_UNCHECKED);
671 : }
672 0 : BYTECODE(LOAD_2_CURRENT_CHARS) {
673 0 : int32_t offset = Load32Aligned(pc + 4);
674 0 : fprintf(stderr, " %d, %d",
675 : insn >> irregexp::BYTECODE_SHIFT,
676 0 : offset);
677 0 : BRANCH(LOAD_2_CURRENT_CHARS, offset);
678 : }
679 0 : BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) {
680 0 : fprintf(stderr, " %d",
681 0 : insn >> irregexp::BYTECODE_SHIFT);
682 0 : ADVANCE(LOAD_2_CURRENT_CHARS_UNCHECKED);
683 : }
684 0 : BYTECODE(LOAD_4_CURRENT_CHARS) {
685 0 : ADVANCE(LOAD_4_CURRENT_CHARS);
686 : }
687 0 : BYTECODE(LOAD_4_CURRENT_CHARS_UNCHECKED) {
688 0 : ADVANCE(LOAD_4_CURRENT_CHARS_UNCHECKED);
689 : }
690 0 : BYTECODE(CHECK_4_CHARS) {
691 0 : int32_t offset = Load32Aligned(pc + 8);
692 0 : fprintf(stderr, " %d, %d",
693 : Load32Aligned(pc + 4),
694 0 : offset);
695 0 : BRANCH(CHECK_4_CHARS, offset);
696 : }
697 0 : BYTECODE(CHECK_CHAR) {
698 0 : int32_t offset = Load32Aligned(pc + 4);
699 0 : fprintf(stderr, " %d, %d",
700 : insn >> irregexp::BYTECODE_SHIFT,
701 0 : offset);
702 0 : BRANCH(CHECK_CHAR, offset);
703 : }
704 0 : BYTECODE(CHECK_NOT_4_CHARS) {
705 0 : int32_t offset = Load32Aligned(pc + 8);
706 0 : fprintf(stderr, " %d, %d",
707 : Load32Aligned(pc + 4),
708 0 : offset);
709 0 : BRANCH(CHECK_NOT_4_CHARS, offset);
710 : }
711 0 : BYTECODE(CHECK_NOT_CHAR) {
712 0 : int32_t offset = Load32Aligned(pc + 4);
713 0 : fprintf(stderr, " %d, %d",
714 : insn >> irregexp::BYTECODE_SHIFT,
715 0 : offset);
716 0 : BRANCH(CHECK_NOT_CHAR, offset);
717 : }
718 0 : BYTECODE(AND_CHECK_4_CHARS) {
719 0 : int32_t offset = Load32Aligned(pc + 12);
720 0 : fprintf(stderr, " %d, %d, %d",
721 : Load32Aligned(pc + 4),
722 : Load32Aligned(pc + 8),
723 0 : offset);
724 0 : BRANCH(AND_CHECK_4_CHARS, offset);
725 : }
726 0 : BYTECODE(AND_CHECK_CHAR) {
727 0 : int32_t offset = Load32Aligned(pc + 8);
728 0 : fprintf(stderr, " %d, %d, %d",
729 : insn >> irregexp::BYTECODE_SHIFT,
730 : Load32Aligned(pc + 4),
731 0 : offset);
732 0 : BRANCH(AND_CHECK_CHAR, offset);
733 : }
734 0 : BYTECODE(AND_CHECK_NOT_4_CHARS) {
735 0 : int32_t offset = Load32Aligned(pc + 12);
736 0 : fprintf(stderr, " %d, %d, %d",
737 : Load32Aligned(pc + 4),
738 : Load32Aligned(pc + 8),
739 0 : offset);
740 0 : BRANCH(AND_CHECK_NOT_4_CHARS, offset);
741 : }
742 0 : BYTECODE(AND_CHECK_NOT_CHAR) {
743 0 : int32_t offset = Load32Aligned(pc + 8);
744 0 : fprintf(stderr, " %d, %d, %d",
745 : insn >> irregexp::BYTECODE_SHIFT,
746 : Load32Aligned(pc + 4),
747 0 : offset);
748 0 : BRANCH(AND_CHECK_NOT_CHAR, offset);
749 : }
750 0 : BYTECODE(MINUS_AND_CHECK_NOT_CHAR) {
751 0 : int32_t offset = Load32Aligned(pc + 8);
752 0 : fprintf(stderr, " %d, %d, %d, %d",
753 : insn >> irregexp::BYTECODE_SHIFT,
754 : Load16Aligned(pc + 4),
755 : Load16Aligned(pc + 6),
756 0 : offset);
757 0 : BRANCH(MINUS_AND_CHECK_NOT_CHAR, offset);
758 : }
759 0 : BYTECODE(CHECK_CHAR_IN_RANGE) {
760 0 : int32_t offset = Load32Aligned(pc + 8);
761 0 : fprintf(stderr, " %d, %d, %d",
762 : Load16Aligned(pc + 4),
763 : Load16Aligned(pc + 6),
764 0 : offset);
765 0 : BRANCH(CHECK_CHAR_IN_RANGE, offset);
766 : }
767 0 : BYTECODE(CHECK_CHAR_NOT_IN_RANGE) {
768 0 : int32_t offset = Load32Aligned(pc + 8);
769 0 : fprintf(stderr, " %d, %d, %d",
770 : Load16Aligned(pc + 4),
771 : Load16Aligned(pc + 6),
772 0 : offset);
773 0 : BRANCH(CHECK_CHAR_NOT_IN_RANGE, offset);
774 : }
775 0 : BYTECODE(CHECK_BIT_IN_TABLE) {
776 0 : int32_t offset = Load32Aligned(pc + 4);
777 0 : fprintf(stderr, " %d, "
778 : "%02x %02x %02x %02x %02x %02x %02x %02x "
779 : "%02x %02x %02x %02x %02x %02x %02x %02x",
780 : offset,
781 0 : pc[8], pc[9], pc[10], pc[11],
782 0 : pc[12], pc[13], pc[14], pc[15],
783 0 : pc[16], pc[17], pc[18], pc[19],
784 0 : pc[20], pc[21], pc[22], pc[23]);
785 0 : BRANCH(CHECK_BIT_IN_TABLE, offset);
786 : }
787 0 : BYTECODE(CHECK_LT) {
788 0 : int32_t offset = Load32Aligned(pc + 4);
789 0 : fprintf(stderr, " %d, %d",
790 : insn >> irregexp::BYTECODE_SHIFT,
791 0 : offset);
792 0 : BRANCH(CHECK_LT, offset);
793 : }
794 0 : BYTECODE(CHECK_GT) {
795 0 : int32_t offset = Load32Aligned(pc + 4);
796 0 : fprintf(stderr, " %d, %d",
797 : insn >> irregexp::BYTECODE_SHIFT,
798 0 : offset);
799 0 : BRANCH(CHECK_GT, offset);
800 : }
801 0 : BYTECODE(CHECK_REGISTER_LT) {
802 0 : int32_t offset = Load32Aligned(pc + 8);
803 0 : fprintf(stderr, " reg[%d], %d, %d",
804 : insn >> irregexp::BYTECODE_SHIFT,
805 : Load32Aligned(pc + 4),
806 0 : offset);
807 0 : BRANCH(CHECK_REGISTER_LT, offset);
808 : }
809 0 : BYTECODE(CHECK_REGISTER_GE) {
810 0 : int32_t offset = Load32Aligned(pc + 8);
811 0 : fprintf(stderr, " reg[%d], %d, %d",
812 : insn >> irregexp::BYTECODE_SHIFT,
813 : Load32Aligned(pc + 4),
814 0 : offset);
815 0 : BRANCH(CHECK_REGISTER_GE, offset);
816 : }
817 0 : BYTECODE(CHECK_REGISTER_EQ_POS) {
818 0 : int32_t offset = Load32Aligned(pc + 4);
819 0 : fprintf(stderr, " reg[%d], %d",
820 : insn >> irregexp::BYTECODE_SHIFT,
821 0 : offset);
822 0 : BRANCH(CHECK_REGISTER_EQ_POS, offset);
823 : }
824 0 : BYTECODE(CHECK_NOT_REGS_EQUAL) {
825 0 : int32_t offset = Load32Aligned(pc + 8);
826 0 : fprintf(stderr, " reg[%d], %d, %d",
827 : insn >> irregexp::BYTECODE_SHIFT,
828 : Load32Aligned(pc + 4),
829 0 : offset);
830 0 : BRANCH(CHECK_NOT_REGS_EQUAL, offset);
831 : }
832 0 : BYTECODE(CHECK_NOT_BACK_REF) {
833 0 : int32_t offset = Load32Aligned(pc + 4);
834 0 : fprintf(stderr, " reg[%d], %d",
835 : insn >> irregexp::BYTECODE_SHIFT,
836 0 : offset);
837 0 : BRANCH(CHECK_NOT_BACK_REF, offset);
838 : }
839 0 : BYTECODE(CHECK_NOT_BACK_REF_NO_CASE) {
840 0 : int32_t offset = Load32Aligned(pc + 4);
841 0 : fprintf(stderr, " reg[%d], %d",
842 : insn >> irregexp::BYTECODE_SHIFT,
843 0 : offset);
844 0 : BRANCH(CHECK_NOT_BACK_REF_NO_CASE, offset);
845 : }
846 0 : BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE) {
847 0 : int32_t offset = Load32Aligned(pc + 4);
848 0 : fprintf(stderr, " reg[%d], %d",
849 : insn >> irregexp::BYTECODE_SHIFT,
850 0 : offset);
851 0 : BRANCH(CHECK_NOT_BACK_REF_NO_CASE_UNICODE, offset);
852 : }
853 0 : BYTECODE(CHECK_AT_START) {
854 0 : int32_t offset = Load32Aligned(pc + 4);
855 : fprintf(stderr, " %d",
856 0 : offset);
857 0 : BRANCH(CHECK_AT_START, offset);
858 : }
859 0 : BYTECODE(CHECK_NOT_AT_START) {
860 0 : int32_t offset = Load32Aligned(pc + 4);
861 : fprintf(stderr, " %d",
862 0 : offset);
863 0 : BRANCH(CHECK_NOT_AT_START, offset);
864 : }
865 0 : BYTECODE(SET_CURRENT_POSITION_FROM_END) {
866 0 : fprintf(stderr, " %u",
867 0 : static_cast<uint32_t>(insn) >> irregexp::BYTECODE_SHIFT);
868 0 : ADVANCE(SET_CURRENT_POSITION_FROM_END);
869 : }
870 : default:
871 0 : MOZ_CRASH("Bad bytecode");
872 : }
873 : }
874 :
875 : #undef BYTECODE
876 : #undef ADVANCE
877 : #undef STOP
878 : #undef JUMP
879 : #undef BRANCH
880 :
881 0 : return true;
882 : }
883 :
884 : /* static */ bool
885 0 : RegExpObject::dumpBytecode(JSContext* cx, Handle<RegExpObject*> regexp,
886 : bool match_only, HandleLinearString input)
887 : {
888 0 : RootedRegExpShared shared(cx, getShared(cx, regexp));
889 0 : if (!shared)
890 0 : return false;
891 :
892 0 : return RegExpShared::dumpBytecode(cx, &shared, match_only, input);
893 : }
894 : #endif
895 :
896 : template <typename CharT>
897 : static MOZ_ALWAYS_INLINE bool
898 128 : IsRegExpMetaChar(CharT ch)
899 : {
900 128 : switch (ch) {
901 : /* ES 2016 draft Mar 25, 2016 21.2.1 SyntaxCharacter. */
902 : case '^': case '$': case '\\': case '.': case '*': case '+':
903 : case '?': case '(': case ')': case '[': case ']': case '{':
904 : case '}': case '|':
905 48 : return true;
906 : default:
907 80 : return false;
908 : }
909 : }
910 :
911 : template <typename CharT>
912 : bool
913 60 : js::HasRegExpMetaChars(const CharT* chars, size_t length)
914 : {
915 140 : for (size_t i = 0; i < length; ++i) {
916 128 : if (IsRegExpMetaChar<CharT>(chars[i]))
917 48 : return true;
918 : }
919 12 : return false;
920 : }
921 :
922 : template bool
923 : js::HasRegExpMetaChars<Latin1Char>(const Latin1Char* chars, size_t length);
924 :
925 : template bool
926 : js::HasRegExpMetaChars<char16_t>(const char16_t* chars, size_t length);
927 :
928 : bool
929 42 : js::StringHasRegExpMetaChars(JSLinearString* str)
930 : {
931 84 : AutoCheckCannotGC nogc;
932 42 : if (str->hasLatin1Chars())
933 42 : return HasRegExpMetaChars(str->latin1Chars(nogc), str->length());
934 :
935 0 : return HasRegExpMetaChars(str->twoByteChars(nogc), str->length());
936 : }
937 :
938 : /* RegExpShared */
939 :
940 69 : RegExpShared::RegExpShared(JSAtom* source, RegExpFlag flags)
941 69 : : source(source), flags(flags), canStringMatch(false), parenCount(0)
942 69 : {}
943 :
944 : void
945 0 : RegExpShared::traceChildren(JSTracer* trc)
946 : {
947 : // Discard code to avoid holding onto ExecutablePools.
948 0 : if (IsMarkingTrace(trc) && trc->runtime()->gc.isShrinkingGC())
949 0 : discardJitCode();
950 :
951 0 : TraceNullableEdge(trc, &source, "RegExpShared source");
952 0 : for (auto& comp : compilationArray)
953 0 : TraceNullableEdge(trc, &comp.jitCode, "RegExpShared code");
954 0 : }
955 :
956 : void
957 0 : RegExpShared::discardJitCode()
958 : {
959 0 : for (auto& comp : compilationArray)
960 0 : comp.jitCode = nullptr;
961 :
962 : // We can also purge the tables used by JIT code.
963 0 : tables.clearAndFree();
964 0 : }
965 :
966 : void
967 0 : RegExpShared::finalize(FreeOp* fop)
968 : {
969 0 : for (auto& comp : compilationArray)
970 0 : js_free(comp.byteCode);
971 0 : tables.~JitCodeTables();
972 0 : }
973 :
974 : /* static */ bool
975 40 : RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input,
976 : CompilationMode mode, ForceByteCodeEnum force)
977 : {
978 40 : TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
979 80 : AutoTraceLog logCompile(logger, TraceLogger_IrregexpCompile);
980 :
981 80 : RootedAtom pattern(cx, re->source);
982 80 : return compile(cx, re, pattern, input, mode, force);
983 : }
984 :
985 : /* static */ bool
986 40 : RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleAtom pattern,
987 : HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force)
988 : {
989 40 : if (!re->ignoreCase() && !StringHasRegExpMetaChars(pattern))
990 3 : re->canStringMatch = true;
991 :
992 80 : CompileOptions options(cx);
993 80 : frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
994 :
995 80 : LifoAllocScope scope(&cx->tempLifoAlloc());
996 :
997 : /* Parse the pattern. */
998 40 : irregexp::RegExpCompileData data;
999 240 : if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern,
1000 80 : re->multiline(), mode == MatchOnly, re->unicode(),
1001 160 : re->ignoreCase(), re->global(), re->sticky(), &data))
1002 : {
1003 0 : return false;
1004 : }
1005 :
1006 40 : re->parenCount = data.capture_count;
1007 :
1008 80 : JitCodeTables tables;
1009 : irregexp::RegExpCode code = irregexp::CompilePattern(cx, re, &data, input,
1010 : false /* global() */,
1011 40 : re->ignoreCase(),
1012 40 : input->hasLatin1Chars(),
1013 : mode == MatchOnly,
1014 : force == ForceByteCode,
1015 40 : re->sticky(),
1016 40 : re->unicode(),
1017 160 : tables);
1018 40 : if (code.empty())
1019 0 : return false;
1020 :
1021 40 : MOZ_ASSERT(!code.jitCode || !code.byteCode);
1022 40 : MOZ_ASSERT_IF(force == ForceByteCode, code.byteCode);
1023 :
1024 40 : RegExpCompilation& compilation = re->compilation(mode, input->hasLatin1Chars());
1025 40 : if (code.jitCode) {
1026 : // First copy the tables. GC can purge the tables if the RegExpShared
1027 : // has no JIT code, so it's important to do this right before setting
1028 : // compilation.jitCode (to ensure no purging happens between adding the
1029 : // tables and setting the JIT code).
1030 56 : for (size_t i = 0; i < tables.length(); i++) {
1031 16 : if (!re->addTable(Move(tables[i])))
1032 0 : return false;
1033 : }
1034 40 : compilation.jitCode = code.jitCode;
1035 0 : } else if (code.byteCode) {
1036 0 : MOZ_ASSERT(tables.empty(), "RegExpInterpreter does not use data tables");
1037 0 : compilation.byteCode = code.byteCode;
1038 : }
1039 :
1040 40 : return true;
1041 : }
1042 :
1043 : /* static */ bool
1044 264 : RegExpShared::compileIfNecessary(JSContext* cx, MutableHandleRegExpShared re,
1045 : HandleLinearString input, CompilationMode mode,
1046 : ForceByteCodeEnum force)
1047 : {
1048 264 : if (re->isCompiled(mode, input->hasLatin1Chars(), force))
1049 224 : return true;
1050 40 : return compile(cx, re, input, mode, force);
1051 : }
1052 :
1053 : /* static */ RegExpRunStatus
1054 264 : RegExpShared::execute(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input,
1055 : size_t start, MatchPairs* matches, size_t* endIndex)
1056 : {
1057 264 : MOZ_ASSERT_IF(matches, !endIndex);
1058 264 : MOZ_ASSERT_IF(!matches, endIndex);
1059 264 : TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
1060 :
1061 264 : CompilationMode mode = matches ? Normal : MatchOnly;
1062 :
1063 : /* Compile the code at point-of-use. */
1064 264 : if (!compileIfNecessary(cx, re, input, mode, DontForceByteCode))
1065 0 : return RegExpRunStatus_Error;
1066 :
1067 : /*
1068 : * Ensure sufficient memory for output vector.
1069 : * No need to initialize it. The RegExp engine fills them in on a match.
1070 : */
1071 264 : if (matches && !matches->allocOrExpandArray(re->pairCount())) {
1072 0 : ReportOutOfMemory(cx);
1073 0 : return RegExpRunStatus_Error;
1074 : }
1075 :
1076 264 : size_t length = input->length();
1077 :
1078 : // Reset the Irregexp backtrack stack if it grows during execution.
1079 528 : irregexp::RegExpStackScope stackScope(cx);
1080 :
1081 264 : if (re->canStringMatch) {
1082 19 : MOZ_ASSERT(re->pairCount() == 1);
1083 19 : size_t sourceLength = re->source->length();
1084 19 : if (re->sticky()) {
1085 : // First part checks size_t overflow.
1086 0 : if (sourceLength + start < sourceLength || sourceLength + start > length)
1087 0 : return RegExpRunStatus_Success_NotFound;
1088 0 : if (!HasSubstringAt(input, re->source, start))
1089 0 : return RegExpRunStatus_Success_NotFound;
1090 :
1091 0 : if (matches) {
1092 0 : (*matches)[0].start = start;
1093 0 : (*matches)[0].limit = start + sourceLength;
1094 :
1095 0 : matches->checkAgainst(length);
1096 0 : } else if (endIndex) {
1097 0 : *endIndex = start + sourceLength;
1098 : }
1099 0 : return RegExpRunStatus_Success;
1100 : }
1101 :
1102 19 : int res = StringFindPattern(input, re->source, start);
1103 19 : if (res == -1)
1104 16 : return RegExpRunStatus_Success_NotFound;
1105 :
1106 3 : if (matches) {
1107 3 : (*matches)[0].start = res;
1108 3 : (*matches)[0].limit = res + sourceLength;
1109 :
1110 3 : matches->checkAgainst(length);
1111 0 : } else if (endIndex) {
1112 0 : *endIndex = res + sourceLength;
1113 : }
1114 3 : return RegExpRunStatus_Success;
1115 : }
1116 :
1117 : do {
1118 245 : jit::JitCode* code = re->compilation(mode, input->hasLatin1Chars()).jitCode;
1119 245 : if (!code)
1120 0 : break;
1121 :
1122 : RegExpRunStatus result;
1123 : {
1124 490 : AutoTraceLog logJIT(logger, TraceLogger_IrregexpExecute);
1125 490 : AutoCheckCannotGC nogc;
1126 245 : if (input->hasLatin1Chars()) {
1127 233 : const Latin1Char* chars = input->latin1Chars(nogc);
1128 233 : result = irregexp::ExecuteCode(cx, code, chars, start, length, matches, endIndex);
1129 : } else {
1130 12 : const char16_t* chars = input->twoByteChars(nogc);
1131 12 : result = irregexp::ExecuteCode(cx, code, chars, start, length, matches, endIndex);
1132 : }
1133 : }
1134 :
1135 245 : if (result == RegExpRunStatus_Error) {
1136 : // An 'Error' result is returned if a stack overflow guard or
1137 : // interrupt guard failed. If CheckOverRecursed doesn't throw, break
1138 : // out and retry the regexp in the bytecode interpreter, which can
1139 : // execute while tolerating future interrupts. Otherwise, if we keep
1140 : // getting interrupted we will never finish executing the regexp.
1141 0 : if (!jit::CheckOverRecursed(cx))
1142 0 : return RegExpRunStatus_Error;
1143 0 : break;
1144 : }
1145 :
1146 245 : if (result == RegExpRunStatus_Success_NotFound)
1147 120 : return RegExpRunStatus_Success_NotFound;
1148 :
1149 125 : MOZ_ASSERT(result == RegExpRunStatus_Success);
1150 :
1151 125 : if (matches)
1152 63 : matches->checkAgainst(length);
1153 125 : return RegExpRunStatus_Success;
1154 : } while (false);
1155 :
1156 : // Compile bytecode for the RegExp if necessary.
1157 0 : if (!compileIfNecessary(cx, re, input, mode, ForceByteCode))
1158 0 : return RegExpRunStatus_Error;
1159 :
1160 0 : uint8_t* byteCode = re->compilation(mode, input->hasLatin1Chars()).byteCode;
1161 0 : AutoTraceLog logInterpreter(logger, TraceLogger_IrregexpExecute);
1162 :
1163 0 : AutoStableStringChars inputChars(cx);
1164 0 : if (!inputChars.init(cx, input))
1165 0 : return RegExpRunStatus_Error;
1166 :
1167 : RegExpRunStatus result;
1168 0 : if (inputChars.isLatin1()) {
1169 0 : const Latin1Char* chars = inputChars.latin1Range().begin().get();
1170 0 : result = irregexp::InterpretCode(cx, byteCode, chars, start, length, matches, endIndex);
1171 : } else {
1172 0 : const char16_t* chars = inputChars.twoByteRange().begin().get();
1173 0 : result = irregexp::InterpretCode(cx, byteCode, chars, start, length, matches, endIndex);
1174 : }
1175 :
1176 0 : if (result == RegExpRunStatus_Success && matches)
1177 0 : matches->checkAgainst(length);
1178 0 : return result;
1179 : }
1180 :
1181 : size_t
1182 0 : RegExpShared::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
1183 : {
1184 0 : size_t n = 0;
1185 :
1186 0 : for (size_t i = 0; i < ArrayLength(compilationArray); i++) {
1187 0 : const RegExpCompilation& compilation = compilationArray[i];
1188 0 : if (compilation.byteCode)
1189 0 : n += mallocSizeOf(compilation.byteCode);
1190 : }
1191 :
1192 0 : n += tables.sizeOfExcludingThis(mallocSizeOf);
1193 0 : for (size_t i = 0; i < tables.length(); i++)
1194 0 : n += mallocSizeOf(tables[i].get());
1195 :
1196 0 : return n;
1197 : }
1198 :
1199 : /* RegExpCompartment */
1200 :
1201 315 : RegExpCompartment::RegExpCompartment(Zone* zone)
1202 : : matchResultTemplateObject_(nullptr),
1203 : optimizableRegExpPrototypeShape_(nullptr),
1204 315 : optimizableRegExpInstanceShape_(nullptr)
1205 315 : {}
1206 :
1207 : ArrayObject*
1208 6 : RegExpCompartment::createMatchResultTemplateObject(JSContext* cx)
1209 : {
1210 6 : MOZ_ASSERT(!matchResultTemplateObject_);
1211 :
1212 : /* Create template array object */
1213 12 : RootedArrayObject templateObject(cx, NewDenseUnallocatedArray(cx, RegExpObject::MaxPairCount,
1214 12 : nullptr, TenuredObject));
1215 6 : if (!templateObject)
1216 0 : return matchResultTemplateObject_; // = nullptr
1217 :
1218 : // Create a new group for the template.
1219 12 : Rooted<TaggedProto> proto(cx, templateObject->taggedProto());
1220 6 : ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, templateObject->getClass(), proto);
1221 6 : if (!group)
1222 0 : return matchResultTemplateObject_; // = nullptr
1223 6 : templateObject->setGroup(group);
1224 :
1225 : /* Set dummy index property */
1226 12 : RootedValue index(cx, Int32Value(0));
1227 6 : if (!NativeDefineProperty(cx, templateObject, cx->names().index, index, nullptr, nullptr,
1228 : JSPROP_ENUMERATE))
1229 : {
1230 0 : return matchResultTemplateObject_; // = nullptr
1231 : }
1232 :
1233 : /* Set dummy input property */
1234 12 : RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString));
1235 6 : if (!NativeDefineProperty(cx, templateObject, cx->names().input, inputVal, nullptr, nullptr,
1236 : JSPROP_ENUMERATE))
1237 : {
1238 0 : return matchResultTemplateObject_; // = nullptr
1239 : }
1240 :
1241 : // Make sure that the properties are in the right slots.
1242 12 : DebugOnly<Shape*> shape = templateObject->lastProperty();
1243 6 : MOZ_ASSERT(shape->previous()->slot() == 0 &&
1244 : shape->previous()->propidRef() == NameToId(cx->names().index));
1245 6 : MOZ_ASSERT(shape->slot() == 1 &&
1246 : shape->propidRef() == NameToId(cx->names().input));
1247 :
1248 : // Make sure type information reflects the indexed properties which might
1249 : // be added.
1250 6 : AddTypePropertyId(cx, templateObject, JSID_VOID, TypeSet::StringType());
1251 6 : AddTypePropertyId(cx, templateObject, JSID_VOID, TypeSet::UndefinedType());
1252 :
1253 6 : matchResultTemplateObject_.set(templateObject);
1254 :
1255 6 : return matchResultTemplateObject_;
1256 : }
1257 :
1258 : bool
1259 31 : RegExpZone::init()
1260 : {
1261 31 : if (!set_.init(0))
1262 0 : return false;
1263 :
1264 31 : return true;
1265 : }
1266 :
1267 : void
1268 0 : RegExpCompartment::sweep(JSRuntime* rt)
1269 : {
1270 0 : if (matchResultTemplateObject_ &&
1271 0 : IsAboutToBeFinalized(&matchResultTemplateObject_))
1272 : {
1273 0 : matchResultTemplateObject_.set(nullptr);
1274 : }
1275 :
1276 0 : if (optimizableRegExpPrototypeShape_ &&
1277 0 : IsAboutToBeFinalized(&optimizableRegExpPrototypeShape_))
1278 : {
1279 0 : optimizableRegExpPrototypeShape_.set(nullptr);
1280 : }
1281 :
1282 0 : if (optimizableRegExpInstanceShape_ &&
1283 0 : IsAboutToBeFinalized(&optimizableRegExpInstanceShape_))
1284 : {
1285 0 : optimizableRegExpInstanceShape_.set(nullptr);
1286 : }
1287 0 : }
1288 :
1289 : RegExpShared*
1290 79 : RegExpZone::get(JSContext* cx, HandleAtom source, RegExpFlag flags)
1291 : {
1292 79 : DependentAddPtr<Set> p(cx, set_, Key(source, flags));
1293 79 : if (p)
1294 10 : return *p;
1295 :
1296 69 : auto shared = Allocate<RegExpShared>(cx);
1297 69 : if (!shared)
1298 0 : return nullptr;
1299 :
1300 69 : new (shared) RegExpShared(source, flags);
1301 :
1302 69 : if (!p.add(cx, set_, Key(source, flags), shared)) {
1303 0 : ReportOutOfMemory(cx);
1304 0 : return nullptr;
1305 : }
1306 :
1307 69 : return shared;
1308 : }
1309 :
1310 : RegExpShared*
1311 0 : RegExpZone::get(JSContext* cx, HandleAtom atom, JSString* opt)
1312 : {
1313 0 : RegExpFlag flags = RegExpFlag(0);
1314 0 : if (opt && !ParseRegExpFlags(cx, opt, &flags))
1315 0 : return nullptr;
1316 :
1317 0 : return get(cx, atom, flags);
1318 : }
1319 :
1320 : size_t
1321 0 : RegExpZone::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
1322 : {
1323 0 : return set_.sizeOfExcludingThis(mallocSizeOf);
1324 : }
1325 :
1326 31 : RegExpZone::RegExpZone(Zone* zone)
1327 31 : : set_(zone, zone->runtimeFromActiveCooperatingThread())
1328 31 : {}
1329 :
1330 : /* Functions */
1331 :
1332 : JSObject*
1333 223 : js::CloneRegExpObject(JSContext* cx, Handle<RegExpObject*> regex)
1334 : {
1335 : // Unlike RegExpAlloc, all clones must use |regex|'s group.
1336 446 : RootedObjectGroup group(cx, regex->group());
1337 446 : Rooted<RegExpObject*> clone(cx, NewObjectWithGroup<RegExpObject>(cx, group, GenericObject));
1338 223 : if (!clone)
1339 0 : return nullptr;
1340 223 : clone->initPrivate(nullptr);
1341 :
1342 223 : if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, clone))
1343 0 : return nullptr;
1344 :
1345 223 : RegExpShared* shared = RegExpObject::getShared(cx, regex);
1346 223 : if (!shared)
1347 0 : return nullptr;
1348 :
1349 223 : clone->initAndZeroLastIndex(shared->getSource(), shared->getFlags(), cx);
1350 223 : clone->setShared(*shared);
1351 :
1352 223 : return clone;
1353 : }
1354 :
1355 : static bool
1356 1 : HandleRegExpFlag(RegExpFlag flag, RegExpFlag* flags)
1357 : {
1358 1 : if (*flags & flag)
1359 0 : return false;
1360 1 : *flags = RegExpFlag(*flags | flag);
1361 1 : return true;
1362 : }
1363 :
1364 : template <typename CharT>
1365 : static size_t
1366 1 : ParseRegExpFlags(const CharT* chars, size_t length, RegExpFlag* flagsOut, char16_t* lastParsedOut)
1367 : {
1368 1 : *flagsOut = RegExpFlag(0);
1369 :
1370 2 : for (size_t i = 0; i < length; i++) {
1371 1 : *lastParsedOut = chars[i];
1372 1 : switch (chars[i]) {
1373 : case 'i':
1374 1 : if (!HandleRegExpFlag(IgnoreCaseFlag, flagsOut))
1375 0 : return false;
1376 1 : break;
1377 : case 'g':
1378 0 : if (!HandleRegExpFlag(GlobalFlag, flagsOut))
1379 0 : return false;
1380 0 : break;
1381 : case 'm':
1382 0 : if (!HandleRegExpFlag(MultilineFlag, flagsOut))
1383 0 : return false;
1384 0 : break;
1385 : case 'y':
1386 0 : if (!HandleRegExpFlag(StickyFlag, flagsOut))
1387 0 : return false;
1388 0 : break;
1389 : case 'u':
1390 0 : if (!HandleRegExpFlag(UnicodeFlag, flagsOut))
1391 0 : return false;
1392 0 : break;
1393 : default:
1394 0 : return false;
1395 : }
1396 : }
1397 :
1398 1 : return true;
1399 : }
1400 :
1401 : bool
1402 1 : js::ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut)
1403 : {
1404 1 : JSLinearString* linear = flagStr->ensureLinear(cx);
1405 1 : if (!linear)
1406 0 : return false;
1407 :
1408 1 : size_t len = linear->length();
1409 :
1410 : bool ok;
1411 : char16_t lastParsed;
1412 1 : if (linear->hasLatin1Chars()) {
1413 2 : AutoCheckCannotGC nogc;
1414 1 : ok = ::ParseRegExpFlags(linear->latin1Chars(nogc), len, flagsOut, &lastParsed);
1415 : } else {
1416 0 : AutoCheckCannotGC nogc;
1417 0 : ok = ::ParseRegExpFlags(linear->twoByteChars(nogc), len, flagsOut, &lastParsed);
1418 : }
1419 :
1420 1 : if (!ok) {
1421 0 : TwoByteChars range(&lastParsed, 1);
1422 0 : UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str());
1423 0 : if (!utf8)
1424 0 : return false;
1425 0 : JS_ReportErrorFlagsAndNumberUTF8(cx, JSREPORT_ERROR, GetErrorMessage, nullptr,
1426 0 : JSMSG_BAD_REGEXP_FLAG, utf8.get());
1427 0 : return false;
1428 : }
1429 :
1430 1 : return true;
1431 : }
1432 :
1433 : template<XDRMode mode>
1434 : bool
1435 339 : js::XDRScriptRegExpObject(XDRState<mode>* xdr, MutableHandle<RegExpObject*> objp)
1436 : {
1437 : /* NB: Keep this in sync with CloneScriptRegExpObject. */
1438 :
1439 677 : RootedAtom source(xdr->cx());
1440 339 : uint32_t flagsword = 0;
1441 :
1442 : if (mode == XDR_ENCODE) {
1443 88 : MOZ_ASSERT(objp);
1444 88 : RegExpObject& reobj = *objp;
1445 88 : source = reobj.getSource();
1446 88 : flagsword = reobj.getFlags();
1447 : }
1448 339 : if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword))
1449 0 : return false;
1450 : if (mode == XDR_DECODE) {
1451 251 : RegExpFlag flags = RegExpFlag(flagsword);
1452 251 : const ReadOnlyCompileOptions* options = nullptr;
1453 251 : if (xdr->hasOptions())
1454 156 : options = &xdr->options();
1455 501 : RegExpObject* reobj = RegExpObject::create(xdr->cx(), source, flags,
1456 : options, nullptr, xdr->lifoAlloc(),
1457 250 : TenuredObject);
1458 250 : if (!reobj)
1459 0 : return false;
1460 :
1461 250 : objp.set(reobj);
1462 : }
1463 338 : return true;
1464 : }
1465 :
1466 : template bool
1467 : js::XDRScriptRegExpObject(XDRState<XDR_ENCODE>* xdr, MutableHandle<RegExpObject*> objp);
1468 :
1469 : template bool
1470 : js::XDRScriptRegExpObject(XDRState<XDR_DECODE>* xdr, MutableHandle<RegExpObject*> objp);
1471 :
1472 : JSObject*
1473 255 : js::CloneScriptRegExpObject(JSContext* cx, RegExpObject& reobj)
1474 : {
1475 : /* NB: Keep this in sync with XDRScriptRegExpObject. */
1476 :
1477 510 : RootedAtom source(cx, reobj.getSource());
1478 255 : cx->markAtom(source);
1479 :
1480 510 : return RegExpObject::create(cx, source, reobj.getFlags(),
1481 : nullptr, nullptr, cx->tempLifoAlloc(),
1482 510 : TenuredObject);
1483 : }
1484 :
1485 : JS_FRIEND_API(RegExpShared*)
1486 0 : js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj)
1487 : {
1488 0 : return RegExpToShared(cx, obj);
1489 : }
1490 :
1491 : JS::ubi::Node::Size
1492 0 : JS::ubi::Concrete<RegExpShared>::size(mozilla::MallocSizeOf mallocSizeOf) const
1493 : {
1494 0 : return js::gc::Arena::thingSize(gc::AllocKind::REGEXP_SHARED) +
1495 0 : get().sizeOfExcludingThis(mallocSizeOf);
1496 : }
|