michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/RegExpObject.h" michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #include "frontend/TokenStream.h" michael@0: #include "vm/MatchPairs.h" michael@0: #include "vm/RegExpStatics.h" michael@0: #include "vm/StringBuffer.h" michael@0: #include "vm/TraceLogging.h" michael@0: #include "vm/Xdr.h" michael@0: #include "yarr/YarrSyntaxChecker.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/Shape-inl.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::DebugOnly; michael@0: using js::frontend::TokenStream; michael@0: michael@0: JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD); michael@0: JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB); michael@0: JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE); michael@0: JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY); michael@0: michael@0: /* RegExpObjectBuilder */ michael@0: michael@0: RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj) michael@0: : cx(cx), reobj_(cx, reobj) michael@0: {} michael@0: michael@0: bool michael@0: RegExpObjectBuilder::getOrCreate() michael@0: { michael@0: if (reobj_) michael@0: return true; michael@0: michael@0: // Note: RegExp objects are always allocated in the tenured heap. This is michael@0: // not strictly required, but simplifies embedding them in jitcode. michael@0: JSObject *obj = NewBuiltinClassInstance(cx, &RegExpObject::class_, TenuredObject); michael@0: if (!obj) michael@0: return false; michael@0: obj->initPrivate(nullptr); michael@0: michael@0: reobj_ = &obj->as(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: RegExpObjectBuilder::getOrCreateClone(HandleTypeObject type) michael@0: { michael@0: JS_ASSERT(!reobj_); michael@0: JS_ASSERT(type->clasp() == &RegExpObject::class_); michael@0: michael@0: JSObject *parent = type->proto().toObject()->getParent(); michael@0: michael@0: // Note: RegExp objects are always allocated in the tenured heap. This is michael@0: // not strictly required, but simplifies embedding them in jitcode. michael@0: JSObject *clone = NewObjectWithType(cx->asJSContext(), type, parent, TenuredObject); michael@0: if (!clone) michael@0: return false; michael@0: clone->initPrivate(nullptr); michael@0: michael@0: reobj_ = &clone->as(); michael@0: return true; michael@0: } michael@0: michael@0: RegExpObject * michael@0: RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared) michael@0: { michael@0: if (!getOrCreate()) michael@0: return nullptr; michael@0: michael@0: if (!reobj_->init(cx, source, shared.getFlags())) michael@0: return nullptr; michael@0: michael@0: reobj_->setShared(cx, shared); michael@0: return reobj_; michael@0: } michael@0: michael@0: RegExpObject * michael@0: RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags) michael@0: { michael@0: if (!getOrCreate()) michael@0: return nullptr; michael@0: michael@0: return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr; michael@0: } michael@0: michael@0: RegExpObject * michael@0: RegExpObjectBuilder::clone(Handle other) michael@0: { michael@0: RootedTypeObject type(cx, other->type()); michael@0: if (!getOrCreateClone(type)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Check that the RegExpShared for the original is okay to use in michael@0: * the clone -- if the |RegExpStatics| provides more flags we'll michael@0: * need a different |RegExpShared|. michael@0: */ michael@0: RegExpStatics *res = other->getProto()->getParent()->as().getRegExpStatics(); michael@0: RegExpFlag origFlags = other->getFlags(); michael@0: RegExpFlag staticsFlags = res->getFlags(); michael@0: if ((origFlags & staticsFlags) != staticsFlags) { michael@0: RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags); michael@0: Rooted source(cx, other->getSource()); michael@0: return build(source, newFlags); michael@0: } michael@0: michael@0: RegExpGuard g(cx); michael@0: if (!other->getShared(cx, &g)) michael@0: return nullptr; michael@0: michael@0: Rooted source(cx, other->getSource()); michael@0: return build(source, *g); michael@0: } michael@0: michael@0: /* MatchPairs */ michael@0: michael@0: bool michael@0: MatchPairs::initArray(size_t pairCount) michael@0: { michael@0: JS_ASSERT(pairCount > 0); michael@0: michael@0: /* Guarantee adequate space in buffer. */ michael@0: if (!allocOrExpandArray(pairCount)) michael@0: return false; michael@0: michael@0: /* Initialize all MatchPair objects to invalid locations. */ michael@0: for (size_t i = 0; i < pairCount; i++) { michael@0: pairs_[i].start = -1; michael@0: pairs_[i].limit = -1; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: MatchPairs::initArrayFrom(MatchPairs ©From) michael@0: { michael@0: JS_ASSERT(copyFrom.pairCount() > 0); michael@0: michael@0: if (!allocOrExpandArray(copyFrom.pairCount())) michael@0: return false; michael@0: michael@0: for (size_t i = 0; i < pairCount_; i++) { michael@0: JS_ASSERT(copyFrom[i].check()); michael@0: pairs_[i].start = copyFrom[i].start; michael@0: pairs_[i].limit = copyFrom[i].limit; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: MatchPairs::displace(size_t disp) michael@0: { michael@0: if (disp == 0) michael@0: return; michael@0: michael@0: for (size_t i = 0; i < pairCount_; i++) { michael@0: JS_ASSERT(pairs_[i].check()); michael@0: pairs_[i].start += (pairs_[i].start < 0) ? 0 : disp; michael@0: pairs_[i].limit += (pairs_[i].limit < 0) ? 0 : disp; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ScopedMatchPairs::allocOrExpandArray(size_t pairCount) michael@0: { michael@0: /* Array expansion is forbidden, but array reuse is acceptable. */ michael@0: if (pairCount_) { michael@0: JS_ASSERT(pairs_); michael@0: JS_ASSERT(pairCount_ == pairCount); michael@0: return true; michael@0: } michael@0: michael@0: JS_ASSERT(!pairs_); michael@0: pairs_ = (MatchPair *)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount); michael@0: if (!pairs_) michael@0: return false; michael@0: michael@0: pairCount_ = pairCount; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: VectorMatchPairs::allocOrExpandArray(size_t pairCount) michael@0: { michael@0: if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount)) michael@0: return false; michael@0: michael@0: pairs_ = &vec_[0]; michael@0: pairCount_ = pairCount; michael@0: return true; michael@0: } michael@0: michael@0: /* RegExpObject */ michael@0: michael@0: static void michael@0: regexp_trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: /* michael@0: * We have to check both conditions, since: michael@0: * 1. During TraceRuntime, isHeapBusy() is true michael@0: * 2. When a write barrier executes, IS_GC_MARKING_TRACER is true. michael@0: */ michael@0: if (trc->runtime()->isHeapBusy() && IS_GC_MARKING_TRACER(trc)) michael@0: obj->setPrivate(nullptr); michael@0: } michael@0: michael@0: const Class RegExpObject::class_ = { michael@0: js_RegExp_str, michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, /* enumerate */ michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr, /* finalize */ michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: regexp_trace michael@0: }; michael@0: michael@0: RegExpObject * michael@0: RegExpObject::create(ExclusiveContext *cx, RegExpStatics *res, const jschar *chars, size_t length, michael@0: RegExpFlag flags, TokenStream *tokenStream) michael@0: { michael@0: RegExpFlag staticsFlags = res->getFlags(); michael@0: return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream); michael@0: } michael@0: michael@0: RegExpObject * michael@0: RegExpObject::createNoStatics(ExclusiveContext *cx, const jschar *chars, size_t length, RegExpFlag flags, michael@0: TokenStream *tokenStream) michael@0: { michael@0: RootedAtom source(cx, AtomizeChars(cx, chars, length)); michael@0: if (!source) michael@0: return nullptr; michael@0: michael@0: return createNoStatics(cx, source, flags, tokenStream); michael@0: } michael@0: michael@0: RegExpObject * michael@0: RegExpObject::createNoStatics(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags, michael@0: TokenStream *tokenStream) michael@0: { michael@0: if (!RegExpShared::checkSyntax(cx, tokenStream, source)) michael@0: return nullptr; michael@0: michael@0: RegExpObjectBuilder builder(cx); michael@0: return builder.build(source, flags); michael@0: } michael@0: michael@0: bool michael@0: RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g) michael@0: { michael@0: Rooted self(cx, this); michael@0: michael@0: JS_ASSERT(!maybeShared()); michael@0: if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g)) michael@0: return false; michael@0: michael@0: self->setShared(cx, **g); michael@0: return true; michael@0: } michael@0: michael@0: Shape * michael@0: RegExpObject::assignInitialShape(ExclusiveContext *cx, Handle self) michael@0: { michael@0: JS_ASSERT(self->nativeEmpty()); michael@0: michael@0: JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0); michael@0: JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1); michael@0: JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1); michael@0: JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1); michael@0: JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1); michael@0: JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1); michael@0: michael@0: /* The lastIndex property alone is writable but non-configurable. */ michael@0: if (!self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT)) michael@0: return nullptr; michael@0: michael@0: /* Remaining instance properties are non-writable and non-configurable. */ michael@0: unsigned attrs = JSPROP_PERMANENT | JSPROP_READONLY; michael@0: if (!self->addDataProperty(cx, cx->names().source, SOURCE_SLOT, attrs)) michael@0: return nullptr; michael@0: if (!self->addDataProperty(cx, cx->names().global, GLOBAL_FLAG_SLOT, attrs)) michael@0: return nullptr; michael@0: if (!self->addDataProperty(cx, cx->names().ignoreCase, IGNORE_CASE_FLAG_SLOT, attrs)) michael@0: return nullptr; michael@0: if (!self->addDataProperty(cx, cx->names().multiline, MULTILINE_FLAG_SLOT, attrs)) michael@0: return nullptr; michael@0: return self->addDataProperty(cx, cx->names().sticky, STICKY_FLAG_SLOT, attrs); michael@0: } michael@0: michael@0: bool michael@0: RegExpObject::init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags) michael@0: { michael@0: Rooted self(cx, this); michael@0: michael@0: if (!EmptyShape::ensureInitialCustomShape(cx, self)) michael@0: return false; michael@0: michael@0: JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().lastIndex))->slot() == michael@0: LAST_INDEX_SLOT); michael@0: JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().source))->slot() == michael@0: SOURCE_SLOT); michael@0: JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().global))->slot() == michael@0: GLOBAL_FLAG_SLOT); michael@0: JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().ignoreCase))->slot() == michael@0: IGNORE_CASE_FLAG_SLOT); michael@0: JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().multiline))->slot() == michael@0: MULTILINE_FLAG_SLOT); michael@0: JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().sticky))->slot() == michael@0: STICKY_FLAG_SLOT); michael@0: michael@0: /* michael@0: * If this is a re-initialization with an existing RegExpShared, 'flags' michael@0: * may not match getShared()->flags, so forget the RegExpShared. michael@0: */ michael@0: self->JSObject::setPrivate(nullptr); michael@0: michael@0: self->zeroLastIndex(); michael@0: self->setSource(source); michael@0: self->setGlobal(flags & GlobalFlag); michael@0: self->setIgnoreCase(flags & IgnoreCaseFlag); michael@0: self->setMultiline(flags & MultilineFlag); michael@0: self->setSticky(flags & StickyFlag); michael@0: return true; michael@0: } michael@0: michael@0: JSFlatString * michael@0: RegExpObject::toString(JSContext *cx) const michael@0: { michael@0: JSAtom *src = getSource(); michael@0: StringBuffer sb(cx); michael@0: if (size_t len = src->length()) { michael@0: if (!sb.reserve(len + 2)) michael@0: return nullptr; michael@0: sb.infallibleAppend('/'); michael@0: sb.infallibleAppend(src->chars(), len); michael@0: sb.infallibleAppend('/'); michael@0: } else { michael@0: if (!sb.append("/(?:)/")) michael@0: return nullptr; michael@0: } michael@0: if (global() && !sb.append('g')) michael@0: return nullptr; michael@0: if (ignoreCase() && !sb.append('i')) michael@0: return nullptr; michael@0: if (multiline() && !sb.append('m')) michael@0: return nullptr; michael@0: if (sticky() && !sb.append('y')) michael@0: return nullptr; michael@0: michael@0: return sb.finishString(); michael@0: } michael@0: michael@0: /* RegExpShared */ michael@0: michael@0: RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber) michael@0: : source(source), flags(flags), parenCount(0), michael@0: #if ENABLE_YARR_JIT michael@0: codeBlock(), michael@0: #endif michael@0: bytecode(nullptr), activeUseCount(0), gcNumberWhenUsed(gcNumber) michael@0: {} michael@0: michael@0: RegExpShared::~RegExpShared() michael@0: { michael@0: #if ENABLE_YARR_JIT michael@0: codeBlock.release(); michael@0: #endif michael@0: js_delete(bytecode); michael@0: } michael@0: michael@0: void michael@0: RegExpShared::reportYarrError(ExclusiveContext *cx, TokenStream *ts, ErrorCode error) michael@0: { michael@0: switch (error) { michael@0: case JSC::Yarr::NoError: michael@0: MOZ_ASSUME_UNREACHABLE("Called reportYarrError with value for no error"); michael@0: #define COMPILE_EMSG(__code, __msg) \ michael@0: case JSC::Yarr::__code: \ michael@0: if (ts) \ michael@0: ts->reportError(__msg); \ michael@0: else \ michael@0: JS_ReportErrorFlagsAndNumberUC(cx->asJSContext(), \ michael@0: JSREPORT_ERROR, js_GetErrorMessage, nullptr, __msg); \ michael@0: return michael@0: COMPILE_EMSG(PatternTooLarge, JSMSG_REGEXP_TOO_COMPLEX); michael@0: COMPILE_EMSG(QuantifierOutOfOrder, JSMSG_BAD_QUANTIFIER); michael@0: COMPILE_EMSG(QuantifierWithoutAtom, JSMSG_BAD_QUANTIFIER); michael@0: COMPILE_EMSG(MissingParentheses, JSMSG_MISSING_PAREN); michael@0: COMPILE_EMSG(ParenthesesUnmatched, JSMSG_UNMATCHED_RIGHT_PAREN); michael@0: COMPILE_EMSG(ParenthesesTypeInvalid, JSMSG_BAD_QUANTIFIER); /* "(?" with bad next char */ michael@0: COMPILE_EMSG(CharacterClassUnmatched, JSMSG_BAD_CLASS_RANGE); michael@0: COMPILE_EMSG(CharacterClassInvalidRange, JSMSG_BAD_CLASS_RANGE); michael@0: COMPILE_EMSG(CharacterClassOutOfOrder, JSMSG_BAD_CLASS_RANGE); michael@0: COMPILE_EMSG(QuantifierTooLarge, JSMSG_BAD_QUANTIFIER); michael@0: COMPILE_EMSG(EscapeUnterminated, JSMSG_TRAILING_SLASH); michael@0: COMPILE_EMSG(RuntimeError, JSMSG_REGEXP_RUNTIME_ERROR); michael@0: #undef COMPILE_EMSG michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Unknown Yarr error code"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: RegExpShared::checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLinearString *source) michael@0: { michael@0: ErrorCode error = JSC::Yarr::checkSyntax(*source); michael@0: if (error == JSC::Yarr::NoError) michael@0: return true; michael@0: michael@0: reportYarrError(cx, tokenStream, error); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: RegExpShared::compile(JSContext *cx, bool matchOnly) michael@0: { michael@0: if (!sticky()) michael@0: return compile(cx, *source, matchOnly); michael@0: michael@0: /* michael@0: * The sticky case we implement hackily by prepending a caret onto the front michael@0: * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp. michael@0: */ michael@0: static const jschar prefix[] = {'^', '(', '?', ':'}; michael@0: static const jschar postfix[] = {')'}; michael@0: michael@0: using mozilla::ArrayLength; michael@0: StringBuffer sb(cx); michael@0: if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix))) michael@0: return false; michael@0: sb.infallibleAppend(prefix, ArrayLength(prefix)); michael@0: sb.infallibleAppend(source->chars(), source->length()); michael@0: sb.infallibleAppend(postfix, ArrayLength(postfix)); michael@0: michael@0: JSAtom *fakeySource = sb.finishAtom(); michael@0: if (!fakeySource) michael@0: return false; michael@0: michael@0: return compile(cx, *fakeySource, matchOnly); michael@0: } michael@0: michael@0: bool michael@0: RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly) michael@0: { michael@0: /* Parse the pattern. */ michael@0: ErrorCode yarrError; michael@0: YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError); michael@0: if (yarrError) { michael@0: reportYarrError(cx, nullptr, yarrError); michael@0: return false; michael@0: } michael@0: this->parenCount = yarrPattern.m_numSubpatterns; michael@0: michael@0: #if ENABLE_YARR_JIT michael@0: if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) { michael@0: JSC::ExecutableAllocator *execAlloc = cx->runtime()->getExecAlloc(cx); michael@0: if (!execAlloc) michael@0: return false; michael@0: michael@0: JSGlobalData globalData(execAlloc); michael@0: YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly michael@0: : JSC::Yarr::IncludeSubpatterns; michael@0: michael@0: jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode); michael@0: michael@0: /* Unset iff the Yarr JIT compilation was successful. */ michael@0: if (!codeBlock.isFallBack()) michael@0: return true; michael@0: } michael@0: codeBlock.setFallBack(true); michael@0: #endif michael@0: michael@0: WTF::BumpPointerAllocator *bumpAlloc = cx->runtime()->getBumpPointerAllocator(cx); michael@0: if (!bumpAlloc) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: bytecode = byteCompile(yarrPattern, bumpAlloc).get(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: RegExpShared::compileIfNecessary(JSContext *cx) michael@0: { michael@0: if (hasCode() || hasBytecode()) michael@0: return true; michael@0: return compile(cx, false); michael@0: } michael@0: michael@0: bool michael@0: RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx) michael@0: { michael@0: if (hasMatchOnlyCode() || hasBytecode()) michael@0: return true; michael@0: return compile(cx, true); michael@0: } michael@0: michael@0: RegExpRunStatus michael@0: RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, michael@0: size_t *lastIndex, MatchPairs &matches) michael@0: { michael@0: TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); michael@0: michael@0: { michael@0: /* Compile the code at point-of-use. */ michael@0: AutoTraceLog logCompile(logger, TraceLogger::YarrCompile); michael@0: if (!compileIfNecessary(cx)) michael@0: return RegExpRunStatus_Error; michael@0: } michael@0: michael@0: /* Ensure sufficient memory for output vector. */ michael@0: if (!matches.initArray(pairCount())) michael@0: return RegExpRunStatus_Error; michael@0: michael@0: /* michael@0: * |displacement| emulates sticky mode by matching from this offset michael@0: * into the char buffer and subtracting the delta off at the end. michael@0: */ michael@0: size_t origLength = length; michael@0: size_t start = *lastIndex; michael@0: size_t displacement = 0; michael@0: michael@0: if (sticky()) { michael@0: displacement = start; michael@0: chars += displacement; michael@0: length -= displacement; michael@0: start = 0; michael@0: } michael@0: michael@0: unsigned *outputBuf = matches.rawBuf(); michael@0: unsigned result; michael@0: michael@0: #if ENABLE_YARR_JIT michael@0: if (codeBlock.isFallBack()) { michael@0: AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); michael@0: result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf); michael@0: } else { michael@0: AutoTraceLog logJIT(logger, TraceLogger::YarrJIT); michael@0: result = codeBlock.execute(chars, start, length, (int *)outputBuf).start; michael@0: } michael@0: #else michael@0: { michael@0: AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); michael@0: result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf); michael@0: } michael@0: #endif michael@0: michael@0: if (result == JSC::Yarr::offsetError) { michael@0: reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError); michael@0: return RegExpRunStatus_Error; michael@0: } michael@0: michael@0: if (result == JSC::Yarr::offsetNoMatch) michael@0: return RegExpRunStatus_Success_NotFound; michael@0: michael@0: matches.displace(displacement); michael@0: matches.checkAgainst(origLength); michael@0: *lastIndex = matches[0].limit; michael@0: return RegExpRunStatus_Success; michael@0: } michael@0: michael@0: RegExpRunStatus michael@0: RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length, michael@0: size_t *lastIndex, MatchPair &match) michael@0: { michael@0: TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); michael@0: michael@0: { michael@0: /* Compile the code at point-of-use. */ michael@0: AutoTraceLog logCompile(logger, TraceLogger::YarrCompile); michael@0: if (!compileMatchOnlyIfNecessary(cx)) michael@0: return RegExpRunStatus_Error; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: const size_t origLength = length; michael@0: #endif michael@0: size_t start = *lastIndex; michael@0: size_t displacement = 0; michael@0: michael@0: if (sticky()) { michael@0: displacement = start; michael@0: chars += displacement; michael@0: length -= displacement; michael@0: start = 0; michael@0: } michael@0: michael@0: #if ENABLE_YARR_JIT michael@0: if (!codeBlock.isFallBack()) { michael@0: AutoTraceLog logJIT(logger, TraceLogger::YarrJIT); michael@0: MatchResult result = codeBlock.execute(chars, start, length); michael@0: if (!result) michael@0: return RegExpRunStatus_Success_NotFound; michael@0: michael@0: match = MatchPair(result.start, result.end); michael@0: match.displace(displacement); michael@0: *lastIndex = match.limit; michael@0: return RegExpRunStatus_Success; michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * The JIT could not be used, so fall back to the Yarr interpreter. michael@0: * Unfortunately, the interpreter does not have a MatchOnly mode, so a michael@0: * temporary output vector must be provided. michael@0: */ michael@0: JS_ASSERT(hasBytecode()); michael@0: ScopedMatchPairs matches(&cx->tempLifoAlloc()); michael@0: if (!matches.initArray(pairCount())) michael@0: return RegExpRunStatus_Error; michael@0: michael@0: unsigned result; michael@0: { michael@0: AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); michael@0: result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf()); michael@0: } michael@0: michael@0: if (result == JSC::Yarr::offsetError) { michael@0: reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError); michael@0: return RegExpRunStatus_Error; michael@0: } michael@0: michael@0: if (result == JSC::Yarr::offsetNoMatch) michael@0: return RegExpRunStatus_Success_NotFound; michael@0: michael@0: match = MatchPair(result, matches[0].limit); michael@0: match.displace(displacement); michael@0: michael@0: #ifdef DEBUG michael@0: matches.displace(displacement); michael@0: matches.checkAgainst(origLength); michael@0: #endif michael@0: michael@0: *lastIndex = match.limit; michael@0: return RegExpRunStatus_Success; michael@0: } michael@0: michael@0: /* RegExpCompartment */ michael@0: michael@0: RegExpCompartment::RegExpCompartment(JSRuntime *rt) michael@0: : map_(rt), inUse_(rt), matchResultTemplateObject_(nullptr) michael@0: {} michael@0: michael@0: RegExpCompartment::~RegExpCompartment() michael@0: { michael@0: JS_ASSERT_IF(map_.initialized(), map_.empty()); michael@0: JS_ASSERT_IF(inUse_.initialized(), inUse_.empty()); michael@0: } michael@0: michael@0: JSObject * michael@0: RegExpCompartment::createMatchResultTemplateObject(JSContext *cx) michael@0: { michael@0: JS_ASSERT(!matchResultTemplateObject_); michael@0: michael@0: /* Create template array object */ michael@0: RootedObject templateObject(cx, NewDenseUnallocatedArray(cx, 0, nullptr, TenuredObject)); michael@0: if (!templateObject) michael@0: return matchResultTemplateObject_; // = nullptr michael@0: michael@0: /* Set dummy index property */ michael@0: RootedValue index(cx, Int32Value(0)); michael@0: if (!baseops::DefineProperty(cx, templateObject, cx->names().index, index, michael@0: JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE)) michael@0: return matchResultTemplateObject_; // = nullptr michael@0: michael@0: /* Set dummy input property */ michael@0: RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString)); michael@0: if (!baseops::DefineProperty(cx, templateObject, cx->names().input, inputVal, michael@0: JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE)) michael@0: return matchResultTemplateObject_; // = nullptr michael@0: michael@0: // Make sure that the properties are in the right slots. michael@0: DebugOnly shape = templateObject->lastProperty(); michael@0: JS_ASSERT(shape->previous()->slot() == 0 && michael@0: shape->previous()->propidRef() == NameToId(cx->names().index)); michael@0: JS_ASSERT(shape->slot() == 1 && michael@0: shape->propidRef() == NameToId(cx->names().input)); michael@0: michael@0: matchResultTemplateObject_ = templateObject; michael@0: michael@0: return matchResultTemplateObject_; michael@0: } michael@0: michael@0: bool michael@0: RegExpCompartment::init(JSContext *cx) michael@0: { michael@0: if (!map_.init(0) || !inUse_.init(0)) { michael@0: if (cx) michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* See the comment on RegExpShared lifetime in RegExpObject.h. */ michael@0: void michael@0: RegExpCompartment::sweep(JSRuntime *rt) michael@0: { michael@0: #ifdef DEBUG michael@0: for (Map::Range r = map_.all(); !r.empty(); r.popFront()) michael@0: JS_ASSERT(inUse_.has(r.front().value())); michael@0: #endif michael@0: michael@0: map_.clear(); michael@0: michael@0: for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) { michael@0: RegExpShared *shared = e.front(); michael@0: if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) { michael@0: js_delete(shared); michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: michael@0: if (matchResultTemplateObject_ && michael@0: IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet())) michael@0: { michael@0: matchResultTemplateObject_ = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: RegExpCompartment::clearTables() michael@0: { michael@0: JS_ASSERT(inUse_.empty()); michael@0: map_.clear(); michael@0: } michael@0: michael@0: bool michael@0: RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g) michael@0: { michael@0: Key key(source, flags); michael@0: Map::AddPtr p = map_.lookupForAdd(key); michael@0: if (p) { michael@0: g->init(*p->value()); michael@0: return true; michael@0: } michael@0: michael@0: uint64_t gcNumber = cx->zone()->gcNumber(); michael@0: ScopedJSDeletePtr shared(cx->new_(source, flags, gcNumber)); michael@0: if (!shared) michael@0: return false; michael@0: michael@0: /* Add to RegExpShared sharing hashmap. */ michael@0: if (!map_.add(p, key, shared)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: /* Add to list of all RegExpShared objects in this RegExpCompartment. */ michael@0: if (!inUse_.put(shared)) { michael@0: map_.remove(key); michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: /* Since error deletes |shared|, only guard |shared| on success. */ michael@0: g->init(*shared.forget()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: RegExpCompartment::get(JSContext *cx, HandleAtom atom, JSString *opt, RegExpGuard *g) michael@0: { michael@0: RegExpFlag flags = RegExpFlag(0); michael@0: if (opt && !ParseRegExpFlags(cx, opt, &flags)) michael@0: return false; michael@0: michael@0: return get(cx, atom, flags, g); michael@0: } michael@0: michael@0: size_t michael@0: RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: size_t n = 0; michael@0: n += map_.sizeOfExcludingThis(mallocSizeOf); michael@0: n += inUse_.sizeOfExcludingThis(mallocSizeOf); michael@0: return n; michael@0: } michael@0: michael@0: /* Functions */ michael@0: michael@0: JSObject * michael@0: js::CloneRegExpObject(JSContext *cx, JSObject *obj_) michael@0: { michael@0: RegExpObjectBuilder builder(cx); michael@0: Rooted regex(cx, &obj_->as()); michael@0: JSObject *res = builder.clone(regex); michael@0: JS_ASSERT_IF(res, res->type() == regex->type()); michael@0: return res; michael@0: } michael@0: michael@0: bool michael@0: js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut) michael@0: { michael@0: size_t n = flagStr->length(); michael@0: const jschar *s = flagStr->getChars(cx); michael@0: if (!s) michael@0: return false; michael@0: michael@0: *flagsOut = RegExpFlag(0); michael@0: for (size_t i = 0; i < n; i++) { michael@0: #define HANDLE_FLAG(name_) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (*flagsOut & (name_)) \ michael@0: goto bad_flag; \ michael@0: *flagsOut = RegExpFlag(*flagsOut | (name_)); \ michael@0: JS_END_MACRO michael@0: switch (s[i]) { michael@0: case 'i': HANDLE_FLAG(IgnoreCaseFlag); break; michael@0: case 'g': HANDLE_FLAG(GlobalFlag); break; michael@0: case 'm': HANDLE_FLAG(MultilineFlag); break; michael@0: case 'y': HANDLE_FLAG(StickyFlag); break; michael@0: default: michael@0: bad_flag: michael@0: { michael@0: char charBuf[2]; michael@0: charBuf[0] = char(s[i]); michael@0: charBuf[1] = '\0'; michael@0: JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr, michael@0: JSMSG_BAD_REGEXP_FLAG, charBuf); michael@0: return false; michael@0: } michael@0: } michael@0: #undef HANDLE_FLAG michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp) michael@0: { michael@0: /* NB: Keep this in sync with CloneScriptRegExpObject. */ michael@0: michael@0: RootedAtom source(xdr->cx()); michael@0: uint32_t flagsword = 0; michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: JS_ASSERT(objp); michael@0: RegExpObject &reobj = (*objp)->as(); michael@0: source = reobj.getSource(); michael@0: flagsword = reobj.getFlags(); michael@0: } michael@0: if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword)) michael@0: return false; michael@0: if (mode == XDR_DECODE) { michael@0: RegExpFlag flags = RegExpFlag(flagsword); michael@0: RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx(), source, flags, nullptr); michael@0: if (!reobj) michael@0: return false; michael@0: michael@0: objp->init(reobj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp); michael@0: michael@0: template bool michael@0: js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp); michael@0: michael@0: JSObject * michael@0: js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj) michael@0: { michael@0: /* NB: Keep this in sync with XDRScriptRegExpObject. */ michael@0: michael@0: RootedAtom source(cx, reobj.getSource()); michael@0: return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr); michael@0: }