From 18de456bcfe6172eb199d14fb89054c22713d097 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 16 Oct 2018 13:44:12 +0100 Subject: [PATCH] Bug 1482153 - Provide a way of associating a private value with a script or module r=jandem rs=hsivonen --- dom/script/ModuleScript.cpp | 7 ++-- dom/script/ScriptLoader.cpp | 23 +++++------- dom/script/ScriptLoader.h | 5 +-- js/src/builtin/ModuleObject.cpp | 23 ++++++------ js/src/builtin/ModuleObject.h | 6 ++-- js/src/jsapi.cpp | 20 ++++++++--- js/src/jsapi.h | 27 ++++++++++---- js/src/shell/ModuleLoader.js | 60 +++++++++++++++++-------------- js/src/shell/js.cpp | 63 ++++++++++++++++++++++++++++++--- js/src/vm/JSScript.h | 20 ++++++++--- js/src/vm/SelfHosting.cpp | 3 +- 11 files changed, 171 insertions(+), 86 deletions(-) diff --git a/dom/script/ModuleScript.cpp b/dom/script/ModuleScript.cpp index 35b188ab8df2..83ab2965234a 100644 --- a/dom/script/ModuleScript.cpp +++ b/dom/script/ModuleScript.cpp @@ -56,9 +56,8 @@ ModuleScript::UnlinkModuleRecord() { // Remove module's back reference to this object request if present. if (mModuleRecord) { - MOZ_ASSERT(JS::GetModuleHostDefinedField(mModuleRecord).toPrivate() == - this); - JS::SetModuleHostDefinedField(mModuleRecord, JS::UndefinedValue()); + MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).toPrivate() == this); + JS::SetModulePrivate(mModuleRecord, JS::UndefinedValue()); mModuleRecord = nullptr; } } @@ -81,7 +80,7 @@ ModuleScript::SetModuleRecord(JS::Handle aModuleRecord) // Make module's host defined field point to this module script object. // This is cleared in the UnlinkModuleRecord(). - JS::SetModuleHostDefinedField(mModuleRecord, JS::PrivateValue(this)); + JS::SetModulePrivate(mModuleRecord, JS::PrivateValue(this)); HoldJSObjects(this); } diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index 481762a7b735..5ea5ac7d4068 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -759,13 +759,13 @@ ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent, // 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier) JSObject* -HostResolveImportedModule(JSContext* aCx, JS::Handle aModule, +HostResolveImportedModule(JSContext* aCx, + JS::Handle aReferencingPrivate, JS::Handle aSpecifier) { // Let referencing module script be referencingModule.[[HostDefined]]. - JS::Value value = JS::GetModuleHostDefinedField(aModule); - auto script = static_cast(value.toPrivate()); - MOZ_ASSERT(script->ModuleRecord() == aModule); + auto script = static_cast(aReferencingPrivate.toPrivate()); + MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); // Let url be the result of resolving a module specifier given referencing // module script and specifier. @@ -792,19 +792,12 @@ HostResolveImportedModule(JSContext* aCx, JS::Handle aModule, } bool -HostPopulateImportMeta(JSContext* aCx, JS::Handle aModule, +HostPopulateImportMeta(JSContext* aCx, + JS::Handle aReferencingPrivate, JS::Handle aMetaObject) { - MOZ_DIAGNOSTIC_ASSERT(aModule); - - JS::Value value = JS::GetModuleHostDefinedField(aModule); - if (value.isUndefined()) { - JS_ReportErrorASCII(aCx, "Module script not found"); - return false; - } - - auto script = static_cast(value.toPrivate()); - MOZ_DIAGNOSTIC_ASSERT(script->ModuleRecord() == aModule); + auto script = static_cast(aReferencingPrivate.toPrivate()); + MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); nsAutoCString url; MOZ_DIAGNOSTIC_ASSERT(script->BaseURL()); diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h index ac0b50683f16..79df5e290795 100644 --- a/dom/script/ScriptLoader.h +++ b/dom/script/ScriptLoader.h @@ -521,8 +521,9 @@ private: ModuleScript* GetFetchedModule(nsIURI* aURL) const; friend JSObject* - HostResolveImportedModule(JSContext* aCx, JS::Handle aModule, - JS::Handle aSpecifier); + HostResolveImportedModule(JSContext* aCx, + JS::Handle aReferencingPrivate, + JS::Handle aSpecifier); // Returns wether we should save the bytecode of this script after the // execution of the script. diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 215f01b5cebd..d1acdb9b5e76 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -873,6 +873,12 @@ ModuleObject::namespace_() return &value.toObject().as(); } +ScriptSourceObject* +ModuleObject::scriptSourceObject() const +{ + return &getReservedSlot(ScriptSourceObjectSlot).toObject().as(); +} + FunctionDeclarationVector* ModuleObject::functionDeclarations() { @@ -887,8 +893,10 @@ ModuleObject::functionDeclarations() void ModuleObject::init(HandleScript script) { + MOZ_ASSERT(script); initReservedSlot(ScriptSlot, PrivateGCThingValue(script)); initReservedSlot(StatusSlot, Int32Value(MODULE_STATUS_UNINSTANTIATED)); + initReservedSlot(ScriptSourceObjectSlot, ObjectValue(script->scriptSourceUnwrap())); } void @@ -1055,18 +1063,6 @@ ModuleObject::setMetaObject(JSObject* obj) setReservedSlot(MetaObjectSlot, ObjectValue(*obj)); } -Value -ModuleObject::hostDefinedField() const -{ - return getReservedSlot(HostDefinedSlot); -} - -void -ModuleObject::setHostDefinedField(const JS::Value& value) -{ - setReservedSlot(HostDefinedSlot, value); -} - Scope* ModuleObject::enclosingScope() const { @@ -1800,7 +1796,8 @@ js::GetOrCreateModuleMetaObject(JSContext* cx, HandleObject moduleArg) return nullptr; } - if (!func(cx, module, metaObject)) { + RootedValue modulePrivate(cx, JS::GetModulePrivate(module)); + if (!func(cx, modulePrivate, metaObject)) { return nullptr; } diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index 91b48e61fb86..3f6ac5cbb7b6 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -260,7 +260,7 @@ class ModuleObject : public NativeObject StatusSlot, EvaluationErrorSlot, MetaObjectSlot, - HostDefinedSlot, + ScriptSourceObjectSlot, RequestedModulesSlot, ImportEntriesSlot, LocalExportEntriesSlot, @@ -312,7 +312,7 @@ class ModuleObject : public NativeObject bool hadEvaluationError() const; Value evaluationError() const; JSObject* metaObject() const; - Value hostDefinedField() const; + ScriptSourceObject* scriptSourceObject() const; ArrayObject& requestedModules() const; ArrayObject& importEntries() const; ArrayObject& localExportEntries() const; @@ -328,8 +328,6 @@ class ModuleObject : public NativeObject void setMetaObject(JSObject* obj); - void setHostDefinedField(const JS::Value& value); - // For BytecodeEmitter. bool noteFunctionDeclaration(JSContext* cx, HandleAtom name, HandleFunction fun); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ac21b8986766..3f886a09fd8f 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4152,15 +4152,27 @@ JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, } JS_PUBLIC_API(void) -JS::SetModuleHostDefinedField(JSObject* module, const JS::Value& value) +JS::SetModulePrivate(JSObject* module, const JS::Value& value) { - module->as().setHostDefinedField(value); + module->as().scriptSourceObject()->setPrivate(value); } JS_PUBLIC_API(JS::Value) -JS::GetModuleHostDefinedField(JSObject* module) +JS::GetModulePrivate(JSObject* module) { - return module->as().hostDefinedField(); + return module->as().scriptSourceObject()->getPrivate(); +} + +JS_PUBLIC_API(void) +JS::SetScriptPrivate(JSScript* script, const JS::Value& value) +{ + script->scriptSourceUnwrap().setPrivate(value); +} + +JS_PUBLIC_API(JS::Value) +JS::GetScriptPrivate(JSScript* script) +{ + return script->scriptSourceUnwrap().getPrivate(); } JS_PUBLIC_API(bool) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 9819abbaa9d7..3963091c0698 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3021,7 +3021,7 @@ JS_DecompileFunction(JSContext* cx, JS::Handle fun); namespace JS { -using ModuleResolveHook = JSObject* (*)(JSContext*, HandleObject, HandleString); +using ModuleResolveHook = JSObject* (*)(JSContext*, HandleValue, HandleString); /** * Get the HostResolveImportedModule hook for the runtime. @@ -3035,7 +3035,7 @@ GetModuleResolveHook(JSRuntime* rt); extern JS_PUBLIC_API(void) SetModuleResolveHook(JSRuntime* rt, ModuleResolveHook func); -using ModuleMetadataHook = bool (*)(JSContext*, HandleObject, HandleObject); +using ModuleMetadataHook = bool (*)(JSContext*, HandleValue, HandleObject); /** * Get the hook for populating the import.meta metadata object. @@ -3059,17 +3059,30 @@ CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, JS::MutableHandleObject moduleRecord); /** - * Set the [[HostDefined]] field of a source text module record to the given - * value. + * Set a private value associated with a source text module record. */ extern JS_PUBLIC_API(void) -SetModuleHostDefinedField(JSObject* module, const JS::Value& value); +SetModulePrivate(JSObject* module, const JS::Value& value); /** - * Get the [[HostDefined]] field of a source text module record. + * Get the private value associated with a source text module record. */ extern JS_PUBLIC_API(JS::Value) -GetModuleHostDefinedField(JSObject* module); +GetModulePrivate(JSObject* module); + +/** + * Set a private value associated with a script. Note that this value is shared + * by all nested scripts compiled from a single source file. + */ +extern JS_PUBLIC_API(void) +SetScriptPrivate(JSScript* script, const JS::Value& value); + +/** + * Get the private value associated with a script. Note that this value is + * shared by all nested scripts compiled from a single source file. + */ +extern JS_PUBLIC_API(JS::Value) +GetScriptPrivate(JSScript* script); /* * Perform the ModuleInstantiate operation on the given source text module diff --git a/js/src/shell/ModuleLoader.js b/js/src/shell/ModuleLoader.js index 8ad48b74cf1a..da9ed7090ad5 100644 --- a/js/src/shell/ModuleLoader.js +++ b/js/src/shell/ModuleLoader.js @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* global getModuleLoadPath setModuleLoadHook setModuleResolveHook setModuleMetadataHook */ -/* global parseModule os */ +/* global getModulePrivate setModulePrivate parseModule os */ // A basic synchronous module loader for testing the shell. { @@ -19,6 +19,7 @@ const StringPrototypeIndexOf = String.prototype.indexOf; const StringPrototypeLastIndexOf = String.prototype.lastIndexOf; const StringPrototypeStartsWith = String.prototype.startsWith; const StringPrototypeSubstring = String.prototype.substring; +const ErrorClass = Error; const ReflectLoader = new class { constructor() { @@ -27,35 +28,39 @@ const ReflectLoader = new class { this.loadPath = getModuleLoadPath(); } - resolve(name, module) { + resolve(name, referencingInfo) { if (os.path.isAbsolute(name)) return name; let loadPath = this.loadPath; - if (module) { - // Treat |name| as a relative path if it starts with either "./" - // or "../". - let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"]) - || ReflectApply(StringPrototypeStartsWith, name, ["../"]) -#ifdef XP_WIN - || ReflectApply(StringPrototypeStartsWith, name, [".\\"]) - || ReflectApply(StringPrototypeStartsWith, name, ["..\\"]) -#endif - ; - // If |name| is a relative path and |module|'s path is available, - // load |name| relative to the referring module. - if (isRelative && ReflectApply(MapPrototypeHas, this.modulePaths, [module])) { - let modulePath = ReflectApply(MapPrototypeGet, this.modulePaths, [module]); - let sepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["/"]); + // Treat |name| as a relative path if it starts with either "./" + // or "../". + let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"]) + || ReflectApply(StringPrototypeStartsWith, name, ["../"]) #ifdef XP_WIN - let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["\\"]); - if (otherSepIndex > sepIndex) - sepIndex = otherSepIndex; + || ReflectApply(StringPrototypeStartsWith, name, [".\\"]) + || ReflectApply(StringPrototypeStartsWith, name, ["..\\"]) #endif - if (sepIndex >= 0) - loadPath = ReflectApply(StringPrototypeSubstring, modulePath, [0, sepIndex]); + ; + + // If |name| is a relative path and the referencing module's path is + // available, load |name| relative to the that path. + if (isRelative) { + if (!referencingInfo) { + throw new ErrorClass("No referencing module for relative import"); } + + let path = referencingInfo.path; + + let sepIndex = ReflectApply(StringPrototypeLastIndexOf, path, ["/"]); +#ifdef XP_WIN + let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, path, ["\\"]); + if (otherSepIndex > sepIndex) + sepIndex = otherSepIndex; +#endif + if (sepIndex >= 0) + loadPath = ReflectApply(StringPrototypeSubstring, path, [0, sepIndex]); } return os.path.join(loadPath, name); @@ -155,8 +160,9 @@ const ReflectLoader = new class { let source = this.fetch(path); let module = parseModule(source, path); + let moduleInfo = { path: normalized }; + setModulePrivate(module, moduleInfo); ReflectApply(MapPrototypeSet, this.registry, [normalized, module]); - ReflectApply(MapPrototypeSet, this.modulePaths, [module, path]); return module; } @@ -175,12 +181,12 @@ const ReflectLoader = new class { return this.loadAndExecute(path); } - populateImportMeta(module, metaObject) { - // For the shell, use the script's filename as the base URL. + populateImportMeta(moduleInfo, metaObject) { + // For the shell, use the module's normalized path as the base URL. let path; - if (ReflectApply(MapPrototypeHas, this.modulePaths, [module])) { - path = ReflectApply(MapPrototypeGet, this.modulePaths, [module]); + if (moduleInfo) { + path = moduleInfo.path; } else { path = "(unknown)"; } diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a5ffb44219a3..3338b2d28c2b 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4723,7 +4723,7 @@ SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) } static JSObject* -CallModuleResolveHook(JSContext* cx, HandleObject module, HandleString specifier) +CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier) { Handle global = cx->global(); RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleResolveHook)); @@ -4734,7 +4734,7 @@ CallModuleResolveHook(JSContext* cx, HandleObject module, HandleString specifier MOZ_ASSERT(hookValue.toObject().is()); JS::AutoValueArray<2> args(cx); - args[0].setObject(*module); + args[0].set(referencingPrivate); args[1].setString(specifier); RootedValue result(cx); @@ -4774,7 +4774,7 @@ SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp) } static bool -CallModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject) +CallModuleMetadataHook(JSContext* cx, HandleValue modulePrivate, HandleObject metaObject) { Handle global = cx->global(); RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleMetadataHook)); @@ -4785,13 +4785,60 @@ CallModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObje MOZ_ASSERT(hookValue.toObject().is()); JS::AutoValueArray<2> args(cx); - args[0].setObject(*module); + args[0].set(modulePrivate); args[1].setObject(*metaObject); RootedValue dummy(cx); return JS_CallFunctionValue(cx, nullptr, hookValue, args, &dummy); } +static bool +ReportArgumentTypeError(JSContext* cx, HandleValue value, const char* expected) +{ + const char* typeName = InformalValueTypeName(value); + JS_ReportErrorASCII(cx, "Expected %s, got %s", expected, typeName); + return false; +} + +static bool +ShellSetModulePrivate(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 2) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "setModulePrivate", "0", "s"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is()) { + return ReportArgumentTypeError(cx, args[0], "module object"); + } + + JS::SetModulePrivate(&args[0].toObject(), args[1]); + args.rval().setUndefined(); + return true; +} + +static bool +ShellGetModulePrivate(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 1) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "getModulePrivate", "0", "s"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is()) { + return ReportArgumentTypeError(cx, args[0], "module object"); + } + + args.rval().set(JS::GetModulePrivate(&args[0].toObject())); + return true; +} + static bool GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp) { @@ -8011,6 +8058,14 @@ static const JSFunctionSpecWithHelp shell_functions[] = { " This hook is used to create the metadata object returned by import.meta for\n" " a module. It should be implemented by the module loader."), + JS_FN_HELP("setModulePrivate", ShellSetModulePrivate, 2, 0, +"setModulePrivate(scriptObject, privateValue)", +" Associate a private value with a module object.\n"), + + JS_FN_HELP("getModulePrivate", ShellGetModulePrivate, 2, 0, +"getModulePrivate(scriptObject)", +" Get the private value associated with a module object.\n"), + JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0, "getModuleLoadPath()", " Return any --module-load-path argument passed to the shell. Used by the\n" diff --git a/js/src/vm/JSScript.h b/js/src/vm/JSScript.h index e83a063c5596..ed304e317bc1 100644 --- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -1221,12 +1221,22 @@ class ScriptSourceObject : public NativeObject return value.toGCThing()->as(); } + void setPrivate(const Value& value) { + setReservedSlot(PRIVATE_SLOT, value); + } + Value getPrivate() const { + return getReservedSlot(PRIVATE_SLOT); + } + private: - static const uint32_t SOURCE_SLOT = 0; - static const uint32_t ELEMENT_SLOT = 1; - static const uint32_t ELEMENT_PROPERTY_SLOT = 2; - static const uint32_t INTRODUCTION_SCRIPT_SLOT = 3; - static const uint32_t RESERVED_SLOTS = 4; + enum { + SOURCE_SLOT = 0, + ELEMENT_SLOT, + ELEMENT_PROPERTY_SLOT, + INTRODUCTION_SCRIPT_SLOT, + PRIVATE_SLOT, + RESERVED_SLOTS + }; }; enum class GeneratorKind : bool { NotGenerator, Generator }; diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 9cfeba1849ed..d4c9a18f8dcf 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2190,7 +2190,8 @@ intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp) } RootedObject result(cx); - result = moduleResolveHook(cx, module, specifier); + RootedValue referencingPrivate(cx, JS::GetModulePrivate(module)); + result = moduleResolveHook(cx, referencingPrivate, specifier); if (!result) { return false; }