From 83b87d22e621ecbfecfce195e43bbbe134f3bd6b Mon Sep 17 00:00:00 2001 From: vdeturckheim Date: Fri, 21 Aug 2020 14:33:03 +0200 Subject: [PATCH 1/2] v8: introduce v8.emitCodeGenFromStringEvent() This introduces emitCodeGenFromStringEvent in the v8 module to intercept calls made to eval and Function constructor. --- doc/api/v8.md | 30 ++++++++++++++++++++ lib/v8.js | 2 ++ src/node_v8.cc | 27 ++++++++++++++++++ test/parallel/test-v8-codegen-from-string.js | 23 +++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 test/parallel/test-v8-codegen-from-string.js diff --git a/doc/api/v8.md b/doc/api/v8.md index 921c4378199496..5ddf03dcfc1bc0 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -503,6 +503,36 @@ added: v8.0.0 A subclass of [`Deserializer`][] corresponding to the format written by [`DefaultSerializer`][]. +## `v8.emitCodeGenFromStringEvent()` + + +After this methode is called, calling `eval(code)` or `new Function(code)` +will emit a `codeGenerationFromString` event on `process` with the value +of `code`. The call to `eval` or `new Function` will happen after the +event is emitted. + +Calls made through the `vm` module will not emit the event. + +Calling `v8.emitCodeGenFromStringEvent()` when the +`--disallow-code-generation-from-strings` flag is used will be a noop. + +```js +const v8 = require('v8'); +v8.emitCodeGenFromStringEvent(); +process.on('codeGenerationFromString', (code) => { + console.log('Generating code from string`', code, '`'); +}); +const item = { foo: 0 }; +eval('item.foo++'); +``` + +will log +```text +Generating code from string` item.foo++ ` +``` + [`Buffer`]: buffer.html [`DefaultDeserializer`]: #v8_class_v8_defaultdeserializer [`DefaultSerializer`]: #v8_class_v8_defaultserializer diff --git a/lib/v8.js b/lib/v8.js index 56e3e6056214ca..84c58098e1e875 100644 --- a/lib/v8.js +++ b/lib/v8.js @@ -72,6 +72,7 @@ class Deserializer extends _Deserializer { } const { cachedDataVersionTag, + emitCodeGenFromStringEvent, setFlagsFromString: _setFlagsFromString, heapStatisticsBuffer, heapSpaceStatisticsBuffer, @@ -265,6 +266,7 @@ function deserialize(buffer) { module.exports = { cachedDataVersionTag, + emitCodeGenFromStringEvent, getHeapSnapshot, getHeapStatistics, getHeapSpaceStatistics, diff --git a/src/node_v8.cc b/src/node_v8.cc index f34125d5f49e16..b7bfdb37a78c28 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -25,6 +25,7 @@ #include "memory_tracker-inl.h" #include "util-inl.h" #include "v8.h" +#include "node_process.h" namespace node { @@ -167,6 +168,29 @@ void SetFlagsFromString(const FunctionCallbackInfo& args) { V8::SetFlagsFromString(*flags, static_cast(flags.length())); } +static v8::ModifyCodeGenerationFromStringsResult CodeGenCallback( + Local context, + Local source) { + Environment* env = Environment::GetCurrent(context); + ProcessEmit(env, "codeGenerationFromString", source); + // expected signature is {bool, Local} but {bool} is also valid and + // fallbacks on the original argument. + return {true}; +} + +static void EmitCodeGenFromStringEvent( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = args.GetIsolate(); + Local context = env->context(); + + // This only makes sense if code generation from string is allowed. + if (context->IsCodeGenerationFromStringsAllowed()) { + // V8 requires that this is set to false for it to call the callback + context->AllowCodeGenerationFromStrings(false); + isolate->SetModifyCodeGenerationFromStringsCallback(CodeGenCallback); + } +} void Initialize(Local target, Local unused, @@ -180,6 +204,9 @@ void Initialize(Local target, env->SetMethodNoSideEffect(target, "cachedDataVersionTag", CachedDataVersionTag); + env->SetMethod( + target, "emitCodeGenFromStringEvent", EmitCodeGenFromStringEvent); + // Export symbols used by v8.getHeapStatistics() env->SetMethod( target, "updateHeapStatisticsBuffer", UpdateHeapStatisticsBuffer); diff --git a/test/parallel/test-v8-codegen-from-string.js b/test/parallel/test-v8-codegen-from-string.js new file mode 100644 index 00000000000000..343b381585d452 --- /dev/null +++ b/test/parallel/test-v8-codegen-from-string.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const v8 = require('v8'); + +const beforeSetupHandler = + common.mustNotCall('called before v8.emitCodeGenFromStringEvent()'); + +process.on('codeGenerationFromString', beforeSetupHandler); + +const item = { foo: 0 }; +eval('++item.foo'); +process.off('codeGenerationFromString', beforeSetupHandler); + +assert.strictEqual(item.foo, 1); + +v8.emitCodeGenFromStringEvent(); +process.on('codeGenerationFromString', common.mustCall((code) => { + assert.strictEqual(code, 'item.foo++'); +})); + +eval('item.foo++'); +assert.strictEqual(item.foo, 2); From a44f4e49731b58b1cbf2a20c29df113d0d7afe8a Mon Sep 17 00:00:00 2001 From: vdeturckheim Date: Fri, 21 Aug 2020 15:54:50 +0200 Subject: [PATCH 2/2] clarify documentation --- doc/api/v8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/v8.md b/doc/api/v8.md index 5ddf03dcfc1bc0..3342f01748fb26 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -513,7 +513,7 @@ will emit a `codeGenerationFromString` event on `process` with the value of `code`. The call to `eval` or `new Function` will happen after the event is emitted. -Calls made through the `vm` module will not emit the event. +Calls to the `vm` module such as `vm.runInContext` will not emit the event. Calling `v8.emitCodeGenFromStringEvent()` when the `--disallow-code-generation-from-strings` flag is used will be a noop.