diff options
-rw-r--r-- | doc/api/errors.md | 10 | ||||
-rw-r--r-- | doc/api/vm.md | 26 | ||||
-rw-r--r-- | lib/internal/errors.js | 2 | ||||
-rw-r--r-- | lib/internal/vm/module.js | 29 | ||||
-rw-r--r-- | src/module_wrap.cc | 73 | ||||
-rw-r--r-- | src/module_wrap.h | 1 | ||||
-rw-r--r-- | src/node_errors.h | 1 | ||||
-rw-r--r-- | test/parallel/test-vm-module-cached-data.js | 30 |
8 files changed, 162 insertions, 10 deletions
diff --git a/doc/api/errors.md b/doc/api/errors.md index c5041c796b..4d9651be8d 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1988,6 +1988,16 @@ the following reasons: * It is being linked (`linkingStatus` is `'linking'`) * Linking has failed for this module (`linkingStatus` is `'errored'`) +<a id="ERR_VM_MODULE_CACHED_DATA_REJECTED"></a> +### `ERR_VM_MODULE_CACHED_DATA_REJECTED` + +The `cachedData` option passed to a module constructor is invalid. + +<a id="ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA"></a> +### `ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA` + +Cached data cannot be created for modules which have already been evaluated. + <a id="ERR_VM_MODULE_DIFFERENT_CONTEXT"></a> ### `ERR_VM_MODULE_DIFFERENT_CONTEXT` diff --git a/doc/api/vm.md b/doc/api/vm.md index 9e832ac8bb..57109f143a 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -566,6 +566,10 @@ defined in the ECMAScript specification. * `identifier` {string} String used in stack traces. **Default:** `'vm:module(i)'` where `i` is a context-specific ascending index. + * `cachedData` {Buffer|TypedArray|DataView} Provides an optional `Buffer` or + `TypedArray`, or `DataView` with V8's code cache data for the supplied + source. The `code` must be the same as the module from which this + `cachedData` was created. * `context` {Object} The [contextified][] object as returned by the `vm.createContext()` method, to compile and evaluate this `Module` in. * `lineOffset` {integer} Specifies the line number offset that is displayed @@ -621,6 +625,28 @@ const contextifiedObject = vm.createContext({ secret: 42 }); })(); ``` +### `sourceTextModule.createCachedData()` +<!-- YAML +added: REPLACEME +--> + +* Returns: {Buffer} + +Creates a code cache that can be used with the SourceTextModule constructor's +`cachedData` option. Returns a Buffer. This method may be called any number +of times before the module has been evaluated. + +```js +// Create an initial module +const module = new vm.SourceTextModule('const a = 1;'); + +// Create cached data from this module +const cachedData = module.createCachedData(); + +// Create a new module using the cached data. The code must be the same. +const module2 = new vm.SourceTextModule('const a = 1;', { cachedData }); +``` + ## Class: `vm.SyntheticModule` <!-- YAML added: v13.0.0 diff --git a/lib/internal/errors.js b/lib/internal/errors.js index ed03b8d47a..15d04db313 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1351,6 +1351,8 @@ E('ERR_VALID_PERFORMANCE_ENTRY_TYPE', 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_CANNOT_CREATE_CACHED_DATA', + 'Cached data cannot be created for a module which has been evaluated', Error); E('ERR_VM_MODULE_DIFFERENT_CONTEXT', 'Linked modules must use the same context', Error); E('ERR_VM_MODULE_LINKING_ERRORED', diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index d12362184a..ed0dedd1e3 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -11,7 +11,10 @@ const { } = primordials; const { isContext } = internalBinding('contextify'); -const { isModuleNamespaceObject } = require('internal/util/types'); +const { + isModuleNamespaceObject, + isArrayBufferView, +} = require('internal/util/types'); const { getConstructorOf, customInspectSymbol, @@ -21,6 +24,7 @@ const { ERR_INVALID_ARG_TYPE, ERR_VM_MODULE_ALREADY_LINKED, ERR_VM_MODULE_DIFFERENT_CONTEXT, + ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA, ERR_VM_MODULE_LINKING_ERRORED, ERR_VM_MODULE_NOT_MODULE, ERR_VM_MODULE_STATUS, @@ -107,7 +111,8 @@ class Module { if (sourceText !== undefined) { this[kWrap] = new ModuleWrap(identifier, context, sourceText, - options.lineOffset, options.columnOffset); + options.lineOffset, options.columnOffset, + options.cachedData); binding.callbackMap.set(this[kWrap], { initializeImportMeta: options.initializeImportMeta, @@ -253,6 +258,7 @@ class SourceTextModule extends Module { importModuleDynamically, context, identifier, + cachedData, } = options; validateInt32(lineOffset, 'options.lineOffset'); @@ -271,12 +277,21 @@ class SourceTextModule extends Module { importModuleDynamically); } + if (cachedData !== undefined && !isArrayBufferView(cachedData)) { + throw new ERR_INVALID_ARG_TYPE( + 'options.cachedData', + ['Buffer', 'TypedArray', 'DataView'], + cachedData + ); + } + super({ sourceText, context, identifier, lineOffset, columnOffset, + cachedData, initializeImportMeta, importModuleDynamically, }); @@ -348,6 +363,16 @@ class SourceTextModule extends Module { } return super.error; } + + createCachedData() { + const { status } = this; + if (status === 'evaluating' || + status === 'evaluated' || + status === 'errored') { + throw new ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(); + } + return this[kWrap].createCachedData(); + } } class SyntheticModule extends Module { diff --git a/src/module_wrap.cc b/src/module_wrap.cc index ff6f0db651..f06b9d40c3 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -2,12 +2,13 @@ #include "env.h" #include "memory_tracker-inl.h" +#include "node_contextify.h" #include "node_errors.h" +#include "node_internals.h" +#include "node_process.h" #include "node_url.h" -#include "util-inl.h" -#include "node_contextify.h" #include "node_watchdog.h" -#include "node_process.h" +#include "util-inl.h" #include <sys/stat.h> // S_IFDIR @@ -22,6 +23,7 @@ using node::contextify::ContextifyContext; using node::url::URL; using node::url::URL_FLAGS_FAILED; using v8::Array; +using v8::ArrayBufferView; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; @@ -44,6 +46,7 @@ using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; +using v8::UnboundModuleScript; using v8::Undefined; using v8::Value; @@ -131,7 +134,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) { // new ModuleWrap(url, context, exportNames, syntheticExecutionFunction) CHECK(args[3]->IsFunction()); } else { - // new ModuleWrap(url, context, source, lineOffset, columOffset) + // new ModuleWrap(url, context, source, lineOffset, columOffset, cachedData) CHECK(args[2]->IsString()); CHECK(args[3]->IsNumber()); line_offset = args[3].As<Integer>(); @@ -167,6 +170,17 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) { module = Module::CreateSyntheticModule(isolate, url, export_names, SyntheticModuleEvaluationStepsCallback); } else { + ScriptCompiler::CachedData* cached_data = nullptr; + if (!args[5]->IsUndefined()) { + CHECK(args[5]->IsArrayBufferView()); + Local<ArrayBufferView> cached_data_buf = args[5].As<ArrayBufferView>(); + uint8_t* data = static_cast<uint8_t*>( + cached_data_buf->Buffer()->GetBackingStore()->Data()); + cached_data = + new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(), + cached_data_buf->ByteLength()); + } + Local<String> source_text = args[2].As<String>(); ScriptOrigin origin(url, line_offset, // line offset @@ -178,8 +192,15 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) { False(isolate), // is WASM True(isolate), // is ES Module host_defined_options); - ScriptCompiler::Source source(source_text, origin); - if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) { + ScriptCompiler::Source source(source_text, origin, cached_data); + ScriptCompiler::CompileOptions options; + if (source.GetCachedData() == nullptr) { + options = ScriptCompiler::kNoCompileOptions; + } else { + options = ScriptCompiler::kConsumeCodeCache; + } + if (!ScriptCompiler::CompileModule(isolate, &source, options) + .ToLocal(&module)) { if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); @@ -189,6 +210,13 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) { } return; } + if (options == ScriptCompiler::kConsumeCodeCache && + source.GetCachedData()->rejected) { + THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( + env, "cachedData buffer was rejected"); + try_catch.ReThrow(); + return; + } } } @@ -1507,8 +1535,7 @@ MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback( return ret; } -void ModuleWrap::SetSyntheticExport( - const v8::FunctionCallbackInfo<v8::Value>& args) { +void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Local<Object> that = args.This(); @@ -1528,6 +1555,35 @@ void ModuleWrap::SetSyntheticExport( USE(module->SetSyntheticModuleExport(isolate, export_name, export_value)); } +void ModuleWrap::CreateCachedData(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + Local<Object> that = args.This(); + + ModuleWrap* obj; + ASSIGN_OR_RETURN_UNWRAP(&obj, that); + + CHECK(!obj->synthetic_); + + Local<Module> module = obj->module_.Get(isolate); + + CHECK_LT(module->GetStatus(), v8::Module::Status::kEvaluating); + + Local<UnboundModuleScript> unbound_module_script = + module->GetUnboundModuleScript(); + std::unique_ptr<ScriptCompiler::CachedData> cached_data( + ScriptCompiler::CreateCodeCache(unbound_module_script)); + Environment* env = Environment::GetCurrent(args); + 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()); + } +} + void ModuleWrap::Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, @@ -1543,6 +1599,7 @@ void ModuleWrap::Initialize(Local<Object> target, env->SetProtoMethod(tpl, "instantiate", Instantiate); env->SetProtoMethod(tpl, "evaluate", Evaluate); env->SetProtoMethod(tpl, "setExport", SetSyntheticExport); + env->SetProtoMethodNoSideEffect(tpl, "createCachedData", CreateCachedData); env->SetProtoMethodNoSideEffect(tpl, "getNamespace", GetNamespace); env->SetProtoMethodNoSideEffect(tpl, "getStatus", GetStatus); env->SetProtoMethodNoSideEffect(tpl, "getError", GetError); diff --git a/src/module_wrap.h b/src/module_wrap.h index bee4000d16..8937431022 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -75,6 +75,7 @@ class ModuleWrap : public BaseObject { v8::Local<v8::Context> context, v8::Local<v8::Module> module); static void SetSyntheticExport( const v8::FunctionCallbackInfo<v8::Value>& args); + static void CreateCachedData(const v8::FunctionCallbackInfo<v8::Value>& args); static v8::MaybeLocal<v8::Module> ResolveCallback( v8::Local<v8::Context> context, diff --git a/src/node_errors.h b/src/node_errors.h index 74413e7456..e554d5fd92 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -59,6 +59,7 @@ void PrintErrorString(const char* format, ...); V(ERR_TLS_INVALID_PROTOCOL_METHOD, TypeError) \ V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, TypeError) \ V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, Error) \ + V(ERR_VM_MODULE_CACHED_DATA_REJECTED, Error) \ #define V(code, type) \ inline v8::Local<v8::Value> code(v8::Isolate* isolate, \ diff --git a/test/parallel/test-vm-module-cached-data.js b/test/parallel/test-vm-module-cached-data.js new file mode 100644 index 0000000000..f91d9e6268 --- /dev/null +++ b/test/parallel/test-vm-module-cached-data.js @@ -0,0 +1,30 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +require('../common'); + +const assert = require('assert'); +const { SourceTextModule } = require('vm'); + +{ + const m = new SourceTextModule('const a = 1'); + const cachedData = m.createCachedData(); + + new SourceTextModule('const a = 1', { cachedData }); + + assert.throws(() => { + new SourceTextModule('differentSource', { cachedData }); + }, { + code: 'ERR_VM_MODULE_CACHED_DATA_REJECTED', + }); +} + +assert.rejects(async () => { + const m = new SourceTextModule('const a = 1'); + await m.link(() => {}); + m.evaluate(); + m.createCachedData(); +}, { + code: 'ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA', +}); |