diff --git a/doc/api/vm.markdown b/doc/api/vm.markdown index a1f412e6baf4dc..612d69945ff778 100644 --- a/doc/api/vm.markdown +++ b/doc/api/vm.markdown @@ -38,6 +38,12 @@ The options when creating a script are: code are controlled by the options to the script's methods. - `timeout`: a number of milliseconds to execute `code` before terminating execution. If execution is terminated, an [`Error`][] will be thrown. +- `cachedData`: an optional `Buffer` with V8's code cache data for the supplied + source. When supplied `cachedDataRejected` value will be set to either + `true` or `false` depending on acceptance of the data by V8. +- `produceCachedData`: if `true` and no `cachedData` is present - a `Buffer` + with V8's code cache data will be produced and stored in `cachedData` property + of the returned `vm.Script` instance. ### script.runInContext(contextifiedSandbox[, options]) diff --git a/src/env.h b/src/env.h index bb0868e1d83140..254f0c8fa123a6 100644 --- a/src/env.h +++ b/src/env.h @@ -61,6 +61,8 @@ namespace node { V(buffer_string, "buffer") \ V(bytes_string, "bytes") \ V(bytes_parsed_string, "bytesParsed") \ + V(cached_data_string, "cachedData") \ + V(cached_data_rejected_string, "cachedDataRejected") \ V(callback_string, "callback") \ V(change_string, "change") \ V(oncertcb_string, "oncertcb") \ @@ -175,6 +177,7 @@ namespace node { V(preference_string, "preference") \ V(priority_string, "priority") \ V(processed_string, "processed") \ + V(produce_cached_data_string, "produceCachedData") \ V(prototype_string, "prototype") \ V(raw_string, "raw") \ V(rdev_string, "rdev") \ diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 6675e2d8a08f70..31d98853d3617d 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -13,6 +13,7 @@ namespace node { using v8::AccessType; using v8::Array; +using v8::ArrayBuffer; using v8::Boolean; using v8::Context; using v8::Debug; @@ -40,6 +41,7 @@ using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::TryCatch; +using v8::Uint8Array; using v8::UnboundScript; using v8::V8; using v8::Value; @@ -507,15 +509,35 @@ class ContextifyScript : public BaseObject { Local lineOffset = GetLineOffsetArg(args, 1); Local columnOffset = GetColumnOffsetArg(args, 1); bool display_errors = GetDisplayErrorsArg(args, 1); + MaybeLocal cached_data_buf = GetCachedData(env, args, 1); + bool produce_cached_data = GetProduceCachedData(env, args, 1); if (try_catch.HasCaught()) { try_catch.ReThrow(); return; } + ScriptCompiler::CachedData* cached_data = nullptr; + if (!cached_data_buf.IsEmpty()) { + ArrayBuffer::Contents contents = + cached_data_buf.ToLocalChecked()->Buffer()->GetContents(); + cached_data = new ScriptCompiler::CachedData( + static_cast(contents.Data()), contents.ByteLength()); + } + ScriptOrigin origin(filename, lineOffset, columnOffset); - ScriptCompiler::Source source(code, origin); - Local v8_script = - ScriptCompiler::CompileUnbound(env->isolate(), &source); + ScriptCompiler::Source source(code, origin, cached_data); + ScriptCompiler::CompileOptions compile_options = + ScriptCompiler::kNoCompileOptions; + + if (source.GetCachedData() != nullptr) + compile_options = ScriptCompiler::kConsumeCodeCache; + else if (produce_cached_data) + compile_options = ScriptCompiler::kProduceCodeCache; + + Local v8_script = ScriptCompiler::CompileUnbound( + env->isolate(), + &source, + compile_options); if (v8_script.IsEmpty()) { if (display_errors) { @@ -525,6 +547,19 @@ class ContextifyScript : public BaseObject { return; } contextify_script->script_.Reset(env->isolate(), v8_script); + + if (compile_options == ScriptCompiler::kConsumeCodeCache) { + args.This()->Set( + env->cached_data_rejected_string(), + Boolean::New(env->isolate(), source.GetCachedData()->rejected)); + } else if (compile_options == ScriptCompiler::kProduceCodeCache) { + const ScriptCompiler::CachedData* cached_data = source.GetCachedData(); + MaybeLocal buf = Buffer::Copy( + env, + reinterpret_cast(cached_data->data), + cached_data->length); + args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); + } } @@ -677,6 +712,43 @@ class ContextifyScript : public BaseObject { } + static MaybeLocal GetCachedData( + Environment* env, + const FunctionCallbackInfo& args, + const int i) { + if (!args[i]->IsObject()) { + return MaybeLocal(); + } + Local value = args[i].As()->Get(env->cached_data_string()); + if (value->IsUndefined()) { + return MaybeLocal(); + } + + if (!value->IsUint8Array()) { + Environment::ThrowTypeError( + args.GetIsolate(), + "options.cachedData must be a Buffer instance"); + return MaybeLocal(); + } + + return value.As(); + } + + + static bool GetProduceCachedData( + Environment* env, + const FunctionCallbackInfo& args, + const int i) { + if (!args[i]->IsObject()) { + return false; + } + Local value = + args[i].As()->Get(env->produce_cached_data_string()); + + return value->IsTrue(); + } + + static Local GetLineOffsetArg( const FunctionCallbackInfo& args, const int i) { diff --git a/test/parallel/test-vm-cached-data.js b/test/parallel/test-vm-cached-data.js new file mode 100644 index 00000000000000..47ea592b531538 --- /dev/null +++ b/test/parallel/test-vm-cached-data.js @@ -0,0 +1,37 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const Buffer = require('buffer').Buffer; + +const originalSource = '(function bcd() { return \'original\'; })'; + +// It should produce code cache +const original = new vm.Script(originalSource, { + produceCachedData: true +}); +assert(original.cachedData instanceof Buffer); + +assert.equal(original.runInThisContext()(), 'original'); + +// It should consume code cache +const success = new vm.Script(originalSource, { + cachedData: original.cachedData +}); +assert(!success.cachedDataRejected); + +assert.equal(success.runInThisContext()(), 'original'); + +// It should reject invalid code cache +const reject = new vm.Script('(function abc() { return \'invalid\'; })', { + cachedData: original.cachedData +}); +assert(reject.cachedDataRejected); +assert.equal(reject.runInThisContext()(), 'invalid'); + +// It should throw on non-Buffer cachedData +assert.throws(() => { + new vm.Script('function abc() {}', { + cachedData: 'ohai' + }); +});