diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index 1b667326b0..efd2614caf 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -123,6 +123,244 @@ nsJSUtils::CompileFunction(AutoJSAPI& jsapi, return NS_OK; } +static nsresult +EvaluationExceptionToNSResult(JSContext* aCx) +{ + if (JS_IsExceptionPending(aCx)) { + return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW; + } + return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE; +} + +nsJSUtils::ExecutionContext::ExecutionContext(JSContext* aCx, + JS::Handle aGlobal) + : mCx(aCx) + , mCompartment(aCx, aGlobal) + , mRetValue(aCx) + , mScopeChain(aCx) + , mRv(NS_OK) + , mSkip(false) + , mCoerceToString(false) +#ifdef DEBUG + , mWantsReturnValue(false) + , mExpectScopeChain(false) +#endif +{ + MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(nsContentUtils::IsInMicroTask()); + MOZ_ASSERT(mRetValue.isUndefined()); + + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aGlobal) == aGlobal); + if (MOZ_UNLIKELY(!xpc::Scriptability::Get(aGlobal).Allowed())) { + mSkip = true; + mRv = NS_OK; + } +} + +void +nsJSUtils::ExecutionContext::SetScopeChain( + const JS::AutoObjectVector& aScopeChain) +{ + if (mSkip) { + return; + } + +#ifdef DEBUG + mExpectScopeChain = true; +#endif + // Now make sure to wrap the scope chain into the right compartment. + if (!mScopeChain.reserve(aScopeChain.length())) { + mSkip = true; + mRv = NS_ERROR_OUT_OF_MEMORY; + return; + } + + for (size_t i = 0; i < aScopeChain.length(); ++i) { + JS::ExposeObjectToActiveJS(aScopeChain[i]); + mScopeChain.infallibleAppend(aScopeChain[i]); + if (!JS_WrapObject(mCx, mScopeChain[i])) { + mSkip = true; + mRv = NS_ERROR_OUT_OF_MEMORY; + return; + } + } +} + +nsresult +nsJSUtils::ExecutionContext::JoinAndExec(void **aOffThreadToken, + JS::MutableHandle aScript) +{ + if (mSkip) { + return mRv; + } + + MOZ_ASSERT(!mWantsReturnValue); + MOZ_ASSERT(!mExpectScopeChain); + aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken)); + *aOffThreadToken = nullptr; // Mark the token as having been finished. + if (!aScript || !JS_ExecuteScript(mCx, mScopeChain, aScript)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return NS_OK; +} + +nsresult +nsJSUtils::ExecutionContext::CompileAndExec(JS::CompileOptions& aCompileOptions, + JS::SourceBufferHolder& aSrcBuf) +{ + if (mSkip) { + return mRv; + } + + MOZ_ASSERT_IF(aCompileOptions.versionSet, + aCompileOptions.version != JSVERSION_UNKNOWN); + MOZ_ASSERT(aSrcBuf.get()); + MOZ_ASSERT(mRetValue.isUndefined()); +#ifdef DEBUG + mWantsReturnValue = !aCompileOptions.noScriptRval; +#endif + MOZ_ASSERT(!mCoerceToString || mWantsReturnValue); + if (!JS::Evaluate(mCx, mScopeChain, aCompileOptions, aSrcBuf, &mRetValue)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return NS_OK; +} + +nsresult +nsJSUtils::ExecutionContext::CompileAndExec(JS::CompileOptions& aCompileOptions, + const nsAString& aScript) +{ + if (mSkip) { + return mRv; + } + + const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); + JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), + JS::SourceBufferHolder::NoOwnership); + return CompileAndExec(aCompileOptions, srcBuf); +} + +nsresult +nsJSUtils::ExecutionContext::DecodeAndExec(JS::CompileOptions& aCompileOptions, + mozilla::Vector& aBytecodeBuf, + size_t aBytecodeIndex) +{ + if (mSkip) { + return mRv; + } + + MOZ_ASSERT(!mWantsReturnValue); + JS::Rooted script(mCx); + JS::TranscodeResult tr = JS::DecodeScript(mCx, aBytecodeBuf, &script, aBytecodeIndex); + // These errors are external parameters which should be handled before the + // decoding phase, and which are the only reasons why you might want to + // fallback on decoding failures. + MOZ_ASSERT(tr != JS::TranscodeResult_Failure_BadBuildId && + tr != JS::TranscodeResult_Failure_WrongCompileOption); + if (tr != JS::TranscodeResult_Ok) { + mSkip = true; + mRv = NS_ERROR_DOM_JS_DECODING_ERROR; + return mRv; + } + + if (!JS_ExecuteScript(mCx, mScopeChain, script)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return mRv; +} + +nsresult +nsJSUtils::ExecutionContext::DecodeJoinAndExec(void **aOffThreadToken) +{ + if (mSkip) { + return mRv; + } + + MOZ_ASSERT(!mWantsReturnValue); + MOZ_ASSERT(!mExpectScopeChain); + JS::Rooted script(mCx); + script.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken)); + *aOffThreadToken = nullptr; // Mark the token as having been finished. + if (!script || !JS_ExecuteScript(mCx, mScopeChain, script)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return NS_OK; +} + +nsresult +nsJSUtils::ExecutionContext::JoinEncodeAndExec(void **aOffThreadToken, + mozilla::Vector& aBytecodeBuf, + JS::MutableHandle aScript) +{ + MOZ_ASSERT_IF(aOffThreadToken, !mWantsReturnValue); + aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken)); + *aOffThreadToken = nullptr; // Mark the token as having been finished. + if (!aScript) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + if (!StartIncrementalEncoding(mCx, aBytecodeBuf, aScript)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return mRv; +} + +nsresult +nsJSUtils::ExecutionContext::ExtractReturnValue(JS::MutableHandle aRetValue) +{ + MOZ_ASSERT(aRetValue.isUndefined()); + if (mSkip) { + // Repeat earlier result, as NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW are not + // failures cases. +#ifdef DEBUG + mWantsReturnValue = false; +#endif + return mRv; + } + + MOZ_ASSERT(mWantsReturnValue); +#ifdef DEBUG + mWantsReturnValue = false; +#endif + if (mCoerceToString && !mRetValue.isUndefined()) { + JSString* str = JS::ToString(mCx, mRetValue); + if (!str) { + // ToString can be a function call, so an exception can be raised while + // executing the function. + mSkip = true; + return EvaluationExceptionToNSResult(mCx); + } + mRetValue.set(JS::StringValue(str)); + } + + aRetValue.set(mRetValue); + return NS_OK; +} + nsresult nsJSUtils::EvaluateString(JSContext* aCx, const nsAString& aScript, diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h index cb80ef4526..ce6f5a2283 100644 --- a/dom/base/nsJSUtils.h +++ b/dom/base/nsJSUtils.h @@ -63,6 +63,107 @@ public: const nsAString& aBody, JSObject** aFunctionObject); + // ExecutionContext is used to switch compartment. + class MOZ_STACK_CLASS ExecutionContext { + JSContext* mCx; + + // Handles switching to our global's compartment. + JSAutoCompartment mCompartment; + + // Set to a valid handle if a return value is expected. + JS::Rooted mRetValue; + + // Scope chain in which the execution takes place. + JS::AutoObjectVector mScopeChain; + + // returned value forwarded when we have to interupt the execution eagerly + // with mSkip. + nsresult mRv; + + // Used to skip upcoming phases in case of a failure. In such case the + // result is carried by mRv. + bool mSkip; + + // Should the result be serialized before being returned. + bool mCoerceToString; + +#ifdef DEBUG + // Should we set the return value. + bool mWantsReturnValue; + + bool mExpectScopeChain; +#endif + + public: + + // Enter compartment in which the code would be executed. The JSContext + // must come from an AutoEntryScript that has had + // TakeOwnershipOfErrorReporting() called on it. + ExecutionContext(JSContext* aCx, JS::Handle aGlobal); + + ExecutionContext(const ExecutionContext&) = delete; + ExecutionContext(ExecutionContext&&) = delete; + + ~ExecutionContext() { + // This flag is resetted, when the returned value is extracted. + MOZ_ASSERT(!mWantsReturnValue); + } + + // The returned value would be converted to a string if the + // |aCoerceToString| is flag set. + ExecutionContext& SetCoerceToString(bool aCoerceToString) { + mCoerceToString = aCoerceToString; + return *this; + } + + // Set the scope chain in which the code should be executed. + void SetScopeChain(const JS::AutoObjectVector& aScopeChain); + + // Copy the returned value in the mutable handle argument, in case of a + // evaluation failure either during the execution or the conversion of the + // result to a string, the nsresult would be set to the corresponding result + // code, and the mutable handle argument would remain unchanged. + // + // The value returned in the mutable handle argument is part of the + // compartment given as argument to the ExecutionContext constructor. If the + // caller is in a different compartment, then the out-param value should be + // wrapped by calling |JS_WrapValue|. + MOZ_MUST_USE nsresult + ExtractReturnValue(JS::MutableHandle aRetValue); + + // After getting a notification that an off-thread compilation terminated, + // this function will take the result of the parser by moving it to the main + // thread before starting the execution of the script. + // + // The compiled script would be returned in the |aScript| out-param. + MOZ_MUST_USE nsresult JoinAndExec(void **aOffThreadToken, + JS::MutableHandle aScript); + + // Compile a script contained in a SourceBuffer, and execute it. + nsresult CompileAndExec(JS::CompileOptions& aCompileOptions, + JS::SourceBufferHolder& aSrcBuf); + + // Compile a script contained in a string, and execute it. + nsresult CompileAndExec(JS::CompileOptions& aCompileOptions, + const nsAString& aScript); + + // Decode a script contained in a buffer, and execute it. + MOZ_MUST_USE nsresult DecodeAndExec(JS::CompileOptions& aCompileOptions, + mozilla::Vector& aBytecodeBuf, + size_t aBytecodeIndex); + + // After getting a notification that an off-thread decoding terminated, this + // function will get the result of the decoder by moving it to the main + // thread before starting the execution of the script. + MOZ_MUST_USE nsresult DecodeJoinAndExec(void **aOffThreadToken); + + // Similar to JoinAndExec, except that in addition to fecthing the source, + // we register the fact that we plan to encode its bytecode later. + MOZ_MUST_USE nsresult JoinEncodeAndExec(void **aOffThreadToken, + mozilla::Vector& aBytecodeBuf, + JS::MutableHandle aScript); + }; + struct MOZ_STACK_CLASS EvaluateOptions { bool coerceToString; JS::AutoObjectVector scopeChain; diff --git a/dom/ipc/StructuredCloneData.h b/dom/ipc/StructuredCloneData.h index 9bf29337d0..528e276c78 100644 --- a/dom/ipc/StructuredCloneData.h +++ b/dom/ipc/StructuredCloneData.h @@ -79,9 +79,6 @@ public: StructuredCloneData& operator=(const StructuredCloneData& aOther) = delete; - StructuredCloneData& - operator=(StructuredCloneData&& aOther) = default; - const nsTArray>& BlobImpls() const { return mBlobImplArray; diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index 64a80891bb..8c50e9d42c 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -62,8 +62,6 @@ using JS::SourceBufferHolder; namespace mozilla { namespace dom { -static LazyLogModule gCspPRLog("CSP"); - void ImplCycleCollectionUnlink(ScriptLoadRequestList& aField); @@ -2343,17 +2341,21 @@ ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) FinishDynamicImport(cx, request, rv); } } else { - // Update our current script. - AutoCurrentScriptUpdater scriptUpdater(this, aRequest->Element()); - JS::CompileOptions options(cx); rv = FillCompileOptionsForRequest(aes, aRequest, global, &options); if (NS_SUCCEEDED(rv)) { - nsAutoString inlineData; - SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData); - rv = nsJSUtils::EvaluateString(cx, srcBuf, global, options, - aRequest->OffThreadTokenPtr()); + { + nsJSUtils::ExecutionContext exec(cx, global); + if (aRequest->mOffThreadToken) { + JS::Rooted script(cx); + rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script); + } else { + nsAutoString inlineData; + SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData); + rv = exec.CompileAndExec(options, srcBuf); + } + } } } } diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 2a78301262..67423eb7ab 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -687,6 +687,13 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha if (!NameFunctions(cx, pn)) return false; + // XDR the newly delazified function. + if (script->scriptSource()->hasEncoder() && + !script->scriptSource()->xdrEncodeFunction(cx, fun, sourceObject)) + { + return false; + } + return true; } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 8e52e8f1bb..d49edeb838 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8854,7 +8854,7 @@ Parser::generatorComprehensionLambda(unsigned begin) ParseContext* outerpc = pc; // If we are off the main thread, the generator meta-objects have - // already been created by js::StartOffThreadParseScript, so cx will not + // already been created by js::StartOffThreadParseTask, so cx will not // be necessary. RootedObject proto(context); JSContext* cx = context->maybeJSContext(); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 3ce22113ed..192f7e5936 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4202,6 +4202,31 @@ JS::CancelOffThreadModule(JSContext* cx, void* token) HelperThreadState().cancelParseTask(cx, ParseTaskKind::Module, token); } +JS_PUBLIC_API(bool) +JS::DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Vector& buffer /* TranscodeBuffer& */, size_t cursor, + OffThreadCompileCallback callback, void* callbackData) +{ + MOZ_ASSERT(CanCompileOffThread(cx, options, buffer.length() - cursor)); + return StartOffThreadDecodeScript(cx, options, buffer, cursor, callback, callbackData); +} + +JS_PUBLIC_API(JSScript*) +JS::FinishOffThreadScriptDecoder(JSContext* cx, void* token) +{ + MOZ_ASSERT(cx); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx)); + return HelperThreadState().finishScriptDecodeTask(cx, token); +} + +JS_PUBLIC_API(void) +JS::CancelOffThreadScriptDecoder(JSContext* cx, void* token) +{ + MOZ_ASSERT(cx); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx)); + HelperThreadState().cancelParseTask(cx, ParseTaskKind::ScriptDecode, token); +} + JS_PUBLIC_API(bool) JS_CompileScript(JSContext* cx, const char* ascii, size_t length, const JS::CompileOptions& options, MutableHandleScript script) @@ -7101,6 +7126,26 @@ JS::DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, return decoder.resultCode(); } +JS_PUBLIC_API(bool) +JS::StartIncrementalEncoding(JSContext* cx, TranscodeBuffer& buffer, JS::HandleScript script) +{ + if (!script) + return false; + if (!script->scriptSource()->xdrEncodeTopLevel(cx, buffer, script)) + return false; + return true; +} + +JS_PUBLIC_API(bool) +JS::FinishIncrementalEncoding(JSContext* cx, JS::HandleScript script) +{ + if (!script) + return false; + if (!script->scriptSource()->xdrFinalizeEncoder()) + return false; + return true; +} + JS_PUBLIC_API(void) JS::SetBuildIdOp(JSContext* cx, JS::BuildIdOp buildIdOp) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index b4ffd52c6f..589b281975 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3748,7 +3748,7 @@ namespace JS { * addrefs/copies/tracing/etc. * * Furthermore, in some cases compile options are propagated from one entity to - * another (e.g. from a scriipt to a function defined in that script). This + * another (e.g. from a script to a function defined in that script). This * involves copying over some, but not all, of the options. * * So, we have a class hierarchy that reflects these four use cases: @@ -4186,6 +4186,17 @@ FinishOffThreadModule(JSContext* cx, void* token); extern JS_PUBLIC_API(void) CancelOffThreadModule(JSContext* cx, void* token); +extern JS_PUBLIC_API(bool) +DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Vector& buffer /* TranscodeBuffer& */, size_t cursor, + OffThreadCompileCallback callback, void* callbackData); + +extern JS_PUBLIC_API(JSScript*) +FinishOffThreadScriptDecoder(JSContext* cx, void* token); + +extern JS_PUBLIC_API(void) +CancelOffThreadScriptDecoder(JSContext* cx, void* token); + /** * Compile a function with envChain plus the global as its scope chain. * envChain must contain objects in the current compartment of cx. The actual @@ -6168,7 +6179,10 @@ enum TranscodeResult TranscodeResult_Failure_AsmJSNotSupported = TranscodeResult_Failure | 0x3, TranscodeResult_Failure_BadDecode = TranscodeResult_Failure | 0x4, - // A error, the JSContext has a pending exception. + TranscodeResult_Failure_WrongCompileOption = TranscodeResult_Failure | 0x5, + TranscodeResult_Failure_NotInterpretedFun = TranscodeResult_Failure | 0x6, + + // There is a pending exception on the context. TranscodeResult_Throw = 0x200 }; @@ -6186,6 +6200,25 @@ extern JS_PUBLIC_API(TranscodeResult) DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, JS::MutableHandleFunction funp, size_t cursorIndex = 0); +// Register an encoder on the given script source, such that all functions can +// be encoded as they are parsed. This strategy is used to avoid blocking the +// main thread in a non-interruptible way. +// +// The |script| argument of |StartIncrementalEncoding| and +// |FinishIncrementalEncoding| should be the top-level script returned either as +// an out-param of any of the |Compile| functions, or the result of +// |FinishOffThreadScript|. +// +// The |buffer| argument should not be used before until +// |FinishIncrementalEncoding| is called on the same script, and returns +// successfully. If any of these functions failed, the |buffer| content is +// undefined. +extern JS_PUBLIC_API(bool) +StartIncrementalEncoding(JSContext* cx, TranscodeBuffer& buffer, JS::HandleScript script); + +extern JS_PUBLIC_API(bool) +FinishIncrementalEncoding(JSContext* cx, JS::HandleScript script); + } /* namespace JS */ namespace js { diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 6a1ca82011..02308314c9 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -538,7 +538,7 @@ js::XDRAtom(XDRState* xdr, MutableHandleAtom atomp) uint32_t length = lengthAndEncoding >> 1; bool latin1 = lengthAndEncoding & 0x1; - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); JSAtom* atom; if (latin1) { const Latin1Char* chars = nullptr; @@ -567,7 +567,7 @@ js::XDRAtom(XDRState* xdr, MutableHandleAtom atomp) * most allocations here will be bigger than tempLifoAlloc's default * chunk size. */ - chars = cx->runtime()->pod_malloc(length); + chars = cx->pod_malloc(length); if (!chars) return false; } diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index fc243fab66..2f3bd8ca55 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -530,7 +530,7 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) template bool js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, - HandleScript enclosingScript, MutableHandleFunction objp) + HandleScriptSource sourceObject, MutableHandleFunction objp) { enum FirstWordFlag { HasAtom = 0x1, @@ -544,21 +544,15 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, uint32_t firstword = 0; /* bitmask of FirstWordFlag */ uint32_t flagsword = 0; /* word for argument count and fun->flags */ - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); RootedFunction fun(cx); RootedScript script(cx); Rooted lazy(cx); if (mode == XDR_ENCODE) { fun = objp; - if (!fun->isInterpreted()) { - JSAutoByteString funNameBytes; - if (const char* name = GetFunctionNameBytes(cx, fun, &funNameBytes)) { - JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, - JSMSG_NOT_SCRIPTED_FUNCTION, name); - } - return false; - } + if (!fun->isInterpreted()) + return xdr->fail(JS::TranscodeResult_Failure_NotInterpretedFun); if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom()) firstword |= HasAtom; @@ -590,6 +584,10 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, fun->environment() == nullptr); } + // Everything added below can substituted by the non-lazy-script version of + // this function later. + js::AutoXDRTree funTree(xdr, xdr->getTreeKey(fun)); + if (!xdr->codeUint32(&firstword)) return false; @@ -601,7 +599,11 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, if (mode == XDR_DECODE) { RootedObject proto(cx); if (firstword & HasStarGeneratorProto) { - proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global()); + // If we are off the main thread, the generator meta-objects have + // already been created by js::StartOffThreadParseTask, so + // JSContext* will not be necessary. + JSContext* context = cx->maybeJSContext(); + proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(context, cx->global()); if (!proto) return false; } @@ -618,10 +620,10 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, } if (firstword & IsLazy) { - if (!XDRLazyScript(xdr, enclosingScope, enclosingScript, fun, &lazy)) + if (!XDRLazyScript(xdr, enclosingScope, sourceObject, fun, &lazy)) return false; } else { - if (!XDRScript(xdr, enclosingScope, enclosingScript, fun, &script)) + if (!XDRScript(xdr, enclosingScope, sourceObject, fun, &script)) return false; } @@ -650,10 +652,10 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, } template bool -js::XDRInterpretedFunction(XDRState*, HandleScope, HandleScript, MutableHandleFunction); +js::XDRInterpretedFunction(XDRState*, HandleScope, HandleScriptSource, MutableHandleFunction); template bool -js::XDRInterpretedFunction(XDRState*, HandleScope, HandleScript, MutableHandleFunction); +js::XDRInterpretedFunction(XDRState*, HandleScope, HandleScriptSource, MutableHandleFunction); /* ES6 (04-25-16) 19.2.3.6 Function.prototype [ @@hasInstance ] */ bool diff --git a/js/src/jsfun.h b/js/src/jsfun.h index a139bc336a..ad6eb07a2c 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -879,7 +879,7 @@ JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource); template bool XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, - HandleScript enclosingScript, MutableHandleFunction objp); + HandleScriptSource sourceObject, MutableHandleFunction objp); /* * Report an error that call.thisv is not compatible with the specified class, diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 3b38c20679..4357aa8f68 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1137,7 +1137,7 @@ js::CloneObject(JSContext* cx, HandleObject obj, Handle proto) } static bool -GetScriptArrayObjectElements(JSContext* cx, HandleObject obj, MutableHandle> values) +GetScriptArrayObjectElements(ExclusiveContext* cx, HandleObject obj, MutableHandle> values) { MOZ_ASSERT(!obj->isSingleton()); MOZ_ASSERT(obj->is() || obj->is()); @@ -1155,7 +1155,7 @@ GetScriptArrayObjectElements(JSContext* cx, HandleObject obj, MutableHandle properties) { if (obj->is()) { @@ -1330,9 +1330,8 @@ js::XDRObjectLiteral(XDRState* xdr, MutableHandleObject obj) { /* NB: Keep this in sync with DeepCloneObjectLiteral. */ - JSContext* cx = xdr->cx(); - MOZ_ASSERT_IF(mode == XDR_ENCODE && obj->isSingleton(), - cx->compartment()->behaviors().getSingletonsAsTemplates()); + ExclusiveContext* cx = xdr->cx(); + assertSameCompartment(cx, obj); // Distinguish between objects and array classes. uint32_t isArray = 0; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index fe0be9b5a2..d8248f984a 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -72,7 +72,7 @@ template bool js::XDRScriptConst(XDRState* xdr, MutableHandleValue vp) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); enum ConstTag { SCRIPT_INT, @@ -195,7 +195,7 @@ template static bool XDRLazyClosedOverBindings(XDRState* xdr, MutableHandle lazy) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); RootedAtom atom(cx); for (size_t i = 0; i < lazy->numClosedOverBindings(); i++) { uint8_t endOfScopeSentinel; @@ -228,7 +228,7 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri MOZ_ASSERT_IF(mode == XDR_ENCODE, script->isRelazifiable() && script->maybeLazyScript()); MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions()); - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); uint64_t packedFields; { @@ -257,7 +257,8 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri return false; if (mode == XDR_DECODE) { - lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, script, + RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap()); + lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, sourceObject, packedFields, begin, end, toStringStart, lineno, column)); if (!lazy) @@ -304,8 +305,9 @@ enum XDRClassKind { template bool -js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScript enclosingScript, - HandleFunction fun, MutableHandleScript scriptp) +js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, + HandleScriptSource sourceObjectArg, HandleFunction fun, + MutableHandleScript scriptp) { /* NB: Keep this in sync with CopyScript. */ @@ -348,17 +350,16 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip uint32_t scriptBits = 0; uint32_t bodyScopeIndex = 0; - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); RootedScript script(cx); natoms = nsrcnotes = 0; nconsts = nobjects = nscopes = nregexps = ntrynotes = nscopenotes = nyieldoffsets = 0; if (mode == XDR_ENCODE) { script = scriptp.get(); - MOZ_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment()); MOZ_ASSERT(script->functionNonDelazifying() == fun); - if (!fun && script->treatAsRunOnce()) { + if (!fun && script->treatAsRunOnce() && script->hasRunOnce()) { // This is a toplevel or eval script that's runOnce. We want to // make sure that we're not XDR-saving an object we emitted for // JSOP_OBJECT that then got modified. So throw if we're not @@ -431,7 +432,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip scriptBits |= (1 << FunctionHasThisBinding); if (script->functionHasExtraBodyVarScope()) scriptBits |= (1 << FunctionHasExtraBodyVarScope); - if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource()) + MOZ_ASSERT_IF(sourceObjectArg, sourceObjectArg->source() == script->scriptSource()); + if (!sourceObjectArg) scriptBits |= (1 << OwnSource); if (script->isGeneratorExp()) scriptBits |= (1 << IsGeneratorExp); @@ -492,15 +494,31 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip if (!xdr->codeUint32(&scriptBits)) return false; + MOZ_ASSERT(!!(scriptBits & (1 << OwnSource)) == !sourceObjectArg); + RootedScriptSource sourceObject(cx, sourceObjectArg); + if (mode == XDR_DECODE) { JSVersion version_ = JSVersion(version); MOZ_ASSERT((version_ & VersionFlags::MASK) == unsigned(version_)); - CompileOptions options(cx); - options.setVersion(version_) - .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval))) - .setSelfHostingMode(!!(scriptBits & (1 << SelfHosted))); - RootedScriptSource sourceObject(cx); + // When loading from the bytecode cache, we get the CompileOption from + // the document, which specify the version to use. If the version does + // not match, then we should fail. + mozilla::Maybe options; + if (xdr->hasOptions()) { + options.emplace(xdr->cx(), xdr->options()); + if (options->version != version_ || + options->noScriptRval != !!(scriptBits & (1 << NoScriptRval)) || + options->selfHostingMode != !!(scriptBits & (1 << SelfHosted))) + { + return xdr->fail(JS::TranscodeResult_Failure_WrongCompileOption); + } + } else { + options.emplace(xdr->cx()->asJSContext()); + (*options).setVersion(version_) + .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval))) + .setSelfHostingMode(!!(scriptBits & (1 << SelfHosted))); + } if (scriptBits & (1 << OwnSource)) { ScriptSource* ss = cx->new_(); if (!ss) @@ -513,22 +531,22 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip * ScriptSourceObject, and those that are (element; elementAttributeName) * aren't preserved by XDR. So this can be simple. */ - CompileOptions options(cx); - ss->initFromOptions(cx, options); + ss->initFromOptions(cx, *options); sourceObject = ScriptSourceObject::create(cx, ss); - if (!sourceObject || - !ScriptSourceObject::initFromOptions(cx, sourceObject, options)) - return false; - } else { - MOZ_ASSERT(enclosingScript); - // When decoding, all the scripts and the script source object - // are in the same compartment, so the script's source object - // should never be a cross-compartment wrapper. - MOZ_ASSERT(enclosingScript->sourceObject()->is()); - sourceObject = &enclosingScript->sourceObject()->as(); + if (xdr->hasScriptSourceObjectOut()) { + // When the ScriptSourceObjectOut is provided by ParseTask, it + // is stored in a location which is traced by the GC. + *xdr->scriptSourceObjectOut() = sourceObject; + } else { + if (!sourceObject || + !ScriptSourceObject::initFromOptions(cx->asJSContext(), sourceObject, *options)) + { + return false; + } + } } - script = JSScript::Create(cx, options, sourceObject, 0, 0, 0, 0); + script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0, 0); if (!script) return false; @@ -536,6 +554,10 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip // decoded may iterate the static scope chain. if (fun) fun->initScript(script); + } else { + // When encoding, we do not mutate any of the JSScript or LazyScript, so + // we can safely unwrap it here. + sourceObject = &script->scriptSourceUnwrap(); } if (mode == XDR_DECODE) { @@ -606,7 +628,7 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip JS_STATIC_ASSERT(sizeof(jssrcnote) == 1); if (scriptBits & (1 << OwnSource)) { - if (!script->scriptSource()->performXDR(xdr)) + if (!sourceObject->source()->performXDR(xdr)) return false; } if (!xdr->codeUint32(&script->sourceStart_)) @@ -846,7 +868,7 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip RootedFunction tmp(cx); if (mode == XDR_ENCODE) tmp = &(*objp)->as(); - if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp)) + if (!XDRInterpretedFunction(xdr, funEnclosingScope, sourceObject, &tmp)) return false; *objp = tmp; break; @@ -922,27 +944,28 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip scriptp.set(script); /* see BytecodeEmitter::tellDebuggerAboutCompiledScript */ - if (!fun) - Debugger::onNewScript(cx, script); + if (!fun && cx->isJSContext()) + Debugger::onNewScript(cx->asJSContext(), script); } return true; } template bool -js::XDRScript(XDRState*, HandleScope, HandleScript, HandleFunction, +js::XDRScript(XDRState*, HandleScope, HandleScriptSource, HandleFunction, MutableHandleScript); template bool -js::XDRScript(XDRState*, HandleScope, HandleScript, HandleFunction, +js::XDRScript(XDRState*, HandleScope, HandleScriptSource, HandleFunction, MutableHandleScript); template bool -js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, HandleScript enclosingScript, - HandleFunction fun, MutableHandle lazy) +js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, + HandleScriptSource sourceObject, HandleFunction fun, + MutableHandle lazy) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); { uint32_t begin; @@ -979,7 +1002,7 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, HandleScript } if (mode == XDR_DECODE) { - lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, enclosingScript, + lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, sourceObject, packedFields, begin, end, toStringStart, lineno, column)); if (!lazy) return false; @@ -1013,11 +1036,11 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, HandleScript } template bool -js::XDRLazyScript(XDRState*, HandleScope, HandleScript, +js::XDRLazyScript(XDRState*, HandleScope, HandleScriptSource, HandleFunction, MutableHandle); template bool -js::XDRLazyScript(XDRState*, HandleScope, HandleScript, +js::XDRLazyScript(XDRState*, HandleScope, HandleScriptSource, HandleFunction, MutableHandle); void @@ -1934,6 +1957,64 @@ ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, info->numScripts++; } +bool +ScriptSource::xdrEncodeTopLevel(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, + HandleScript script) +{ + xdrEncoder_ = js::MakeUnique(cx, buffer, buffer.length()); + if (!xdrEncoder_) { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(hasEncoder()); + auto failureCase = mozilla::MakeScopeExit([&] { + xdrEncoder_.reset(nullptr); + }); + + if (!xdrEncoder_->init()) { + ReportOutOfMemory(cx); + return false; + } + + RootedScript s(cx, script); + if (!xdrEncoder_->codeScript(&s)) + return false; + + failureCase.release(); + return true; +} + +bool +ScriptSource::xdrEncodeFunction(ExclusiveContext* cx, HandleFunction fun, HandleScriptSource sourceObject) +{ + MOZ_ASSERT(sourceObject->source() == this); + MOZ_ASSERT(hasEncoder()); + auto failureCase = mozilla::MakeScopeExit([&] { + xdrEncoder_.reset(nullptr); + }); + + RootedFunction f(cx, fun); + if (!xdrEncoder_->codeFunction(&f, sourceObject)) + return false; + + failureCase.release(); + return true; +} + +bool +ScriptSource::xdrFinalizeEncoder() +{ + MOZ_ASSERT(hasEncoder()); + auto cleanup = mozilla::MakeScopeExit([&] { + xdrEncoder_.reset(nullptr); + }); + + if (!xdrEncoder_->linearize()) + return false; + return true; +} + template bool ScriptSource::performXDR(XDRState* xdr) @@ -2073,7 +2154,10 @@ ScriptSource::performXDR(XDRState* xdr) const char* fn = filename(); if (!xdr->codeCString(&fn)) return false; - if (mode == XDR_DECODE && !setFilename(xdr->cx(), fn)) + // Note: If the decoder has an option, then the filename is defined by + // the CompileOption from the document. + MOZ_ASSERT_IF(mode == XDR_DECODE && xdr->hasOptions(), filename()); + if (mode == XDR_DECODE && !xdr->hasOptions() && !setFilename(xdr->cx(), fn)) return false; } @@ -4135,7 +4219,7 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, /* static */ LazyScript* LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, HandleScript script, HandleScope enclosingScope, - HandleScript enclosingScript, + HandleScriptSource sourceObject, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t toStringStart, uint32_t lineno, uint32_t column) { @@ -4166,11 +4250,11 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, // values should only be non-null if we have a non-lazy enclosing script. // AddLazyFunctionsForCompartment relies on the source object being null // if we're nested inside another lazy function. - MOZ_ASSERT(!!enclosingScript == !!enclosingScope); + MOZ_ASSERT(!!sourceObject == !!enclosingScope); MOZ_ASSERT(!res->sourceObject()); MOZ_ASSERT(!res->enclosingScope()); - if (enclosingScript) - res->setEnclosingScopeAndSource(enclosingScope, &enclosingScript->scriptSourceUnwrap()); + if (sourceObject) + res->setEnclosingScopeAndSource(enclosingScope, sourceObject); MOZ_ASSERT(!res->hasScript()); if (script) diff --git a/js/src/jsscript.h b/js/src/jsscript.h index fb5d7c79bf..198f96115f 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -431,6 +431,11 @@ class ScriptSource // memory management. const char* introductionType_; + // The bytecode cache encoder is used to encode only the content of function + // which are delazified. If this value is not nullptr, then each delazified + // function should be recorded before their first execution. + UniquePtr xdrEncoder_; + // True if we can call JSRuntime::sourceHook to load the source on // demand. If sourceRetrievable_ and hasSourceData() are false, it is not // possible to get source at all. @@ -452,6 +457,7 @@ class ScriptSource parameterListEnd_(0), introducerFilename_(nullptr), introductionType_(nullptr), + xdrEncoder_(nullptr), sourceRetrievable_(false), hasIntroductionOffset_(false) { @@ -574,6 +580,29 @@ class ScriptSource introductionOffset_ = offset; hasIntroductionOffset_ = true; } + + // Return wether an XDR encoder is present or not. + bool hasEncoder() const { return bool(xdrEncoder_); } + + // Create a new XDR encoder, and encode the top-level JSScript. The result + // of the encoding would be available in the |buffer| provided as argument, + // as soon as |xdrFinalize| is called and all xdr function calls returned + // successfully. + bool xdrEncodeTopLevel(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, HandleScript script); + + // Encode a delazified JSFunction. In case of errors, the XDR encoder is + // freed and the |buffer| provided as argument to |xdrEncodeTopLevel| is + // considered undefined. + // + // The |sourceObject| argument is the object holding the current + // ScriptSource. + bool xdrEncodeFunction(ExclusiveContext* cx, HandleFunction fun, + HandleScriptSource sourceObject); + + // Linearize the encoded content in the |buffer| provided as argument to + // |xdrEncodeTopLevel|, and free the XDR encoder. In case of errors, the + // |buffer| is considered undefined. + bool xdrFinalizeEncoder(); }; class ScriptSourceHolder @@ -695,12 +724,12 @@ AsyncKindFromBits(unsigned val) { */ template bool -XDRScript(XDRState* xdr, HandleScope enclosingScope, HandleScript enclosingScript, +XDRScript(XDRState* xdr, HandleScope enclosingScope, HandleScriptSource sourceObject, HandleFunction fun, MutableHandleScript scriptp); template bool -XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, HandleScript enclosingScript, +XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, HandleScriptSource sourceObject, HandleFunction fun, MutableHandle lazy); /* @@ -808,7 +837,7 @@ class JSScript : public js::gc::TenuredCell friend bool js::XDRScript(js::XDRState* xdr, js::HandleScope enclosingScope, - js::HandleScript enclosingScript, js::HandleFunction fun, + js::HandleScriptSource sourceObject, js::HandleFunction fun, js::MutableHandleScript scriptp); friend bool @@ -1211,11 +1240,11 @@ class JSScript : public js::gc::TenuredCell return funLength_; } - size_t sourceStart() const { + uint32_t sourceStart() const { return sourceStart_; } - size_t sourceEnd() const { + uint32_t sourceEnd() const { return sourceEnd_; } @@ -2076,11 +2105,11 @@ class LazyScript : public gc::TenuredCell // The "script" argument to this function can be null. If it's non-null, // then this LazyScript should be associated with the given JSScript. // - // The enclosingScript and enclosingScope arguments may be null if the + // The sourceObject and enclosingScope arguments may be null if the // enclosing function is also lazy. static LazyScript* Create(ExclusiveContext* cx, HandleFunction fun, HandleScript script, HandleScope enclosingScope, - HandleScript enclosingScript, + HandleScriptSource sourceObject, uint64_t packedData, uint32_t begin, uint32_t end, uint32_t toStringStart, uint32_t lineno, uint32_t column); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 6b1675bc09..19b77b328b 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1605,6 +1605,7 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) bool catchTermination = false; bool loadBytecode = false; bool saveBytecode = false; + bool saveIncrementalBytecode = false; bool assertEqBytecode = false; RootedObject callerGlobal(cx, cx->global()); @@ -1668,6 +1669,11 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) if (!v.isUndefined()) saveBytecode = ToBoolean(v); + if (!JS_GetProperty(cx, opts, "saveIncrementalBytecode", &v)) + return false; + if (!v.isUndefined()) + saveIncrementalBytecode = ToBoolean(v); + if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) return false; if (!v.isUndefined()) @@ -1675,12 +1681,17 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) // We cannot load or save the bytecode if we have no object where the // bytecode cache is stored. - if (loadBytecode || saveBytecode) { + if (loadBytecode || saveBytecode || saveIncrementalBytecode) { if (!cacheEntry) { JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate"); return false; } + if (saveIncrementalBytecode && saveBytecode) { + JS_ReportErrorASCII(cx, "saveIncrementalBytecode and saveBytecode cannot be used" + " at the same time."); + return false; + } } } @@ -1758,6 +1769,15 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) if (!script->scriptSource()->setSourceMapURL(cx, smurl)) return false; } + + // If we want to save the bytecode incrementally, then we should + // register ahead the fact that every JSFunction which is being + // delazified should be encoded at the end of the delazification. + if (saveIncrementalBytecode) { + if (!StartIncrementalEncoding(cx, saveBuffer, script)) + return false; + } + if (!JS_ExecuteScript(cx, script, args.rval())) { if (catchTermination && !JS_IsExceptionPending(cx)) { JSAutoCompartment ac1(cx, callerGlobal); @@ -1770,14 +1790,21 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) return false; } + // Encode the bytecode after the execution of the script. if (saveBytecode) { JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script); if (!ConvertTranscodeResultToJSException(cx, rv)) return false; } + // Serialize the encoded bytecode, recorded before the execution, into a + // buffer which can be deserialized linearly. + if (saveIncrementalBytecode) { + if (!FinishIncrementalEncoding(cx, script)) + return false; + } } - if (saveBytecode) { + if (saveBytecode || saveIncrementalBytecode) { // If we are both loading and saving, we assert that we are going to // replace the current bytecode by the same stream of bytes. if (loadBytecode && assertEqBytecode) { diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 51db3664bf..4ce8728ace 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -304,6 +304,18 @@ ParseTask::ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusi { } +ParseTask::ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal, + JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor, + JS::OffThreadCompileCallback callback, void* callbackData) + : kind(kind), cx(cx), options(initCx), buffer(&buffer), cursor(cursor), + alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), + exclusiveContextGlobal(exclusiveContextGlobal), + callback(callback), callbackData(callbackData), + script(nullptr), sourceObject(nullptr), + errors(cx), overRecursed(false), outOfMemory(false) +{ +} + bool ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options) { @@ -389,6 +401,29 @@ ModuleParseTask::parse() script = module->script(); } +ScriptDecodeTask::ScriptDecodeTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal, + JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor, + JS::OffThreadCompileCallback callback, void* callbackData) + : ParseTask(ParseTaskKind::ScriptDecode, cx, exclusiveContextGlobal, initCx, + buffer, cursor, callback, callbackData) +{ +} + +void +ScriptDecodeTask::parse() +{ + RootedScript resultScript(cx); + XDROffThreadDecoder decoder(cx, alloc, &options, /* sourceObjectOut = */ &sourceObject, + *buffer, cursor); + decoder.codeScript(&resultScript); + MOZ_ASSERT(bool(resultScript) == (decoder.resultCode() == JS::TranscodeResult_Ok)); + if (decoder.resultCode() == JS::TranscodeResult_Ok) { + script = resultScript.get(); + } else { + sourceObject = nullptr; + } +} + void js::CancelOffThreadParses(JSRuntime* rt) { @@ -557,10 +592,10 @@ QueueOffThreadParseTask(JSContext* cx, ParseTask* task) return true; } +template bool -js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options, - const char16_t* chars, size_t length, - JS::OffThreadCompileCallback callback, void* callbackData) +StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options, + ParseTaskKind kind, TaskFunctor& taskFunctor) { // Suppress GC so that calls below do not trigger a new incremental GC // which could require barriers on the atoms compartment. @@ -568,7 +603,7 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime()); AutoSuppressAllocationMetadataBuilder suppressMetadata(cx); - JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Script, nogc); + JSObject* global = CreateGlobalForOffThreadParse(cx, kind, nogc); if (!global) return false; @@ -578,9 +613,7 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio if (!helpercx) return false; - ScopedJSDeletePtr task( - cx->new_(helpercx.get(), global, cx, chars, length, - callback, callbackData)); + ScopedJSDeletePtr task(taskFunctor(helpercx.get(), global)); if (!task) return false; @@ -594,41 +627,40 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio return true; } +bool +js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, + JS::OffThreadCompileCallback callback, void* callbackData) +{ + auto functor = [&](ExclusiveContext* helpercx, JSObject* global) -> ScriptParseTask* { + return cx->new_(helpercx, global, cx, chars, length, + callback, callbackData); + }; + return StartOffThreadParseTask(cx, options, ParseTaskKind::Script, functor); +} + bool js::StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData) { - // Suppress GC so that calls below do not trigger a new incremental GC - // which could require barriers on the atoms compartment. - gc::AutoSuppressGC nogc(cx); - gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime()); - AutoSuppressAllocationMetadataBuilder suppressMetadata(cx); + auto functor = [&](ExclusiveContext* helpercx, JSObject* global) -> ModuleParseTask* { + return cx->new_(helpercx, global, cx, chars, length, + callback, callbackData); + }; + return StartOffThreadParseTask(cx, options, ParseTaskKind::Module, functor); +} - JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Module, nogc); - if (!global) - return false; - - ScopedJSDeletePtr helpercx( - cx->new_(cx->runtime(), (PerThreadData*) nullptr, - ExclusiveContext::Context_Exclusive, cx->options())); - if (!helpercx) - return false; - - ScopedJSDeletePtr task( - cx->new_(helpercx.get(), global, cx, chars, length, - callback, callbackData)); - if (!task) - return false; - - helpercx.forget(); - - if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task)) - return false; - - task.forget(); - - return true; +bool +js::StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options, + JS::TranscodeBuffer& buffer, size_t cursor, + JS::OffThreadCompileCallback callback, void* callbackData) +{ + auto functor = [&](ExclusiveContext* helpercx, JSObject* global) -> ScriptDecodeTask* { + return cx->new_(helpercx, global, cx, buffer, cursor, + callback, callbackData); + }; + return StartOffThreadParseTask(cx, options, ParseTaskKind::ScriptDecode, functor); } void @@ -1280,6 +1312,14 @@ GlobalHelperThreadState::finishScriptParseTask(JSContext* cx, void* token) return script; } +JSScript* +GlobalHelperThreadState::finishScriptDecodeTask(JSContext* cx, void* token) +{ + JSScript* script = finishParseTask(cx, ParseTaskKind::ScriptDecode, token); + MOZ_ASSERT_IF(script, script->isGlobalCode()); + return script; +} + JSObject* GlobalHelperThreadState::finishModuleParseTask(JSContext* cx, void* token) { diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h index 18997183cb..51a18a8060 100644 --- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -48,7 +48,8 @@ namespace wasm { enum class ParseTaskKind { Script, - Module + Module, + ScriptDecode }; // Per-process state for off thread work items. @@ -244,6 +245,7 @@ class GlobalHelperThreadState public: JSScript* finishScriptParseTask(JSContext* cx, void* token); + JSScript* finishScriptDecodeTask(JSContext* cx, void* token); JSObject* finishModuleParseTask(JSContext* cx, void* token); bool compressionInProgress(SourceCompressionTask* task, const AutoLockHelperThreadState& lock); SourceCompressionTask* compressionTaskForSource(ScriptSource* ss, const AutoLockHelperThreadState& lock); @@ -482,6 +484,11 @@ StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); +bool +StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options, + JS::TranscodeBuffer& buffer, size_t cursor, + JS::OffThreadCompileCallback callback, void* callbackData); + /* * Called at the end of GC to enqueue any Parse tasks that were waiting on an * atoms-zone GC to finish. @@ -534,8 +541,21 @@ struct ParseTask ParseTaskKind kind; ExclusiveContext* cx; OwningCompileOptions options; - const char16_t* chars; - size_t length; + // Anonymous union, the only correct interpretation is provided by the + // ParseTaskKind value, or from the virtual parse function. + union { + struct { + const char16_t* chars; + size_t length; + }; + struct { + // This should be a reference, but C++ prevents us from using union + // with references as it assumes the reference constness might be + // violated. + JS::TranscodeBuffer* const buffer; + size_t cursor; + }; + }; LifoAlloc alloc; // Rooted pointer to the global object used by 'cx'. @@ -562,6 +582,9 @@ struct ParseTask ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal, JSContext* initCx, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); + ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal, + JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor, + JS::OffThreadCompileCallback callback, void* callbackData); bool init(JSContext* cx, const ReadOnlyCompileOptions& options); void activate(JSRuntime* rt); @@ -593,6 +616,14 @@ struct ModuleParseTask : public ParseTask void parse() override; }; +struct ScriptDecodeTask : public ParseTask +{ + ScriptDecodeTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal, + JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor, + JS::OffThreadCompileCallback callback, void* callbackData); + void parse() override; +}; + // Return whether, if a new parse task was started, it would need to wait for // an in-progress GC to complete before starting. extern bool diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 66bd9aba36..f46f4b4510 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -1552,7 +1552,7 @@ js::XDRScriptRegExpObject(XDRState* xdr, MutableHandle objp if (mode == XDR_DECODE) { RegExpFlag flags = RegExpFlag(flagsword); RegExpObject* reobj = RegExpObject::create(xdr->cx(), source, flags, nullptr, - xdr->cx()->tempLifoAlloc()); + xdr->lifoAlloc()); if (!reobj) return false; diff --git a/js/src/vm/Scope.cpp b/js/src/vm/Scope.cpp index a0995aa62e..5cb9abaecf 100644 --- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -202,7 +202,7 @@ NewEmptyScopeData(ExclusiveContext* cx, uint32_t length = 0) static bool XDRBindingName(XDRState* xdr, BindingName* bindingName) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); RootedAtom atom(cx, bindingName->name()); bool hasAtom = !!atom; @@ -220,7 +220,7 @@ XDRBindingName(XDRState* xdr, BindingName* bindingName) static bool XDRBindingName(XDRState* xdr, BindingName* bindingName) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); uint8_t u8; if (!xdr->codeUint8(&u8)) @@ -254,7 +254,7 @@ Scope::XDRSizedBindingNames(XDRState* xdr, Handle scope, { MOZ_ASSERT(!data); - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); uint32_t length; if (mode == XDR_ENCODE) @@ -542,7 +542,7 @@ template LexicalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); Rooted data(cx); if (!XDRSizedBindingNames(xdr, scope.as(), &data)) @@ -725,7 +725,7 @@ template FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosing, MutableHandleScope scope) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); Rooted data(cx); if (!XDRSizedBindingNames(xdr, scope.as(), &data)) return false; @@ -861,7 +861,7 @@ template VarScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); Rooted data(cx); if (!XDRSizedBindingNames(xdr, scope.as(), &data)) return false; @@ -962,7 +962,7 @@ GlobalScope::XDR(XDRState* xdr, ScopeKind kind, MutableHandleScope scope) { MOZ_ASSERT((mode == XDR_DECODE) == !scope); - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); Rooted data(cx); if (!XDRSizedBindingNames(xdr, scope.as(), &data)) return false; @@ -1084,7 +1084,7 @@ template EvalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope) { - JSContext* cx = xdr->cx(); + ExclusiveContext* cx = xdr->cx(); Rooted data(cx); { diff --git a/js/src/vm/Xdr.cpp b/js/src/vm/Xdr.cpp index 4f8b3fed46..1c599f0d61 100644 --- a/js/src/vm/Xdr.cpp +++ b/js/src/vm/Xdr.cpp @@ -10,6 +10,7 @@ #include #include "jsapi.h" +#include "jscntxt.h" #include "jsscript.h" #include "vm/Debugger.h" @@ -18,11 +19,17 @@ using namespace js; using mozilla::PodEqual; +template +LifoAlloc& +XDRState::lifoAlloc() const { + return buf.cx()->asJSContext()->tempLifoAlloc(); +} + template void -XDRState::postProcessContextErrors(JSContext* cx) +XDRState::postProcessContextErrors(ExclusiveContext* cx) { - if (cx->isExceptionPending()) { + if (cx->isJSContext() && cx->asJSContext()->isExceptionPending()) { MOZ_ASSERT(resultCode_ == JS::TranscodeResult_Ok); resultCode_ = JS::TranscodeResult_Throw; } @@ -40,7 +47,7 @@ XDRState::codeChars(const Latin1Char* chars, size_t nchars) return true; uint8_t* ptr = buf.write(nchars); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); mozilla::PodCopy(ptr, chars, nchars); return true; @@ -56,7 +63,7 @@ XDRState::codeChars(char16_t* chars, size_t nchars) if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(nbytes); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars); } else { const uint8_t* ptr = buf.read(nbytes); @@ -70,11 +77,8 @@ static bool VersionCheck(XDRState* xdr) { JS::BuildIdCharVector buildId; - if (!xdr->cx()->buildIdOp() || !xdr->cx()->buildIdOp()(&buildId)) { - JS_ReportErrorNumberASCII(xdr->cx(), GetErrorMessage, nullptr, - JSMSG_BUILD_ID_NOT_AVAILABLE); - return false; - } + if (!xdr->cx()->buildIdOp() || !xdr->cx()->buildIdOp()(&buildId)) + return xdr->fail(JS::TranscodeResult_Failure_BadBuildId); MOZ_ASSERT(!buildId.empty()); uint32_t buildIdLength; @@ -97,7 +101,7 @@ VersionCheck(XDRState* xdr) // buildId. if (!decodedBuildId.resize(buildIdLength)) { ReportOutOfMemory(xdr->cx()); - return false; + return xdr->fail(JS::TranscodeResult_Throw); } if (!xdr->codeBytes(decodedBuildId.begin(), buildIdLength)) @@ -113,20 +117,26 @@ VersionCheck(XDRState* xdr) template bool -XDRState::codeFunction(MutableHandleFunction funp) +XDRState::codeFunction(MutableHandleFunction funp, HandleScriptSource sourceObject) { - if (mode == XDR_DECODE) + RootedScope scope(cx(), &cx()->global()->emptyGlobalScope()); + if (mode == XDR_DECODE) { + MOZ_ASSERT(!sourceObject); funp.set(nullptr); - else + } else if (getTreeKey(funp) != AutoXDRTree::noKey) { + MOZ_ASSERT(sourceObject); + scope = funp->nonLazyScript()->enclosingScope(); + } else { + MOZ_ASSERT(!sourceObject); MOZ_ASSERT(funp->nonLazyScript()->enclosingScope()->is()); + } if (!VersionCheck(this)) { postProcessContextErrors(cx()); return false; } - RootedScope scope(cx(), &cx()->global()->emptyGlobalScope()); - if (!XDRInterpretedFunction(this, scope, nullptr, funp)) { + if (!XDRInterpretedFunction(this, scope, sourceObject, funp)) { postProcessContextErrors(cx()); funp.set(nullptr); return false; @@ -144,6 +154,8 @@ XDRState::codeScript(MutableHandleScript scriptp) else MOZ_ASSERT(!scriptp->enclosingScope()); + AutoXDRTree scriptTree(this, getTopLevelTreeKey()); + if (!VersionCheck(this)) { postProcessContextErrors(cx()); return false; @@ -167,3 +179,189 @@ XDRState::codeConstValue(MutableHandleValue vp) template class js::XDRState; template class js::XDRState; + +AutoXDRTree::AutoXDRTree(XDRCoderBase* xdr, AutoXDRTree::Key key) + : key_(key), + parent_(this), + xdr_(xdr) +{ + if (key_ != AutoXDRTree::noKey) + xdr->createOrReplaceSubTree(this); +} + +AutoXDRTree::~AutoXDRTree() +{ + if (key_ != AutoXDRTree::noKey) + xdr_->endSubTree(); +} + +constexpr AutoXDRTree::Key AutoXDRTree::noKey; +constexpr AutoXDRTree::Key AutoXDRTree::noSubTree; +constexpr AutoXDRTree::Key AutoXDRTree::topLevel; + +AutoXDRTree::Key +XDRIncrementalEncoder::getTopLevelTreeKey() const +{ + return AutoXDRTree::topLevel; +} + +AutoXDRTree::Key +XDRIncrementalEncoder::getTreeKey(JSFunction* fun) const +{ + if (fun->isInterpretedLazy()) { + static_assert(sizeof(fun->lazyScript()->begin()) == 4 || + sizeof(fun->lazyScript()->end()) == 4, + "AutoXDRTree key requires LazyScripts positions to be uint32"); + return uint64_t(fun->lazyScript()->begin()) << 32 | fun->lazyScript()->end(); + } + + if (fun->isInterpreted()) { + static_assert(sizeof(fun->nonLazyScript()->sourceStart()) == 4 || + sizeof(fun->nonLazyScript()->sourceEnd()) == 4, + "AutoXDRTree key requires JSScripts positions to be uint32"); + return uint64_t(fun->nonLazyScript()->sourceStart()) << 32 | fun->nonLazyScript()->sourceEnd(); + } + + return AutoXDRTree::noKey; +} + +bool +XDRIncrementalEncoder::init() +{ + if (!tree_.init()) + return false; + return true; +} + +void +XDRIncrementalEncoder::createOrReplaceSubTree(AutoXDRTree* child) +{ + AutoXDRTree* parent = scope_; + child->parent_ = parent; + scope_ = child; + if (oom_) + return; + + size_t cursor = buf.cursor(); + + // End the parent slice here, set the key to the child. + if (parent) { + Slice& last = node_->back(); + last.sliceLength = cursor - last.sliceBegin; + last.child = child->key_; + MOZ_ASSERT_IF(uint32_t(parent->key_) != 0, + uint32_t(parent->key_ >> 32) <= uint32_t(child->key_ >> 32) && + uint32_t(child->key_) <= uint32_t(parent->key_)); + } + + // Create or replace the part with what is going to be encoded next. + SlicesTree::AddPtr p = tree_.lookupForAdd(child->key_); + SlicesNode tmp; + if (!p) { + // Create a new sub-tree node. + if (!tree_.add(p, child->key_, mozilla::Move(tmp))) { + oom_ = true; + return; + } + } else { + // Replace an exisiting sub-tree. + p->value() = mozilla::Move(tmp); + } + node_ = &p->value(); + + // Add content to the root of the new sub-tree, + // i-e an empty slice with no children. + if (!node_->append(Slice { cursor, 0, AutoXDRTree::noSubTree })) + MOZ_CRASH("SlicesNode have a reserved space of 1."); +} + +void +XDRIncrementalEncoder::endSubTree() +{ + AutoXDRTree* child = scope_; + AutoXDRTree* parent = child->parent_; + scope_ = parent; + if (oom_) + return; + + size_t cursor = buf.cursor(); + + // End the child sub-tree. + Slice& last = node_->back(); + last.sliceLength = cursor - last.sliceBegin; + MOZ_ASSERT(last.child == AutoXDRTree::noSubTree); + + // Stop at the top-level. + if (!parent) { + node_ = nullptr; + return; + } + + // Restore the parent node. + SlicesTree::Ptr p = tree_.lookup(parent->key_); + node_ = &p->value(); + + // Append the new slice in the parent node. + if (!node_->append(Slice { cursor, 0, AutoXDRTree::noSubTree })) { + oom_ = true; + return; + } +} + +bool +XDRIncrementalEncoder::linearize() +{ + if (oom_) { + ReportOutOfMemory(cx()); + return fail(JS::TranscodeResult_Throw); + } + + // Do not linearize while we are currently adding bytes. + MOZ_ASSERT(scope_ == nullptr); + + // Visit the tree parts in a depth first order, to linearize the bits. + Vector depthFirst(cx()); + + SlicesTree::Ptr p = tree_.lookup(AutoXDRTree::topLevel); + MOZ_ASSERT(p); + + if (!depthFirst.append(((const SlicesNode&) p->value()).all())) { + ReportOutOfMemory(cx()); + return fail(JS::TranscodeResult_Throw); + } + + while (!depthFirst.empty()) { + SlicesNode::ConstRange& iter = depthFirst.back(); + Slice slice = iter.popCopyFront(); + // These fields have different meaning, but they should be correlated if + // the tree is well formatted. + MOZ_ASSERT_IF(slice.child == AutoXDRTree::noSubTree, iter.empty()); + if (iter.empty()) + depthFirst.popBack(); + + // Copy the bytes associated with the current slice to the transcode + // buffer which would be serialized. + MOZ_ASSERT(slice.sliceBegin <= slices_.length()); + MOZ_ASSERT(slice.sliceBegin + slice.sliceLength <= slices_.length()); + if (!buffer_.append(slices_.begin() + slice.sliceBegin, slice.sliceLength)) { + ReportOutOfMemory(cx()); + return fail(JS::TranscodeResult_Throw); + } + + // If we are at the end, go to back to the parent script. + if (slice.child == AutoXDRTree::noSubTree) + continue; + + // Visit the sub-parts before visiting the rest of the current slice. + SlicesTree::Ptr p = tree_.lookup(slice.child); + MOZ_ASSERT(p); + if (!depthFirst.append(((const SlicesNode&) p->value()).all())) { + ReportOutOfMemory(cx()); + return fail(JS::TranscodeResult_Throw); + } + } + + tree_.finish(); + slices_.clearAndFree(); + return true; +} diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 4804ad894b..494c9b1721 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -16,10 +16,10 @@ namespace js { class XDRBuffer { public: - XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) + XDRBuffer(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) : context_(cx), buffer_(buffer), cursor_(cursor) { } - JSContext* cx() const { + ExclusiveContext* cx() const { return context_; } @@ -42,7 +42,7 @@ class XDRBuffer { uint8_t* write(size_t n) { MOZ_ASSERT(n != 0); if (!buffer_.growByUninitialized(n)) { - JS_ReportOutOfMemory(cx()); + ReportOutOfMemory(cx()); return nullptr; } uint8_t* ptr = &buffer_[cursor_]; @@ -50,30 +50,108 @@ class XDRBuffer { return ptr; } + size_t cursor() const { + return cursor_; + } + private: - JSContext* const context_; + ExclusiveContext* const context_; JS::TranscodeBuffer& buffer_; size_t cursor_; }; +class XDRCoderBase; +class XDRIncrementalEncoder; + +// An AutoXDRTree is used to identify section encoded by an XDRIncrementalEncoder. +// +// Its primary goal is to identify functions, such that we can first encode them +// as LazyScript, and later replaced by them by their corresponding bytecode +// once delazified. +// +// As a convenience, this is also used to identify the top-level of the content +// encoded by an XDRIncrementalEncoder. +// +// Sections can be encoded any number of times in an XDRIncrementalEncoder, and +// the latest encoded version would replace all the previous one. +class MOZ_RAII AutoXDRTree +{ + public: + // For a JSFunction, a tree key is defined as being: + // script()->begin << 32 | script()->end + // + // Based on the invariant that |begin <= end|, we can make special + // keys, such as the top-level script. + using Key = uint64_t; + + AutoXDRTree(XDRCoderBase* xdr, Key key); + ~AutoXDRTree(); + + // Indicate the lack of a key for the current tree. + static constexpr Key noKey = 0; + + // Used to end the slices when there is no children. + static constexpr Key noSubTree = Key(1) << 32; + + // Used as the root key of the tree in the hash map. + static constexpr Key topLevel = Key(2) << 32; + + private: + friend class XDRIncrementalEncoder; + + Key key_; + AutoXDRTree* parent_; + XDRCoderBase* xdr_; +}; + +class XDRCoderBase +{ + protected: + XDRCoderBase() {} + + public: + virtual AutoXDRTree::Key getTopLevelTreeKey() const { return AutoXDRTree::noKey; } + virtual AutoXDRTree::Key getTreeKey(JSFunction* fun) const { return AutoXDRTree::noKey; } + virtual void createOrReplaceSubTree(AutoXDRTree* child) {}; + virtual void endSubTree() {}; +}; + /* * XDR serialization state. All data is encoded in little endian. */ template -class XDRState { +class XDRState : public XDRCoderBase +{ public: XDRBuffer buf; + private: JS::TranscodeResult resultCode_; - XDRState(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) - : buf(cx, buffer, cursor), resultCode_(JS::TranscodeResult_Ok) { } + public: + XDRState(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) + : buf(cx, buffer, cursor), + resultCode_(JS::TranscodeResult_Ok) + { + } - JSContext* cx() const { + virtual ~XDRState() {}; + + ExclusiveContext* cx() const { return buf.cx(); } + virtual LifoAlloc& lifoAlloc() const; + + virtual bool hasOptions() const { return false; } + virtual const ReadOnlyCompileOptions& options() { + MOZ_CRASH("does not have options"); + } + virtual bool hasScriptSourceObjectOut() const { return false; } + virtual ScriptSourceObject** scriptSourceObjectOut() { + MOZ_CRASH("does not have scriptSourceObjectOut."); + } // Record logical failures of XDR. - void postProcessContextErrors(JSContext* cx); + void postProcessContextErrors(ExclusiveContext* cx); JS::TranscodeResult resultCode() const { return resultCode_; } @@ -87,7 +165,7 @@ class XDRState { if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(sizeof(*n)); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); *ptr = *n; } else { *n = *buf.read(sizeof(*n)); @@ -99,7 +177,7 @@ class XDRState { if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(sizeof(*n)); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); mozilla::LittleEndian::writeUint16(ptr, *n); } else { const uint8_t* ptr = buf.read(sizeof(*n)); @@ -112,7 +190,7 @@ class XDRState { if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(sizeof(*n)); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); mozilla::LittleEndian::writeUint32(ptr, *n); } else { const uint8_t* ptr = buf.read(sizeof(*n)); @@ -125,7 +203,7 @@ class XDRState { if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(sizeof(*n)); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); mozilla::LittleEndian::writeUint64(ptr, *n); } else { const uint8_t* ptr = buf.read(sizeof(*n)); @@ -188,7 +266,7 @@ class XDRState { if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(len); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); memcpy(ptr, bytes, len); } else { memcpy(bytes, buf.read(len), len); @@ -207,7 +285,7 @@ class XDRState { size_t n = strlen(*sp) + 1; uint8_t* ptr = buf.write(n); if (!ptr) - return false; + return fail(JS::TranscodeResult_Throw); memcpy(ptr, *sp, n); } else { *sp = buf.readCString(); @@ -218,7 +296,7 @@ class XDRState { bool codeChars(const JS::Latin1Char* chars, size_t nchars); bool codeChars(char16_t* chars, size_t nchars); - bool codeFunction(JS::MutableHandleFunction objp); + bool codeFunction(JS::MutableHandleFunction objp, HandleScriptSource sourceObject = nullptr); bool codeScript(MutableHandleScript scriptp); bool codeConstValue(MutableHandleValue vp); }; @@ -226,6 +304,141 @@ class XDRState { using XDREncoder = XDRState; using XDRDecoder = XDRState; +class XDROffThreadDecoder : public XDRDecoder +{ + const ReadOnlyCompileOptions* options_; + ScriptSourceObject** sourceObjectOut_; + LifoAlloc& alloc_; + + public: + // Note, when providing an ExclusiveContext, where isJSContext is false, + // then the initialization of the ScriptSourceObject would remain + // incomplete. Thus, the sourceObjectOut must be used to finish the + // initialization with ScriptSourceObject::initFromOptions after the + // decoding. + // + // When providing a sourceObjectOut pointer, you have to ensure that it is + // marked by the GC to avoid dangling pointers. + XDROffThreadDecoder(ExclusiveContext* cx, LifoAlloc& alloc, + const ReadOnlyCompileOptions* options, + ScriptSourceObject** sourceObjectOut, + JS::TranscodeBuffer& buffer, size_t cursor = 0) + : XDRDecoder(cx, buffer, cursor), + options_(options), + sourceObjectOut_(sourceObjectOut), + alloc_(alloc) + { + MOZ_ASSERT(options); + MOZ_ASSERT(sourceObjectOut); + MOZ_ASSERT(*sourceObjectOut == nullptr); + } + + LifoAlloc& lifoAlloc() const override { + return alloc_; + } + + bool hasOptions() const override { return true; } + const ReadOnlyCompileOptions& options() override { + return *options_; + } + bool hasScriptSourceObjectOut() const override { return true; } + ScriptSourceObject** scriptSourceObjectOut() override { + return sourceObjectOut_; + } +}; + +class XDRIncrementalEncoder : public XDREncoder +{ + // The incremental encoder encodes the content of scripts and functions in + // the XDRBuffer. It can be used to encode multiple times the same AutoXDRTree, + // and uses its key to identify which part to replace. + // + // Internally, this encoder keeps a tree representation of the scopes. Each + // node is composed of a vector of slices which are interleaved by child + // nodes. + // + // A slice corresponds to an index and a length within the content of the + // slices_ buffer. The index is updated when a slice is created, and the + // length is updated when the slice is ended, either by creating a new scope + // child, or by closing the scope and going back to the parent. + // + // +---+---+---+ + // begin | | | | + // length | | | | + // child | . | . | . | + // +-|-+-|-+---+ + // | | + // +---------+ +---------+ + // | | + // v v + // +---+---+ +---+ + // | | | | | + // | | | | | + // | . | . | | . | + // +-|-+---+ +---+ + // | + // | + // | + // v + // +---+ + // | | + // | | + // | . | + // +---+ + // + // + // The tree key is used to identify the child nodes, and to make them + // easily replaceable. + // + // The tree is rooted at the |topLevel| key. + // + + struct Slice { + size_t sliceBegin; + size_t sliceLength; + AutoXDRTree::Key child; + }; + + using SlicesNode = Vector; + using SlicesTree = HashMap, + SystemAllocPolicy>; + + // Last opened XDR-tree on the stack. + AutoXDRTree* scope_; + // Node corresponding to the opened scope. + SlicesNode* node_; + // Tree of slices. + SlicesTree tree_; + JS::TranscodeBuffer slices_; + JS::TranscodeBuffer& buffer_; + bool oom_; + + public: + XDRIncrementalEncoder(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor) + : XDREncoder(cx, slices_, 0), + scope_(nullptr), + node_(nullptr), + buffer_(buffer), + oom_(false) + { + MOZ_ASSERT(buffer.length() == cursor, "NYI"); + } + + virtual ~XDRIncrementalEncoder() {} + + AutoXDRTree::Key getTopLevelTreeKey() const override; + AutoXDRTree::Key getTreeKey(JSFunction* fun) const override; + + MOZ_MUST_USE bool init(); + + void createOrReplaceSubTree(AutoXDRTree* child) override; + void endSubTree() override; + + // In the current XDRBuffer, move replaceable-parts to form a linear + // sequence of bytes. + MOZ_MUST_USE bool linearize(); +}; + } /* namespace js */ #endif /* vm_Xdr_h */ diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 462e43289c..192bc16e06 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -568,6 +568,13 @@ ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC, FAILURE(1027)), ERROR(NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC, FAILURE(1028)), + + /* When manipulating the bytecode cache with the JS API, some transcoding + * errors, such as a different bytecode format can cause failures of the + * decoding process. + */ + ERROR(NS_ERROR_DOM_JS_DECODING_ERROR, FAILURE(1030)), + /* May be used to indicate when e.g. setting a property value didn't * actually change the value, like for obj.foo = "bar"; obj.foo = "bar"; * the second assignment throws NS_SUCCESS_DOM_NO_OPERATION.