summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/errors.md10
-rw-r--r--doc/api/vm.md26
-rw-r--r--lib/internal/errors.js2
-rw-r--r--lib/internal/vm/module.js29
-rw-r--r--src/module_wrap.cc73
-rw-r--r--src/module_wrap.h1
-rw-r--r--src/node_errors.h1
-rw-r--r--test/parallel/test-vm-module-cached-data.js30
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',
+});