vm: support parsing a script in a specific context
PR-URL: https://github.com/nodejs/node/pull/14888 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Eugene Ostroukhov <eostroukhov@google.com>
This commit is contained in:
parent
86e7c61a07
commit
d932e80231
41
lib/vm.js
41
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
|
||||
};
|
||||
|
@ -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) \
|
||||
|
||||
|
@ -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<Symbol> 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<bool> maybe_display_errors = GetDisplayErrorsArg(env, options);
|
||||
MaybeLocal<Uint8Array> cached_data_buf = GetCachedData(env, options);
|
||||
Maybe<bool> maybe_produce_cached_data = GetProduceCachedData(env, options);
|
||||
MaybeLocal<Context> 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<UnboundScript> v8_script = ScriptCompiler::CompileUnboundScript(
|
||||
env->isolate(),
|
||||
&source,
|
||||
@ -935,6 +949,41 @@ class ContextifyScript : public BaseObject {
|
||||
return value->ToInteger(env->context());
|
||||
}
|
||||
|
||||
static MaybeLocal<Context> GetContext(Environment* env,
|
||||
Local<Value> options) {
|
||||
if (!options->IsObject())
|
||||
return MaybeLocal<Context>();
|
||||
|
||||
MaybeLocal<Value> maybe_value =
|
||||
options.As<Object>()->Get(env->context(),
|
||||
env->vm_parsing_context_symbol());
|
||||
Local<Value> value;
|
||||
if (!maybe_value.ToLocal(&value))
|
||||
return MaybeLocal<Context>();
|
||||
|
||||
if (!value->IsObject()) {
|
||||
if (!value->IsNullOrUndefined()) {
|
||||
env->ThrowTypeError(
|
||||
"contextifiedSandbox argument must be an object.");
|
||||
}
|
||||
return MaybeLocal<Context>();
|
||||
}
|
||||
|
||||
ContextifyContext* sandbox =
|
||||
ContextifyContext::ContextFromContextifiedSandbox(
|
||||
env, value.As<Object>());
|
||||
if (!sandbox) {
|
||||
env->ThrowTypeError(
|
||||
"sandbox argument must have been converted to a context.");
|
||||
return MaybeLocal<Context>();
|
||||
}
|
||||
|
||||
Local<Context> context = sandbox->context();
|
||||
if (context.IsEmpty())
|
||||
return MaybeLocal<Context>();
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
static bool EvalMachine(Environment* env,
|
||||
const int64_t timeout,
|
||||
|
101
test/inspector/test-scriptparsed-context.js
Normal file
101
test/inspector/test-scriptparsed-context.js
Normal file
@ -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();
|
Loading…
x
Reference in New Issue
Block a user