diff --git a/doc/api/errors.md b/doc/api/errors.md index df81718e7d7fb0..0ebd66edfe6825 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1036,6 +1036,12 @@ is set for the `Http2Stream`. `http2.connect()` was passed a URL that uses any protocol other than `http:` or `https:`. + +### ERR_DYNAMIC_IMPORT_CALLBACK_MISSING + +There was a dynamic import request but the script or module did not provide +a callback to handle it. + ### ERR_INDEX_OUT_OF_RANGE diff --git a/doc/api/vm.md b/doc/api/vm.md index 6c7bdeffda23f7..9c10d2ca871f07 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -437,6 +437,9 @@ changes: `cachedData` property of the returned `vm.Script` instance. The `cachedDataProduced` value will be set to either `true` or `false` depending on whether code cache data is produced successfully. + * `resolveDynamicImport` {Function} See [`module.link()`][] + * `specifier` {string} + * `scriptOrModule` {vm.Script|vm.Module|Module Namespace Object} Creating a new `vm.Script` object compiles `code` but does not run it. The compiled `vm.Script` can be run later multiple times. The `code` is not bound to @@ -894,6 +897,7 @@ associating it with the `sandbox` object is what this document refers to as [`Error`]: errors.html#errors_class_error [`URL`]: url.html#url_class_url [`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval +[`module.link()`]: #vm_module_link_linker [`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options [`script.runInThisContext()`]: #vm_script_runinthiscontext_options [`url.origin`]: url.html#url_url_origin diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 09f58506c44ea0..75ecbd89fad3f1 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -927,6 +927,8 @@ E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error); E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error); E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error); E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error); +E('ERR_DYNAMIC_IMPORT_CALLBACK_MISSING', + 'No callback available for dynamic import request: %s', Error); E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented', Error); E('ERR_MISSING_ARGS', missingArgs, TypeError); E('ERR_MISSING_MODULE', 'Cannot find module %s', Error); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 652378ad5782fb..abbec4073c76a8 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -646,7 +646,12 @@ Module.prototype._compile = function(content, filename) { var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, - displayErrors: true + displayErrors: true, + resolveDynamicImport: experimentalModules ? async (specifier, script) => { + const loader = await asyncESM.loaderPromise; + const referrer = getURLFromFilePath(script.filename).href; + return loader.import(specifier, referrer); + } : undefined, }); var inspectorWrapper = null; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index d181647c4505e8..9a8bea6f73942f 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -14,6 +14,11 @@ const fs = require('fs'); const { _makeLong } = require('path'); const { SafeMap } = require('internal/safe_globals'); const { URL } = require('url'); +const { + initializeImportMetaMap, + importModuleDynamicallyMap, + loaderPromise, +} = require('internal/process/esm_loader'); const debug = require('util').debuglog('esm'); const readFileAsync = require('util').promisify(fs.readFile); const readFileSync = fs.readFileSync; @@ -27,10 +32,13 @@ module.exports = translators; translators.set('esm', async (url) => { const source = `${await readFileAsync(new URL(url))}`; debug(`Translating StandardModule ${url}`); - return { - module: new ModuleWrap(stripShebang(source), url), - reflect: undefined - }; + const module = new ModuleWrap(stripShebang(source), url); + initializeImportMetaMap.set(module, (meta) => { meta.url = url; }); + importModuleDynamicallyMap.set(module, async (specifier) => { + const loader = await loaderPromise; + return loader.import(specifier, url); + }); + return { module, reflect: undefined }; }); // Strategy for loading a node-style CommonJS module diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index db28ca04b16cdc..888aff01b59d74 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -5,37 +5,11 @@ const { setImportModuleDynamicallyCallback, setInitializeImportMetaObjectCallback } = internalBinding('module_wrap'); - +const { + ERR_DYNAMIC_IMPORT_CALLBACK_MISSING, +} = require('internal/errors').codes; const { getURLFromFilePath } = require('internal/url'); const Loader = require('internal/modules/esm/loader'); -const path = require('path'); -const { URL } = require('url'); -const { - initImportMetaMap, - wrapToModuleMap -} = require('internal/vm/module'); - -function normalizeReferrerURL(referrer) { - if (typeof referrer === 'string' && path.isAbsolute(referrer)) { - return getURLFromFilePath(referrer).href; - } - return new URL(referrer).href; -} - -function initializeImportMetaObject(wrap, meta) { - const vmModule = wrapToModuleMap.get(wrap); - if (vmModule === undefined) { - // This ModuleWrap belongs to the Loader. - meta.url = wrap.url; - } else { - const initializeImportMeta = initImportMetaMap.get(vmModule); - if (initializeImportMeta !== undefined) { - // This ModuleWrap belongs to vm.Module, initializer callback was - // provided. - initializeImportMeta(meta, vmModule); - } - } -} let loaderResolve; exports.loaderPromise = new Promise((resolve, reject) => { @@ -45,8 +19,6 @@ exports.loaderPromise = new Promise((resolve, reject) => { exports.ESMLoader = undefined; exports.setup = function() { - setInitializeImportMetaObjectCallback(initializeImportMetaObject); - let ESMLoader = new Loader(); const loaderPromise = (async () => { const userLoader = process.binding('config').userLoader; @@ -61,10 +33,24 @@ exports.setup = function() { })(); loaderResolve(loaderPromise); - setImportModuleDynamicallyCallback(async (referrer, specifier) => { - const loader = await loaderPromise; - return loader.import(specifier, normalizeReferrerURL(referrer)); - }); - exports.ESMLoader = ESMLoader; }; + +const initializeImportMetaMap = exports.initializeImportMetaMap = new WeakMap(); +const importModuleDynamicallyMap = + exports.importModuleDynamicallyMap = new WeakMap(); + +const config = process.binding('config'); +if (config.experimentalModules || config.experimentalVMModules) { + setInitializeImportMetaObjectCallback((meta, wrap) => { + if (initializeImportMetaMap.has(wrap)) + return initializeImportMetaMap.get(wrap)(meta); + }); + + setImportModuleDynamicallyCallback(async (referrer, specifier, wrap) => { + if (importModuleDynamicallyMap.has(wrap)) + return importModuleDynamicallyMap.get(wrap)(specifier); + + throw new ERR_DYNAMIC_IMPORT_CALLBACK_MISSING(specifier); + }); +} diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index 7284c8bd619901..7c3f3470b7fe6e 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -12,13 +12,14 @@ const { ERR_VM_MODULE_LINKING_ERRORED, ERR_VM_MODULE_NOT_LINKED, ERR_VM_MODULE_NOT_MODULE, - ERR_VM_MODULE_STATUS + ERR_VM_MODULE_STATUS, } = require('internal/errors').codes; const { getConstructorOf, customInspectSymbol, } = require('internal/util'); const { SafePromise } = require('internal/safe_globals'); +const { isModuleNamespaceObject } = require('util').types; const { ModuleWrap, @@ -44,10 +45,7 @@ const perContextModuleId = new WeakMap(); const wrapMap = new WeakMap(); const dependencyCacheMap = new WeakMap(); const linkingStatusMap = new WeakMap(); -// vm.Module -> function -const initImportMetaMap = new WeakMap(); -// ModuleWrap -> vm.Module -const wrapToModuleMap = new WeakMap(); +const linkerFnMap = new WeakMap(); class Module { constructor(src, options = {}) { @@ -62,7 +60,6 @@ class Module { context, lineOffset = 0, columnOffset = 0, - initializeImportMeta } = options; if (context !== undefined) { @@ -95,9 +92,11 @@ class Module { validateInteger(lineOffset, 'options.lineOffset'); validateInteger(columnOffset, 'options.columnOffset'); + let { initializeImportMeta } = options; if (initializeImportMeta !== undefined) { if (typeof initializeImportMeta === 'function') { - initImportMetaMap.set(this, initializeImportMeta); + const fn = initializeImportMeta; + initializeImportMeta = (meta) => fn(meta, this); } else { throw new ERR_INVALID_ARG_TYPE( 'options.initializeImportMeta', 'function', initializeImportMeta); @@ -105,9 +104,22 @@ class Module { } const wrap = new ModuleWrap(src, url, context, lineOffset, columnOffset); + + const { + initializeImportMetaMap, + importModuleDynamicallyMap, + } = require('internal/process/esm_loader'); + + if (initializeImportMeta) + initializeImportMetaMap.set(wrap, initializeImportMeta); + + importModuleDynamicallyMap.set(wrap, async (specifier) => { + const linker = linkerFnMap.get(this); + return callLinkerForNamespace(linker, specifier, this); + }); + wrapMap.set(this, wrap); linkingStatusMap.set(this, 'unlinked'); - wrapToModuleMap.set(wrap, this); Object.defineProperties(this, { url: { value: url, enumerable: true }, @@ -160,20 +172,9 @@ class Module { throw new ERR_VM_MODULE_STATUS('must be uninstantiated'); linkingStatusMap.set(this, 'linking'); + linkerFnMap.set(this, linker); - const promises = wrap.link(async (specifier) => { - const m = await linker(specifier, this); - if (!m || !wrapMap.has(m)) - throw new ERR_VM_MODULE_NOT_MODULE(); - if (m.context !== this.context) - throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); - const childLinkingStatus = linkingStatusMap.get(m); - if (childLinkingStatus === 'errored') - throw new ERR_VM_MODULE_LINKING_ERRORED(); - if (childLinkingStatus === 'unlinked') - await m.link(linker); - return wrapMap.get(m); - }); + const promises = wrap.link((s) => callLinkerForModuleWrap(linker, s, this)); try { if (promises !== undefined) @@ -252,8 +253,43 @@ function validateInteger(prop, propName) { } } +async function getWrapFromModule(m, scriptOrModule, linker) { + if (!m || !wrapMap.has(m)) + throw new ERR_VM_MODULE_NOT_MODULE(); + + if (scriptOrModule instanceof Module && + (m.context !== scriptOrModule.context)) + throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); + + const childLinkingStatus = linkingStatusMap.get(m); + + if (childLinkingStatus === 'errored') + throw new ERR_VM_MODULE_LINKING_ERRORED(); + if (childLinkingStatus === 'unlinked') + await m.link(linker); + + return wrapMap.get(m); +} + +async function callLinkerForModuleWrap(linker, specifier, scriptOrModule) { + const m = await linker(specifier, scriptOrModule); + return getWrapFromModule(m, scriptOrModule, linker); +} + +async function callLinkerForNamespace(linker, specifier, scriptOrModule) { + const m = await linker(specifier, scriptOrModule); + if (isModuleNamespaceObject(m)) + return m; + const wrap = await getWrapFromModule(m, scriptOrModule, linker); + const status = wrap.getStatus(); + if (status < kInstantiated) + wrap.instantiate(); + if (status < kEvaluated) + await wrap.evaluate(-1, false); + return wrap.namespace(); +} + module.exports = { Module, - initImportMetaMap, - wrapToModuleMap + callLinkerForNamespace, }; diff --git a/lib/repl.js b/lib/repl.js index a7977dc72fb781..986b22217be6aa 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -49,6 +49,7 @@ const { } = require('internal/modules/cjs/helpers'); const internalUtil = require('internal/util'); const { isTypedArray } = require('internal/util/types'); +const { getURLFromFilePath } = require('internal/url'); const util = require('util'); const utilBinding = process.binding('util'); const { inherits } = util; @@ -256,7 +257,17 @@ function REPLServer(prompt, } script = vm.createScript(code, { filename: file, - displayErrors: true + displayErrors: true, + async resolveDynamicImport(specifier, scriptOrModule) { + const loader = await require('internal/process/esm_loader') + .loaderPromise; + + const filename = scriptOrModule.filename; + const referrer = scriptOrModule.url || + getURLFromFilePath(filename === 'repl' ? + path.join(process.cwd(), filename) : filename).href; + return loader.import(specifier, referrer); + }, }); } catch (e) { debug('parse error %j', code, e); diff --git a/lib/vm.js b/lib/vm.js index 5a5130d7c9c328..3f1d843c15beaa 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -50,6 +50,7 @@ class Script extends ContextifyScript { columnOffset = 0, cachedData, produceCachedData = false, + resolveDynamicImport, [kParsingContext]: parsingContext } = options; @@ -67,6 +68,19 @@ class Script extends ContextifyScript { produceCachedData); } + + let importModuleDynamically; + if (resolveDynamicImport !== undefined && + typeof resolveDynamicImport !== 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'options.resolveDynamicImport', 'function', resolveDynamicImport); + } else if (resolveDynamicImport !== undefined) { + const { callLinkerForNamespace } = require('internal/vm/module'); + importModuleDynamically = async (specifier) => { + return callLinkerForNamespace(resolveDynamicImport, specifier, this); + }; + } + // Calling `ReThrow()` on a native TryCatch does not generate a new // abort-on-uncaught-exception check. A dummy try/catch in JS land // protects against that. @@ -81,6 +95,12 @@ class Script extends ContextifyScript { } catch (e) { throw e; /* node-do-not-add-exception-line */ } + + if (importModuleDynamically !== undefined) { + require('internal/process/esm_loader') + .importModuleDynamicallyMap + .set(this, importModuleDynamically); + } } runInThisContext(options) { diff --git a/src/env-inl.h b/src/env-inl.h index fa241f9706ec65..d74bf7e4fe6678 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -445,6 +445,10 @@ inline double Environment::get_default_trigger_async_id() { return default_trigger_async_id; } +inline int Environment::get_next_module_id() { + return module_id_counter_++; +} + inline double* Environment::heap_statistics_buffer() const { CHECK_NE(heap_statistics_buffer_, nullptr); return heap_statistics_buffer_; diff --git a/src/env.h b/src/env.h index af4470ad8632fe..0467fe6508450f 100644 --- a/src/env.h +++ b/src/env.h @@ -163,6 +163,7 @@ struct PackageConfig { V(fatal_exception_string, "_fatalException") \ V(fd_string, "fd") \ V(file_string, "file") \ + V(filename_string, "filename") \ V(fingerprint_string, "fingerprint") \ V(fingerprint256_string, "fingerprint256") \ V(flags_string, "flags") \ @@ -628,7 +629,11 @@ class Environment { // List of id's that have been destroyed and need the destroy() cb called. inline std::vector* destroy_async_id_list(); - std::unordered_multimap module_map; + std::unordered_map id_to_module_wrap_map; + std::unordered_multimap module_to_module_wrap_map; + std::unordered_map id_to_script_wrap_map; + + inline int get_next_module_id(); std::unordered_map package_json_cache; @@ -865,6 +870,8 @@ class Environment { v8::Local promise, v8::Local parent); + int module_id_counter_ = 0; + #define V(PropertyName, TypeName) Persistent PropertyName ## _; ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) #undef V diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 9bcdb4dce75ff2..af1fff3b1610a8 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -34,6 +34,7 @@ using v8::MaybeLocal; using v8::Module; using v8::Nothing; using v8::Object; +using v8::PrimitiveArray; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; @@ -50,31 +51,40 @@ ModuleWrap::ModuleWrap(Environment* env, Local url) : BaseObject(env, object) { module_.Reset(env->isolate(), module); url_.Reset(env->isolate(), url); + id_ = env->get_next_module_id(); } ModuleWrap::~ModuleWrap() { HandleScope scope(env()->isolate()); Local module = module_.Get(env()->isolate()); - auto range = env()->module_map.equal_range(module->GetIdentityHash()); + env()->id_to_module_wrap_map.erase(id_); + auto range = env()->module_to_module_wrap_map.equal_range( + module->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { if (it->second == this) { - env()->module_map.erase(it); + env()->module_to_module_wrap_map.erase(it); break; } } } +ModuleWrap* ModuleWrap::GetFromID(Environment* env, int id) { + auto module_wrap_it = env->id_to_module_wrap_map.find(id); + if (module_wrap_it == env->id_to_module_wrap_map.end()) + return nullptr; + + return module_wrap_it->second; +} + ModuleWrap* ModuleWrap::GetFromModule(Environment* env, Local module) { - ModuleWrap* ret = nullptr; - auto range = env->module_map.equal_range(module->GetIdentityHash()); + auto range = env->module_to_module_wrap_map.equal_range( + module->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { - if (it->second->module_ == module) { - ret = it->second; - break; - } + if (it->second->module_ == module) + return it->second; } - return ret; + return nullptr; } void ModuleWrap::New(const FunctionCallbackInfo& args) { @@ -126,6 +136,8 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { TryCatch try_catch(isolate); Local module; + Local host_defined_options = PrimitiveArray::New(isolate, 2); + // compile { ScriptOrigin origin(url, @@ -136,7 +148,8 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { Local(), // source map URL False(isolate), // is opaque (?) False(isolate), // is WASM - True(isolate)); // is ES6 module + True(isolate), // is ES6 module + host_defined_options); Context::Scope context_scope(context); ScriptCompiler::Source source(source_text, origin); if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) { @@ -157,7 +170,13 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { ModuleWrap* obj = new ModuleWrap(env, that, module, url); obj->context_.Reset(isolate, context); - env->module_map.emplace(module->GetIdentityHash(), obj); + host_defined_options->Set(0, + Integer::New(isolate, contextify::SourceType::kModule)); + host_defined_options->Set(1, Integer::New(isolate, obj->GetID())); + + env->id_to_module_wrap_map[obj->GetID()] = obj; + env->module_to_module_wrap_map.emplace(module->GetIdentityHash(), obj); + Wrap(that, obj); that->SetIntegrityLevel(context, IntegrityLevel::kFrozen); @@ -365,15 +384,10 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, Local referrer) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); - if (env->module_map.count(referrer->GetIdentityHash()) == 0) { - env->ThrowError("linking error, unknown module"); - return MaybeLocal(); - } ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer); - if (dependent == nullptr) { - env->ThrowError("linking error, null dep"); + env->ThrowError("linking error, unknown module"); return MaybeLocal(); } @@ -700,29 +714,37 @@ static MaybeLocal ImportModuleDynamically( Environment* env = Environment::GetCurrent(context); v8::EscapableHandleScope handle_scope(iso); - if (env->context() != context) { - auto maybe_resolver = Promise::Resolver::New(context); - Local resolver; - if (maybe_resolver.ToLocal(&resolver)) { - // TODO(jkrems): Turn into proper error object w/ code - Local error = v8::Exception::Error( - OneByteString(iso, "import() called outside of main context")); - if (resolver->Reject(context, error).IsJust()) { - return handle_scope.Escape(resolver.As()); - } - } - return MaybeLocal(); - } - Local import_callback = env->host_import_module_dynamically_callback(); + Local import_args[] = { referrer->GetResourceName(), - Local(specifier) + Local(specifier), + Undefined(iso), }; + + Local host_defined_options = + referrer->GetHostDefinedOptions(); + + if (host_defined_options->Length() == 2) { + int type = host_defined_options->Get(0).As()->Value(); + if (type == contextify::SourceType::kScript) { + int id = host_defined_options->Get(1).As()->Value(); + contextify::ContextifyScript* wrap = + contextify::ContextifyScript::GetFromID(env, id); + CHECK_NE(wrap, nullptr); + import_args[2] = wrap->object(); + } else if (type == contextify::SourceType::kModule) { + int id = host_defined_options->Get(1).As()->Value(); + ModuleWrap* wrap = ModuleWrap::GetFromID(env, id); + CHECK_NE(wrap, nullptr); + import_args[2] = wrap->object(); + } + } + MaybeLocal maybe_result = import_callback->Call(context, v8::Undefined(iso), - 2, + 3, import_args); Local result; @@ -752,16 +774,18 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback( Environment* env = Environment::GetCurrent(context); ModuleWrap* module_wrap = ModuleWrap::GetFromModule(env, module); - if (module_wrap == nullptr) { + if (module_wrap == nullptr) return; - } - Local wrap = module_wrap->object(); Local callback = env->host_initialize_import_meta_object_callback(); - Local args[] = { wrap, meta }; - callback->Call(context, Undefined(isolate), arraysize(args), args) - .ToLocalChecked(); + + Local args[] = { meta, module_wrap->object() }; + + TryCatch try_catch(isolate); + USE(callback->Call(context, Undefined(isolate), 2, args)); + if (try_catch.HasCaught()) + try_catch.ReThrow(); } void ModuleWrap::SetInitializeImportMetaObjectCallback( diff --git a/src/module_wrap.h b/src/module_wrap.h index 3969e2a37878fc..1e3b67a4c00fad 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -32,6 +32,9 @@ class ModuleWrap : public BaseObject { v8::Local context, v8::Local module, v8::Local meta); + static ModuleWrap* GetFromID(Environment*, int); + + inline int GetID() { return id_; } private: ModuleWrap(Environment* env, @@ -59,9 +62,9 @@ class ModuleWrap : public BaseObject { v8::Local context, v8::Local specifier, v8::Local referrer); - static ModuleWrap* GetFromModule(node::Environment*, v8::Local); - + static ModuleWrap* GetFromModule(Environment*, v8::Local); + int id_; Persistent module_; Persistent url_; bool linked_ = false; diff --git a/src/node_contextify.cc b/src/node_contextify.cc index e07d5ebcd29d0d..03759b2ce3406e 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -49,6 +49,7 @@ using v8::Name; using v8::NamedPropertyHandlerConfiguration; using v8::Object; using v8::ObjectTemplate; +using v8::PrimitiveArray; using v8::PropertyAttribute; using v8::PropertyCallbackInfo; using v8::PropertyDescriptor; @@ -584,312 +585,335 @@ void ContextifyContext::IndexedPropertyDeleterCallback( args.GetReturnValue().Set(false); } -class ContextifyScript : public BaseObject { - private: - Persistent script_; - - public: - static void Init(Environment* env, Local target) { - HandleScope scope(env->isolate()); - Local class_name = - FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); - - Local script_tmpl = env->NewFunctionTemplate(New); - script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); - script_tmpl->SetClassName(class_name); - env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); - env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); - - target->Set(class_name, script_tmpl->GetFunction()); - env->set_script_context_constructor_template(script_tmpl); - - Local parsing_context_symbol = - Symbol::New(env->isolate(), - FIXED_ONE_BYTE_STRING(env->isolate(), - "script parsing context")); - env->set_vm_parsing_context_symbol(parsing_context_symbol); - target->Set(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "kParsingContext"), - parsing_context_symbol) - .FromJust(); - } - - - static void New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); - Local context = env->context(); - - CHECK(args.IsConstructCall()); - - const int argc = args.Length(); - CHECK_GE(argc, 2); - - CHECK(args[0]->IsString()); - Local code = args[0].As(); - - CHECK(args[1]->IsString()); - Local filename = args[1].As(); - - Local line_offset; - Local column_offset; - Local cached_data_buf; - bool produce_cached_data = false; - Local parsing_context = context; - - if (argc > 2) { - // new ContextifyScript(code, filename, lineOffset, columnOffset - // cachedData, produceCachedData, parsingContext) - CHECK_EQ(argc, 7); - CHECK(args[2]->IsNumber()); - line_offset = args[2].As(); - CHECK(args[3]->IsNumber()); - column_offset = args[3].As(); - if (!args[4]->IsUndefined()) { - CHECK(args[4]->IsUint8Array()); - cached_data_buf = args[4].As(); - } - CHECK(args[5]->IsBoolean()); - produce_cached_data = args[5]->IsTrue(); - if (!args[6]->IsUndefined()) { - CHECK(args[6]->IsObject()); - ContextifyContext* sandbox = - ContextifyContext::ContextFromContextifiedSandbox( - env, args[6].As()); - CHECK_NE(sandbox, nullptr); - parsing_context = sandbox->context(); - } - } else { - line_offset = Integer::New(isolate, 0); - column_offset = Integer::New(isolate, 0); - } +ContextifyScript* ContextifyScript::GetFromID(Environment* env, int id) { + auto contextify_script_it = env->id_to_script_wrap_map.find(id); + if (contextify_script_it == env->id_to_script_wrap_map.end()) + return nullptr; - ContextifyScript* contextify_script = - new ContextifyScript(env, args.This()); + return reinterpret_cast(contextify_script_it->second); +} - ScriptCompiler::CachedData* cached_data = nullptr; - if (!cached_data_buf.IsEmpty()) { - ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents(); - uint8_t* data = static_cast(contents.Data()); - cached_data = new ScriptCompiler::CachedData( - data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); - } +void ContextifyScript::Init(Environment* env, Local target) { + HandleScope scope(env->isolate()); + Local class_name = + FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); + + Local script_tmpl = env->NewFunctionTemplate(New); + script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + script_tmpl->SetClassName(class_name); + env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); + env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); + + target->Set(class_name, script_tmpl->GetFunction()); + env->set_script_context_constructor_template(script_tmpl); + + Local parsing_context_symbol = + Symbol::New(env->isolate(), + FIXED_ONE_BYTE_STRING(env->isolate(), + "script parsing context")); + env->set_vm_parsing_context_symbol(parsing_context_symbol); + target->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "kParsingContext"), + parsing_context_symbol) + .FromJust(); +} - ScriptOrigin origin(filename, line_offset, column_offset); - ScriptCompiler::Source source(code, origin, cached_data); - ScriptCompiler::CompileOptions compile_options = - ScriptCompiler::kNoCompileOptions; +void ContextifyScript::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + Local context = env->context(); - if (source.GetCachedData() != nullptr) - compile_options = ScriptCompiler::kConsumeCodeCache; + CHECK(args.IsConstructCall()); - TryCatch try_catch(isolate); - Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env); - Context::Scope scope(parsing_context); + const int argc = args.Length(); + CHECK_GE(argc, 2); - MaybeLocal v8_script = ScriptCompiler::CompileUnboundScript( - isolate, - &source, - compile_options); + CHECK(args[0]->IsString()); + Local code = args[0].As(); - if (v8_script.IsEmpty()) { - DecorateErrorStack(env, try_catch); - no_abort_scope.Close(); - try_catch.ReThrow(); - return; + CHECK(args[1]->IsString()); + Local filename = args[1].As(); + + Local line_offset; + Local column_offset; + Local cached_data_buf; + bool produce_cached_data = false; + Local parsing_context = context; + + if (argc > 2) { + // new ContextifyScript(code, filename, lineOffset, columnOffset + // cachedData, produceCachedData, parsingContext) + CHECK_EQ(argc, 7); + CHECK(args[2]->IsNumber()); + line_offset = args[2].As(); + CHECK(args[3]->IsNumber()); + column_offset = args[3].As(); + if (!args[4]->IsUndefined()) { + CHECK(args[4]->IsUint8Array()); + cached_data_buf = args[4].As(); } - contextify_script->script_.Reset(isolate, v8_script.ToLocalChecked()); - - if (compile_options == ScriptCompiler::kConsumeCodeCache) { - args.This()->Set( - env->cached_data_rejected_string(), - Boolean::New(isolate, source.GetCachedData()->rejected)); - } else if (produce_cached_data) { - const ScriptCompiler::CachedData* cached_data = - ScriptCompiler::CreateCodeCache(v8_script.ToLocalChecked(), code); - bool cached_data_produced = cached_data != nullptr; - if (cached_data_produced) { - MaybeLocal buf = Buffer::Copy( - env, - reinterpret_cast(cached_data->data), - cached_data->length); - args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); - } - args.This()->Set( - env->cached_data_produced_string(), - Boolean::New(isolate, cached_data_produced)); + CHECK(args[5]->IsBoolean()); + produce_cached_data = args[5]->IsTrue(); + if (!args[6]->IsUndefined()) { + CHECK(args[6]->IsObject()); + ContextifyContext* sandbox = + ContextifyContext::ContextFromContextifiedSandbox( + env, args[6].As()); + CHECK_NE(sandbox, nullptr); + parsing_context = sandbox->context(); } + } else { + line_offset = Integer::New(isolate, 0); + column_offset = Integer::New(isolate, 0); } + ContextifyScript* contextify_script = + new ContextifyScript(env, args.This()); - static bool InstanceOf(Environment* env, const Local& value) { - return !value.IsEmpty() && - env->script_context_constructor_template()->HasInstance(value); + ScriptCompiler::CachedData* cached_data = nullptr; + if (!cached_data_buf.IsEmpty()) { + ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents(); + uint8_t* data = static_cast(contents.Data()); + cached_data = new ScriptCompiler::CachedData( + data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); } + Local host_defined_options = + PrimitiveArray::New(env->isolate(), 2); + host_defined_options->Set(0, + Integer::New(env->isolate(), contextify::SourceType::kScript)); + + ScriptOrigin origin(filename, + line_offset, + column_offset, + v8::False(env->isolate()), // is cross origin + Local(), // script id + Local(), // source map url + v8::False(env->isolate()), // is opaque + v8::False(env->isolate()), // is wasm + v8::False(env->isolate()), // is es6 module + host_defined_options); // host options + + ScriptCompiler::Source source(code, origin, cached_data); + ScriptCompiler::CompileOptions compile_options = + ScriptCompiler::kNoCompileOptions; + + if (source.GetCachedData() != nullptr) + compile_options = ScriptCompiler::kConsumeCodeCache; + + TryCatch try_catch(isolate); + Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env); + Context::Scope scope(parsing_context); + + MaybeLocal v8_script = ScriptCompiler::CompileUnboundScript( + isolate, + &source, + compile_options); + + if (v8_script.IsEmpty()) { + DecorateErrorStack(env, try_catch); + no_abort_scope.Close(); + try_catch.ReThrow(); + return; + } + contextify_script->script_.Reset(isolate, v8_script.ToLocalChecked()); + + host_defined_options->Set(1, + Integer::New(env->isolate(), contextify_script->GetID())); + + Local that = args.This(); + if (compile_options == ScriptCompiler::kConsumeCodeCache) { + that->Set( + env->cached_data_rejected_string(), + Boolean::New(isolate, source.GetCachedData()->rejected)); + } else if (produce_cached_data) { + const ScriptCompiler::CachedData* cached_data = + ScriptCompiler::CreateCodeCache(v8_script.ToLocalChecked(), code); + bool cached_data_produced = cached_data != nullptr; + if (cached_data_produced) { + MaybeLocal buf = Buffer::Copy( + env, + reinterpret_cast(cached_data->data), + cached_data->length); + that->Set(env->cached_data_string(), buf.ToLocalChecked()); + } + that->Set( + env->cached_data_produced_string(), + Boolean::New(isolate, cached_data_produced)); + } - static void RunInThisContext(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + that->Set(env->filename_string(), filename); - CHECK_EQ(args.Length(), 3); + env->id_to_script_wrap_map[contextify_script->GetID()] = contextify_script; +} - CHECK(args[0]->IsNumber()); - int64_t timeout = args[0]->IntegerValue(env->context()).FromJust(); - CHECK(args[1]->IsBoolean()); - bool display_errors = args[1]->IsTrue(); +bool ContextifyScript::InstanceOf(Environment* env, const Local& value) { + return !value.IsEmpty() && + env->script_context_constructor_template()->HasInstance(value); +} - CHECK(args[2]->IsBoolean()); - bool break_on_sigint = args[2]->IsTrue(); - // Do the eval within this context - EvalMachine(env, timeout, display_errors, break_on_sigint, args); - } +// args: [options] +void ContextifyScript::RunInThisContext( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); - static void RunInContext(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + CHECK_EQ(args.Length(), 3); - CHECK_EQ(args.Length(), 4); + CHECK(args[0]->IsNumber()); + int64_t timeout = args[0]->IntegerValue(env->context()).FromJust(); - CHECK(args[0]->IsObject()); - Local sandbox = args[0].As(); - // Get the context from the sandbox - ContextifyContext* contextify_context = - ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); - CHECK_NE(contextify_context, nullptr); + CHECK(args[1]->IsBoolean()); + bool display_errors = args[1]->IsTrue(); - if (contextify_context->context().IsEmpty()) - return; + bool break_on_sigint = args[2]->IsTrue(); - CHECK(args[1]->IsNumber()); - int64_t timeout = args[1]->IntegerValue(env->context()).FromJust(); + // Do the eval within this context + EvalMachine(env, timeout, display_errors, break_on_sigint, args); +} - CHECK(args[2]->IsBoolean()); - bool display_errors = args[2]->IsTrue(); +// args: sandbox, [options] +void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); - CHECK(args[3]->IsBoolean()); - bool break_on_sigint = args[3]->IsTrue(); + CHECK_EQ(args.Length(), 4); - // Do the eval within the context - Context::Scope context_scope(contextify_context->context()); - EvalMachine(contextify_context->env(), - timeout, - display_errors, - break_on_sigint, - args); - } + CHECK(args[0]->IsObject()); + Local sandbox = args[0].As(); - static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) { - Local exception = try_catch.Exception(); + // Get the context from the sandbox + ContextifyContext* contextify_context = + ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); + CHECK_NE(contextify_context, nullptr); - if (!exception->IsObject()) - return; + if (contextify_context->context().IsEmpty()) + return; - Local err_obj = exception.As(); - if (IsExceptionDecorated(env, err_obj)) - return; + CHECK(args[1]->IsNumber()); + int64_t timeout = args[1]->IntegerValue(env->context()).FromJust(); - AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); - Local stack = err_obj->Get(env->stack_string()); - MaybeLocal maybe_value = - err_obj->GetPrivate( - env->context(), - env->arrow_message_private_symbol()); + CHECK(args[2]->IsBoolean()); + bool display_errors = args[2]->IsTrue(); - Local arrow; - if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { - return; - } + CHECK(args[3]->IsBoolean()); + bool break_on_sigint = args[3]->IsTrue(); + + // Do the eval within the context + Context::Scope context_scope(contextify_context->context()); + EvalMachine(contextify_context->env(), + timeout, + display_errors, + break_on_sigint, + args); +} - if (stack.IsEmpty() || !stack->IsString()) { - return; - } +void ContextifyScript::DecorateErrorStack( + Environment* env, const TryCatch& try_catch) { + Local exception = try_catch.Exception(); - Local decorated_stack = String::Concat( - String::Concat(arrow.As(), - FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), - stack.As()); - err_obj->Set(env->stack_string(), decorated_stack); - err_obj->SetPrivate( - env->context(), - env->decorated_private_symbol(), - True(env->isolate())); - } + if (!exception->IsObject()) + return; - static bool EvalMachine(Environment* env, - const int64_t timeout, - const bool display_errors, - const bool break_on_sigint, - const FunctionCallbackInfo& args) { - if (!ContextifyScript::InstanceOf(env, args.Holder())) { - env->ThrowTypeError( - "Script methods can only be called on script instances."); - return false; - } - TryCatch try_catch(env->isolate()); - ContextifyScript* wrapped_script; - ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder(), false); - Local unbound_script = - PersistentToLocal(env->isolate(), wrapped_script->script_); - Local