js/src/vm/RegExpObject.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:179e390dace5
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
11 #include "frontend/TokenStream.h"
12 #include "vm/MatchPairs.h"
13 #include "vm/RegExpStatics.h"
14 #include "vm/StringBuffer.h"
15 #include "vm/TraceLogging.h"
16 #include "vm/Xdr.h"
17 #include "yarr/YarrSyntaxChecker.h"
18
19 #include "jsobjinlines.h"
20
21 #include "vm/Shape-inl.h"
22
23 using namespace js;
24
25 using mozilla::DebugOnly;
26 using js::frontend::TokenStream;
27
28 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
29 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
30 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
31 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
32
33 /* RegExpObjectBuilder */
34
35 RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj)
36 : cx(cx), reobj_(cx, reobj)
37 {}
38
39 bool
40 RegExpObjectBuilder::getOrCreate()
41 {
42 if (reobj_)
43 return true;
44
45 // Note: RegExp objects are always allocated in the tenured heap. This is
46 // not strictly required, but simplifies embedding them in jitcode.
47 JSObject *obj = NewBuiltinClassInstance(cx, &RegExpObject::class_, TenuredObject);
48 if (!obj)
49 return false;
50 obj->initPrivate(nullptr);
51
52 reobj_ = &obj->as<RegExpObject>();
53 return true;
54 }
55
56 bool
57 RegExpObjectBuilder::getOrCreateClone(HandleTypeObject type)
58 {
59 JS_ASSERT(!reobj_);
60 JS_ASSERT(type->clasp() == &RegExpObject::class_);
61
62 JSObject *parent = type->proto().toObject()->getParent();
63
64 // Note: RegExp objects are always allocated in the tenured heap. This is
65 // not strictly required, but simplifies embedding them in jitcode.
66 JSObject *clone = NewObjectWithType(cx->asJSContext(), type, parent, TenuredObject);
67 if (!clone)
68 return false;
69 clone->initPrivate(nullptr);
70
71 reobj_ = &clone->as<RegExpObject>();
72 return true;
73 }
74
75 RegExpObject *
76 RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared)
77 {
78 if (!getOrCreate())
79 return nullptr;
80
81 if (!reobj_->init(cx, source, shared.getFlags()))
82 return nullptr;
83
84 reobj_->setShared(cx, shared);
85 return reobj_;
86 }
87
88 RegExpObject *
89 RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags)
90 {
91 if (!getOrCreate())
92 return nullptr;
93
94 return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr;
95 }
96
97 RegExpObject *
98 RegExpObjectBuilder::clone(Handle<RegExpObject *> other)
99 {
100 RootedTypeObject type(cx, other->type());
101 if (!getOrCreateClone(type))
102 return nullptr;
103
104 /*
105 * Check that the RegExpShared for the original is okay to use in
106 * the clone -- if the |RegExpStatics| provides more flags we'll
107 * need a different |RegExpShared|.
108 */
109 RegExpStatics *res = other->getProto()->getParent()->as<GlobalObject>().getRegExpStatics();
110 RegExpFlag origFlags = other->getFlags();
111 RegExpFlag staticsFlags = res->getFlags();
112 if ((origFlags & staticsFlags) != staticsFlags) {
113 RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
114 Rooted<JSAtom *> source(cx, other->getSource());
115 return build(source, newFlags);
116 }
117
118 RegExpGuard g(cx);
119 if (!other->getShared(cx, &g))
120 return nullptr;
121
122 Rooted<JSAtom *> source(cx, other->getSource());
123 return build(source, *g);
124 }
125
126 /* MatchPairs */
127
128 bool
129 MatchPairs::initArray(size_t pairCount)
130 {
131 JS_ASSERT(pairCount > 0);
132
133 /* Guarantee adequate space in buffer. */
134 if (!allocOrExpandArray(pairCount))
135 return false;
136
137 /* Initialize all MatchPair objects to invalid locations. */
138 for (size_t i = 0; i < pairCount; i++) {
139 pairs_[i].start = -1;
140 pairs_[i].limit = -1;
141 }
142
143 return true;
144 }
145
146 bool
147 MatchPairs::initArrayFrom(MatchPairs &copyFrom)
148 {
149 JS_ASSERT(copyFrom.pairCount() > 0);
150
151 if (!allocOrExpandArray(copyFrom.pairCount()))
152 return false;
153
154 for (size_t i = 0; i < pairCount_; i++) {
155 JS_ASSERT(copyFrom[i].check());
156 pairs_[i].start = copyFrom[i].start;
157 pairs_[i].limit = copyFrom[i].limit;
158 }
159
160 return true;
161 }
162
163 void
164 MatchPairs::displace(size_t disp)
165 {
166 if (disp == 0)
167 return;
168
169 for (size_t i = 0; i < pairCount_; i++) {
170 JS_ASSERT(pairs_[i].check());
171 pairs_[i].start += (pairs_[i].start < 0) ? 0 : disp;
172 pairs_[i].limit += (pairs_[i].limit < 0) ? 0 : disp;
173 }
174 }
175
176 bool
177 ScopedMatchPairs::allocOrExpandArray(size_t pairCount)
178 {
179 /* Array expansion is forbidden, but array reuse is acceptable. */
180 if (pairCount_) {
181 JS_ASSERT(pairs_);
182 JS_ASSERT(pairCount_ == pairCount);
183 return true;
184 }
185
186 JS_ASSERT(!pairs_);
187 pairs_ = (MatchPair *)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount);
188 if (!pairs_)
189 return false;
190
191 pairCount_ = pairCount;
192 return true;
193 }
194
195 bool
196 VectorMatchPairs::allocOrExpandArray(size_t pairCount)
197 {
198 if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
199 return false;
200
201 pairs_ = &vec_[0];
202 pairCount_ = pairCount;
203 return true;
204 }
205
206 /* RegExpObject */
207
208 static void
209 regexp_trace(JSTracer *trc, JSObject *obj)
210 {
211 /*
212 * We have to check both conditions, since:
213 * 1. During TraceRuntime, isHeapBusy() is true
214 * 2. When a write barrier executes, IS_GC_MARKING_TRACER is true.
215 */
216 if (trc->runtime()->isHeapBusy() && IS_GC_MARKING_TRACER(trc))
217 obj->setPrivate(nullptr);
218 }
219
220 const Class RegExpObject::class_ = {
221 js_RegExp_str,
222 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
223 JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
224 JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
225 JS_PropertyStub, /* addProperty */
226 JS_DeletePropertyStub, /* delProperty */
227 JS_PropertyStub, /* getProperty */
228 JS_StrictPropertyStub, /* setProperty */
229 JS_EnumerateStub, /* enumerate */
230 JS_ResolveStub,
231 JS_ConvertStub,
232 nullptr, /* finalize */
233 nullptr, /* call */
234 nullptr, /* hasInstance */
235 nullptr, /* construct */
236 regexp_trace
237 };
238
239 RegExpObject *
240 RegExpObject::create(ExclusiveContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
241 RegExpFlag flags, TokenStream *tokenStream)
242 {
243 RegExpFlag staticsFlags = res->getFlags();
244 return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
245 }
246
247 RegExpObject *
248 RegExpObject::createNoStatics(ExclusiveContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
249 TokenStream *tokenStream)
250 {
251 RootedAtom source(cx, AtomizeChars(cx, chars, length));
252 if (!source)
253 return nullptr;
254
255 return createNoStatics(cx, source, flags, tokenStream);
256 }
257
258 RegExpObject *
259 RegExpObject::createNoStatics(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags,
260 TokenStream *tokenStream)
261 {
262 if (!RegExpShared::checkSyntax(cx, tokenStream, source))
263 return nullptr;
264
265 RegExpObjectBuilder builder(cx);
266 return builder.build(source, flags);
267 }
268
269 bool
270 RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g)
271 {
272 Rooted<RegExpObject*> self(cx, this);
273
274 JS_ASSERT(!maybeShared());
275 if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
276 return false;
277
278 self->setShared(cx, **g);
279 return true;
280 }
281
282 Shape *
283 RegExpObject::assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> self)
284 {
285 JS_ASSERT(self->nativeEmpty());
286
287 JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
288 JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
289 JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
290 JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
291 JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
292 JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
293
294 /* The lastIndex property alone is writable but non-configurable. */
295 if (!self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT))
296 return nullptr;
297
298 /* Remaining instance properties are non-writable and non-configurable. */
299 unsigned attrs = JSPROP_PERMANENT | JSPROP_READONLY;
300 if (!self->addDataProperty(cx, cx->names().source, SOURCE_SLOT, attrs))
301 return nullptr;
302 if (!self->addDataProperty(cx, cx->names().global, GLOBAL_FLAG_SLOT, attrs))
303 return nullptr;
304 if (!self->addDataProperty(cx, cx->names().ignoreCase, IGNORE_CASE_FLAG_SLOT, attrs))
305 return nullptr;
306 if (!self->addDataProperty(cx, cx->names().multiline, MULTILINE_FLAG_SLOT, attrs))
307 return nullptr;
308 return self->addDataProperty(cx, cx->names().sticky, STICKY_FLAG_SLOT, attrs);
309 }
310
311 bool
312 RegExpObject::init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags)
313 {
314 Rooted<RegExpObject *> self(cx, this);
315
316 if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, self))
317 return false;
318
319 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().lastIndex))->slot() ==
320 LAST_INDEX_SLOT);
321 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().source))->slot() ==
322 SOURCE_SLOT);
323 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().global))->slot() ==
324 GLOBAL_FLAG_SLOT);
325 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().ignoreCase))->slot() ==
326 IGNORE_CASE_FLAG_SLOT);
327 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().multiline))->slot() ==
328 MULTILINE_FLAG_SLOT);
329 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().sticky))->slot() ==
330 STICKY_FLAG_SLOT);
331
332 /*
333 * If this is a re-initialization with an existing RegExpShared, 'flags'
334 * may not match getShared()->flags, so forget the RegExpShared.
335 */
336 self->JSObject::setPrivate(nullptr);
337
338 self->zeroLastIndex();
339 self->setSource(source);
340 self->setGlobal(flags & GlobalFlag);
341 self->setIgnoreCase(flags & IgnoreCaseFlag);
342 self->setMultiline(flags & MultilineFlag);
343 self->setSticky(flags & StickyFlag);
344 return true;
345 }
346
347 JSFlatString *
348 RegExpObject::toString(JSContext *cx) const
349 {
350 JSAtom *src = getSource();
351 StringBuffer sb(cx);
352 if (size_t len = src->length()) {
353 if (!sb.reserve(len + 2))
354 return nullptr;
355 sb.infallibleAppend('/');
356 sb.infallibleAppend(src->chars(), len);
357 sb.infallibleAppend('/');
358 } else {
359 if (!sb.append("/(?:)/"))
360 return nullptr;
361 }
362 if (global() && !sb.append('g'))
363 return nullptr;
364 if (ignoreCase() && !sb.append('i'))
365 return nullptr;
366 if (multiline() && !sb.append('m'))
367 return nullptr;
368 if (sticky() && !sb.append('y'))
369 return nullptr;
370
371 return sb.finishString();
372 }
373
374 /* RegExpShared */
375
376 RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber)
377 : source(source), flags(flags), parenCount(0),
378 #if ENABLE_YARR_JIT
379 codeBlock(),
380 #endif
381 bytecode(nullptr), activeUseCount(0), gcNumberWhenUsed(gcNumber)
382 {}
383
384 RegExpShared::~RegExpShared()
385 {
386 #if ENABLE_YARR_JIT
387 codeBlock.release();
388 #endif
389 js_delete<BytecodePattern>(bytecode);
390 }
391
392 void
393 RegExpShared::reportYarrError(ExclusiveContext *cx, TokenStream *ts, ErrorCode error)
394 {
395 switch (error) {
396 case JSC::Yarr::NoError:
397 MOZ_ASSUME_UNREACHABLE("Called reportYarrError with value for no error");
398 #define COMPILE_EMSG(__code, __msg) \
399 case JSC::Yarr::__code: \
400 if (ts) \
401 ts->reportError(__msg); \
402 else \
403 JS_ReportErrorFlagsAndNumberUC(cx->asJSContext(), \
404 JSREPORT_ERROR, js_GetErrorMessage, nullptr, __msg); \
405 return
406 COMPILE_EMSG(PatternTooLarge, JSMSG_REGEXP_TOO_COMPLEX);
407 COMPILE_EMSG(QuantifierOutOfOrder, JSMSG_BAD_QUANTIFIER);
408 COMPILE_EMSG(QuantifierWithoutAtom, JSMSG_BAD_QUANTIFIER);
409 COMPILE_EMSG(MissingParentheses, JSMSG_MISSING_PAREN);
410 COMPILE_EMSG(ParenthesesUnmatched, JSMSG_UNMATCHED_RIGHT_PAREN);
411 COMPILE_EMSG(ParenthesesTypeInvalid, JSMSG_BAD_QUANTIFIER); /* "(?" with bad next char */
412 COMPILE_EMSG(CharacterClassUnmatched, JSMSG_BAD_CLASS_RANGE);
413 COMPILE_EMSG(CharacterClassInvalidRange, JSMSG_BAD_CLASS_RANGE);
414 COMPILE_EMSG(CharacterClassOutOfOrder, JSMSG_BAD_CLASS_RANGE);
415 COMPILE_EMSG(QuantifierTooLarge, JSMSG_BAD_QUANTIFIER);
416 COMPILE_EMSG(EscapeUnterminated, JSMSG_TRAILING_SLASH);
417 COMPILE_EMSG(RuntimeError, JSMSG_REGEXP_RUNTIME_ERROR);
418 #undef COMPILE_EMSG
419 default:
420 MOZ_ASSUME_UNREACHABLE("Unknown Yarr error code");
421 }
422 }
423
424 bool
425 RegExpShared::checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLinearString *source)
426 {
427 ErrorCode error = JSC::Yarr::checkSyntax(*source);
428 if (error == JSC::Yarr::NoError)
429 return true;
430
431 reportYarrError(cx, tokenStream, error);
432 return false;
433 }
434
435 bool
436 RegExpShared::compile(JSContext *cx, bool matchOnly)
437 {
438 if (!sticky())
439 return compile(cx, *source, matchOnly);
440
441 /*
442 * The sticky case we implement hackily by prepending a caret onto the front
443 * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
444 */
445 static const jschar prefix[] = {'^', '(', '?', ':'};
446 static const jschar postfix[] = {')'};
447
448 using mozilla::ArrayLength;
449 StringBuffer sb(cx);
450 if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix)))
451 return false;
452 sb.infallibleAppend(prefix, ArrayLength(prefix));
453 sb.infallibleAppend(source->chars(), source->length());
454 sb.infallibleAppend(postfix, ArrayLength(postfix));
455
456 JSAtom *fakeySource = sb.finishAtom();
457 if (!fakeySource)
458 return false;
459
460 return compile(cx, *fakeySource, matchOnly);
461 }
462
463 bool
464 RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly)
465 {
466 /* Parse the pattern. */
467 ErrorCode yarrError;
468 YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError);
469 if (yarrError) {
470 reportYarrError(cx, nullptr, yarrError);
471 return false;
472 }
473 this->parenCount = yarrPattern.m_numSubpatterns;
474
475 #if ENABLE_YARR_JIT
476 if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
477 JSC::ExecutableAllocator *execAlloc = cx->runtime()->getExecAlloc(cx);
478 if (!execAlloc)
479 return false;
480
481 JSGlobalData globalData(execAlloc);
482 YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly
483 : JSC::Yarr::IncludeSubpatterns;
484
485 jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode);
486
487 /* Unset iff the Yarr JIT compilation was successful. */
488 if (!codeBlock.isFallBack())
489 return true;
490 }
491 codeBlock.setFallBack(true);
492 #endif
493
494 WTF::BumpPointerAllocator *bumpAlloc = cx->runtime()->getBumpPointerAllocator(cx);
495 if (!bumpAlloc) {
496 js_ReportOutOfMemory(cx);
497 return false;
498 }
499
500 bytecode = byteCompile(yarrPattern, bumpAlloc).get();
501 return true;
502 }
503
504 bool
505 RegExpShared::compileIfNecessary(JSContext *cx)
506 {
507 if (hasCode() || hasBytecode())
508 return true;
509 return compile(cx, false);
510 }
511
512 bool
513 RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx)
514 {
515 if (hasMatchOnlyCode() || hasBytecode())
516 return true;
517 return compile(cx, true);
518 }
519
520 RegExpRunStatus
521 RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length,
522 size_t *lastIndex, MatchPairs &matches)
523 {
524 TraceLogger *logger = TraceLoggerForMainThread(cx->runtime());
525
526 {
527 /* Compile the code at point-of-use. */
528 AutoTraceLog logCompile(logger, TraceLogger::YarrCompile);
529 if (!compileIfNecessary(cx))
530 return RegExpRunStatus_Error;
531 }
532
533 /* Ensure sufficient memory for output vector. */
534 if (!matches.initArray(pairCount()))
535 return RegExpRunStatus_Error;
536
537 /*
538 * |displacement| emulates sticky mode by matching from this offset
539 * into the char buffer and subtracting the delta off at the end.
540 */
541 size_t origLength = length;
542 size_t start = *lastIndex;
543 size_t displacement = 0;
544
545 if (sticky()) {
546 displacement = start;
547 chars += displacement;
548 length -= displacement;
549 start = 0;
550 }
551
552 unsigned *outputBuf = matches.rawBuf();
553 unsigned result;
554
555 #if ENABLE_YARR_JIT
556 if (codeBlock.isFallBack()) {
557 AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
558 result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf);
559 } else {
560 AutoTraceLog logJIT(logger, TraceLogger::YarrJIT);
561 result = codeBlock.execute(chars, start, length, (int *)outputBuf).start;
562 }
563 #else
564 {
565 AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
566 result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf);
567 }
568 #endif
569
570 if (result == JSC::Yarr::offsetError) {
571 reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError);
572 return RegExpRunStatus_Error;
573 }
574
575 if (result == JSC::Yarr::offsetNoMatch)
576 return RegExpRunStatus_Success_NotFound;
577
578 matches.displace(displacement);
579 matches.checkAgainst(origLength);
580 *lastIndex = matches[0].limit;
581 return RegExpRunStatus_Success;
582 }
583
584 RegExpRunStatus
585 RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length,
586 size_t *lastIndex, MatchPair &match)
587 {
588 TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime());
589
590 {
591 /* Compile the code at point-of-use. */
592 AutoTraceLog logCompile(logger, TraceLogger::YarrCompile);
593 if (!compileMatchOnlyIfNecessary(cx))
594 return RegExpRunStatus_Error;
595 }
596
597 #ifdef DEBUG
598 const size_t origLength = length;
599 #endif
600 size_t start = *lastIndex;
601 size_t displacement = 0;
602
603 if (sticky()) {
604 displacement = start;
605 chars += displacement;
606 length -= displacement;
607 start = 0;
608 }
609
610 #if ENABLE_YARR_JIT
611 if (!codeBlock.isFallBack()) {
612 AutoTraceLog logJIT(logger, TraceLogger::YarrJIT);
613 MatchResult result = codeBlock.execute(chars, start, length);
614 if (!result)
615 return RegExpRunStatus_Success_NotFound;
616
617 match = MatchPair(result.start, result.end);
618 match.displace(displacement);
619 *lastIndex = match.limit;
620 return RegExpRunStatus_Success;
621 }
622 #endif
623
624 /*
625 * The JIT could not be used, so fall back to the Yarr interpreter.
626 * Unfortunately, the interpreter does not have a MatchOnly mode, so a
627 * temporary output vector must be provided.
628 */
629 JS_ASSERT(hasBytecode());
630 ScopedMatchPairs matches(&cx->tempLifoAlloc());
631 if (!matches.initArray(pairCount()))
632 return RegExpRunStatus_Error;
633
634 unsigned result;
635 {
636 AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
637 result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf());
638 }
639
640 if (result == JSC::Yarr::offsetError) {
641 reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError);
642 return RegExpRunStatus_Error;
643 }
644
645 if (result == JSC::Yarr::offsetNoMatch)
646 return RegExpRunStatus_Success_NotFound;
647
648 match = MatchPair(result, matches[0].limit);
649 match.displace(displacement);
650
651 #ifdef DEBUG
652 matches.displace(displacement);
653 matches.checkAgainst(origLength);
654 #endif
655
656 *lastIndex = match.limit;
657 return RegExpRunStatus_Success;
658 }
659
660 /* RegExpCompartment */
661
662 RegExpCompartment::RegExpCompartment(JSRuntime *rt)
663 : map_(rt), inUse_(rt), matchResultTemplateObject_(nullptr)
664 {}
665
666 RegExpCompartment::~RegExpCompartment()
667 {
668 JS_ASSERT_IF(map_.initialized(), map_.empty());
669 JS_ASSERT_IF(inUse_.initialized(), inUse_.empty());
670 }
671
672 JSObject *
673 RegExpCompartment::createMatchResultTemplateObject(JSContext *cx)
674 {
675 JS_ASSERT(!matchResultTemplateObject_);
676
677 /* Create template array object */
678 RootedObject templateObject(cx, NewDenseUnallocatedArray(cx, 0, nullptr, TenuredObject));
679 if (!templateObject)
680 return matchResultTemplateObject_; // = nullptr
681
682 /* Set dummy index property */
683 RootedValue index(cx, Int32Value(0));
684 if (!baseops::DefineProperty(cx, templateObject, cx->names().index, index,
685 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE))
686 return matchResultTemplateObject_; // = nullptr
687
688 /* Set dummy input property */
689 RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString));
690 if (!baseops::DefineProperty(cx, templateObject, cx->names().input, inputVal,
691 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE))
692 return matchResultTemplateObject_; // = nullptr
693
694 // Make sure that the properties are in the right slots.
695 DebugOnly<Shape *> shape = templateObject->lastProperty();
696 JS_ASSERT(shape->previous()->slot() == 0 &&
697 shape->previous()->propidRef() == NameToId(cx->names().index));
698 JS_ASSERT(shape->slot() == 1 &&
699 shape->propidRef() == NameToId(cx->names().input));
700
701 matchResultTemplateObject_ = templateObject;
702
703 return matchResultTemplateObject_;
704 }
705
706 bool
707 RegExpCompartment::init(JSContext *cx)
708 {
709 if (!map_.init(0) || !inUse_.init(0)) {
710 if (cx)
711 js_ReportOutOfMemory(cx);
712 return false;
713 }
714
715 return true;
716 }
717
718 /* See the comment on RegExpShared lifetime in RegExpObject.h. */
719 void
720 RegExpCompartment::sweep(JSRuntime *rt)
721 {
722 #ifdef DEBUG
723 for (Map::Range r = map_.all(); !r.empty(); r.popFront())
724 JS_ASSERT(inUse_.has(r.front().value()));
725 #endif
726
727 map_.clear();
728
729 for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) {
730 RegExpShared *shared = e.front();
731 if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) {
732 js_delete(shared);
733 e.removeFront();
734 }
735 }
736
737 if (matchResultTemplateObject_ &&
738 IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet()))
739 {
740 matchResultTemplateObject_ = nullptr;
741 }
742 }
743
744 void
745 RegExpCompartment::clearTables()
746 {
747 JS_ASSERT(inUse_.empty());
748 map_.clear();
749 }
750
751 bool
752 RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
753 {
754 Key key(source, flags);
755 Map::AddPtr p = map_.lookupForAdd(key);
756 if (p) {
757 g->init(*p->value());
758 return true;
759 }
760
761 uint64_t gcNumber = cx->zone()->gcNumber();
762 ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, gcNumber));
763 if (!shared)
764 return false;
765
766 /* Add to RegExpShared sharing hashmap. */
767 if (!map_.add(p, key, shared)) {
768 js_ReportOutOfMemory(cx);
769 return false;
770 }
771
772 /* Add to list of all RegExpShared objects in this RegExpCompartment. */
773 if (!inUse_.put(shared)) {
774 map_.remove(key);
775 js_ReportOutOfMemory(cx);
776 return false;
777 }
778
779 /* Since error deletes |shared|, only guard |shared| on success. */
780 g->init(*shared.forget());
781 return true;
782 }
783
784 bool
785 RegExpCompartment::get(JSContext *cx, HandleAtom atom, JSString *opt, RegExpGuard *g)
786 {
787 RegExpFlag flags = RegExpFlag(0);
788 if (opt && !ParseRegExpFlags(cx, opt, &flags))
789 return false;
790
791 return get(cx, atom, flags, g);
792 }
793
794 size_t
795 RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
796 {
797 size_t n = 0;
798 n += map_.sizeOfExcludingThis(mallocSizeOf);
799 n += inUse_.sizeOfExcludingThis(mallocSizeOf);
800 return n;
801 }
802
803 /* Functions */
804
805 JSObject *
806 js::CloneRegExpObject(JSContext *cx, JSObject *obj_)
807 {
808 RegExpObjectBuilder builder(cx);
809 Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>());
810 JSObject *res = builder.clone(regex);
811 JS_ASSERT_IF(res, res->type() == regex->type());
812 return res;
813 }
814
815 bool
816 js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
817 {
818 size_t n = flagStr->length();
819 const jschar *s = flagStr->getChars(cx);
820 if (!s)
821 return false;
822
823 *flagsOut = RegExpFlag(0);
824 for (size_t i = 0; i < n; i++) {
825 #define HANDLE_FLAG(name_) \
826 JS_BEGIN_MACRO \
827 if (*flagsOut & (name_)) \
828 goto bad_flag; \
829 *flagsOut = RegExpFlag(*flagsOut | (name_)); \
830 JS_END_MACRO
831 switch (s[i]) {
832 case 'i': HANDLE_FLAG(IgnoreCaseFlag); break;
833 case 'g': HANDLE_FLAG(GlobalFlag); break;
834 case 'm': HANDLE_FLAG(MultilineFlag); break;
835 case 'y': HANDLE_FLAG(StickyFlag); break;
836 default:
837 bad_flag:
838 {
839 char charBuf[2];
840 charBuf[0] = char(s[i]);
841 charBuf[1] = '\0';
842 JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr,
843 JSMSG_BAD_REGEXP_FLAG, charBuf);
844 return false;
845 }
846 }
847 #undef HANDLE_FLAG
848 }
849 return true;
850 }
851
852 template<XDRMode mode>
853 bool
854 js::XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp)
855 {
856 /* NB: Keep this in sync with CloneScriptRegExpObject. */
857
858 RootedAtom source(xdr->cx());
859 uint32_t flagsword = 0;
860
861 if (mode == XDR_ENCODE) {
862 JS_ASSERT(objp);
863 RegExpObject &reobj = (*objp)->as<RegExpObject>();
864 source = reobj.getSource();
865 flagsword = reobj.getFlags();
866 }
867 if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword))
868 return false;
869 if (mode == XDR_DECODE) {
870 RegExpFlag flags = RegExpFlag(flagsword);
871 RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx(), source, flags, nullptr);
872 if (!reobj)
873 return false;
874
875 objp->init(reobj);
876 }
877 return true;
878 }
879
880 template bool
881 js::XDRScriptRegExpObject(XDRState<XDR_ENCODE> *xdr, HeapPtrObject *objp);
882
883 template bool
884 js::XDRScriptRegExpObject(XDRState<XDR_DECODE> *xdr, HeapPtrObject *objp);
885
886 JSObject *
887 js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj)
888 {
889 /* NB: Keep this in sync with XDRScriptRegExpObject. */
890
891 RootedAtom source(cx, reobj.getSource());
892 return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr);
893 }

mercurial