js/src/vm/RegExpObject.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     7 #include "vm/RegExpObject.h"
     9 #include "mozilla/MemoryReporting.h"
    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"
    19 #include "jsobjinlines.h"
    21 #include "vm/Shape-inl.h"
    23 using namespace js;
    25 using mozilla::DebugOnly;
    26 using js::frontend::TokenStream;
    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);
    33 /* RegExpObjectBuilder */
    35 RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj)
    36   : cx(cx), reobj_(cx, reobj)
    37 {}
    39 bool
    40 RegExpObjectBuilder::getOrCreate()
    41 {
    42     if (reobj_)
    43         return true;
    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);
    52     reobj_ = &obj->as<RegExpObject>();
    53     return true;
    54 }
    56 bool
    57 RegExpObjectBuilder::getOrCreateClone(HandleTypeObject type)
    58 {
    59     JS_ASSERT(!reobj_);
    60     JS_ASSERT(type->clasp() == &RegExpObject::class_);
    62     JSObject *parent = type->proto().toObject()->getParent();
    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);
    71     reobj_ = &clone->as<RegExpObject>();
    72     return true;
    73 }
    75 RegExpObject *
    76 RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared)
    77 {
    78     if (!getOrCreate())
    79         return nullptr;
    81     if (!reobj_->init(cx, source, shared.getFlags()))
    82         return nullptr;
    84     reobj_->setShared(cx, shared);
    85     return reobj_;
    86 }
    88 RegExpObject *
    89 RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags)
    90 {
    91     if (!getOrCreate())
    92         return nullptr;
    94     return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr;
    95 }
    97 RegExpObject *
    98 RegExpObjectBuilder::clone(Handle<RegExpObject *> other)
    99 {
   100     RootedTypeObject type(cx, other->type());
   101     if (!getOrCreateClone(type))
   102         return nullptr;
   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     }
   118     RegExpGuard g(cx);
   119     if (!other->getShared(cx, &g))
   120         return nullptr;
   122     Rooted<JSAtom *> source(cx, other->getSource());
   123     return build(source, *g);
   124 }
   126 /* MatchPairs */
   128 bool
   129 MatchPairs::initArray(size_t pairCount)
   130 {
   131     JS_ASSERT(pairCount > 0);
   133     /* Guarantee adequate space in buffer. */
   134     if (!allocOrExpandArray(pairCount))
   135         return false;
   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     }
   143     return true;
   144 }
   146 bool
   147 MatchPairs::initArrayFrom(MatchPairs &copyFrom)
   148 {
   149     JS_ASSERT(copyFrom.pairCount() > 0);
   151     if (!allocOrExpandArray(copyFrom.pairCount()))
   152         return false;
   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     }
   160     return true;
   161 }
   163 void
   164 MatchPairs::displace(size_t disp)
   165 {
   166     if (disp == 0)
   167         return;
   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 }
   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     }
   186     JS_ASSERT(!pairs_);
   187     pairs_ = (MatchPair *)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount);
   188     if (!pairs_)
   189         return false;
   191     pairCount_ = pairCount;
   192     return true;
   193 }
   195 bool
   196 VectorMatchPairs::allocOrExpandArray(size_t pairCount)
   197 {
   198     if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
   199         return false;
   201     pairs_ = &vec_[0];
   202     pairCount_ = pairCount;
   203     return true;
   204 }
   206 /* RegExpObject */
   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 }
   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 };
   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 }
   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;
   255     return createNoStatics(cx, source, flags, tokenStream);
   256 }
   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;
   265     RegExpObjectBuilder builder(cx);
   266     return builder.build(source, flags);
   267 }
   269 bool
   270 RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g)
   271 {
   272     Rooted<RegExpObject*> self(cx, this);
   274     JS_ASSERT(!maybeShared());
   275     if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
   276         return false;
   278     self->setShared(cx, **g);
   279     return true;
   280 }
   282 Shape *
   283 RegExpObject::assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> self)
   284 {
   285     JS_ASSERT(self->nativeEmpty());
   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);
   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;
   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 }
   311 bool
   312 RegExpObject::init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags)
   313 {
   314     Rooted<RegExpObject *> self(cx, this);
   316     if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, self))
   317         return false;
   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);
   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);
   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 }
   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;
   371     return sb.finishString();
   372 }
   374 /* RegExpShared */
   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 {}
   384 RegExpShared::~RegExpShared()
   385 {
   386 #if ENABLE_YARR_JIT
   387     codeBlock.release();
   388 #endif
   389     js_delete<BytecodePattern>(bytecode);
   390 }
   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 }
   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;
   431     reportYarrError(cx, tokenStream, error);
   432     return false;
   433 }
   435 bool
   436 RegExpShared::compile(JSContext *cx, bool matchOnly)
   437 {
   438     if (!sticky())
   439         return compile(cx, *source, matchOnly);
   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[] = {')'};
   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));
   456     JSAtom *fakeySource = sb.finishAtom();
   457     if (!fakeySource)
   458         return false;
   460     return compile(cx, *fakeySource, matchOnly);
   461 }
   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;
   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;
   481         JSGlobalData globalData(execAlloc);
   482         YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly
   483                                                    : JSC::Yarr::IncludeSubpatterns;
   485         jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode);
   487         /* Unset iff the Yarr JIT compilation was successful. */
   488         if (!codeBlock.isFallBack())
   489             return true;
   490     }
   491     codeBlock.setFallBack(true);
   492 #endif
   494     WTF::BumpPointerAllocator *bumpAlloc = cx->runtime()->getBumpPointerAllocator(cx);
   495     if (!bumpAlloc) {
   496         js_ReportOutOfMemory(cx);
   497         return false;
   498     }
   500     bytecode = byteCompile(yarrPattern, bumpAlloc).get();
   501     return true;
   502 }
   504 bool
   505 RegExpShared::compileIfNecessary(JSContext *cx)
   506 {
   507     if (hasCode() || hasBytecode())
   508         return true;
   509     return compile(cx, false);
   510 }
   512 bool
   513 RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx)
   514 {
   515     if (hasMatchOnlyCode() || hasBytecode())
   516         return true;
   517     return compile(cx, true);
   518 }
   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());
   526     {
   527         /* Compile the code at point-of-use. */
   528         AutoTraceLog logCompile(logger, TraceLogger::YarrCompile);
   529         if (!compileIfNecessary(cx))
   530             return RegExpRunStatus_Error;
   531     }
   533     /* Ensure sufficient memory for output vector. */
   534     if (!matches.initArray(pairCount()))
   535         return RegExpRunStatus_Error;
   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;
   545     if (sticky()) {
   546         displacement = start;
   547         chars += displacement;
   548         length -= displacement;
   549         start = 0;
   550     }
   552     unsigned *outputBuf = matches.rawBuf();
   553     unsigned result;
   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
   570     if (result == JSC::Yarr::offsetError) {
   571         reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError);
   572         return RegExpRunStatus_Error;
   573     }
   575     if (result == JSC::Yarr::offsetNoMatch)
   576         return RegExpRunStatus_Success_NotFound;
   578     matches.displace(displacement);
   579     matches.checkAgainst(origLength);
   580     *lastIndex = matches[0].limit;
   581     return RegExpRunStatus_Success;
   582 }
   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());
   590     {
   591         /* Compile the code at point-of-use. */
   592         AutoTraceLog logCompile(logger, TraceLogger::YarrCompile);
   593         if (!compileMatchOnlyIfNecessary(cx))
   594             return RegExpRunStatus_Error;
   595     }
   597 #ifdef DEBUG
   598     const size_t origLength = length;
   599 #endif
   600     size_t start = *lastIndex;
   601     size_t displacement = 0;
   603     if (sticky()) {
   604         displacement = start;
   605         chars += displacement;
   606         length -= displacement;
   607         start = 0;
   608     }
   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;
   617         match = MatchPair(result.start, result.end);
   618         match.displace(displacement);
   619         *lastIndex = match.limit;
   620         return RegExpRunStatus_Success;
   621     }
   622 #endif
   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;
   634     unsigned result;
   635     {
   636         AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
   637         result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf());
   638     }
   640     if (result == JSC::Yarr::offsetError) {
   641         reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError);
   642         return RegExpRunStatus_Error;
   643     }
   645     if (result == JSC::Yarr::offsetNoMatch)
   646         return RegExpRunStatus_Success_NotFound;
   648     match = MatchPair(result, matches[0].limit);
   649     match.displace(displacement);
   651 #ifdef DEBUG
   652     matches.displace(displacement);
   653     matches.checkAgainst(origLength);
   654 #endif
   656     *lastIndex = match.limit;
   657     return RegExpRunStatus_Success;
   658 }
   660 /* RegExpCompartment */
   662 RegExpCompartment::RegExpCompartment(JSRuntime *rt)
   663   : map_(rt), inUse_(rt), matchResultTemplateObject_(nullptr)
   664 {}
   666 RegExpCompartment::~RegExpCompartment()
   667 {
   668     JS_ASSERT_IF(map_.initialized(), map_.empty());
   669     JS_ASSERT_IF(inUse_.initialized(), inUse_.empty());
   670 }
   672 JSObject *
   673 RegExpCompartment::createMatchResultTemplateObject(JSContext *cx)
   674 {
   675     JS_ASSERT(!matchResultTemplateObject_);
   677     /* Create template array object */
   678     RootedObject templateObject(cx, NewDenseUnallocatedArray(cx, 0, nullptr, TenuredObject));
   679     if (!templateObject)
   680         return matchResultTemplateObject_; // = nullptr
   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
   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
   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));
   701     matchResultTemplateObject_ = templateObject;
   703     return matchResultTemplateObject_;
   704 }
   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     }
   715     return true;
   716 }
   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
   727     map_.clear();
   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     }
   737     if (matchResultTemplateObject_ &&
   738         IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet()))
   739     {
   740         matchResultTemplateObject_ = nullptr;
   741     }
   742 }
   744 void
   745 RegExpCompartment::clearTables()
   746 {
   747     JS_ASSERT(inUse_.empty());
   748     map_.clear();
   749 }
   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     }
   761     uint64_t gcNumber = cx->zone()->gcNumber();
   762     ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, gcNumber));
   763     if (!shared)
   764         return false;
   766     /* Add to RegExpShared sharing hashmap. */
   767     if (!map_.add(p, key, shared)) {
   768         js_ReportOutOfMemory(cx);
   769         return false;
   770     }
   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     }
   779     /* Since error deletes |shared|, only guard |shared| on success. */
   780     g->init(*shared.forget());
   781     return true;
   782 }
   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;
   791     return get(cx, atom, flags, g);
   792 }
   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 }
   803 /* Functions */
   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 }
   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;
   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 }
   852 template<XDRMode mode>
   853 bool
   854 js::XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp)
   855 {
   856     /* NB: Keep this in sync with CloneScriptRegExpObject. */
   858     RootedAtom source(xdr->cx());
   859     uint32_t flagsword = 0;
   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;
   875         objp->init(reobj);
   876     }
   877     return true;
   878 }
   880 template bool
   881 js::XDRScriptRegExpObject(XDRState<XDR_ENCODE> *xdr, HeapPtrObject *objp);
   883 template bool
   884 js::XDRScriptRegExpObject(XDRState<XDR_DECODE> *xdr, HeapPtrObject *objp);
   886 JSObject *
   887 js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj)
   888 {
   889     /* NB: Keep this in sync with XDRScriptRegExpObject. */
   891     RootedAtom source(cx, reobj.getSource());
   892     return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr);
   893 }

mercurial