Bug 1342012 - Refactor nsJSUtils::ExecutionContext to separate compilation and execution steps and allow extraction of compiled JSScript r=nbp r=smaug

This commit is contained in:
Jon Coppeard 2018-12-06 16:52:16 -05:00
Родитель 4346ae1ac4
Коммит e6bd951e8e
7 изменённых файлов: 151 добавлений и 150 удалений

Просмотреть файл

@ -6001,7 +6001,8 @@ bool nsGlobalWindowInner::RunTimeoutHandler(Timeout* aTimeout,
nsresult rv;
{
nsJSUtils::ExecutionContext exec(aes.cx(), global);
rv = exec.CompileAndExec(options, script);
exec.Compile(options, script);
rv = exec.ExecScript();
}
if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {

Просмотреть файл

@ -124,6 +124,7 @@ nsJSUtils::ExecutionContext::ExecutionContext(JSContext* aCx,
mRealm(aCx, aGlobal),
mRetValue(aCx),
mScopeChain(aCx),
mScript(aCx),
mRv(NS_OK),
mSkip(false),
mCoerceToString(false),
@ -131,7 +132,8 @@ nsJSUtils::ExecutionContext::ExecutionContext(JSContext* aCx,
#ifdef DEBUG
,
mWantsReturnValue(false),
mExpectScopeChain(false)
mExpectScopeChain(false),
mScriptUsed(false)
#endif
{
MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
@ -174,30 +176,24 @@ void nsJSUtils::ExecutionContext::SetScopeChain(
}
}
nsresult nsJSUtils::ExecutionContext::JoinAndExec(
JS::OffThreadToken** aOffThreadToken,
JS::MutableHandle<JSScript*> aScript) {
nsresult nsJSUtils::ExecutionContext::JoinCompile(
JS::OffThreadToken** aOffThreadToken) {
if (mSkip) {
return mRv;
}
MOZ_ASSERT(!mWantsReturnValue);
MOZ_ASSERT(!mExpectScopeChain);
aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
MOZ_ASSERT(!mScript);
mScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (!aScript) {
if (!mScript) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
@ -206,9 +202,8 @@ nsresult nsJSUtils::ExecutionContext::JoinAndExec(
return NS_OK;
}
nsresult nsJSUtils::ExecutionContext::CompileAndExec(
JS::CompileOptions& aCompileOptions, JS::SourceText<char16_t>& aSrcBuf,
JS::MutableHandle<JSScript*> aScript) {
nsresult nsJSUtils::ExecutionContext::Compile(
JS::CompileOptions& aCompileOptions, JS::SourceText<char16_t>& aSrcBuf) {
if (mSkip) {
return mRv;
}
@ -219,29 +214,23 @@ nsresult nsJSUtils::ExecutionContext::CompileAndExec(
mWantsReturnValue = !aCompileOptions.noScriptRval;
#endif
MOZ_ASSERT(!mScript);
bool compiled = true;
if (mScopeChain.length() == 0) {
compiled = JS::Compile(mCx, aCompileOptions, aSrcBuf, aScript);
compiled = JS::Compile(mCx, aCompileOptions, aSrcBuf, &mScript);
} else {
compiled =
JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf, aScript);
compiled = JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf,
&mScript);
}
MOZ_ASSERT_IF(compiled, aScript);
MOZ_ASSERT_IF(compiled, mScript);
if (!compiled) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
if (!JS_ExecuteScript(mCx, mScopeChain, aScript, &mRetValue)) {
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
@ -250,10 +239,8 @@ nsresult nsJSUtils::ExecutionContext::CompileAndExec(
return NS_OK;
}
nsresult nsJSUtils::ExecutionContext::CompileAndExec(
nsresult nsJSUtils::ExecutionContext::Compile(
JS::CompileOptions& aCompileOptions, const nsAString& aScript) {
MOZ_ASSERT(!mEncodeBytecode,
"A JSScript is needed for calling FinishIncrementalEncoding");
if (mSkip) {
return mRv;
}
@ -267,23 +254,19 @@ nsresult nsJSUtils::ExecutionContext::CompileAndExec(
return mRv;
}
JS::Rooted<JSScript*> script(mCx);
return CompileAndExec(aCompileOptions, srcBuf, &script);
return Compile(aCompileOptions, srcBuf);
}
nsresult nsJSUtils::ExecutionContext::DecodeAndExec(
nsresult nsJSUtils::ExecutionContext::Decode(
JS::CompileOptions& aCompileOptions, mozilla::Vector<uint8_t>& aBytecodeBuf,
size_t aBytecodeIndex) {
MOZ_ASSERT(!mEncodeBytecode,
"A JSScript is needed for calling FinishIncrementalEncoding");
if (mSkip) {
return mRv;
}
MOZ_ASSERT(!mWantsReturnValue);
JS::Rooted<JSScript*> script(mCx);
JS::TranscodeResult tr =
JS::DecodeScript(mCx, aBytecodeBuf, &script, aBytecodeIndex);
JS::DecodeScript(mCx, aBytecodeBuf, &mScript, 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.
@ -295,16 +278,10 @@ nsresult nsJSUtils::ExecutionContext::DecodeAndExec(
return mRv;
}
if (!JS_ExecuteScript(mCx, mScopeChain, script)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return mRv;
}
nsresult nsJSUtils::ExecutionContext::DecodeJoinAndExec(
nsresult nsJSUtils::ExecutionContext::JoinDecode(
JS::OffThreadToken** aOffThreadToken) {
if (mSkip) {
return mRv;
@ -312,10 +289,9 @@ nsresult nsJSUtils::ExecutionContext::DecodeJoinAndExec(
MOZ_ASSERT(!mWantsReturnValue);
MOZ_ASSERT(!mExpectScopeChain);
JS::Rooted<JSScript*> script(mCx);
script.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
mScript.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (!script || !JS_ExecuteScript(mCx, mScopeChain, script)) {
if (!mScript) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
@ -324,9 +300,8 @@ nsresult nsJSUtils::ExecutionContext::DecodeJoinAndExec(
return NS_OK;
}
nsresult nsJSUtils::ExecutionContext::DecodeBinASTJoinAndExec(
JS::OffThreadToken** aOffThreadToken,
JS::MutableHandle<JSScript*> aScript) {
nsresult nsJSUtils::ExecutionContext::JoinDecodeBinAST(
JS::OffThreadToken** aOffThreadToken) {
#ifdef JS_BUILD_BINAST
if (mSkip) {
return mRv;
@ -335,22 +310,16 @@ nsresult nsJSUtils::ExecutionContext::DecodeBinASTJoinAndExec(
MOZ_ASSERT(!mWantsReturnValue);
MOZ_ASSERT(!mExpectScopeChain);
aScript.set(JS::FinishOffThreadBinASTDecode(mCx, *aOffThreadToken));
mScript.set(JS::FinishOffThreadBinASTDecode(mCx, *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (!aScript) {
if (!mScript) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
@ -362,9 +331,8 @@ nsresult nsJSUtils::ExecutionContext::DecodeBinASTJoinAndExec(
#endif
}
nsresult nsJSUtils::ExecutionContext::DecodeBinASTAndExec(
JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength,
JS::MutableHandle<JSScript*> aScript) {
nsresult nsJSUtils::ExecutionContext::DecodeBinAST(
JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength) {
#ifdef JS_BUILD_BINAST
MOZ_ASSERT(mScopeChain.length() == 0,
"BinAST decoding is not supported in non-syntactic scopes");
@ -379,22 +347,15 @@ nsresult nsJSUtils::ExecutionContext::DecodeBinASTAndExec(
mWantsReturnValue = !aCompileOptions.noScriptRval;
#endif
aScript.set(JS::DecodeBinAST(mCx, aCompileOptions, aBuf, aLength));
mScript.set(JS::DecodeBinAST(mCx, aCompileOptions, aBuf, aLength));
if (!aScript) {
if (!mScript) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
if (!JS_ExecuteScript(mCx, mScopeChain, aScript, &mRetValue)) {
if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
@ -406,6 +367,32 @@ nsresult nsJSUtils::ExecutionContext::DecodeBinASTAndExec(
#endif
}
JSScript* nsJSUtils::ExecutionContext::GetScript() {
#ifdef DEBUG
MOZ_ASSERT(!mSkip);
MOZ_ASSERT(mScript);
mScriptUsed = true;
#endif
return mScript;
}
nsresult nsJSUtils::ExecutionContext::ExecScript() {
if (mSkip) {
return mRv;
}
MOZ_ASSERT(mScript);
if (!JS_ExecuteScript(mCx, mScopeChain, mScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return NS_OK;
}
static bool IsPromiseValue(JSContext* aCx, JS::Handle<JS::Value> aValue) {
if (!aValue.isObject()) {
return false;
@ -419,43 +406,45 @@ static bool IsPromiseValue(JSContext* aCx, JS::Handle<JS::Value> aValue) {
return JS::IsPromiseObject(obj);
}
nsresult nsJSUtils::ExecutionContext::ExtractReturnValue(
nsresult nsJSUtils::ExecutionContext::ExecScript(
JS::MutableHandle<JS::Value> 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
aRetValue.setUndefined();
return mRv;
}
MOZ_ASSERT(mScript);
MOZ_ASSERT(mWantsReturnValue);
if (!JS_ExecuteScript(mCx, mScopeChain, mScript, aRetValue)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
#ifdef DEBUG
mWantsReturnValue = false;
#endif
if (mCoerceToString && IsPromiseValue(mCx, mRetValue)) {
if (mCoerceToString && IsPromiseValue(mCx, aRetValue)) {
// We're a javascript: url and we should treat Promise return values as
// undefined.
//
// Once bug 1477821 is fixed this code might be able to go away, or will
// become enshrined in the spec, depending.
mRetValue.setUndefined();
aRetValue.setUndefined();
}
if (mCoerceToString && !mRetValue.isUndefined()) {
JSString* str = JS::ToString(mCx, mRetValue);
if (mCoerceToString && !aRetValue.isUndefined()) {
JSString* str = JS::ToString(mCx, aRetValue);
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(JS::StringValue(str));
}
aRetValue.set(mRetValue);
return NS_OK;
}
@ -465,7 +454,6 @@ nsresult nsJSUtils::CompileModule(JSContext* aCx,
JS::CompileOptions& aCompileOptions,
JS::MutableHandle<JSObject*> aModule) {
AUTO_PROFILER_LABEL("nsJSUtils::CompileModule", JS);
MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
MOZ_ASSERT(aSrcBuf.get());
MOZ_ASSERT(JS_IsGlobalObject(aEvaluationGlobal));

Просмотреть файл

@ -81,6 +81,9 @@ class nsJSUtils {
// Scope chain in which the execution takes place.
JS::AutoObjectVector mScopeChain;
// The compiled script.
JS::Rooted<JSScript*> mScript;
// returned value forwarded when we have to interupt the execution eagerly
// with mSkip.
nsresult mRv;
@ -100,6 +103,8 @@ class nsJSUtils {
bool mWantsReturnValue;
bool mExpectScopeChain;
bool mScriptUsed;
#endif
public:
@ -111,8 +116,12 @@ class nsJSUtils {
ExecutionContext(ExecutionContext&&) = delete;
~ExecutionContext() {
// This flag is resetted, when the returned value is extracted.
MOZ_ASSERT(!mWantsReturnValue);
// This flag is reset when the returned value is extracted.
MOZ_ASSERT_IF(!mSkip, !mWantsReturnValue);
// If encoding was started we expect the script to have been
// used when ending the encoding.
MOZ_ASSERT_IF(mEncodeBytecode && mScript && mRv == NS_OK, mScriptUsed);
}
// The returned value would be converted to a string if the
@ -134,54 +143,53 @@ class nsJSUtils {
// 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
// After getting a notification that an off-thread compilation terminated,
// this function will take the result of the parser and move it to the main
// thread.
MOZ_MUST_USE nsresult JoinCompile(JS::OffThreadToken** aOffThreadToken);
// Compile a script contained in a SourceText.
nsresult Compile(JS::CompileOptions& aCompileOptions,
JS::SourceText<char16_t>& aSrcBuf);
// Compile a script contained in a string.
nsresult Compile(JS::CompileOptions& aCompileOptions,
const nsAString& aScript);
// Decode a script contained in a buffer.
nsresult Decode(JS::CompileOptions& aCompileOptions,
mozilla::Vector<uint8_t>& aBytecodeBuf,
size_t aBytecodeIndex);
// After getting a notification that an off-thread decoding terminated, this
// function will get the result of the decoder and move it to the main
// thread.
nsresult JoinDecode(JS::OffThreadToken** aOffThreadToken);
nsresult JoinDecodeBinAST(JS::OffThreadToken** aOffThreadToken);
// Decode a BinAST encoded script contained in a buffer.
nsresult DecodeBinAST(JS::CompileOptions& aCompileOptions,
const uint8_t* aBuf, size_t aLength);
// Get a successfully compiled script.
JSScript* GetScript();
// Execute the compiled script and ignore the return value.
MOZ_MUST_USE nsresult ExecScript();
// Execute the compiled script a get the return value.
//
// Copy the returned value into 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.
// result to a string, the nsresult is be set to the corresponding result
// code and the mutable handle argument remains 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<JS::Value> 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(JS::OffThreadToken** aOffThreadToken,
JS::MutableHandle<JSScript*> aScript);
// Compile a script contained in a SourceText, and execute it.
nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
JS::SourceText<char16_t>& aSrcBuf,
JS::MutableHandle<JSScript*> aScript);
// 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<uint8_t>& 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(JS::OffThreadToken** aOffThreadToken);
MOZ_MUST_USE nsresult
DecodeBinASTJoinAndExec(JS::OffThreadToken** aOffThreadToken,
JS::MutableHandle<JSScript*> aScript);
// Decode a BinAST encoded script contained in a buffer, and execute it.
nsresult DecodeBinASTAndExec(JS::CompileOptions& aCompileOptions,
const uint8_t* aBuf, size_t aLength,
JS::MutableHandle<JSScript*> aScript);
MOZ_MUST_USE nsresult ExecScript(JS::MutableHandle<JS::Value> aRetValue);
};
static nsresult CompileModule(JSContext* aCx,

Просмотреть файл

@ -254,8 +254,8 @@ nsresult nsJSThunk::EvaluateScript(
{
nsJSUtils::ExecutionContext exec(cx, globalJSObject);
exec.SetCoerceToString(true);
exec.CompileAndExec(options, NS_ConvertUTF8toUTF16(script));
rv = exec.ExtractReturnValue(&v);
exec.Compile(options, NS_ConvertUTF8toUTF16(script));
rv = exec.ExecScript(&v);
}
js::AssertSameCompartment(cx, v);

Просмотреть файл

@ -998,8 +998,8 @@ bool _evaluate(NPP npp, NPObject *npobj, NPString *script, NPVariant *result) {
{
nsJSUtils::ExecutionContext exec(cx, obj);
exec.SetScopeChain(scopeChain);
exec.CompileAndExec(options, utf16script);
rv = exec.ExtractReturnValue(&rval);
exec.Compile(options, utf16script);
rv = exec.ExecScript(&rval);
}
if (!JS_WrapValue(cx, &rval)) {

Просмотреть файл

@ -8,8 +8,8 @@
#include "ScriptLoadHandler.h"
#include "ScriptLoadRequest.h"
#include "ScriptTrace.h"
#include "ModuleLoadRequest.h"
#include "LoadedScript.h"
#include "ModuleLoadRequest.h"
#include "prsystem.h"
#include "jsapi.h"
@ -2470,13 +2470,14 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
if (aRequest->mOffThreadToken) {
LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
aRequest));
rv = exec.DecodeJoinAndExec(&aRequest->mOffThreadToken);
exec.JoinDecode(&aRequest->mOffThreadToken);
} else {
LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute",
aRequest));
rv = exec.DecodeAndExec(options, aRequest->mScriptBytecode,
aRequest->mBytecodeOffset);
exec.Decode(options, aRequest->mScriptBytecode,
aRequest->mBytecodeOffset);
}
rv = exec.ExecScript();
// We do not expect to be saving anything when we already have some
// bytecode.
MOZ_ASSERT(!aRequest->mCacheInfo);
@ -2496,30 +2497,34 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
"Execute",
aRequest));
if (aRequest->IsBinASTSource()) {
rv = exec.DecodeBinASTJoinAndExec(&aRequest->mOffThreadToken,
&script);
rv = exec.JoinDecodeBinAST(&aRequest->mOffThreadToken);
} else {
MOZ_ASSERT(aRequest->IsTextSource());
rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
rv = exec.JoinCompile(&aRequest->mOffThreadToken);
}
} else {
// Main thread parsing (inline and small scripts)
LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
if (aRequest->IsBinASTSource()) {
rv = exec.DecodeBinASTAndExec(
options, aRequest->ScriptBinASTData().begin(),
aRequest->ScriptBinASTData().length(), &script);
rv = exec.DecodeBinAST(options,
aRequest->ScriptBinASTData().begin(),
aRequest->ScriptBinASTData().length());
} else {
MOZ_ASSERT(aRequest->IsTextSource());
auto srcBuf = GetScriptSource(cx, aRequest);
if (srcBuf) {
rv = exec.CompileAndExec(options, *srcBuf, &script);
rv = exec.Compile(options, *srcBuf);
} else {
rv = NS_ERROR_OUT_OF_MEMORY;
}
}
}
if (rv == NS_OK) {
script = exec.GetScript();
rv = exec.ExecScript();
}
}
// Queue the current script load request to later save the bytecode.

Просмотреть файл

@ -415,9 +415,8 @@ nsresult nsXBLProtoImplField::InstallField(
{
nsJSUtils::ExecutionContext exec(cx, scopeObject);
exec.SetScopeChain(scopeChain);
exec.CompileAndExec(options,
nsDependentString(mFieldText, mFieldTextLength));
rv = exec.ExtractReturnValue(&result);
exec.Compile(options, nsDependentString(mFieldText, mFieldTextLength));
rv = exec.ExecScript(&result);
}
if (NS_FAILED(rv)) {