Bug 1702278 - Defer setting debug metadata until after script compilation is finished. r=tcampbell,smaug

Differential Revision: https://phabricator.services.mozilla.com/D110459
This commit is contained in:
Matthew Gaudet 2021-04-20 15:31:14 +00:00
Родитель ac23d0ea49
Коммит 810f80185c
20 изменённых файлов: 393 добавлений и 211 удалений

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

@ -14,6 +14,7 @@
#include "mozilla/dom/JSExecutionContext.h"
#include <utility>
#include "ErrorList.h"
#include "MainThreadUtils.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h"
@ -47,9 +48,11 @@ static nsresult EvaluationExceptionToNSResult(JSContext* aCx) {
return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE;
}
JSExecutionContext::JSExecutionContext(JSContext* aCx,
JS::Handle<JSObject*> aGlobal,
JS::CompileOptions& aCompileOptions)
JSExecutionContext::JSExecutionContext(
JSContext* aCx, JS::Handle<JSObject*> aGlobal,
JS::CompileOptions& aCompileOptions,
JS::Handle<JS::Value> aDebuggerPrivateValue,
JS::Handle<JSScript*> aDebuggerIntroductionScript)
:
#ifdef MOZ_GECKO_PROFILER
mAutoProfilerLabel("JSExecutionContext",
@ -61,6 +64,8 @@ JSExecutionContext::JSExecutionContext(JSContext* aCx,
mRetValue(aCx),
mScript(aCx),
mCompileOptions(aCompileOptions),
mDebuggerPrivateValue(aCx, aDebuggerPrivateValue),
mDebuggerIntroductionScript(aCx, aDebuggerIntroductionScript),
mRv(NS_OK),
mSkip(false),
mCoerceToString(false),
@ -105,6 +110,9 @@ nsresult JSExecutionContext::JoinCompile(JS::OffThreadToken** aOffThreadToken) {
return mRv;
}
if (!UpdateDebugMetadata()) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
@ -135,6 +143,10 @@ nsresult JSExecutionContext::InternalCompile(JS::SourceText<Unit>& aSrcBuf) {
return mRv;
}
if (!UpdateDebugMetadata()) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
@ -183,9 +195,22 @@ nsresult JSExecutionContext::Decode(mozilla::Vector<uint8_t>& aBytecodeBuf,
return mRv;
}
if (!UpdateDebugMetadata()) {
return NS_ERROR_OUT_OF_MEMORY;
}
return mRv;
}
bool JSExecutionContext::UpdateDebugMetadata() {
if (!mCompileOptions.deferDebugMetadata) {
return true;
}
return JS::UpdateDebugMetadata(mCx, mScript, mCompileOptions,
mDebuggerPrivateValue, nullptr,
mDebuggerIntroductionScript, nullptr);
}
nsresult JSExecutionContext::JoinDecode(JS::OffThreadToken** aOffThreadToken) {
if (mSkip) {
return mRv;
@ -200,6 +225,10 @@ nsresult JSExecutionContext::JoinDecode(JS::OffThreadToken** aOffThreadToken) {
return mRv;
}
if (!UpdateDebugMetadata()) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}

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

@ -10,6 +10,7 @@
#include "GeckoProfiler.h"
#include "js/GCVector.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "jsapi.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
@ -47,6 +48,14 @@ class MOZ_STACK_CLASS JSExecutionContext final {
// The compilation options applied throughout
JS::CompileOptions& mCompileOptions;
// Debug Metadata: Values managed for the benefit of the debugger when
// inspecting code.
//
// For more details see CompilationAndEvaluation.h, and the comments on
// UpdateDebugMetadata
JS::Rooted<JS::Value> mDebuggerPrivateValue;
JS::Rooted<JSScript*> mDebuggerIntroductionScript;
// returned value forwarded when we have to interupt the execution eagerly
// with mSkip.
nsresult mRv;
@ -68,6 +77,8 @@ class MOZ_STACK_CLASS JSExecutionContext final {
bool mScriptUsed;
#endif
bool UpdateDebugMetadata();
private:
// Compile a script contained in a SourceText.
template <typename Unit>
@ -76,8 +87,14 @@ class MOZ_STACK_CLASS JSExecutionContext final {
public:
// Enter compartment in which the code would be executed. The JSContext
// must come from an AutoEntryScript.
JSExecutionContext(JSContext* aCx, JS::Handle<JSObject*> aGlobal,
JS::CompileOptions& aCompileOptions);
//
// The JS engine can associate metadata for the debugger with scripts at
// compile time. The optional last arguments here cover that metadata.
JSExecutionContext(
JSContext* aCx, JS::Handle<JSObject*> aGlobal,
JS::CompileOptions& aCompileOptions,
JS::Handle<JS::Value> aDebuggerPrivateValue = JS::UndefinedHandleValue,
JS::Handle<JSScript*> aDebuggerIntroductionScript = nullptr);
JSExecutionContext(const JSExecutionContext&) = delete;
JSExecutionContext(JSExecutionContext&&) = delete;

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

@ -74,6 +74,29 @@ uint64_t nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext* aContext) {
return win ? win->WindowID() : 0;
}
nsresult nsJSUtils::UpdateFunctionDebugMetadata(
AutoJSAPI& jsapi, JS::Handle<JSObject*> aFun, JS::CompileOptions& aOptions,
JS::Handle<JSString*> aElementAttributeName,
JS::Handle<JS::Value> aPrivateValue) {
JSContext* cx = jsapi.cx();
JS::Rooted<JSFunction*> fun(cx, JS_GetObjectFunction(aFun));
if (!fun) {
return NS_ERROR_FAILURE;
}
JS::RootedScript script(cx, JS_GetFunctionScript(cx, fun));
if (!script) {
return NS_OK;
}
if (!JS::UpdateDebugMetadata(cx, script, aOptions, aPrivateValue,
aElementAttributeName, nullptr, nullptr)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult nsJSUtils::CompileFunction(AutoJSAPI& jsapi,
JS::HandleVector<JSObject*> aScopeChain,
JS::CompileOptions& aOptions,

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

@ -63,6 +63,11 @@ class nsJSUtils {
const nsAString& aBody,
JSObject** aFunctionObject);
static nsresult UpdateFunctionDebugMetadata(
mozilla::dom::AutoJSAPI& jsapi, JS::Handle<JSObject*> aFun,
JS::CompileOptions& aOptions, JS::Handle<JSString*> aElementAttributeName,
JS::Handle<JS::Value> aPrivateValue);
static nsresult CompileModule(JSContext* aCx,
JS::SourceText<char16_t>& aSrcBuf,
JS::Handle<JSObject*> aEvaluationGlobal,

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

@ -1044,8 +1044,7 @@ nsresult EventListenerManager::CompileEventHandlerInternal(
// Use line 0 to make the function body starts from line 1.
options.setIntroductionType("eventHandler")
.setFileAndLine(url.get(), 0)
.setElementAttributeName(jsStr)
.setPrivateValue(JS::PrivateValue(eventScript));
.setdeferDebugMetadata(true);
JS::Rooted<JSObject*> handler(cx);
result = nsJSUtils::CompileFunction(jsapi, scopeChain, options,
@ -1054,6 +1053,11 @@ nsresult EventListenerManager::CompileEventHandlerInternal(
NS_ENSURE_SUCCESS(result, result);
NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE);
JS::Rooted<JS::Value> privateValue(cx, JS::PrivateValue(eventScript));
result = nsJSUtils::UpdateFunctionDebugMetadata(jsapi, handler, options,
jsStr, privateValue);
NS_ENSURE_SUCCESS(result, result);
MOZ_ASSERT(js::IsObjectInContextCompartment(handler, cx));
JS::Rooted<JSObject*> handlerGlobal(cx, JS::CurrentGlobalOrNull(cx));

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

@ -652,18 +652,19 @@ nsresult ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
{
JSContext* cx = aes.cx();
JS::Rooted<JSObject*> module(cx);
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
if (aRequest->mWasCompiledOMT) {
module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
aRequest->mOffThreadToken = nullptr;
rv = module ? NS_OK : NS_ERROR_FAILURE;
} else {
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
JS::CompileOptions options(cx);
JS::RootedScript introductionScript(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options,
&introductionScript);
JS::CompileOptions options(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
if (NS_SUCCEEDED(rv)) {
if (NS_SUCCEEDED(rv)) {
if (aRequest->mWasCompiledOMT) {
module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
aRequest->mOffThreadToken = nullptr;
rv = module ? NS_OK : NS_ERROR_FAILURE;
} else {
MaybeSourceText maybeSource;
rv = GetScriptSource(cx, aRequest, &maybeSource);
if (NS_SUCCEEDED(rv)) {
@ -680,6 +681,15 @@ nsresult ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
if (module) {
JS::RootedValue privateValue(cx);
JS::RootedScript moduleScript(cx, JS::GetModuleScript(module));
if (!JS::UpdateDebugMetadata(cx, moduleScript, options, privateValue,
nullptr, introductionScript, nullptr)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
RefPtr<ModuleScript> moduleScript =
new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
aRequest->mModuleScript = moduleScript;
@ -2542,7 +2552,11 @@ nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
JS::CompileOptions options(cx);
nsresult rv = FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
// Introduction script will actually be computed and set when the script is
// collected from offthread
JS::RootedScript dummyIntroductionScript(cx);
nsresult rv = FillCompileOptionsForRequest(jsapi, aRequest, global, &options,
&dummyIntroductionScript);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -2908,7 +2922,8 @@ already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject(
nsresult ScriptLoader::FillCompileOptionsForRequest(
const mozilla::dom::AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions) {
JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions,
JS::MutableHandle<JSScript*> aIntroductionScript) {
// It's very important to use aRequest->mURI, not the final URI of the channel
// aRequest ended up getting script data from, as the script filename.
nsresult rv = aRequest->mURI->GetSpec(aRequest->mURL);
@ -2931,7 +2946,8 @@ nsresult ScriptLoader::FillCompileOptionsForRequest(
} else {
introductionType = "injectedScript";
}
aOptions->setIntroductionInfoToCaller(jsapi.cx(), introductionType);
aOptions->setIntroductionInfoToCaller(jsapi.cx(), introductionType,
aIntroductionScript);
aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
aOptions->setIsRunOnce(true);
aOptions->setNoScriptRval(true);
@ -2948,6 +2964,8 @@ nsresult ScriptLoader::FillCompileOptionsForRequest(
aOptions->hideScriptFromDebugger = true;
}
aOptions->setdeferDebugMetadata(true);
return NS_OK;
}
@ -3240,15 +3258,18 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
// Create a ClassicScript object and associate it with the JSScript.
RefPtr<ClassicScript> classicScript =
new ClassicScript(aRequest->mFetchOptions, aRequest->mBaseURL);
JS::RootedValue classicScriptValue(cx, JS::PrivateValue(classicScript));
JS::CompileOptions options(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
options.setPrivateValue(JS::PrivateValue(classicScript));
JS::RootedScript introductionScript(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options,
&introductionScript);
if (NS_SUCCEEDED(rv)) {
if (aRequest->IsBytecode()) {
TRACE_FOR_TEST(aRequest->GetScriptElement(), "scriptloader_execute");
JSExecutionContext exec(cx, global, options);
JSExecutionContext exec(cx, global, options, classicScriptValue,
introductionScript);
if (aRequest->mOffThreadToken) {
LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
aRequest));
@ -3280,7 +3301,8 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
bool encodeBytecode = ShouldCacheBytecode(aRequest);
{
JSExecutionContext exec(cx, global, options);
JSExecutionContext exec(cx, global, options, classicScriptValue,
introductionScript);
exec.SetEncodeBytecode(encodeBytecode);
TRACE_FOR_TEST(aRequest->GetScriptElement(),
"scriptloader_execute");

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

@ -637,10 +637,12 @@ class ScriptLoader final : public nsISupports {
already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject(
WebExtGlobal aWebExtGlobal);
nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
ScriptLoadRequest* aRequest,
JS::Handle<JSObject*> aScopeChain,
JS::CompileOptions* aOptions);
// Fill in CompileOptions, as well as produce the introducer script for
// subsequent calls to UpdateDebuggerMetadata
nsresult FillCompileOptionsForRequest(
const mozilla::dom::AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions,
JS::MutableHandle<JSScript*> aIntroductionScript);
uint32_t NumberOfProcessors();
nsresult PrepareLoadedRequest(ScriptLoadRequest* aRequest,

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

@ -242,6 +242,30 @@ extern JS_PUBLIC_API JSFunction* CompileFunctionUtf8(
extern JS_PUBLIC_API void ExposeScriptToDebugger(JSContext* cx,
Handle<JSScript*> script);
/*
* JSScripts have associated with them (via their ScriptSourceObjects) some
* metadata used by the debugger. The following API functions are used to set
* that metadata on scripts, functions and modules.
*
* The metadata consists of:
* - A privateValue, which is used to keep some object value associated
* with the script.
* - The elementAttributeName is used by Gecko
* - The introductionScript is used by the debugger to identify which
* script created which. Only set for dynamicaly generated scripts.
* - scriptOrModule is used to transfer private value metadata from
* script to script
*
* Callers using UpdateDebugMetaData need to have set deferDebugMetadata
* in the compile options; this hides the script from the debugger until
* the debug metadata is provided by the UpdateDebugMetadata call.
*/
extern JS_PUBLIC_API bool UpdateDebugMetadata(
JSContext* cx, Handle<JSScript*> script,
const ReadOnlyCompileOptions& options, HandleValue privateValue,
HandleString elementAttributeName, HandleScript introScript,
HandleScript scriptOrModule);
extern JS_PUBLIC_API void SetGetElementCallback(JSContext* cx,
JSGetElementCallback callback);

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

@ -128,6 +128,17 @@ class JS_PUBLIC_API TransitiveCompileOptions {
bool sourceIsLazy = false;
bool allowHTMLComments = true;
bool hideScriptFromDebugger = false;
// If set, this script will be hidden from the debugger. The requirement
// is that once compilation is finished, a call to UpdateDebugMetadata will
// be made, which will update the SSO with the appropiate debug metadata,
// and expose the script to the debugger (if hideScriptFromDebugger isn't set)
bool deferDebugMetadata = false;
bool hideFromNewScriptInitial() const {
return deferDebugMetadata || hideScriptFromDebugger;
}
bool nonSyntacticScope = false;
bool privateClassFields = false;
bool privateClassMethods = false;
@ -172,18 +183,6 @@ class JS_PUBLIC_API TransitiveCompileOptions {
const char* filename() const { return filename_; }
const char* introducerFilename() const { return introducerFilename_; }
const char16_t* sourceMapURL() const { return sourceMapURL_; }
virtual Value privateValue() const = 0;
virtual JSString* elementAttributeName() const = 0;
virtual JSScript* introductionScript() const = 0;
// For some compilations the spec requires the ScriptOrModule field of the
// resulting script to be set to the currently executing script. This can be
// achieved by setting this option with setScriptOrModule() below.
//
// Note that this field doesn't explicitly exist in our implementation;
// instead the ScriptSourceObject's private value is set to that associated
// with the specified script.
virtual JSScript* scriptOrModule() const = 0;
TransitiveCompileOptions(const TransitiveCompileOptions&) = delete;
TransitiveCompileOptions& operator=(const TransitiveCompileOptions&) = delete;
@ -243,25 +242,11 @@ class JS_PUBLIC_API ReadOnlyCompileOptions : public TransitiveCompileOptions {
* anything else it entrains, will never be freed.
*/
class JS_PUBLIC_API OwningCompileOptions final : public ReadOnlyCompileOptions {
PersistentRooted<JSString*> elementAttributeNameRoot;
PersistentRooted<JSScript*> introductionScriptRoot;
PersistentRooted<JSScript*> scriptOrModuleRoot;
PersistentRooted<Value> privateValueRoot;
public:
// A minimal constructor, for use with OwningCompileOptions::copy.
explicit OwningCompileOptions(JSContext* cx);
~OwningCompileOptions();
Value privateValue() const override { return privateValueRoot; }
JSString* elementAttributeName() const override {
return elementAttributeNameRoot;
}
JSScript* introductionScript() const override {
return introductionScriptRoot;
}
JSScript* scriptOrModule() const override { return scriptOrModuleRoot; }
/** Set this to a copy of |rhs|. Return false on OOM. */
bool copy(JSContext* cx, const ReadOnlyCompileOptions& rhs);
@ -301,12 +286,6 @@ class JS_PUBLIC_API OwningCompileOptions final : public ReadOnlyCompileOptions {
*/
class MOZ_STACK_CLASS JS_PUBLIC_API CompileOptions final
: public ReadOnlyCompileOptions {
private:
Rooted<JSString*> elementAttributeNameRoot;
Rooted<JSScript*> introductionScriptRoot;
Rooted<JSScript*> scriptOrModuleRoot;
Rooted<Value> privateValueRoot;
public:
// Default options determined using the JSContext.
explicit CompileOptions(JSContext* cx);
@ -314,35 +293,15 @@ class MOZ_STACK_CLASS JS_PUBLIC_API CompileOptions final
// Copy both the transitive and the non-transitive options from another
// options object.
CompileOptions(JSContext* cx, const ReadOnlyCompileOptions& rhs)
: ReadOnlyCompileOptions(),
elementAttributeNameRoot(cx),
introductionScriptRoot(cx),
scriptOrModuleRoot(cx),
privateValueRoot(cx) {
: ReadOnlyCompileOptions() {
copyPODNonTransitiveOptions(rhs);
copyPODTransitiveOptions(rhs);
filename_ = rhs.filename();
introducerFilename_ = rhs.introducerFilename();
sourceMapURL_ = rhs.sourceMapURL();
privateValueRoot = rhs.privateValue();
elementAttributeNameRoot = rhs.elementAttributeName();
introductionScriptRoot = rhs.introductionScript();
scriptOrModuleRoot = rhs.scriptOrModule();
}
Value privateValue() const override { return privateValueRoot; }
JSString* elementAttributeName() const override {
return elementAttributeNameRoot;
}
JSScript* introductionScript() const override {
return introductionScriptRoot;
}
JSScript* scriptOrModule() const override { return scriptOrModuleRoot; }
CompileOptions& setFile(const char* f) {
filename_ = f;
return *this;
@ -364,21 +323,6 @@ class MOZ_STACK_CLASS JS_PUBLIC_API CompileOptions final
return *this;
}
CompileOptions& setPrivateValue(const Value& v) {
privateValueRoot = v;
return *this;
}
CompileOptions& setElementAttributeName(JSString* p) {
elementAttributeNameRoot = p;
return *this;
}
CompileOptions& setScriptOrModule(JSScript* s) {
scriptOrModuleRoot = s;
return *this;
}
CompileOptions& setMutedErrors(bool mute) {
mutedErrors_ = mute;
return *this;
@ -429,21 +373,26 @@ class MOZ_STACK_CLASS JS_PUBLIC_API CompileOptions final
return *this;
}
CompileOptions& setdeferDebugMetadata(bool v = true) {
deferDebugMetadata = v;
return *this;
}
CompileOptions& setIntroductionInfo(const char* introducerFn,
const char* intro, unsigned line,
JSScript* script, uint32_t offset) {
uint32_t offset) {
introducerFilename_ = introducerFn;
introductionType = intro;
introductionLineno = line;
introductionScriptRoot = script;
introductionOffset = offset;
hasIntroductionInfo = true;
return *this;
}
// Set introduction information according to any currently executing script.
CompileOptions& setIntroductionInfoToCaller(JSContext* cx,
const char* introductionType);
CompileOptions& setIntroductionInfoToCaller(
JSContext* cx, const char* introductionType,
JS::MutableHandle<JSScript*> introductionScript);
CompileOptions& setForceFullParse() {
forceFullParse_ = true;

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

@ -12,6 +12,7 @@
#include "ds/LifoAlloc.h"
#include "frontend/BytecodeCompilation.h"
#include "gc/HashUtil.h"
#include "js/CompilationAndEvaluation.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/JSMEnvironment.h" // JS::NewJSMEnvironment, JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::IsJSMEnvironment
#include "js/friend/WindowProxy.h" // js::IsWindowProxy
@ -300,7 +301,9 @@ static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
options.setIsRunOnce(true)
.setNoScriptRval(false)
.setMutedErrors(mutedErrors)
.setScriptOrModule(maybeScript);
.setdeferDebugMetadata();
RootedScript introScript(cx);
if (evalType == DIRECT_EVAL && IsStrictEvalPC(pc)) {
options.setForceStrictMode();
@ -308,8 +311,8 @@ static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
if (introducerFilename) {
options.setFileAndLine(filename, 1);
options.setIntroductionInfo(introducerFilename, "eval", lineno,
maybeScript, pcOffset);
options.setIntroductionInfo(introducerFilename, "eval", lineno, pcOffset);
introScript = maybeScript;
} else {
options.setFileAndLine("eval", 1);
options.setIntroductionType("eval");
@ -332,12 +335,18 @@ static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
return false;
}
JSScript* script =
frontend::CompileEvalScript(cx, options, srcBuf, enclosing, env);
RootedScript script(
cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing, env));
if (!script) {
return false;
}
RootedValue undefValue(cx);
if (!JS::UpdateDebugMetadata(cx, script, options, undefValue, nullptr,
introScript, maybeScript)) {
return false;
}
esg.setNewScript(script);
}

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

@ -397,7 +397,7 @@ bool frontend::InstantiateStencils(JSContext* cx, CompilationInput& input,
}
Rooted<JSScript*> script(cx, gcOutput.script);
if (!input.options.hideScriptFromDebugger) {
if (!input.options.hideFromNewScriptInitial()) {
DebugAPI::onNewScript(cx, script);
}
}
@ -1219,7 +1219,7 @@ static JSFunction* CompileStandaloneFunction(
MOZ_ASSERT(!cx->isHelperThreadContext());
Rooted<JSScript*> script(cx, gcOutput.get().script);
if (!options.hideScriptFromDebugger) {
if (!options.hideFromNewScriptInitial()) {
DebugAPI::onNewScript(cx, script);
}
}

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

@ -5,17 +5,17 @@
offThreadCompileScript("Error()");
assertEq(!!runOffThreadScript().stack.match(/^@<string>:1:1\n/), true);
offThreadCompileScript("Error()", { fileName: "candelabra", lineNumber: 6502 });
offThreadCompileScript("Error()", { fileName: "candelabra", lineNumber: 6502 });
assertEq(!!runOffThreadScript().stack.match(/^@candelabra:6502:1\n/), true);
var element = {};
offThreadCompileScript("Error()", { element }); // shouldn't crash
runOffThreadScript();
var job = offThreadCompileScript("Error()"); // shouldn't crash
runOffThreadScript(job, { element });
var elementAttributeName = "molybdenum";
elementAttributeName +=
elementAttributeName + elementAttributeName + elementAttributeName;
offThreadCompileScript("Error()", {
job = offThreadCompileScript("Error()"); // shouldn't crash
runOffThreadScript(job, {
elementAttributeName,
}); // shouldn't crash
runOffThreadScript();
});

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

@ -3,7 +3,7 @@
// Owning elements and attribute names are attached to scripts compiled
// off-thread.
var g = newGlobal({newCompartment: true});
var g = newGlobal({ newCompartment: true });
var dbg = new Debugger;
var gDO = dbg.addDebuggee(g);
@ -18,9 +18,11 @@ dbg.onDebuggerStatement = function (frame) {
assertEq(source.elementAttributeName, 'mass');
};
g.offThreadCompileScript('debugger;',
{ element: elt,
elementAttributeName: 'mass' });
var job = g.offThreadCompileScript('debugger;');
log += 'o';
g.runOffThreadScript();
g.runOffThreadScript(job,
{
element: elt,
elementAttributeName: 'mass'
});
assertEq(log, 'od');

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

@ -3464,6 +3464,7 @@ void JS::TransitiveCompileOptions::copyPODTransitiveOptions(
introductionOffset = rhs.introductionOffset;
hasIntroductionInfo = rhs.hasIntroductionInfo;
hideScriptFromDebugger = rhs.hideScriptFromDebugger;
deferDebugMetadata = rhs.deferDebugMetadata;
nonSyntacticScope = rhs.nonSyntacticScope;
privateClassFields = rhs.privateClassFields;
privateClassMethods = rhs.privateClassMethods;
@ -3482,11 +3483,7 @@ void JS::ReadOnlyCompileOptions::copyPODNonTransitiveOptions(
}
JS::OwningCompileOptions::OwningCompileOptions(JSContext* cx)
: ReadOnlyCompileOptions(),
elementAttributeNameRoot(cx),
introductionScriptRoot(cx),
scriptOrModuleRoot(cx),
privateValueRoot(cx) {}
: ReadOnlyCompileOptions() {}
void JS::OwningCompileOptions::release() {
// OwningCompileOptions always owns these, so these casts are okay.
@ -3515,11 +3512,6 @@ bool JS::OwningCompileOptions::copy(JSContext* cx,
copyPODNonTransitiveOptions(rhs);
copyPODTransitiveOptions(rhs);
elementAttributeNameRoot = rhs.elementAttributeName();
introductionScriptRoot = rhs.introductionScript();
scriptOrModuleRoot = rhs.scriptOrModule();
privateValueRoot = rhs.privateValue();
if (rhs.filename()) {
filename_ = DuplicateString(cx, rhs.filename()).release();
if (!filename_) {
@ -3545,12 +3537,7 @@ bool JS::OwningCompileOptions::copy(JSContext* cx,
return true;
}
JS::CompileOptions::CompileOptions(JSContext* cx)
: ReadOnlyCompileOptions(),
elementAttributeNameRoot(cx),
introductionScriptRoot(cx),
scriptOrModuleRoot(cx),
privateValueRoot(cx) {
JS::CompileOptions::CompileOptions(JSContext* cx) : ReadOnlyCompileOptions() {
discardSource = cx->realm()->behaviors().discardSource();
if (!cx->options().asmJS()) {
asmJSOption = AsmJSOption::Disabled;
@ -3583,7 +3570,8 @@ JS::CompileOptions::CompileOptions(JSContext* cx)
}
CompileOptions& CompileOptions::setIntroductionInfoToCaller(
JSContext* cx, const char* introductionType) {
JSContext* cx, const char* introductionType,
MutableHandle<JSScript*> introductionScript) {
RootedScript maybeScript(cx);
const char* filename;
unsigned lineno;
@ -3592,11 +3580,10 @@ CompileOptions& CompileOptions::setIntroductionInfoToCaller(
DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
&pcOffset, &mutedErrors);
if (filename) {
return setIntroductionInfo(filename, introductionType, lineno, maybeScript,
pcOffset);
} else {
return setIntroductionType(introductionType);
introductionScript.set(maybeScript);
return setIntroductionInfo(filename, introductionType, lineno, pcOffset);
}
return setIntroductionType(introductionType);
}
JS_PUBLIC_API JSObject* JS_GetGlobalFromScript(JSScript* script) {

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

@ -1989,14 +1989,58 @@ static bool LoadScriptRelativeToScript(JSContext* cx, unsigned argc,
return LoadScript(cx, argc, vp, true);
}
static bool ParseDebugMetadata(JSContext* cx, HandleObject opts,
MutableHandleValue privateValue,
MutableHandleString elementAttributeName) {
RootedValue v(cx);
RootedString s(cx);
if (!JS_GetProperty(cx, opts, "element", &v)) {
return false;
}
if (v.isObject()) {
RootedObject infoObject(cx, CreateScriptPrivate(cx));
if (!infoObject) {
return false;
}
RootedValue elementValue(cx, v);
if (!JS_WrapValue(cx, &elementValue)) {
return false;
}
if (!JS_DefineProperty(cx, infoObject, "element", elementValue, 0)) {
return false;
}
privateValue.set(ObjectValue(*infoObject));
}
if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) {
return false;
}
if (!v.isUndefined()) {
s = ToString(cx, v);
if (!s) {
return false;
}
elementAttributeName.set(s);
}
return true;
}
// Populate |options| with the options given by |opts|'s properties. If we
// need to convert a filename to a C string, let fileNameBytes own the
// bytes.
static bool ParseCompileOptions(JSContext* cx, CompileOptions& options,
HandleObject opts, UniqueChars* fileNameBytes) {
HandleObject opts, UniqueChars* fileNameBytes,
MutableHandleValue privateValue,
MutableHandleString elementAttributeName) {
RootedValue v(cx);
RootedString s(cx);
if (!ParseDebugMetadata(cx, opts, privateValue, elementAttributeName)) {
return false;
}
if (!JS_GetProperty(cx, opts, "isRunOnce", &v)) {
return false;
}
@ -2037,35 +2081,6 @@ static bool ParseCompileOptions(JSContext* cx, CompileOptions& options,
options.setSkipFilenameValidation(ToBoolean(v));
}
if (!JS_GetProperty(cx, opts, "element", &v)) {
return false;
}
if (v.isObject()) {
RootedObject infoObject(cx, CreateScriptPrivate(cx));
if (!infoObject) {
return false;
}
RootedValue elementValue(cx, v);
if (!JS_WrapValue(cx, &elementValue)) {
return false;
}
if (!JS_DefineProperty(cx, infoObject, "element", elementValue, 0)) {
return false;
}
options.setPrivateValue(ObjectValue(*infoObject));
}
if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) {
return false;
}
if (!v.isUndefined()) {
s = ToString(cx, v);
if (!s) {
return false;
}
options.setElementAttributeName(s);
}
if (!JS_GetProperty(cx, opts, "lineNumber", &v)) {
return false;
}
@ -2374,16 +2389,21 @@ static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) {
CacheOptionSet optionSet;
options.setIntroductionType("js shell evaluate")
.setFileAndLine("@evaluate", 1);
.setFileAndLine("@evaluate", 1)
.setdeferDebugMetadata();
global = JS::CurrentGlobalOrNull(cx);
MOZ_ASSERT(global);
RootedValue privateValue(cx);
RootedString elementAttributeName(cx);
if (args.length() == 2) {
RootedObject opts(cx, &args[1].toObject());
RootedValue v(cx);
if (!ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
if (!ParseCompileOptions(cx, options, opts, &fileNameBytes, &privateValue,
&elementAttributeName)) {
return false;
}
@ -2675,6 +2695,11 @@ static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) {
}
}
if (!JS::UpdateDebugMetadata(cx, script, options, privateValue,
elementAttributeName, nullptr, nullptr)) {
return false;
}
if (!transcodeOnly) {
if (!(envChain.empty()
? JS_ExecuteScript(cx, script, args.rval())
@ -5438,7 +5463,10 @@ static bool Compile(JSContext* cx, unsigned argc, Value* vp) {
options.setIntroductionType("js shell compile")
.setFileAndLine("<string>", 1)
.setIsRunOnce(true)
.setNoScriptRval(true);
.setNoScriptRval(true)
.setdeferDebugMetadata();
RootedValue privateValue(cx);
RootedString elementAttributeName(cx);
if (args.length() >= 2) {
if (args[1].isPrimitive()) {
@ -5448,7 +5476,8 @@ static bool Compile(JSContext* cx, unsigned argc, Value* vp) {
}
RootedObject opts(cx, &args[1].toObject());
if (!ParseCompileOptions(cx, options, opts, nullptr)) {
if (!ParseCompileOptions(cx, options, opts, nullptr, &privateValue,
&elementAttributeName)) {
return false;
}
}
@ -5464,6 +5493,11 @@ static bool Compile(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
if (!JS::UpdateDebugMetadata(cx, script, options, privateValue,
elementAttributeName, nullptr, nullptr)) {
return false;
}
args.rval().setUndefined();
return true;
}
@ -5853,8 +5887,10 @@ static bool FrontendTest(JSContext* cx, unsigned argc, Value* vp,
typeName);
return false;
}
if (!ParseCompileOptions(cx, options, objOptions, nullptr)) {
RootedValue dummyValue(cx);
RootedString dummyAttributeName(cx);
if (!ParseCompileOptions(cx, options, objOptions, nullptr, &dummyValue,
&dummyAttributeName)) {
return false;
}
@ -6148,7 +6184,8 @@ static bool OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp) {
UniqueChars fileNameBytes;
CompileOptions options(cx);
options.setIntroductionType("js shell offThreadCompileScript")
.setFileAndLine("<string>", 1);
.setFileAndLine("<string>", 1)
.setdeferDebugMetadata();
if (args.length() >= 2) {
if (args[1].isPrimitive()) {
@ -6157,8 +6194,13 @@ static bool OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
// Offthread compilation requires that the debug metadata be set when the
// script is collected from offthread, rather than when compiled.
RootedValue dummyPrivateValue(cx);
RootedString dummyElementAttributeName(cx);
RootedObject opts(cx, &args[1].toObject());
if (!ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
if (!ParseCompileOptions(cx, options, opts, &fileNameBytes,
&dummyPrivateValue, &dummyElementAttributeName)) {
return false;
}
}
@ -6242,6 +6284,28 @@ static bool runOffThreadScript(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
RootedValue privateValue(cx);
RootedString elementAttributeName(cx);
if (args.length() >= 2) {
if (args[1].isPrimitive()) {
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
JSSMSG_INVALID_ARGS, "compile");
return false;
}
RootedObject opts(cx, &args[1].toObject());
if (!ParseDebugMetadata(cx, opts, &privateValue, &elementAttributeName)) {
return false;
}
}
CompileOptions dummyOptions(cx);
if (!JS::UpdateDebugMetadata(cx, script, dummyOptions, privateValue,
elementAttributeName, nullptr, nullptr)) {
return false;
}
return JS_ExecuteScript(cx, script, args.rval());
}
@ -6383,7 +6447,10 @@ static bool OffThreadDecodeScript(JSContext* cx, unsigned argc, Value* vp) {
}
RootedObject opts(cx, &args[1].toObject());
if (!ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
RootedValue dummyValue(cx);
RootedString dummyAttributeName(cx);
if (!ParseCompileOptions(cx, options, opts, &fileNameBytes, &dummyValue,
&dummyAttributeName)) {
return false;
}
}

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

@ -421,6 +421,55 @@ JS_PUBLIC_API void JS::ExposeScriptToDebugger(JSContext* cx,
DebugAPI::onNewScript(cx, script);
}
JS_PUBLIC_API bool JS::UpdateDebugMetadata(
JSContext* cx, Handle<JSScript*> script,
const ReadOnlyCompileOptions& options, HandleValue privateValue,
HandleString elementAttributeName, HandleScript introScript,
HandleScript scriptOrModule) {
RootedScriptSourceObject sso(cx, script->sourceObject());
if (!ScriptSourceObject::initElementProperties(cx, sso,
elementAttributeName)) {
return false;
}
// There is no equivalent of cross-compartment wrappers for scripts. If the
// introduction script and ScriptSourceObject are in different compartments,
// we would be creating a cross-compartment script reference, which is
// forbidden. We can still store a CCW to the script source object though.
RootedValue introductionScript(cx);
if (introScript) {
if (introScript->compartment() == cx->compartment()) {
introductionScript.setPrivateGCThing(introScript);
}
}
sso->setIntroductionScript(introductionScript);
RootedValue privateValueStore(cx, UndefinedValue());
if (privateValue.isUndefined()) {
// Set the private value to that of the script or module that this source is
// part of, if any.
if (scriptOrModule) {
privateValueStore = scriptOrModule->sourceObject()->canonicalPrivate();
}
} else {
privateValueStore = privateValue;
}
if (!privateValueStore.isUndefined()) {
if (!JS_WrapValue(cx, &privateValueStore)) {
return false;
}
}
sso->setPrivate(cx->runtime(), privateValueStore);
if (!options.hideScriptFromDebugger) {
JS::ExposeScriptToDebugger(cx, script);
}
return true;
}
JS_PUBLIC_API void JS::SetGetElementCallback(JSContext* cx,
JSGetElementCallback callback) {
MOZ_ASSERT(cx->runtime());

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

@ -2109,7 +2109,7 @@ JSScript* GlobalHelperThreadState::finishSingleParseTask(
// The Debugger only needs to be told about the topmost script that was
// compiled.
if (!parseTask->options.hideScriptFromDebugger) {
if (!parseTask->options.hideFromNewScriptInitial()) {
DebugAPI::onNewScript(cx, script);
}
} else {
@ -2187,7 +2187,7 @@ bool GlobalHelperThreadState::finishMultiParseTask(
// The Debugger only needs to be told about the topmost scripts that were
// compiled.
if (!parseTask->options.hideScriptFromDebugger) {
if (!parseTask->options.hideFromNewScriptInitial()) {
JS::RootedScript rooted(cx);
for (auto& script : scripts) {
MOZ_ASSERT(script->isGlobalCode());

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

@ -36,6 +36,7 @@
#include "jit/InlinableNatives.h"
#include "jit/Ion.h"
#include "js/CallNonGenericMethod.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
@ -1676,8 +1677,8 @@ static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
.setFileAndLine(filename, 1)
.setNoScriptRval(false)
.setIntroductionInfo(introducerFilename, introductionType, lineno,
maybeScript, pcOffset)
.setScriptOrModule(maybeScript);
pcOffset)
.setdeferDebugMetadata();
JSStringBuilder sb(cx);
@ -1812,6 +1813,13 @@ static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
return false;
}
RootedValue undefValue(cx);
RootedScript funScript(cx, JS_GetFunctionScript(cx, fun));
if (funScript && !UpdateDebugMetadata(cx, funScript, options, undefValue,
nullptr, maybeScript, maybeScript)) {
return false;
}
if (fun->isInterpreted()) {
fun->initEnvironment(&cx->global()->lexicalEnvironment());
}

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

@ -1226,7 +1226,8 @@ XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
}
/* see BytecodeEmitter::tellDebuggerAboutCompiledScript */
if (!isFunctionScript && !cx->isHelperThreadContext()) {
if (!isFunctionScript && !cx->isHelperThreadContext() &&
!xdr->options().hideFromNewScriptInitial()) {
DebugAPI::onNewScript(cx, script);
}
}
@ -1686,41 +1687,21 @@ bool ScriptSourceObject::initFromOptions(
return false;
}
RootedString elementAttributeName(cx, options.elementAttributeName());
if (options.deferDebugMetadata) {
return true;
}
// Initialize the element attribute slot and introduction script slot
// this marks the SSO as initialized for asserts.
RootedString elementAttributeName(cx);
if (!initElementProperties(cx, source, elementAttributeName)) {
return false;
}
// There is no equivalent of cross-compartment wrappers for scripts. If the
// introduction script and ScriptSourceObject are in different compartments,
// we would be creating a cross-compartment script reference, which is
// forbidden. We can still store a CCW to the script source object though.
RootedValue introductionScript(cx);
if (JSScript* script = options.introductionScript()) {
if (script->compartment() == cx->compartment()) {
introductionScript.setPrivateGCThing(options.introductionScript());
}
}
source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
RootedValue privateValue(cx, UndefinedValue());
if (options.privateValue().isUndefined()) {
// Set the private value to that of the script or module that this source is
// part of, if any.
if (JSScript* script = options.scriptOrModule()) {
privateValue = script->sourceObject()->canonicalPrivate();
}
} else {
privateValue = options.privateValue();
}
if (!privateValue.isUndefined()) {
if (!JS_WrapValue(cx, &privateValue)) {
return false;
}
}
source->setPrivate(cx->runtime(), privateValue);
return true;
}

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

@ -1134,6 +1134,10 @@ class ScriptSourceObject : public NativeObject {
void setPrivate(JSRuntime* rt, const Value& value);
void setIntroductionScript(const Value& introductionScript) {
setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
}
Value canonicalPrivate() const {
MOZ_ASSERT(isInitialized());
Value value = getReservedSlot(PRIVATE_SLOT);