Skip to content

Commit

Permalink
vm: support parsing a script in a specific context
Browse files Browse the repository at this point in the history
PR-URL: nodejs/node#14888
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Eugene Ostroukhov <[email protected]>
  • Loading branch information
TimothyGu authored and addaleax committed Sep 5, 2017
1 parent 80a2562 commit b89ba80
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 9 deletions.
41 changes: 32 additions & 9 deletions lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}

Expand All @@ -124,5 +147,5 @@ module.exports = {
runInContext,
runInNewContext,
runInThisContext,
isContext: binding.isContext
isContext
};
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \

Expand Down
49 changes: 49 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}


Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
101 changes: 101 additions & 0 deletions test/inspector/test-scriptparsed-context.js
Original file line number Diff line number Diff line change
@@ -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();

0 comments on commit b89ba80

Please sign in to comment.