From b89ba806cddf622aa946a44e16fbd79fc4bd748c Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 17 Jul 2017 13:06:23 +0800 Subject: [PATCH] vm: support parsing a script in a specific context PR-URL: https://github.com/nodejs/node/pull/14888 Reviewed-By: James M Snell Reviewed-By: Ben Noordhuis Reviewed-By: Eugene Ostroukhov --- lib/vm.js | 41 ++++++-- src/env.h | 1 + src/node_contextify.cc | 49 ++++++++++ test/inspector/test-scriptparsed-context.js | 101 ++++++++++++++++++++ 4 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 test/inspector/test-scriptparsed-context.js diff --git a/lib/vm.js b/lib/vm.js index 5bee450bec..e7fccc9749 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -21,8 +21,14 @@ 'use strict'; -const binding = process.binding('contextify'); -const Script = binding.ContextifyScript; +const { + ContextifyScript: Script, + kParsingContext, + + makeContext, + isContext, + runInDebugContext +} = process.binding('contextify'); // The binding provides a few useful primitives: // - Script(code, { filename = "evalmachine.anonymous", @@ -62,11 +68,11 @@ Script.prototype.runInNewContext = function(sandbox, options) { function createContext(sandbox) { if (sandbox === undefined) { sandbox = {}; - } else if (binding.isContext(sandbox)) { + } else if (isContext(sandbox)) { return sandbox; } - binding.makeContext(sandbox); + makeContext(sandbox); return sandbox; } @@ -99,16 +105,33 @@ function sigintHandlersWrap(fn, thisArg, argsArray) { } } -function runInDebugContext(code) { - return binding.runInDebugContext(code); -} - function runInContext(code, contextifiedSandbox, options) { + if (typeof options === 'string') { + options = { + filename: options, + [kParsingContext]: contextifiedSandbox + }; + } else { + options = Object.assign({}, options, { + [kParsingContext]: contextifiedSandbox + }); + } return createScript(code, options) .runInContext(contextifiedSandbox, options); } function runInNewContext(code, sandbox, options) { + sandbox = createContext(sandbox); + if (typeof options === 'string') { + options = { + filename: options, + [kParsingContext]: sandbox + }; + } else { + options = Object.assign({}, options, { + [kParsingContext]: sandbox + }); + } return createScript(code, options).runInNewContext(sandbox, options); } @@ -124,5 +147,5 @@ module.exports = { runInContext, runInNewContext, runInThisContext, - isContext: binding.isContext + isContext }; diff --git a/src/env.h b/src/env.h index 9f9a3a23e3..fded721b80 100644 --- a/src/env.h +++ b/src/env.h @@ -318,6 +318,7 @@ struct http2_state; V(tls_wrap_constructor_function, v8::Function) \ V(tty_constructor_template, v8::FunctionTemplate) \ V(udp_constructor_function, v8::Function) \ + V(vm_parsing_context_symbol, v8::Symbol) \ V(url_constructor_function, v8::Function) \ V(write_wrap_constructor_function, v8::Function) \ diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 792b1c4f80..c2037c4cbe 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -61,6 +61,7 @@ using v8::Script; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; +using v8::Symbol; using v8::TryCatch; using v8::Uint8Array; using v8::UnboundScript; @@ -531,6 +532,16 @@ class ContextifyScript : public BaseObject { target->Set(class_name, script_tmpl->GetFunction()); env->set_script_context_constructor_template(script_tmpl); + + Local parsing_context_symbol = + Symbol::New(env->isolate(), + FIXED_ONE_BYTE_STRING(env->isolate(), + "script parsing context")); + env->set_vm_parsing_context_symbol(parsing_context_symbol); + target->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "kParsingContext"), + parsing_context_symbol) + .FromJust(); } @@ -555,6 +566,7 @@ class ContextifyScript : public BaseObject { Maybe maybe_display_errors = GetDisplayErrorsArg(env, options); MaybeLocal cached_data_buf = GetCachedData(env, options); Maybe maybe_produce_cached_data = GetProduceCachedData(env, options); + MaybeLocal maybe_context = GetContext(env, options); if (try_catch.HasCaught()) { try_catch.ReThrow(); return; @@ -583,6 +595,8 @@ class ContextifyScript : public BaseObject { else if (produce_cached_data) compile_options = ScriptCompiler::kProduceCodeCache; + Context::Scope scope(maybe_context.FromMaybe(env->context())); + MaybeLocal v8_script = ScriptCompiler::CompileUnboundScript( env->isolate(), &source, @@ -935,6 +949,41 @@ class ContextifyScript : public BaseObject { return value->ToInteger(env->context()); } + static MaybeLocal GetContext(Environment* env, + Local options) { + if (!options->IsObject()) + return MaybeLocal(); + + MaybeLocal maybe_value = + options.As()->Get(env->context(), + env->vm_parsing_context_symbol()); + Local value; + if (!maybe_value.ToLocal(&value)) + return MaybeLocal(); + + if (!value->IsObject()) { + if (!value->IsNullOrUndefined()) { + env->ThrowTypeError( + "contextifiedSandbox argument must be an object."); + } + return MaybeLocal(); + } + + ContextifyContext* sandbox = + ContextifyContext::ContextFromContextifiedSandbox( + env, value.As()); + if (!sandbox) { + env->ThrowTypeError( + "sandbox argument must have been converted to a context."); + return MaybeLocal(); + } + + Local context = sandbox->context(); + if (context.IsEmpty()) + return MaybeLocal(); + return context; + } + static bool EvalMachine(Environment* env, const int64_t timeout, diff --git a/test/inspector/test-scriptparsed-context.js b/test/inspector/test-scriptparsed-context.js new file mode 100644 index 0000000000..f1258639f5 --- /dev/null +++ b/test/inspector/test-scriptparsed-context.js @@ -0,0 +1,101 @@ +'use strict'; +const common = require('../common'); +common.skipIfInspectorDisabled(); +common.crashOnUnhandledRejection(); +const { NodeInstance } = require('./inspector-helper.js'); +const assert = require('assert'); + +const script = ` + 'use strict'; + const assert = require('assert'); + const vm = require('vm'); + const { kParsingContext } = process.binding('contextify'); + debugger; + + global.outer = true; + global.inner = false; + const context = vm.createContext({ + outer: false, + inner: true + }); + debugger; + + const scriptMain = new vm.Script("outer"); + debugger; + + const scriptContext = new vm.Script("inner", { + [kParsingContext]: context + }); + debugger; + + assert.strictEqual(scriptMain.runInThisContext(), true); + assert.strictEqual(scriptMain.runInContext(context), false); + assert.strictEqual(scriptContext.runInThisContext(), false); + assert.strictEqual(scriptContext.runInContext(context), true); + debugger; + + vm.runInContext('inner', context); + debugger; + + vm.runInNewContext('Array', {}); + debugger; +`; + +async function getContext(session) { + const created = + await session.waitForNotification('Runtime.executionContextCreated'); + return created.params.context; +} + +async function checkScriptContext(session, context) { + const scriptParsed = + await session.waitForNotification('Debugger.scriptParsed'); + assert.strictEqual(scriptParsed.params.executionContextId, context.id); +} + +async function runTests() { + const instance = new NodeInstance(['--inspect-brk=0', '--expose-internals'], + script); + const session = await instance.connectInspectorSession(); + await session.send([ + { 'method': 'Debugger.enable' }, + { 'method': 'Runtime.runIfWaitingForDebugger' } + ]); + await session.waitForBreakOnLine(5, '[eval]'); + + await session.send({ 'method': 'Runtime.enable' }); + const topContext = await getContext(session); + await session.send({ 'method': 'Debugger.resume' }); + const childContext = await getContext(session); + await session.waitForBreakOnLine(13, '[eval]'); + + console.error('[test]', 'Script associated with current context by default'); + await session.send({ 'method': 'Debugger.resume' }); + await checkScriptContext(session, topContext); + await session.waitForBreakOnLine(16, '[eval]'); + + console.error('[test]', 'Script associated with selected context'); + await session.send({ 'method': 'Debugger.resume' }); + await checkScriptContext(session, childContext); + await session.waitForBreakOnLine(21, '[eval]'); + + console.error('[test]', 'Script is unbound'); + await session.send({ 'method': 'Debugger.resume' }); + await session.waitForBreakOnLine(27, '[eval]'); + + console.error('[test]', 'vm.runInContext associates script with context'); + await session.send({ 'method': 'Debugger.resume' }); + await checkScriptContext(session, childContext); + await session.waitForBreakOnLine(30, '[eval]'); + + console.error('[test]', 'vm.runInNewContext associates script with context'); + await session.send({ 'method': 'Debugger.resume' }); + const thirdContext = await getContext(session); + await checkScriptContext(session, thirdContext); + await session.waitForBreakOnLine(33, '[eval]'); + + await session.runToCompletion(); + assert.strictEqual(0, (await instance.expectShutdown()).exitCode); +} + +runTests();