js/src/vm/RegExpObject.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/vm/RegExpObject.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,893 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "vm/RegExpObject.h"
    1.11 +
    1.12 +#include "mozilla/MemoryReporting.h"
    1.13 +
    1.14 +#include "frontend/TokenStream.h"
    1.15 +#include "vm/MatchPairs.h"
    1.16 +#include "vm/RegExpStatics.h"
    1.17 +#include "vm/StringBuffer.h"
    1.18 +#include "vm/TraceLogging.h"
    1.19 +#include "vm/Xdr.h"
    1.20 +#include "yarr/YarrSyntaxChecker.h"
    1.21 +
    1.22 +#include "jsobjinlines.h"
    1.23 +
    1.24 +#include "vm/Shape-inl.h"
    1.25 +
    1.26 +using namespace js;
    1.27 +
    1.28 +using mozilla::DebugOnly;
    1.29 +using js::frontend::TokenStream;
    1.30 +
    1.31 +JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
    1.32 +JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
    1.33 +JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
    1.34 +JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
    1.35 +
    1.36 +/* RegExpObjectBuilder */
    1.37 +
    1.38 +RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj)
    1.39 +  : cx(cx), reobj_(cx, reobj)
    1.40 +{}
    1.41 +
    1.42 +bool
    1.43 +RegExpObjectBuilder::getOrCreate()
    1.44 +{
    1.45 +    if (reobj_)
    1.46 +        return true;
    1.47 +
    1.48 +    // Note: RegExp objects are always allocated in the tenured heap. This is
    1.49 +    // not strictly required, but simplifies embedding them in jitcode.
    1.50 +    JSObject *obj = NewBuiltinClassInstance(cx, &RegExpObject::class_, TenuredObject);
    1.51 +    if (!obj)
    1.52 +        return false;
    1.53 +    obj->initPrivate(nullptr);
    1.54 +
    1.55 +    reobj_ = &obj->as<RegExpObject>();
    1.56 +    return true;
    1.57 +}
    1.58 +
    1.59 +bool
    1.60 +RegExpObjectBuilder::getOrCreateClone(HandleTypeObject type)
    1.61 +{
    1.62 +    JS_ASSERT(!reobj_);
    1.63 +    JS_ASSERT(type->clasp() == &RegExpObject::class_);
    1.64 +
    1.65 +    JSObject *parent = type->proto().toObject()->getParent();
    1.66 +
    1.67 +    // Note: RegExp objects are always allocated in the tenured heap. This is
    1.68 +    // not strictly required, but simplifies embedding them in jitcode.
    1.69 +    JSObject *clone = NewObjectWithType(cx->asJSContext(), type, parent, TenuredObject);
    1.70 +    if (!clone)
    1.71 +        return false;
    1.72 +    clone->initPrivate(nullptr);
    1.73 +
    1.74 +    reobj_ = &clone->as<RegExpObject>();
    1.75 +    return true;
    1.76 +}
    1.77 +
    1.78 +RegExpObject *
    1.79 +RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared)
    1.80 +{
    1.81 +    if (!getOrCreate())
    1.82 +        return nullptr;
    1.83 +
    1.84 +    if (!reobj_->init(cx, source, shared.getFlags()))
    1.85 +        return nullptr;
    1.86 +
    1.87 +    reobj_->setShared(cx, shared);
    1.88 +    return reobj_;
    1.89 +}
    1.90 +
    1.91 +RegExpObject *
    1.92 +RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags)
    1.93 +{
    1.94 +    if (!getOrCreate())
    1.95 +        return nullptr;
    1.96 +
    1.97 +    return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr;
    1.98 +}
    1.99 +
   1.100 +RegExpObject *
   1.101 +RegExpObjectBuilder::clone(Handle<RegExpObject *> other)
   1.102 +{
   1.103 +    RootedTypeObject type(cx, other->type());
   1.104 +    if (!getOrCreateClone(type))
   1.105 +        return nullptr;
   1.106 +
   1.107 +    /*
   1.108 +     * Check that the RegExpShared for the original is okay to use in
   1.109 +     * the clone -- if the |RegExpStatics| provides more flags we'll
   1.110 +     * need a different |RegExpShared|.
   1.111 +     */
   1.112 +    RegExpStatics *res = other->getProto()->getParent()->as<GlobalObject>().getRegExpStatics();
   1.113 +    RegExpFlag origFlags = other->getFlags();
   1.114 +    RegExpFlag staticsFlags = res->getFlags();
   1.115 +    if ((origFlags & staticsFlags) != staticsFlags) {
   1.116 +        RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
   1.117 +        Rooted<JSAtom *> source(cx, other->getSource());
   1.118 +        return build(source, newFlags);
   1.119 +    }
   1.120 +
   1.121 +    RegExpGuard g(cx);
   1.122 +    if (!other->getShared(cx, &g))
   1.123 +        return nullptr;
   1.124 +
   1.125 +    Rooted<JSAtom *> source(cx, other->getSource());
   1.126 +    return build(source, *g);
   1.127 +}
   1.128 +
   1.129 +/* MatchPairs */
   1.130 +
   1.131 +bool
   1.132 +MatchPairs::initArray(size_t pairCount)
   1.133 +{
   1.134 +    JS_ASSERT(pairCount > 0);
   1.135 +
   1.136 +    /* Guarantee adequate space in buffer. */
   1.137 +    if (!allocOrExpandArray(pairCount))
   1.138 +        return false;
   1.139 +
   1.140 +    /* Initialize all MatchPair objects to invalid locations. */
   1.141 +    for (size_t i = 0; i < pairCount; i++) {
   1.142 +        pairs_[i].start = -1;
   1.143 +        pairs_[i].limit = -1;
   1.144 +    }
   1.145 +
   1.146 +    return true;
   1.147 +}
   1.148 +
   1.149 +bool
   1.150 +MatchPairs::initArrayFrom(MatchPairs &copyFrom)
   1.151 +{
   1.152 +    JS_ASSERT(copyFrom.pairCount() > 0);
   1.153 +
   1.154 +    if (!allocOrExpandArray(copyFrom.pairCount()))
   1.155 +        return false;
   1.156 +
   1.157 +    for (size_t i = 0; i < pairCount_; i++) {
   1.158 +        JS_ASSERT(copyFrom[i].check());
   1.159 +        pairs_[i].start = copyFrom[i].start;
   1.160 +        pairs_[i].limit = copyFrom[i].limit;
   1.161 +    }
   1.162 +
   1.163 +    return true;
   1.164 +}
   1.165 +
   1.166 +void
   1.167 +MatchPairs::displace(size_t disp)
   1.168 +{
   1.169 +    if (disp == 0)
   1.170 +        return;
   1.171 +
   1.172 +    for (size_t i = 0; i < pairCount_; i++) {
   1.173 +        JS_ASSERT(pairs_[i].check());
   1.174 +        pairs_[i].start += (pairs_[i].start < 0) ? 0 : disp;
   1.175 +        pairs_[i].limit += (pairs_[i].limit < 0) ? 0 : disp;
   1.176 +    }
   1.177 +}
   1.178 +
   1.179 +bool
   1.180 +ScopedMatchPairs::allocOrExpandArray(size_t pairCount)
   1.181 +{
   1.182 +    /* Array expansion is forbidden, but array reuse is acceptable. */
   1.183 +    if (pairCount_) {
   1.184 +        JS_ASSERT(pairs_);
   1.185 +        JS_ASSERT(pairCount_ == pairCount);
   1.186 +        return true;
   1.187 +    }
   1.188 +
   1.189 +    JS_ASSERT(!pairs_);
   1.190 +    pairs_ = (MatchPair *)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount);
   1.191 +    if (!pairs_)
   1.192 +        return false;
   1.193 +
   1.194 +    pairCount_ = pairCount;
   1.195 +    return true;
   1.196 +}
   1.197 +
   1.198 +bool
   1.199 +VectorMatchPairs::allocOrExpandArray(size_t pairCount)
   1.200 +{
   1.201 +    if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
   1.202 +        return false;
   1.203 +
   1.204 +    pairs_ = &vec_[0];
   1.205 +    pairCount_ = pairCount;
   1.206 +    return true;
   1.207 +}
   1.208 +
   1.209 +/* RegExpObject */
   1.210 +
   1.211 +static void
   1.212 +regexp_trace(JSTracer *trc, JSObject *obj)
   1.213 +{
   1.214 +     /*
   1.215 +      * We have to check both conditions, since:
   1.216 +      *   1. During TraceRuntime, isHeapBusy() is true
   1.217 +      *   2. When a write barrier executes, IS_GC_MARKING_TRACER is true.
   1.218 +      */
   1.219 +    if (trc->runtime()->isHeapBusy() && IS_GC_MARKING_TRACER(trc))
   1.220 +        obj->setPrivate(nullptr);
   1.221 +}
   1.222 +
   1.223 +const Class RegExpObject::class_ = {
   1.224 +    js_RegExp_str,
   1.225 +    JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
   1.226 +    JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
   1.227 +    JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
   1.228 +    JS_PropertyStub,         /* addProperty */
   1.229 +    JS_DeletePropertyStub,   /* delProperty */
   1.230 +    JS_PropertyStub,         /* getProperty */
   1.231 +    JS_StrictPropertyStub,   /* setProperty */
   1.232 +    JS_EnumerateStub,        /* enumerate */
   1.233 +    JS_ResolveStub,
   1.234 +    JS_ConvertStub,
   1.235 +    nullptr,                 /* finalize */
   1.236 +    nullptr,                 /* call */
   1.237 +    nullptr,                 /* hasInstance */
   1.238 +    nullptr,                 /* construct */
   1.239 +    regexp_trace
   1.240 +};
   1.241 +
   1.242 +RegExpObject *
   1.243 +RegExpObject::create(ExclusiveContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
   1.244 +                     RegExpFlag flags, TokenStream *tokenStream)
   1.245 +{
   1.246 +    RegExpFlag staticsFlags = res->getFlags();
   1.247 +    return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
   1.248 +}
   1.249 +
   1.250 +RegExpObject *
   1.251 +RegExpObject::createNoStatics(ExclusiveContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
   1.252 +                              TokenStream *tokenStream)
   1.253 +{
   1.254 +    RootedAtom source(cx, AtomizeChars(cx, chars, length));
   1.255 +    if (!source)
   1.256 +        return nullptr;
   1.257 +
   1.258 +    return createNoStatics(cx, source, flags, tokenStream);
   1.259 +}
   1.260 +
   1.261 +RegExpObject *
   1.262 +RegExpObject::createNoStatics(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags,
   1.263 +                              TokenStream *tokenStream)
   1.264 +{
   1.265 +    if (!RegExpShared::checkSyntax(cx, tokenStream, source))
   1.266 +        return nullptr;
   1.267 +
   1.268 +    RegExpObjectBuilder builder(cx);
   1.269 +    return builder.build(source, flags);
   1.270 +}
   1.271 +
   1.272 +bool
   1.273 +RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g)
   1.274 +{
   1.275 +    Rooted<RegExpObject*> self(cx, this);
   1.276 +
   1.277 +    JS_ASSERT(!maybeShared());
   1.278 +    if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
   1.279 +        return false;
   1.280 +
   1.281 +    self->setShared(cx, **g);
   1.282 +    return true;
   1.283 +}
   1.284 +
   1.285 +Shape *
   1.286 +RegExpObject::assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> self)
   1.287 +{
   1.288 +    JS_ASSERT(self->nativeEmpty());
   1.289 +
   1.290 +    JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
   1.291 +    JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
   1.292 +    JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
   1.293 +    JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
   1.294 +    JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
   1.295 +    JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
   1.296 +
   1.297 +    /* The lastIndex property alone is writable but non-configurable. */
   1.298 +    if (!self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT))
   1.299 +        return nullptr;
   1.300 +
   1.301 +    /* Remaining instance properties are non-writable and non-configurable. */
   1.302 +    unsigned attrs = JSPROP_PERMANENT | JSPROP_READONLY;
   1.303 +    if (!self->addDataProperty(cx, cx->names().source, SOURCE_SLOT, attrs))
   1.304 +        return nullptr;
   1.305 +    if (!self->addDataProperty(cx, cx->names().global, GLOBAL_FLAG_SLOT, attrs))
   1.306 +        return nullptr;
   1.307 +    if (!self->addDataProperty(cx, cx->names().ignoreCase, IGNORE_CASE_FLAG_SLOT, attrs))
   1.308 +        return nullptr;
   1.309 +    if (!self->addDataProperty(cx, cx->names().multiline, MULTILINE_FLAG_SLOT, attrs))
   1.310 +        return nullptr;
   1.311 +    return self->addDataProperty(cx, cx->names().sticky, STICKY_FLAG_SLOT, attrs);
   1.312 +}
   1.313 +
   1.314 +bool
   1.315 +RegExpObject::init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags)
   1.316 +{
   1.317 +    Rooted<RegExpObject *> self(cx, this);
   1.318 +
   1.319 +    if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, self))
   1.320 +        return false;
   1.321 +
   1.322 +    JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().lastIndex))->slot() ==
   1.323 +              LAST_INDEX_SLOT);
   1.324 +    JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().source))->slot() ==
   1.325 +              SOURCE_SLOT);
   1.326 +    JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().global))->slot() ==
   1.327 +              GLOBAL_FLAG_SLOT);
   1.328 +    JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().ignoreCase))->slot() ==
   1.329 +              IGNORE_CASE_FLAG_SLOT);
   1.330 +    JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().multiline))->slot() ==
   1.331 +              MULTILINE_FLAG_SLOT);
   1.332 +    JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().sticky))->slot() ==
   1.333 +              STICKY_FLAG_SLOT);
   1.334 +
   1.335 +    /*
   1.336 +     * If this is a re-initialization with an existing RegExpShared, 'flags'
   1.337 +     * may not match getShared()->flags, so forget the RegExpShared.
   1.338 +     */
   1.339 +    self->JSObject::setPrivate(nullptr);
   1.340 +
   1.341 +    self->zeroLastIndex();
   1.342 +    self->setSource(source);
   1.343 +    self->setGlobal(flags & GlobalFlag);
   1.344 +    self->setIgnoreCase(flags & IgnoreCaseFlag);
   1.345 +    self->setMultiline(flags & MultilineFlag);
   1.346 +    self->setSticky(flags & StickyFlag);
   1.347 +    return true;
   1.348 +}
   1.349 +
   1.350 +JSFlatString *
   1.351 +RegExpObject::toString(JSContext *cx) const
   1.352 +{
   1.353 +    JSAtom *src = getSource();
   1.354 +    StringBuffer sb(cx);
   1.355 +    if (size_t len = src->length()) {
   1.356 +        if (!sb.reserve(len + 2))
   1.357 +            return nullptr;
   1.358 +        sb.infallibleAppend('/');
   1.359 +        sb.infallibleAppend(src->chars(), len);
   1.360 +        sb.infallibleAppend('/');
   1.361 +    } else {
   1.362 +        if (!sb.append("/(?:)/"))
   1.363 +            return nullptr;
   1.364 +    }
   1.365 +    if (global() && !sb.append('g'))
   1.366 +        return nullptr;
   1.367 +    if (ignoreCase() && !sb.append('i'))
   1.368 +        return nullptr;
   1.369 +    if (multiline() && !sb.append('m'))
   1.370 +        return nullptr;
   1.371 +    if (sticky() && !sb.append('y'))
   1.372 +        return nullptr;
   1.373 +
   1.374 +    return sb.finishString();
   1.375 +}
   1.376 +
   1.377 +/* RegExpShared */
   1.378 +
   1.379 +RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber)
   1.380 +  : source(source), flags(flags), parenCount(0),
   1.381 +#if ENABLE_YARR_JIT
   1.382 +    codeBlock(),
   1.383 +#endif
   1.384 +    bytecode(nullptr), activeUseCount(0), gcNumberWhenUsed(gcNumber)
   1.385 +{}
   1.386 +
   1.387 +RegExpShared::~RegExpShared()
   1.388 +{
   1.389 +#if ENABLE_YARR_JIT
   1.390 +    codeBlock.release();
   1.391 +#endif
   1.392 +    js_delete<BytecodePattern>(bytecode);
   1.393 +}
   1.394 +
   1.395 +void
   1.396 +RegExpShared::reportYarrError(ExclusiveContext *cx, TokenStream *ts, ErrorCode error)
   1.397 +{
   1.398 +    switch (error) {
   1.399 +      case JSC::Yarr::NoError:
   1.400 +        MOZ_ASSUME_UNREACHABLE("Called reportYarrError with value for no error");
   1.401 +#define COMPILE_EMSG(__code, __msg)                                                              \
   1.402 +      case JSC::Yarr::__code:                                                                    \
   1.403 +        if (ts)                                                                                  \
   1.404 +            ts->reportError(__msg);                                                              \
   1.405 +        else                                                                                     \
   1.406 +            JS_ReportErrorFlagsAndNumberUC(cx->asJSContext(),                                    \
   1.407 +                                           JSREPORT_ERROR, js_GetErrorMessage, nullptr, __msg);     \
   1.408 +        return
   1.409 +      COMPILE_EMSG(PatternTooLarge, JSMSG_REGEXP_TOO_COMPLEX);
   1.410 +      COMPILE_EMSG(QuantifierOutOfOrder, JSMSG_BAD_QUANTIFIER);
   1.411 +      COMPILE_EMSG(QuantifierWithoutAtom, JSMSG_BAD_QUANTIFIER);
   1.412 +      COMPILE_EMSG(MissingParentheses, JSMSG_MISSING_PAREN);
   1.413 +      COMPILE_EMSG(ParenthesesUnmatched, JSMSG_UNMATCHED_RIGHT_PAREN);
   1.414 +      COMPILE_EMSG(ParenthesesTypeInvalid, JSMSG_BAD_QUANTIFIER); /* "(?" with bad next char */
   1.415 +      COMPILE_EMSG(CharacterClassUnmatched, JSMSG_BAD_CLASS_RANGE);
   1.416 +      COMPILE_EMSG(CharacterClassInvalidRange, JSMSG_BAD_CLASS_RANGE);
   1.417 +      COMPILE_EMSG(CharacterClassOutOfOrder, JSMSG_BAD_CLASS_RANGE);
   1.418 +      COMPILE_EMSG(QuantifierTooLarge, JSMSG_BAD_QUANTIFIER);
   1.419 +      COMPILE_EMSG(EscapeUnterminated, JSMSG_TRAILING_SLASH);
   1.420 +      COMPILE_EMSG(RuntimeError, JSMSG_REGEXP_RUNTIME_ERROR);
   1.421 +#undef COMPILE_EMSG
   1.422 +      default:
   1.423 +        MOZ_ASSUME_UNREACHABLE("Unknown Yarr error code");
   1.424 +    }
   1.425 +}
   1.426 +
   1.427 +bool
   1.428 +RegExpShared::checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLinearString *source)
   1.429 +{
   1.430 +    ErrorCode error = JSC::Yarr::checkSyntax(*source);
   1.431 +    if (error == JSC::Yarr::NoError)
   1.432 +        return true;
   1.433 +
   1.434 +    reportYarrError(cx, tokenStream, error);
   1.435 +    return false;
   1.436 +}
   1.437 +
   1.438 +bool
   1.439 +RegExpShared::compile(JSContext *cx, bool matchOnly)
   1.440 +{
   1.441 +    if (!sticky())
   1.442 +        return compile(cx, *source, matchOnly);
   1.443 +
   1.444 +    /*
   1.445 +     * The sticky case we implement hackily by prepending a caret onto the front
   1.446 +     * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
   1.447 +     */
   1.448 +    static const jschar prefix[] = {'^', '(', '?', ':'};
   1.449 +    static const jschar postfix[] = {')'};
   1.450 +
   1.451 +    using mozilla::ArrayLength;
   1.452 +    StringBuffer sb(cx);
   1.453 +    if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix)))
   1.454 +        return false;
   1.455 +    sb.infallibleAppend(prefix, ArrayLength(prefix));
   1.456 +    sb.infallibleAppend(source->chars(), source->length());
   1.457 +    sb.infallibleAppend(postfix, ArrayLength(postfix));
   1.458 +
   1.459 +    JSAtom *fakeySource = sb.finishAtom();
   1.460 +    if (!fakeySource)
   1.461 +        return false;
   1.462 +
   1.463 +    return compile(cx, *fakeySource, matchOnly);
   1.464 +}
   1.465 +
   1.466 +bool
   1.467 +RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly)
   1.468 +{
   1.469 +    /* Parse the pattern. */
   1.470 +    ErrorCode yarrError;
   1.471 +    YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError);
   1.472 +    if (yarrError) {
   1.473 +        reportYarrError(cx, nullptr, yarrError);
   1.474 +        return false;
   1.475 +    }
   1.476 +    this->parenCount = yarrPattern.m_numSubpatterns;
   1.477 +
   1.478 +#if ENABLE_YARR_JIT
   1.479 +    if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
   1.480 +        JSC::ExecutableAllocator *execAlloc = cx->runtime()->getExecAlloc(cx);
   1.481 +        if (!execAlloc)
   1.482 +            return false;
   1.483 +
   1.484 +        JSGlobalData globalData(execAlloc);
   1.485 +        YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly
   1.486 +                                                   : JSC::Yarr::IncludeSubpatterns;
   1.487 +
   1.488 +        jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode);
   1.489 +
   1.490 +        /* Unset iff the Yarr JIT compilation was successful. */
   1.491 +        if (!codeBlock.isFallBack())
   1.492 +            return true;
   1.493 +    }
   1.494 +    codeBlock.setFallBack(true);
   1.495 +#endif
   1.496 +
   1.497 +    WTF::BumpPointerAllocator *bumpAlloc = cx->runtime()->getBumpPointerAllocator(cx);
   1.498 +    if (!bumpAlloc) {
   1.499 +        js_ReportOutOfMemory(cx);
   1.500 +        return false;
   1.501 +    }
   1.502 +
   1.503 +    bytecode = byteCompile(yarrPattern, bumpAlloc).get();
   1.504 +    return true;
   1.505 +}
   1.506 +
   1.507 +bool
   1.508 +RegExpShared::compileIfNecessary(JSContext *cx)
   1.509 +{
   1.510 +    if (hasCode() || hasBytecode())
   1.511 +        return true;
   1.512 +    return compile(cx, false);
   1.513 +}
   1.514 +
   1.515 +bool
   1.516 +RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx)
   1.517 +{
   1.518 +    if (hasMatchOnlyCode() || hasBytecode())
   1.519 +        return true;
   1.520 +    return compile(cx, true);
   1.521 +}
   1.522 +
   1.523 +RegExpRunStatus
   1.524 +RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length,
   1.525 +                      size_t *lastIndex, MatchPairs &matches)
   1.526 +{
   1.527 +    TraceLogger *logger = TraceLoggerForMainThread(cx->runtime());
   1.528 +
   1.529 +    {
   1.530 +        /* Compile the code at point-of-use. */
   1.531 +        AutoTraceLog logCompile(logger, TraceLogger::YarrCompile);
   1.532 +        if (!compileIfNecessary(cx))
   1.533 +            return RegExpRunStatus_Error;
   1.534 +    }
   1.535 +
   1.536 +    /* Ensure sufficient memory for output vector. */
   1.537 +    if (!matches.initArray(pairCount()))
   1.538 +        return RegExpRunStatus_Error;
   1.539 +
   1.540 +    /*
   1.541 +     * |displacement| emulates sticky mode by matching from this offset
   1.542 +     * into the char buffer and subtracting the delta off at the end.
   1.543 +     */
   1.544 +    size_t origLength = length;
   1.545 +    size_t start = *lastIndex;
   1.546 +    size_t displacement = 0;
   1.547 +
   1.548 +    if (sticky()) {
   1.549 +        displacement = start;
   1.550 +        chars += displacement;
   1.551 +        length -= displacement;
   1.552 +        start = 0;
   1.553 +    }
   1.554 +
   1.555 +    unsigned *outputBuf = matches.rawBuf();
   1.556 +    unsigned result;
   1.557 +
   1.558 +#if ENABLE_YARR_JIT
   1.559 +    if (codeBlock.isFallBack()) {
   1.560 +        AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
   1.561 +        result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf);
   1.562 +    } else {
   1.563 +        AutoTraceLog logJIT(logger, TraceLogger::YarrJIT);
   1.564 +        result = codeBlock.execute(chars, start, length, (int *)outputBuf).start;
   1.565 +    }
   1.566 +#else
   1.567 +    {
   1.568 +        AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
   1.569 +        result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf);
   1.570 +    }
   1.571 +#endif
   1.572 +
   1.573 +    if (result == JSC::Yarr::offsetError) {
   1.574 +        reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError);
   1.575 +        return RegExpRunStatus_Error;
   1.576 +    }
   1.577 +
   1.578 +    if (result == JSC::Yarr::offsetNoMatch)
   1.579 +        return RegExpRunStatus_Success_NotFound;
   1.580 +
   1.581 +    matches.displace(displacement);
   1.582 +    matches.checkAgainst(origLength);
   1.583 +    *lastIndex = matches[0].limit;
   1.584 +    return RegExpRunStatus_Success;
   1.585 +}
   1.586 +
   1.587 +RegExpRunStatus
   1.588 +RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length,
   1.589 +                               size_t *lastIndex, MatchPair &match)
   1.590 +{
   1.591 +    TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime());
   1.592 +
   1.593 +    {
   1.594 +        /* Compile the code at point-of-use. */
   1.595 +        AutoTraceLog logCompile(logger, TraceLogger::YarrCompile);
   1.596 +        if (!compileMatchOnlyIfNecessary(cx))
   1.597 +            return RegExpRunStatus_Error;
   1.598 +    }
   1.599 +
   1.600 +#ifdef DEBUG
   1.601 +    const size_t origLength = length;
   1.602 +#endif
   1.603 +    size_t start = *lastIndex;
   1.604 +    size_t displacement = 0;
   1.605 +
   1.606 +    if (sticky()) {
   1.607 +        displacement = start;
   1.608 +        chars += displacement;
   1.609 +        length -= displacement;
   1.610 +        start = 0;
   1.611 +    }
   1.612 +
   1.613 +#if ENABLE_YARR_JIT
   1.614 +    if (!codeBlock.isFallBack()) {
   1.615 +        AutoTraceLog logJIT(logger, TraceLogger::YarrJIT);
   1.616 +        MatchResult result = codeBlock.execute(chars, start, length);
   1.617 +        if (!result)
   1.618 +            return RegExpRunStatus_Success_NotFound;
   1.619 +
   1.620 +        match = MatchPair(result.start, result.end);
   1.621 +        match.displace(displacement);
   1.622 +        *lastIndex = match.limit;
   1.623 +        return RegExpRunStatus_Success;
   1.624 +    }
   1.625 +#endif
   1.626 +
   1.627 +    /*
   1.628 +     * The JIT could not be used, so fall back to the Yarr interpreter.
   1.629 +     * Unfortunately, the interpreter does not have a MatchOnly mode, so a
   1.630 +     * temporary output vector must be provided.
   1.631 +     */
   1.632 +    JS_ASSERT(hasBytecode());
   1.633 +    ScopedMatchPairs matches(&cx->tempLifoAlloc());
   1.634 +    if (!matches.initArray(pairCount()))
   1.635 +        return RegExpRunStatus_Error;
   1.636 +
   1.637 +    unsigned result;
   1.638 +    {
   1.639 +        AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret);
   1.640 +        result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf());
   1.641 +    }
   1.642 +
   1.643 +    if (result == JSC::Yarr::offsetError) {
   1.644 +        reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError);
   1.645 +        return RegExpRunStatus_Error;
   1.646 +    }
   1.647 +
   1.648 +    if (result == JSC::Yarr::offsetNoMatch)
   1.649 +        return RegExpRunStatus_Success_NotFound;
   1.650 +
   1.651 +    match = MatchPair(result, matches[0].limit);
   1.652 +    match.displace(displacement);
   1.653 +
   1.654 +#ifdef DEBUG
   1.655 +    matches.displace(displacement);
   1.656 +    matches.checkAgainst(origLength);
   1.657 +#endif
   1.658 +
   1.659 +    *lastIndex = match.limit;
   1.660 +    return RegExpRunStatus_Success;
   1.661 +}
   1.662 +
   1.663 +/* RegExpCompartment */
   1.664 +
   1.665 +RegExpCompartment::RegExpCompartment(JSRuntime *rt)
   1.666 +  : map_(rt), inUse_(rt), matchResultTemplateObject_(nullptr)
   1.667 +{}
   1.668 +
   1.669 +RegExpCompartment::~RegExpCompartment()
   1.670 +{
   1.671 +    JS_ASSERT_IF(map_.initialized(), map_.empty());
   1.672 +    JS_ASSERT_IF(inUse_.initialized(), inUse_.empty());
   1.673 +}
   1.674 +
   1.675 +JSObject *
   1.676 +RegExpCompartment::createMatchResultTemplateObject(JSContext *cx)
   1.677 +{
   1.678 +    JS_ASSERT(!matchResultTemplateObject_);
   1.679 +
   1.680 +    /* Create template array object */
   1.681 +    RootedObject templateObject(cx, NewDenseUnallocatedArray(cx, 0, nullptr, TenuredObject));
   1.682 +    if (!templateObject)
   1.683 +        return matchResultTemplateObject_; // = nullptr
   1.684 +
   1.685 +    /* Set dummy index property */
   1.686 +    RootedValue index(cx, Int32Value(0));
   1.687 +    if (!baseops::DefineProperty(cx, templateObject, cx->names().index, index,
   1.688 +                                 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE))
   1.689 +        return matchResultTemplateObject_; // = nullptr
   1.690 +
   1.691 +    /* Set dummy input property */
   1.692 +    RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString));
   1.693 +    if (!baseops::DefineProperty(cx, templateObject, cx->names().input, inputVal,
   1.694 +                                 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE))
   1.695 +        return matchResultTemplateObject_; // = nullptr
   1.696 +
   1.697 +    // Make sure that the properties are in the right slots.
   1.698 +    DebugOnly<Shape *> shape = templateObject->lastProperty();
   1.699 +    JS_ASSERT(shape->previous()->slot() == 0 &&
   1.700 +              shape->previous()->propidRef() == NameToId(cx->names().index));
   1.701 +    JS_ASSERT(shape->slot() == 1 &&
   1.702 +              shape->propidRef() == NameToId(cx->names().input));
   1.703 +
   1.704 +    matchResultTemplateObject_ = templateObject;
   1.705 +
   1.706 +    return matchResultTemplateObject_;
   1.707 +}
   1.708 +
   1.709 +bool
   1.710 +RegExpCompartment::init(JSContext *cx)
   1.711 +{
   1.712 +    if (!map_.init(0) || !inUse_.init(0)) {
   1.713 +        if (cx)
   1.714 +            js_ReportOutOfMemory(cx);
   1.715 +        return false;
   1.716 +    }
   1.717 +
   1.718 +    return true;
   1.719 +}
   1.720 +
   1.721 +/* See the comment on RegExpShared lifetime in RegExpObject.h. */
   1.722 +void
   1.723 +RegExpCompartment::sweep(JSRuntime *rt)
   1.724 +{
   1.725 +#ifdef DEBUG
   1.726 +    for (Map::Range r = map_.all(); !r.empty(); r.popFront())
   1.727 +        JS_ASSERT(inUse_.has(r.front().value()));
   1.728 +#endif
   1.729 +
   1.730 +    map_.clear();
   1.731 +
   1.732 +    for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) {
   1.733 +        RegExpShared *shared = e.front();
   1.734 +        if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) {
   1.735 +            js_delete(shared);
   1.736 +            e.removeFront();
   1.737 +        }
   1.738 +    }
   1.739 +
   1.740 +    if (matchResultTemplateObject_ &&
   1.741 +        IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet()))
   1.742 +    {
   1.743 +        matchResultTemplateObject_ = nullptr;
   1.744 +    }
   1.745 +}
   1.746 +
   1.747 +void
   1.748 +RegExpCompartment::clearTables()
   1.749 +{
   1.750 +    JS_ASSERT(inUse_.empty());
   1.751 +    map_.clear();
   1.752 +}
   1.753 +
   1.754 +bool
   1.755 +RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
   1.756 +{
   1.757 +    Key key(source, flags);
   1.758 +    Map::AddPtr p = map_.lookupForAdd(key);
   1.759 +    if (p) {
   1.760 +        g->init(*p->value());
   1.761 +        return true;
   1.762 +    }
   1.763 +
   1.764 +    uint64_t gcNumber = cx->zone()->gcNumber();
   1.765 +    ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, gcNumber));
   1.766 +    if (!shared)
   1.767 +        return false;
   1.768 +
   1.769 +    /* Add to RegExpShared sharing hashmap. */
   1.770 +    if (!map_.add(p, key, shared)) {
   1.771 +        js_ReportOutOfMemory(cx);
   1.772 +        return false;
   1.773 +    }
   1.774 +
   1.775 +    /* Add to list of all RegExpShared objects in this RegExpCompartment. */
   1.776 +    if (!inUse_.put(shared)) {
   1.777 +        map_.remove(key);
   1.778 +        js_ReportOutOfMemory(cx);
   1.779 +        return false;
   1.780 +    }
   1.781 +
   1.782 +    /* Since error deletes |shared|, only guard |shared| on success. */
   1.783 +    g->init(*shared.forget());
   1.784 +    return true;
   1.785 +}
   1.786 +
   1.787 +bool
   1.788 +RegExpCompartment::get(JSContext *cx, HandleAtom atom, JSString *opt, RegExpGuard *g)
   1.789 +{
   1.790 +    RegExpFlag flags = RegExpFlag(0);
   1.791 +    if (opt && !ParseRegExpFlags(cx, opt, &flags))
   1.792 +        return false;
   1.793 +
   1.794 +    return get(cx, atom, flags, g);
   1.795 +}
   1.796 +
   1.797 +size_t
   1.798 +RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
   1.799 +{
   1.800 +    size_t n = 0;
   1.801 +    n += map_.sizeOfExcludingThis(mallocSizeOf);
   1.802 +    n += inUse_.sizeOfExcludingThis(mallocSizeOf);
   1.803 +    return n;
   1.804 +}
   1.805 +
   1.806 +/* Functions */
   1.807 +
   1.808 +JSObject *
   1.809 +js::CloneRegExpObject(JSContext *cx, JSObject *obj_)
   1.810 +{
   1.811 +    RegExpObjectBuilder builder(cx);
   1.812 +    Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>());
   1.813 +    JSObject *res = builder.clone(regex);
   1.814 +    JS_ASSERT_IF(res, res->type() == regex->type());
   1.815 +    return res;
   1.816 +}
   1.817 +
   1.818 +bool
   1.819 +js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
   1.820 +{
   1.821 +    size_t n = flagStr->length();
   1.822 +    const jschar *s = flagStr->getChars(cx);
   1.823 +    if (!s)
   1.824 +        return false;
   1.825 +
   1.826 +    *flagsOut = RegExpFlag(0);
   1.827 +    for (size_t i = 0; i < n; i++) {
   1.828 +#define HANDLE_FLAG(name_)                                                    \
   1.829 +        JS_BEGIN_MACRO                                                        \
   1.830 +            if (*flagsOut & (name_))                                          \
   1.831 +                goto bad_flag;                                                \
   1.832 +            *flagsOut = RegExpFlag(*flagsOut | (name_));                      \
   1.833 +        JS_END_MACRO
   1.834 +        switch (s[i]) {
   1.835 +          case 'i': HANDLE_FLAG(IgnoreCaseFlag); break;
   1.836 +          case 'g': HANDLE_FLAG(GlobalFlag); break;
   1.837 +          case 'm': HANDLE_FLAG(MultilineFlag); break;
   1.838 +          case 'y': HANDLE_FLAG(StickyFlag); break;
   1.839 +          default:
   1.840 +          bad_flag:
   1.841 +          {
   1.842 +            char charBuf[2];
   1.843 +            charBuf[0] = char(s[i]);
   1.844 +            charBuf[1] = '\0';
   1.845 +            JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr,
   1.846 +                                         JSMSG_BAD_REGEXP_FLAG, charBuf);
   1.847 +            return false;
   1.848 +          }
   1.849 +        }
   1.850 +#undef HANDLE_FLAG
   1.851 +    }
   1.852 +    return true;
   1.853 +}
   1.854 +
   1.855 +template<XDRMode mode>
   1.856 +bool
   1.857 +js::XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp)
   1.858 +{
   1.859 +    /* NB: Keep this in sync with CloneScriptRegExpObject. */
   1.860 +
   1.861 +    RootedAtom source(xdr->cx());
   1.862 +    uint32_t flagsword = 0;
   1.863 +
   1.864 +    if (mode == XDR_ENCODE) {
   1.865 +        JS_ASSERT(objp);
   1.866 +        RegExpObject &reobj = (*objp)->as<RegExpObject>();
   1.867 +        source = reobj.getSource();
   1.868 +        flagsword = reobj.getFlags();
   1.869 +    }
   1.870 +    if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword))
   1.871 +        return false;
   1.872 +    if (mode == XDR_DECODE) {
   1.873 +        RegExpFlag flags = RegExpFlag(flagsword);
   1.874 +        RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx(), source, flags, nullptr);
   1.875 +        if (!reobj)
   1.876 +            return false;
   1.877 +
   1.878 +        objp->init(reobj);
   1.879 +    }
   1.880 +    return true;
   1.881 +}
   1.882 +
   1.883 +template bool
   1.884 +js::XDRScriptRegExpObject(XDRState<XDR_ENCODE> *xdr, HeapPtrObject *objp);
   1.885 +
   1.886 +template bool
   1.887 +js::XDRScriptRegExpObject(XDRState<XDR_DECODE> *xdr, HeapPtrObject *objp);
   1.888 +
   1.889 +JSObject *
   1.890 +js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj)
   1.891 +{
   1.892 +    /* NB: Keep this in sync with XDRScriptRegExpObject. */
   1.893 +
   1.894 +    RootedAtom source(cx, reobj.getSource());
   1.895 +    return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr);
   1.896 +}

mercurial