summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGus Caplan <me@gus.host>2018-08-17 17:26:34 -0500
committerGus Caplan <me@gus.host>2018-10-06 17:33:25 -0500
commit4c37df779cf944b5666fc72e2a27fbf2e745881f (patch)
tree80d135f6cbd7cd9545bee950c280659985c276b8
parent124a8e21238f8452028614625fe491b3049f7244 (diff)
downloadandroid-node-v8-4c37df779cf944b5666fc72e2a27fbf2e745881f.tar.gz
android-node-v8-4c37df779cf944b5666fc72e2a27fbf2e745881f.tar.bz2
android-node-v8-4c37df779cf944b5666fc72e2a27fbf2e745881f.zip
vm: add dynamic import support
PR-URL: https://github.com/nodejs/node/pull/22381 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
-rw-r--r--doc/api/errors.md5
-rw-r--r--doc/api/vm.md22
-rw-r--r--lib/internal/bootstrap/loaders.js2
-rw-r--r--lib/internal/errors.js2
-rw-r--r--lib/internal/modules/cjs/loader.js15
-rw-r--r--lib/internal/modules/esm/translators.js22
-rw-r--r--lib/internal/process/esm_loader.js49
-rw-r--r--lib/internal/vm/source_text_module.js47
-rw-r--r--lib/vm.js34
-rw-r--r--src/env-inl.h7
-rw-r--r--src/env.h15
-rw-r--r--src/module_wrap.cc113
-rw-r--r--src/module_wrap.h16
-rw-r--r--src/node_contextify.cc618
-rw-r--r--src/node_contextify.h32
-rw-r--r--test/es-module/test-esm-dynamic-import.js35
-rw-r--r--test/parallel/test-bootstrap-modules.js2
-rw-r--r--test/parallel/test-vm-module-dynamic-import.js71
18 files changed, 674 insertions, 433 deletions
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 75146c24c3..7cdee52e79 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1779,6 +1779,11 @@ The V8 `BreakIterator` API was used but the full ICU data set is not installed.
While using the Performance Timing API (`perf_hooks`), no valid performance
entry types were found.
+<a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING"></a>
+### ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
+
+A dynamic import callback was not specified.
+
<a id="ERR_VM_MODULE_ALREADY_LINKED"></a>
### ERR_VM_MODULE_ALREADY_LINKED
diff --git a/doc/api/vm.md b/doc/api/vm.md
index 6259b9b2ff..07923de585 100644
--- a/doc/api/vm.md
+++ b/doc/api/vm.md
@@ -167,10 +167,19 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });
in stack traces produced by this `Module`.
* `columnOffset` {integer} Specifies the column number offset that is
displayed in stack traces produced by this `Module`.
- * `initalizeImportMeta` {Function} Called during evaluation of this `Module`
+ * `initializeImportMeta` {Function} Called during evaluation of this `Module`
to initialize the `import.meta`. This function has the signature `(meta,
module)`, where `meta` is the `import.meta` object in the `Module`, and
`module` is this `vm.SourceTextModule` object.
+ * `importModuleDynamically` {Function} Called during evaluation of this
+ module when `import()` is called. This function has the signature
+ `(specifier, module)` where `specifier` is the specifier passed to
+ `import()` and `module` is this `vm.SourceTextModule`. If this option is
+ not specified, calls to `import()` will reject with
+ [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. This method can return a
+ [Module Namespace Object][], but returning a `vm.SourceTextModule` is
+ recommended in order to take advantage of error tracking, and to avoid
+ issues with namespaces that contain `then` function exports.
Creates a new ES `Module` object.
@@ -436,6 +445,15 @@ changes:
The `cachedDataProduced` value will be set to either `true` or `false`
depending on whether code cache data is produced successfully.
This option is deprecated in favor of `script.createCachedData()`.
+ * `importModuleDynamically` {Function} Called during evaluation of this
+ module when `import()` is called. This function has the signature
+ `(specifier, module)` where `specifier` is the specifier passed to
+ `import()` and `module` is this `vm.SourceTextModule`. If this option is
+ not specified, calls to `import()` will reject with
+ [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. This method can return a
+ [Module Namespace Object][], but returning a `vm.SourceTextModule` is
+ recommended in order to take advantage of error tracking, and to avoid
+ issues with namespaces that contain `then` function exports.
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
@@ -945,6 +963,7 @@ associating it with the `sandbox` object is what this document refers to as
"contextifying" the `sandbox`.
[`Error`]: errors.html#errors_class_error
+[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
[`URL`]: url.html#url_class_url
[`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
[`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options
@@ -954,6 +973,7 @@ associating it with the `sandbox` object is what this document refers to as
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
[GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace
+[Module Namespace Object]: https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects
[ECMAScript Module Loader]: esm.html#esm_ecmascript_modules
[Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation
[HostResolveImportedModule]: https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js
index 8234275700..359812e1e9 100644
--- a/lib/internal/bootstrap/loaders.js
+++ b/lib/internal/bootstrap/loaders.js
@@ -107,6 +107,8 @@
};
}
+ // Create this WeakMap in js-land because V8 has no C++ API for WeakMap
+ internalBinding('module_wrap').callbackMap = new WeakMap();
const { ContextifyScript } = internalBinding('contextify');
// Set up NativeModule
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 43124cc66b..4094a40f6b 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -873,6 +873,8 @@ E('ERR_V8BREAKITERATOR',
// This should probably be a `TypeError`.
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
'At least one valid performance entry type is required', Error);
+E('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING',
+ 'A dynamic import callback was not specified.', TypeError);
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked', Error);
E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
'Linked modules must use the same context', Error);
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index f3f8b0c8e0..6cdafac3de 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -29,6 +29,7 @@ const assert = require('assert').ok;
const fs = require('fs');
const internalFS = require('internal/fs/utils');
const path = require('path');
+const { URL } = require('url');
const {
internalModuleReadJSON,
internalModuleStat
@@ -656,6 +657,13 @@ Module.prototype.require = function(id) {
// (needed for setting breakpoint when called with --inspect-brk)
var resolvedArgv;
+function normalizeReferrerURL(referrer) {
+ if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
+ return pathToFileURL(referrer).href;
+ }
+ return new URL(referrer).href;
+}
+
// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
@@ -671,7 +679,12 @@ Module.prototype._compile = function(content, filename) {
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
- displayErrors: true
+ displayErrors: true,
+ importModuleDynamically: experimentalModules ? async (specifier) => {
+ if (asyncESM === undefined) lazyLoadESM();
+ const loader = await asyncESM.loaderPromise;
+ return loader.import(specifier, normalizeReferrerURL(filename));
+ } : undefined,
});
var inspectorWrapper = null;
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index df3c446cab..0c34283b8a 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -1,7 +1,7 @@
'use strict';
const { NativeModule } = require('internal/bootstrap/loaders');
-const { ModuleWrap } = internalBinding('module_wrap');
+const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const {
stripShebang,
stripBOM
@@ -15,6 +15,8 @@ const { _makeLong } = require('path');
const { SafeMap } = require('internal/safe_globals');
const { URL } = require('url');
const { debuglog, promisify } = require('util');
+const esmLoader = require('internal/process/esm_loader');
+
const readFileAsync = promisify(fs.readFile);
const readFileSync = fs.readFileSync;
const StringReplace = Function.call.bind(String.prototype.replace);
@@ -25,13 +27,27 @@ const debug = debuglog('esm');
const translators = new SafeMap();
module.exports = translators;
+function initializeImportMeta(meta, { url }) {
+ meta.url = url;
+}
+
+async function importModuleDynamically(specifier, { url }) {
+ const loader = await esmLoader.loaderPromise;
+ return loader.import(specifier, url);
+}
+
// Strategy for loading a standard JavaScript module
translators.set('esm', async (url) => {
const source = `${await readFileAsync(new URL(url))}`;
debug(`Translating StandardModule ${url}`);
+ const module = new ModuleWrap(stripShebang(source), url);
+ callbackMap.set(module, {
+ initializeImportMeta,
+ importModuleDynamically,
+ });
return {
- module: new ModuleWrap(stripShebang(source), url),
- reflect: undefined
+ module,
+ reflect: undefined,
};
});
diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js
index 23b98c620e..b2415ec171 100644
--- a/lib/internal/process/esm_loader.js
+++ b/lib/internal/process/esm_loader.js
@@ -2,40 +2,42 @@
const {
setImportModuleDynamicallyCallback,
- setInitializeImportMetaObjectCallback
+ setInitializeImportMetaObjectCallback,
+ callbackMap,
} = internalBinding('module_wrap');
const { pathToFileURL } = require('internal/url');
const Loader = require('internal/modules/esm/loader');
-const path = require('path');
-const { URL } = require('url');
const {
- initImportMetaMap,
- wrapToModuleMap
+ wrapToModuleMap,
} = require('internal/vm/source_text_module');
+const {
+ ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
+} = require('internal/errors').codes;
-function normalizeReferrerURL(referrer) {
- if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
- return pathToFileURL(referrer).href;
+function initializeImportMetaObject(wrap, meta) {
+ if (callbackMap.has(wrap)) {
+ const { initializeImportMeta } = callbackMap.get(wrap);
+ if (initializeImportMeta !== undefined) {
+ initializeImportMeta(meta, wrapToModuleMap.get(wrap) || wrap);
+ }
}
- 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.SourceTextModule,
- // initializer callback was provided.
- initializeImportMeta(meta, vmModule);
+async function importModuleDynamicallyCallback(wrap, specifier) {
+ if (callbackMap.has(wrap)) {
+ const { importModuleDynamically } = callbackMap.get(wrap);
+ if (importModuleDynamically !== undefined) {
+ return importModuleDynamically(
+ specifier, wrapToModuleMap.get(wrap) || wrap);
}
}
+ throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
}
+setInitializeImportMetaObjectCallback(initializeImportMetaObject);
+setImportModuleDynamicallyCallback(importModuleDynamicallyCallback);
+
let loaderResolve;
exports.loaderPromise = new Promise((resolve, reject) => {
loaderResolve = resolve;
@@ -44,8 +46,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;
@@ -60,10 +60,5 @@ exports.setup = function() {
})();
loaderResolve(loaderPromise);
- setImportModuleDynamicallyCallback(async (referrer, specifier) => {
- const loader = await loaderPromise;
- return loader.import(specifier, normalizeReferrerURL(referrer));
- });
-
exports.ESMLoader = ESMLoader;
};
diff --git a/lib/internal/vm/source_text_module.js b/lib/internal/vm/source_text_module.js
index c1c3611d8f..d22db6e914 100644
--- a/lib/internal/vm/source_text_module.js
+++ b/lib/internal/vm/source_text_module.js
@@ -1,5 +1,6 @@
'use strict';
+const { isModuleNamespaceObject } = require('util').types;
const { URL } = require('internal/url');
const { isContext } = internalBinding('contextify');
const {
@@ -9,7 +10,7 @@ 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,
@@ -21,6 +22,7 @@ const { validateInt32, validateUint32 } = require('internal/validators');
const {
ModuleWrap,
+ callbackMap,
kUninstantiated,
kInstantiating,
kInstantiated,
@@ -43,8 +45,6 @@ const perContextModuleId = new WeakMap();
const wrapMap = new WeakMap();
const dependencyCacheMap = new WeakMap();
const linkingStatusMap = new WeakMap();
-// vm.SourceTextModule -> function
-const initImportMetaMap = new WeakMap();
// ModuleWrap -> vm.SourceTextModule
const wrapToModuleMap = new WeakMap();
const defaultModuleName = 'vm:module';
@@ -63,7 +63,8 @@ class SourceTextModule {
context,
lineOffset = 0,
columnOffset = 0,
- initializeImportMeta
+ initializeImportMeta,
+ importModuleDynamically,
} = options;
if (context !== undefined) {
@@ -96,13 +97,16 @@ class SourceTextModule {
validateInt32(lineOffset, 'options.lineOffset');
validateInt32(columnOffset, 'options.columnOffset');
- if (initializeImportMeta !== undefined) {
- if (typeof initializeImportMeta === 'function') {
- initImportMetaMap.set(this, initializeImportMeta);
- } else {
- throw new ERR_INVALID_ARG_TYPE(
- 'options.initializeImportMeta', 'function', initializeImportMeta);
- }
+ if (initializeImportMeta !== undefined &&
+ typeof initializeImportMeta !== 'function') {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'options.initializeImportMeta', 'function', initializeImportMeta);
+ }
+
+ if (importModuleDynamically !== undefined &&
+ typeof importModuleDynamically !== 'function') {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'options.importModuleDynamically', 'function', importModuleDynamically);
}
const wrap = new ModuleWrap(src, url, context, lineOffset, columnOffset);
@@ -110,6 +114,22 @@ class SourceTextModule {
linkingStatusMap.set(this, 'unlinked');
wrapToModuleMap.set(wrap, this);
+ callbackMap.set(wrap, {
+ initializeImportMeta,
+ importModuleDynamically: importModuleDynamically ? async (...args) => {
+ const m = await importModuleDynamically(...args);
+ if (isModuleNamespaceObject(m)) {
+ return m;
+ }
+ if (!m || !wrapMap.has(m))
+ throw new ERR_VM_MODULE_NOT_MODULE();
+ const childLinkingStatus = linkingStatusMap.get(m);
+ if (childLinkingStatus === 'errored')
+ throw m.error;
+ return m.namespace;
+ } : undefined,
+ });
+
Object.defineProperties(this, {
url: { value: url, enumerable: true },
context: { value: context, enumerable: true },
@@ -245,6 +265,7 @@ class SourceTextModule {
module.exports = {
SourceTextModule,
- initImportMetaMap,
- wrapToModuleMap
+ wrapToModuleMap,
+ wrapMap,
+ linkingStatusMap,
};
diff --git a/lib/vm.js b/lib/vm.js
index 373fb4029d..869b4aa654 100644
--- a/lib/vm.js
+++ b/lib/vm.js
@@ -27,9 +27,12 @@ const {
isContext: _isContext,
compileFunction: _compileFunction
} = internalBinding('contextify');
-
-const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
-const { isUint8Array } = require('internal/util/types');
+const { callbackMap } = internalBinding('module_wrap');
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_VM_MODULE_NOT_MODULE,
+} = require('internal/errors').codes;
+const { isModuleNamespaceObject, isUint8Array } = require('util').types;
const { validateInt32, validateUint32 } = require('internal/validators');
const kParsingContext = Symbol('script parsing context');
@@ -52,7 +55,8 @@ class Script extends ContextifyScript {
columnOffset = 0,
cachedData,
produceCachedData = false,
- [kParsingContext]: parsingContext
+ importModuleDynamically,
+ [kParsingContext]: parsingContext,
} = options;
if (typeof filename !== 'string') {
@@ -83,6 +87,28 @@ class Script extends ContextifyScript {
} catch (e) {
throw e; /* node-do-not-add-exception-line */
}
+
+ if (importModuleDynamically !== undefined) {
+ if (typeof importModuleDynamically !== 'function') {
+ throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically',
+ 'function',
+ importModuleDynamically);
+ }
+ const { wrapMap, linkingStatusMap } =
+ require('internal/vm/source_text_module');
+ callbackMap.set(this, { importModuleDynamically: async (...args) => {
+ const m = await importModuleDynamically(...args);
+ if (isModuleNamespaceObject(m)) {
+ return m;
+ }
+ if (!m || !wrapMap.has(m))
+ throw new ERR_VM_MODULE_NOT_MODULE();
+ const childLinkingStatus = linkingStatusMap.get(m);
+ if (childLinkingStatus === 'errored')
+ throw m.error;
+ return m.namespace;
+ } });
+ }
}
runInThisContext(options) {
diff --git a/src/env-inl.h b/src/env-inl.h
index e4a635c84d..6ace0bf825 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -446,6 +446,13 @@ Environment::trace_category_state() {
return trace_category_state_;
}
+inline uint32_t Environment::get_next_module_id() {
+ return module_id_counter_++;
+}
+inline uint32_t Environment::get_next_script_id() {
+ return script_id_counter_++;
+}
+
Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
Environment* env)
: env_(env) {
diff --git a/src/env.h b/src/env.h
index a210252643..1eb333b0ef 100644
--- a/src/env.h
+++ b/src/env.h
@@ -47,6 +47,10 @@ struct nghttp2_rcbuf;
namespace node {
+namespace contextify {
+class ContextifyScript;
+}
+
namespace fs {
class FileHandleReadWrap;
}
@@ -674,7 +678,13 @@ class Environment {
// List of id's that have been destroyed and need the destroy() cb called.
inline std::vector<double>* destroy_async_id_list();
- std::unordered_multimap<int, loader::ModuleWrap*> module_map;
+ std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;
+ std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
+ std::unordered_map<uint32_t, contextify::ContextifyScript*>
+ id_to_script_map;
+
+ inline uint32_t get_next_module_id();
+ inline uint32_t get_next_script_id();
std::unordered_map<std::string, const loader::PackageConfig>
package_json_cache;
@@ -924,6 +934,9 @@ class Environment {
std::shared_ptr<EnvironmentOptions> options_;
+ uint32_t module_id_counter_ = 0;
+ uint32_t script_id_counter_ = 0;
+
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
int should_not_abort_scope_counter_ = 0;
diff --git a/src/module_wrap.cc b/src/module_wrap.cc
index 1ef22b270d..4a7be86af8 100644
--- a/src/module_wrap.cc
+++ b/src/module_wrap.cc
@@ -33,7 +33,9 @@ using v8::Maybe;
using v8::MaybeLocal;
using v8::Module;
using v8::Nothing;
+using v8::Number;
using v8::Object;
+using v8::PrimitiveArray;
using v8::Promise;
using v8::ScriptCompiler;
using v8::ScriptOrigin;
@@ -47,18 +49,22 @@ static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"};
ModuleWrap::ModuleWrap(Environment* env,
Local<Object> object,
Local<Module> module,
- Local<String> url) : BaseObject(env, object) {
+ Local<String> url) :
+ BaseObject(env, object),
+ id_(env->get_next_module_id()) {
module_.Reset(env->isolate(), module);
url_.Reset(env->isolate(), url);
+ env->id_to_module_map.emplace(id_, this);
}
ModuleWrap::~ModuleWrap() {
HandleScope scope(env()->isolate());
Local<Module> module = module_.Get(env()->isolate());
- auto range = env()->module_map.equal_range(module->GetIdentityHash());
+ env()->id_to_module_map.erase(id_);
+ auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash());
for (auto it = range.first; it != range.second; ++it) {
if (it->second == this) {
- env()->module_map.erase(it);
+ env()->hash_to_module_map.erase(it);
break;
}
}
@@ -66,15 +72,21 @@ ModuleWrap::~ModuleWrap() {
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
Local<Module> module) {
- ModuleWrap* ret = nullptr;
- auto range = env->module_map.equal_range(module->GetIdentityHash());
+ auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash());
for (auto it = range.first; it != range.second; ++it) {
if (it->second->module_ == module) {
- ret = it->second;
- break;
+ return it->second;
}
}
- return ret;
+ return nullptr;
+}
+
+ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) {
+ auto module_wrap_it = env->id_to_module_map.find(id);
+ if (module_wrap_it == env->id_to_module_map.end()) {
+ return nullptr;
+ }
+ return module_wrap_it->second;
}
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
@@ -126,6 +138,11 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
TryCatch try_catch(isolate);
Local<Module> module;
+ Local<PrimitiveArray> host_defined_options =
+ PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
+ host_defined_options->Set(isolate, HostDefinedOptions::kType,
+ Number::New(isolate, ScriptType::kModule));
+
// compile
{
ScriptOrigin origin(url,
@@ -136,7 +153,8 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
Local<Value>(), // source map URL
False(isolate), // is opaque (?)
False(isolate), // is WASM
- True(isolate)); // is ES6 module
+ True(isolate), // is ES Module
+ host_defined_options);
Context::Scope context_scope(context);
ScriptCompiler::Source source(source_text, origin);
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
@@ -157,7 +175,10 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
obj->context_.Reset(isolate, context);
- env->module_map.emplace(module->GetIdentityHash(), obj);
+ env->hash_to_module_map.emplace(module->GetIdentityHash(), obj);
+
+ host_defined_options->Set(isolate, HostDefinedOptions::kID,
+ Number::New(isolate, obj->id()));
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
args.GetReturnValue().Set(that);
@@ -364,19 +385,14 @@ MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
Environment* env = Environment::GetCurrent(context);
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
Isolate* isolate = env->isolate();
- if (env->module_map.count(referrer->GetIdentityHash()) == 0) {
- env->ThrowError("linking error, unknown module");
- return MaybeLocal<Module>();
- }
ModuleWrap* dependent = GetFromModule(env, referrer);
-
if (dependent == nullptr) {
env->ThrowError("linking error, null dep");
return MaybeLocal<Module>();
}
- Utf8Value specifier_utf8(env->isolate(), specifier);
+ Utf8Value specifier_utf8(isolate, specifier);
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
if (dependent->resolve_cache_.count(specifier_std) != 1) {
@@ -402,7 +418,7 @@ MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
ModuleWrap* module;
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
- return module->module_.Get(env->isolate());
+ return module->module_.Get(isolate);
}
namespace {
@@ -704,35 +720,56 @@ static MaybeLocal<Promise> ImportModuleDynamically(
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
v8::EscapableHandleScope handle_scope(iso);
- if (env->context() != context) {
- auto maybe_resolver = Promise::Resolver::New(context);
- Local<Promise::Resolver> resolver;
- if (maybe_resolver.ToLocal(&resolver)) {
- // TODO(jkrems): Turn into proper error object w/ code
- Local<Value> error = v8::Exception::Error(
- OneByteString(iso, "import() called outside of main context"));
- if (resolver->Reject(context, error).IsJust()) {
- return handle_scope.Escape(resolver.As<Promise>());
- }
- }
- return MaybeLocal<Promise>();
- }
-
Local<Function> import_callback =
env->host_import_module_dynamically_callback();
+
+ Local<PrimitiveArray> options = referrer->GetHostDefinedOptions();
+ if (options->Length() != HostDefinedOptions::kLength) {
+ Local<Promise::Resolver> resolver =
+ Promise::Resolver::New(context).ToLocalChecked();
+ resolver
+ ->Reject(context,
+ v8::Exception::TypeError(FIXED_ONE_BYTE_STRING(
+ context->GetIsolate(), "Invalid host defined options")))
+ .ToChecked();
+ return handle_scope.Escape(resolver->GetPromise());
+ }
+
+ Local<Value> object;
+
+ int type = options->Get(iso, HostDefinedOptions::kType)
+ .As<Number>()
+ ->Int32Value(context)
+ .ToChecked();
+ uint32_t id = options->Get(iso, HostDefinedOptions::kID)
+ .As<Number>()
+ ->Uint32Value(context)
+ .ToChecked();
+ if (type == ScriptType::kScript) {
+ contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second;
+ object = wrap->object();
+ } else if (type == ScriptType::kModule) {
+ ModuleWrap* wrap = ModuleWrap::GetFromID(env, id);
+ object = wrap->object();
+ } else {
+ UNREACHABLE();
+ }
+
Local<Value> import_args[] = {
- referrer->GetResourceName(),
- Local<Value>(specifier)
+ object,
+ Local<Value>(specifier),
};
- MaybeLocal<Value> maybe_result = import_callback->Call(context,
- v8::Undefined(iso),
- 2,
- import_args);
Local<Value> result;
- if (maybe_result.ToLocal(&result)) {
+ if (import_callback->Call(
+ context,
+ v8::Undefined(iso),
+ arraysize(import_args),
+ import_args).ToLocal(&result)) {
+ CHECK(result->IsPromise());
return handle_scope.Escape(result.As<Promise>());
}
+
return MaybeLocal<Promise>();
}
diff --git a/src/module_wrap.h b/src/module_wrap.h
index d6593c4813..0e352c6575 100644
--- a/src/module_wrap.h
+++ b/src/module_wrap.h
@@ -17,6 +17,17 @@ enum PackageMainCheck : bool {
IgnoreMain = false
};
+enum ScriptType : int {
+ kScript,
+ kModule,
+};
+
+enum HostDefinedOptions : int {
+ kType = 8,
+ kID = 9,
+ kLength = 10,
+};
+
v8::Maybe<url::URL> Resolve(Environment* env,
const std::string& specifier,
const url::URL& base,
@@ -38,6 +49,9 @@ class ModuleWrap : public BaseObject {
tracker->TrackField("resolve_cache", resolve_cache_);
}
+ inline uint32_t id() { return id_; }
+ static ModuleWrap* GetFromID(node::Environment*, uint32_t id);
+
SET_MEMORY_INFO_NAME(ModuleWrap)
SET_SELF_SIZE(ModuleWrap)
@@ -69,12 +83,12 @@ class ModuleWrap : public BaseObject {
v8::Local<v8::Module> referrer);
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
-
Persistent<v8::Module> module_;
Persistent<v8::String> url_;
bool linked_ = false;
std::unordered_map<std::string, Persistent<v8::Promise>> resolve_cache_;
Persistent<v8::Context> context_;
+ uint32_t id_;
};
} // namespace loader
diff --git a/src/node_contextify.cc b/src/node_contextify.cc
index 4239f07f06..023a659ebb 100644
--- a/src/node_contextify.cc
+++ b/src/node_contextify.cc
@@ -26,6 +26,7 @@
#include "node_contextify.h"
#include "node_context_data.h"
#include "node_errors.h"
+#include "module_wrap.h"
namespace node {
namespace contextify {
@@ -49,8 +50,10 @@ using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::NamedPropertyHandlerConfiguration;
+using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
+using v8::PrimitiveArray;
using v8::PropertyAttribute;
using v8::PropertyCallbackInfo;
using v8::PropertyDescriptor;
@@ -586,368 +589,381 @@ void ContextifyContext::IndexedPropertyDeleterCallback(
args.GetReturnValue().Set(false);
}
-class ContextifyScript : public BaseObject {
- private:
- Persistent<UnboundScript> script_;
-
- public:
- SET_NO_MEMORY_INFO()
- SET_MEMORY_INFO_NAME(ContextifyScript)
- SET_SELF_SIZE(ContextifyScript)
-
- static void Init(Environment* env, Local<Object> target) {
- HandleScope scope(env->isolate());
- Local<String> class_name =
- FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
-
- Local<FunctionTemplate> script_tmpl = env->NewFunctionTemplate(New);
- script_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
- script_tmpl->SetClassName(class_name);
- env->SetProtoMethod(script_tmpl, "createCachedData", CreateCachedData);
- env->SetProtoMethod(script_tmpl, "runInContext", RunInContext);
- env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);
-
- target->Set(class_name,
- script_tmpl->GetFunction(env->context()).ToLocalChecked());
- env->set_script_context_constructor_template(script_tmpl);
- }
-
-
- static void New(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- Isolate* isolate = env->isolate();
- Local<Context> context = env->context();
-
- CHECK(args.IsConstructCall());
-
- const int argc = args.Length();
- CHECK_GE(argc, 2);
-
- CHECK(args[0]->IsString());
- Local<String> code = args[0].As<String>();
-
- CHECK(args[1]->IsString());
- Local<String> filename = args[1].As<String>();
-
- Local<Integer> line_offset;
- Local<Integer> column_offset;
- Local<Uint8Array> cached_data_buf;
- bool produce_cached_data = false;
- Local<Context> 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<Integer>();
- CHECK(args[3]->IsNumber());
- column_offset = args[3].As<Integer>();
- if (!args[4]->IsUndefined()) {
- CHECK(args[4]->IsUint8Array());
- cached_data_buf = args[4].As<Uint8Array>();
- }
- 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<Object>());
- CHECK_NOT_NULL(sandbox);
- 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());
-
- if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
- TRACING_CATEGORY_NODE2(vm, script)) != 0) {
- Utf8Value fn(isolate, filename);
- TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
- TRACING_CATEGORY_NODE2(vm, script),
- "ContextifyScript::New",
- contextify_script,
- "filename", TRACE_STR_COPY(*fn));
- }
+void ContextifyScript::Init(Environment* env, Local<Object> target) {
+ HandleScope scope(env->isolate());
+ Local<String> class_name =
+ FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
+
+ Local<FunctionTemplate> script_tmpl = env->NewFunctionTemplate(New);
+ script_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
+ script_tmpl->SetClassName(class_name);
+ env->SetProtoMethod(script_tmpl, "createCachedData", CreateCachedData);
+ env->SetProtoMethod(script_tmpl, "runInContext", RunInContext);
+ env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);
+
+ target->Set(class_name,
+ script_tmpl->GetFunction(env->context()).ToLocalChecked());
+ env->set_script_context_constructor_template(script_tmpl);
+}
- ScriptCompiler::CachedData* cached_data = nullptr;
- if (!cached_data_buf.IsEmpty()) {
- ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents();
- uint8_t* data = static_cast<uint8_t*>(contents.Data());
- cached_data = new ScriptCompiler::CachedData(
- data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
- }
+void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Isolate* isolate = env->isolate();
+ Local<Context> context = env->context();
- ScriptOrigin origin(filename, line_offset, column_offset);
- ScriptCompiler::Source source(code, origin, cached_data);
- ScriptCompiler::CompileOptions compile_options =
- ScriptCompiler::kNoCompileOptions;
+ CHECK(args.IsConstructCall());
- if (source.GetCachedData() != nullptr)
- compile_options = ScriptCompiler::kConsumeCodeCache;
+ const int argc = args.Length();
+ CHECK_GE(argc, 2);
- TryCatch try_catch(isolate);
- Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
- Context::Scope scope(parsing_context);
+ CHECK(args[0]->IsString());
+ Local<String> code = args[0].As<String>();
- MaybeLocal<UnboundScript> v8_script = ScriptCompiler::CompileUnboundScript(
- isolate,
- &source,
- compile_options);
+ CHECK(args[1]->IsString());
+ Local<String> filename = args[1].As<String>();
- if (v8_script.IsEmpty()) {
- DecorateErrorStack(env, try_catch);
- no_abort_scope.Close();
- try_catch.ReThrow();
- TRACE_EVENT_NESTABLE_ASYNC_END0(
- TRACING_CATEGORY_NODE2(vm, script),
- "ContextifyScript::New",
- contextify_script);
- return;
+ Local<Integer> line_offset;
+ Local<Integer> column_offset;
+ Local<Uint8Array> cached_data_buf;
+ bool produce_cached_data = false;
+ Local<Context> 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<Integer>();
+ CHECK(args[3]->IsNumber());
+ column_offset = args[3].As<Integer>();
+ if (!args[4]->IsUndefined()) {
+ CHECK(args[4]->IsUint8Array());
+ cached_data_buf = args[4].As<Uint8Array>();
}
- 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());
- bool cached_data_produced = cached_data != nullptr;
- if (cached_data_produced) {
- MaybeLocal<Object> buf = Buffer::Copy(
- env,
- reinterpret_cast<const char*>(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<Object>());
+ CHECK_NOT_NULL(sandbox);
+ parsing_context = sandbox->context();
}
- TRACE_EVENT_NESTABLE_ASYNC_END0(
+ } else {
+ line_offset = Integer::New(isolate, 0);
+ column_offset = Integer::New(isolate, 0);
+ }
+
+ ContextifyScript* contextify_script =
+ new ContextifyScript(env, args.This());
+
+ if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
+ TRACING_CATEGORY_NODE2(vm, script)) != 0) {
+ Utf8Value fn(isolate, filename);
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
TRACING_CATEGORY_NODE2(vm, script),
"ContextifyScript::New",
- contextify_script);
+ contextify_script,
+ "filename", TRACE_STR_COPY(*fn));
}
-
- static bool InstanceOf(Environment* env, const Local<Value>& 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<uint8_t*>(contents.Data());
+ cached_data = new ScriptCompiler::CachedData(
+ data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
}
+ Local<PrimitiveArray> host_defined_options =
+ PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
+ host_defined_options->Set(isolate, loader::HostDefinedOptions::kType,
+ Number::New(isolate, loader::ScriptType::kScript));
+ host_defined_options->Set(isolate, loader::HostDefinedOptions::kID,
+ Number::New(isolate, contextify_script->id()));
+
+ ScriptOrigin origin(filename,
+ line_offset, // line offset
+ column_offset, // column offset
+ False(isolate), // is cross origin
+ Local<Integer>(), // script id
+ Local<Value>(), // source map URL
+ False(isolate), // is opaque (?)
+ False(isolate), // is WASM
+ False(isolate), // is ES Module
+ host_defined_options);
+ ScriptCompiler::Source source(code, origin, cached_data);
+ ScriptCompiler::CompileOptions compile_options =
+ ScriptCompiler::kNoCompileOptions;
- static void CreateCachedData(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- ContextifyScript* wrapped_script;
- ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
- Local<UnboundScript> unbound_script =
- PersistentToLocal(env->isolate(), wrapped_script->script_);
- std::unique_ptr<ScriptCompiler::CachedData> cached_data(
- ScriptCompiler::CreateCodeCache(unbound_script));
- if (!cached_data) {
- args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked());
- } else {
+ if (source.GetCachedData() != nullptr)
+ compile_options = ScriptCompiler::kConsumeCodeCache;
+
+ TryCatch try_catch(isolate);
+ Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
+ Context::Scope scope(parsing_context);
+
+ MaybeLocal<UnboundScript> v8_script = ScriptCompiler::CompileUnboundScript(
+ isolate,
+ &source,
+ compile_options);
+
+ if (v8_script.IsEmpty()) {
+ DecorateErrorStack(env, try_catch);
+ no_abort_scope.Close();
+ try_catch.ReThrow();
+ TRACE_EVENT_NESTABLE_ASYNC_END0(
+ TRACING_CATEGORY_NODE2(vm, script),
+ "ContextifyScript::New",
+ contextify_script);
+ return;
+ }
+ 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());
+ bool cached_data_produced = cached_data != nullptr;
+ if (cached_data_produced) {
MaybeLocal<Object> buf = Buffer::Copy(
env,
reinterpret_cast<const char*>(cached_data->data),
cached_data->length);
- args.GetReturnValue().Set(buf.ToLocalChecked());
+ args.This()->Set(env->cached_data_string(), buf.ToLocalChecked());
}
+ args.This()->Set(
+ env->cached_data_produced_string(),
+ Boolean::New(isolate, cached_data_produced));
}
+ TRACE_EVENT_NESTABLE_ASYNC_END0(
+ TRACING_CATEGORY_NODE2(vm, script),
+ "ContextifyScript::New",
+ contextify_script);
+}
+bool ContextifyScript::InstanceOf(Environment* env,
+ const Local<Value>& value) {
+ return !value.IsEmpty() &&
+ env->script_context_constructor_template()->HasInstance(value);
+}
- static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
+void ContextifyScript::CreateCachedData(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ ContextifyScript* wrapped_script;
+ ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
+ Local<UnboundScript> unbound_script =
+ PersistentToLocal(env->isolate(), wrapped_script->script_);
+ std::unique_ptr<ScriptCompiler::CachedData> cached_data(
+ ScriptCompiler::CreateCodeCache(unbound_script));
+ if (!cached_data) {
+ args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked());
+ } else {
+ MaybeLocal<Object> buf = Buffer::Copy(
+ env,
+ reinterpret_cast<const char*>(cached_data->data),
+ cached_data->length);
+ args.GetReturnValue().Set(buf.ToLocalChecked());
+ }
+}
- ContextifyScript* wrapped_script;
- ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
+void ContextifyScript::RunInThisContext(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
- TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
- TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script);
+ ContextifyScript* wrapped_script;
+ ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
- CHECK_EQ(args.Length(), 3);
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
+ TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script);
- CHECK(args[0]->IsNumber());
- int64_t timeout = args[0]->IntegerValue(env->context()).FromJust();
+ CHECK_EQ(args.Length(), 3);
- CHECK(args[1]->IsBoolean());
- bool display_errors = args[1]->IsTrue();
+ CHECK(args[0]->IsNumber());
+ int64_t timeout = args[0]->IntegerValue(env->context()).FromJust();
- CHECK(args[2]->IsBoolean());
- bool break_on_sigint = args[2]->IsTrue();
+ CHECK(args[1]->IsBoolean());
+ bool display_errors = args[1]->IsTrue();
- // Do the eval within this context
- EvalMachine(env, timeout, display_errors, break_on_sigint, args);
+ CHECK(args[2]->IsBoolean());
+ bool break_on_sigint = args[2]->IsTrue();
- TRACE_EVENT_NESTABLE_ASYNC_END0(
- TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script);
- }
+ // Do the eval within this context
+ EvalMachine(env, timeout, display_errors, break_on_sigint, args);
- static void RunInContext(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
+ TRACE_EVENT_NESTABLE_ASYNC_END0(
+ TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script);
+}
- ContextifyScript* wrapped_script;
- ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
+void ContextifyScript::RunInContext(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
- CHECK_EQ(args.Length(), 4);
+ ContextifyScript* wrapped_script;
+ ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
- CHECK(args[0]->IsObject());
- Local<Object> sandbox = args[0].As<Object>();
- // Get the context from the sandbox
- ContextifyContext* contextify_context =
- ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
- CHECK_NOT_NULL(contextify_context);
+ CHECK_EQ(args.Length(), 4);
- if (contextify_context->context().IsEmpty())
- return;
+ CHECK(args[0]->IsObject());
+ Local<Object> sandbox = args[0].As<Object>();
+ // Get the context from the sandbox
+ ContextifyContext* contextify_context =
+ ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
+ CHECK_NOT_NULL(contextify_context);
- TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
- TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
+ if (contextify_context->context().IsEmpty())
+ return;
- CHECK(args[1]->IsNumber());
- int64_t timeout = args[1]->IntegerValue(env->context()).FromJust();
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
+ TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
- CHECK(args[2]->IsBoolean());
- bool display_errors = args[2]->IsTrue();
+ CHECK(args[1]->IsNumber());
+ int64_t timeout = args[1]->IntegerValue(env->context()).FromJust();
- CHECK(args[3]->IsBoolean());
- bool break_on_sigint = args[3]->IsTrue();
+ CHECK(args[2]->IsBoolean());
+ bool display_errors = args[2]->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);
+ 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);
+
+ TRACE_EVENT_NESTABLE_ASYNC_END0(
+ TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
+}
- TRACE_EVENT_NESTABLE_ASYNC_END0(
- TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
- }
+void ContextifyScript::DecorateErrorStack(
+ Environment* env, const TryCatch& try_catch) {
+ Local<Value> exception = try_catch.Exception();
- static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) {
- Local<Value> exception = try_catch.Exception();
+ if (!exception->IsObject())
+ return;
- if (!exception->IsObject())
- return;
+ Local<Object> err_obj = exception.As<Object>();
- Local<Object> err_obj = exception.As<Object>();
+ if (IsExceptionDecorated(env, err_obj))
+ return;
- if (IsExceptionDecorated(env, err_obj))
- return;
+ AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR);
+ Local<Value> stack = err_obj->Get(env->stack_string());
+ MaybeLocal<Value> maybe_value =
+ err_obj->GetPrivate(
+ env->context(),
+ env->arrow_message_private_symbol());
- AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR);
- Local<Value> stack = err_obj->Get(env->stack_string());
- MaybeLocal<Value> maybe_value =
- err_obj->GetPrivate(
- env->context(),
- env->arrow_message_private_symbol());
+ Local<Value> arrow;
+ if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) {
+ return;
+ }
- Local<Value> arrow;
- if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) {
- return;
- }
+ if (stack.IsEmpty() || !stack->IsString()) {
+ return;
+ }
- if (stack.IsEmpty() || !stack->IsString()) {
- return;
- }
+ Local<String> decorated_stack = String::Concat(
+ env->isolate(),
+ String::Concat(env->isolate(),
+ arrow.As<String>(),
+ FIXED_ONE_BYTE_STRING(env->isolate(), "\n")),
+ stack.As<String>());
+ err_obj->Set(env->stack_string(), decorated_stack);
+ err_obj->SetPrivate(
+ env->context(),
+ env->decorated_private_symbol(),
+ True(env->isolate()));
+}
- Local<String> decorated_stack = String::Concat(
- env->isolate(),
- String::Concat(env->isolate(),
- arrow.As<String>(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "\n")),
- stack.As<String>());
- err_obj->Set(env->stack_string(), decorated_stack);
- err_obj->SetPrivate(
- env->context(),
- env->decorated_private_symbol(),
- True(env->isolate()));
+bool ContextifyScript::EvalMachine(Environment* env,
+ const int64_t timeout,
+ const bool display_errors,
+ const bool break_on_sigint,
+ const FunctionCallbackInfo<Value>& args) {
+ if (!env->can_call_into_js())
+ return false;
+ 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<UnboundScript> unbound_script =
+ PersistentToLocal(env->isolate(), wrapped_script->script_);
+ Local<Script> script = unbound_script->BindToCurrentContext();
+
+ MaybeLocal<Value> result;
+ bool timed_out = false;
+ bool received_signal = false;
+ if (break_on_sigint && timeout != -1) {
+ Watchdog wd(env->isolate(), timeout, &timed_out);
+ SigintWatchdog swd(env->isolate(), &received_signal);
+ result = script->Run(env->context());
+ } else if (break_on_sigint) {
+ SigintWatchdog swd(env->isolate(), &received_signal);
+ result = script->Run(env->context());
+ } else if (timeout != -1) {
+ Watchdog wd(env->isolate(), timeout, &timed_out);
+ result = script->Run(env->context());
+ } else {
+ result = script->Run(env->context());
}
- static bool EvalMachine(Environment* env,
- const int64_t timeout,
- const bool display_errors,
- const bool break_on_sigint,
- const FunctionCallbackInfo<Value>& args) {
- if (!env->can_call_into_js())
- return false;
- 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<UnboundScript> unbound_script =
- PersistentToLocal(env->isolate(), wrapped_script->script_);
- Local<Script> script = unbound_script->BindToCurrentContext();
-
- MaybeLocal<Value> result;
- bool timed_out = false;
- bool received_signal = false;
- if (break_on_sigint && timeout != -1) {
- Watchdog wd(env->isolate(), timeout, &timed_out);
- SigintWatchdog swd(env->isolate(), &received_signal);
- result = script->Run(env->context());
- } else if (break_on_sigint) {
- SigintWatchdog swd(env->isolate(), &received_signal);
- result = script->Run(env->context());
- } else if (timeout != -1) {
- Watchdog wd(env->isolate(), timeout, &timed_out);
- result = script->Run(env->context());
- } else {
- result = script->Run(env->context());
+ // Convert the termination exception into a regular exception.
+ if (timed_out || received_signal) {
+ env->isolate()->CancelTerminateExecution();
+ // It is possible that execution was terminated by another timeout in
+ // which this timeout is nested, so check whether one of the watchdogs
+ // from this invocation is responsible for termination.
+ if (timed_out) {
+ node::THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, timeout);
+ } else if (received_signal) {
+ node::THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(env);
}
+ }
- // Convert the termination exception into a regular exception.
- if (timed_out || received_signal) {
- env->isolate()->CancelTerminateExecution();
- // It is possible that execution was terminated by another timeout in
- // which this timeout is nested, so check whether one of the watchdogs
- // from this invocation is responsible for termination.
- if (timed_out) {
- node::THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, timeout);
- } else if (received_signal) {
- node::THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(env);
- }
+ if (try_catch.HasCaught()) {
+ if (!timed_out && !received_signal && display_errors) {
+ // We should decorate non-termination exceptions
+ DecorateErrorStack(env, try_catch);
}
- if (try_catch.HasCaught()) {
- if (!timed_out && !received_signal && display_errors) {
- // We should decorate non-termination exceptions
- DecorateErrorStack(env, try_catch);
- }
+ // If there was an exception thrown during script execution, re-throw it.
+ // If one of the above checks threw, re-throw the exception instead of
+ // letting try_catch catch it.
+ // If execution has been terminated, but not by one of the watchdogs from
+ // this invocation, this will re-throw a `null` value.
+ try_catch.ReThrow();
- // If there was an exception thrown during script execution, re-throw it.
- // If one of the above checks threw, re-throw the exception instead of
- // letting try_catch catch it.
- // If execution has been terminated, but not by one of the watchdogs from
- // this invocation, this will re-throw a `null` value.
- try_catch.ReThrow();
+ return false;
+ }
- return false;
- }
+ args.GetReturnValue().Set(result.ToLocalChecked());
+ return true;
+}
- args.GetReturnValue().Set(result.ToLocalChecked());
- return true;
- }
+
+ContextifyScript::ContextifyScript(Environment* env, Local<Object> object)
+ : BaseObject(env, object),
+ id_(env->get_next_script_id()) {
+ MakeWeak();
+ env->id_to_script_map.emplace(id_, this);
+}
- ContextifyScript(Environment* env, Local<Object> object)
- : BaseObject(env, object) {
- MakeWeak();
- }
-};
+ContextifyScript::~ContextifyScript() {
+ env()->id_to_script_map.erase(id_);
+}
void ContextifyContext::CompileFunction(
diff --git a/src/node_contextify.h b/src/node_contextify.h
index d2b8387f21..6d6dc12b4b 100644
--- a/src/node_contextify.h
+++ b/src/node_contextify.h
@@ -5,6 +5,7 @@
#include "node_internals.h"
#include "node_context_data.h"
+#include "base_object-inl.h"
namespace node {
namespace contextify {
@@ -102,6 +103,37 @@ class ContextifyContext {
Persistent<v8::Context> context_;
};
+class ContextifyScript : public BaseObject {
+ public:
+ SET_NO_MEMORY_INFO()
+ SET_MEMORY_INFO_NAME(ContextifyScript)
+ SET_SELF_SIZE(ContextifyScript)
+
+ ContextifyScript(Environment* env, v8::Local<v8::Object> object);
+ ~ContextifyScript();
+
+ static void Init(Environment* env, v8::Local<v8::Object> target);
+ static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static bool InstanceOf(Environment* env, const v8::Local<v8::Value>& args);
+ static void CreateCachedData(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RunInThisContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RunInContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void DecorateErrorStack(Environment* env,
+ const v8::TryCatch& try_catch);
+ static bool EvalMachine(Environment* env,
+ const int64_t timeout,
+ const bool display_errors,
+ const bool break_on_sigint,
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ inline uint32_t id() { return id_; }
+
+ private:
+ node::Persistent<v8::UnboundScript> script_;
+ uint32_t id_;
+};
+
} // namespace contextify
} // namespace node
diff --git a/test/es-module/test-esm-dynamic-import.js b/test/es-module/test-esm-dynamic-import.js
index 6a80da4947..6cbbd0ac67 100644
--- a/test/es-module/test-esm-dynamic-import.js
+++ b/test/es-module/test-esm-dynamic-import.js
@@ -75,38 +75,3 @@ function expectFsNamespace(result) {
expectMissingModuleError(import("node:fs"));
expectMissingModuleError(import('http://example.com/foo.js'));
})();
-
-// vm.runInThisContext:
-// * Supports built-ins, always
-// * Supports imports if the script has a known defined origin
-(function testRunInThisContext() {
- // Succeeds because it's got an valid base url
- expectFsNamespace(vm.runInThisContext(`import("fs")`, {
- filename: __filename,
- }));
- expectOkNamespace(vm.runInThisContext(`import("${relativePath}")`, {
- filename: __filename,
- }));
- // Rejects because it's got an invalid referrer URL.
- // TODO(jkrems): Arguably the first two (built-in + absolute URL) could work
- // with some additional effort.
- expectInvalidReferrerError(vm.runInThisContext('import("fs")'));
- expectInvalidReferrerError(vm.runInThisContext(`import("${targetURL}")`));
- expectInvalidReferrerError(vm.runInThisContext(`import("${relativePath}")`));
-})();
-
-// vm.runInNewContext is currently completely unsupported, pending well-defined
-// semantics for per-context/realm module maps in node.
-(function testRunInNewContext() {
- // Rejects because it's running in the wrong context
- expectInvalidContextError(
- vm.runInNewContext(`import("${targetURL}")`, undefined, {
- filename: __filename,
- })
- );
-
- // Rejects because it's running in the wrong context
- expectInvalidContextError(vm.runInNewContext(`import("fs")`, undefined, {
- filename: __filename,
- }));
-})();
diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js
index e2e5075f6b..885a58ff9f 100644
--- a/test/parallel/test-bootstrap-modules.js
+++ b/test/parallel/test-bootstrap-modules.js
@@ -11,4 +11,4 @@ const list = process.moduleLoadList.slice();
const assert = require('assert');
-assert(list.length <= 76, list);
+assert(list.length <= 77, list);
diff --git a/test/parallel/test-vm-module-dynamic-import.js b/test/parallel/test-vm-module-dynamic-import.js
index 1c4b10947f..5c0f6f81fa 100644
--- a/test/parallel/test-vm-module-dynamic-import.js
+++ b/test/parallel/test-vm-module-dynamic-import.js
@@ -5,11 +5,9 @@
const common = require('../common');
const assert = require('assert');
-const { SourceTextModule, createContext } = require('vm');
+const { Script, SourceTextModule, createContext } = require('vm');
-const finished = common.mustCall();
-
-(async function() {
+async function testNoCallback() {
const m = new SourceTextModule('import("foo")', { context: createContext() });
await m.link(common.mustNotCall());
m.instantiate();
@@ -19,8 +17,67 @@ const finished = common.mustCall();
await result;
} catch (err) {
threw = true;
- assert.strictEqual(err.message, 'import() called outside of main context');
+ assert.strictEqual(err.code, 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING');
}
assert(threw);
- finished();
-}());
+}
+
+async function test() {
+ const foo = new SourceTextModule('export const a = 1;');
+ await foo.link(common.mustNotCall());
+ foo.instantiate();
+ await foo.evaluate();
+
+ {
+ const s = new Script('import("foo")', {
+ importModuleDynamically: common.mustCall((specifier, wrap) => {
+ assert.strictEqual(specifier, 'foo');
+ assert.strictEqual(wrap, s);
+ return foo;
+ }),
+ });
+
+ const result = s.runInThisContext();
+ assert.strictEqual(foo.namespace, await result);
+ }
+
+ {
+ const m = new SourceTextModule('import("foo")', {
+ importModuleDynamically: common.mustCall((specifier, wrap) => {
+ assert.strictEqual(specifier, 'foo');
+ assert.strictEqual(wrap, m);
+ return foo;
+ }),
+ });
+ await m.link(common.mustNotCall());
+ m.instantiate();
+ const { result } = await m.evaluate();
+ assert.strictEqual(foo.namespace, await result);
+ }
+}
+
+async function testInvalid() {
+ const m = new SourceTextModule('import("foo")', {
+ importModuleDynamically: common.mustCall((specifier, wrap) => {
+ return 5;
+ }),
+ });
+ await m.link(common.mustNotCall());
+ m.instantiate();
+ const { result } = await m.evaluate();
+ await result.catch(common.mustCall((e) => {
+ assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE');
+ }));
+}
+
+const done = common.mustCallAtLeast(3);
+(async function() {
+ await testNoCallback();
+ done();
+
+ await test();
+ done();
+
+ await testInvalid();
+ done();
+}()).then(common.mustCall());