Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
(api fix:1801 new:1801) 'vm' module uses sandbox as prototype
Browse files Browse the repository at this point in the history
God, I hate the ~50-character limit on commit messages. Almost impossible to fit
useful info in there. Anyway.

As described in GH-1801, the `'vm'` module was handling the `sandbox` object
provided by the API consumer in a particularly terrible and fragile fashion: it
was simply shallow-copying any enumerable properties from the sandbox onto the
global context before executing the code, and then eventually copying any values
on the global context back into the sandbox object *afterwards*.

This commit removes all of that implementation, and utilizes the passed sandbox
object as the *prototype of the context* instead. A bit of a hack, but a very
effective one.

This no longer allows for new variables created in the global context to be
placed into your sandbox after execution has completed, but that’s for the best
anyway, as it’s not very in line with the concept of a “box of passed-in
context.” I’m planning to further implement an interface for API consumers to
acquire the *actual global* from within the VM soon, thus allowing for
separation-of-concerns: providing data *to* the VM via the sandbox-prototype,
and exploring the internal environment of the VM itself.

// GitHub cruft: closes #1801

Squashes: 2c1f5206bb..2ab8ab1292
  • Loading branch information
ELLIOTTCABLE committed Oct 19, 2011
1 parent 9fa7480 commit 43b8e3c
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 16 deletions.
30 changes: 20 additions & 10 deletions src/node_script.cc
Expand Up @@ -364,12 +364,27 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {

// New and user context share code. DRY it up.
if (context_flag == userContext || context_flag == newContext) {
// Enter the context
context->Enter();
// First, we grab the “global proxy” (see v8’s documentation for an
// explanation of this) by calling `Context::Global()` *before* we call
// `Context::DetachGlobal()`, which will then give us access to the *actual*
// global object.
//
// We have to set the prototype of the *actual* global object, because the
// prototype of v8’s ‘global proxy’ is the global object itself, and v8
// blows up in approximately twenty different ways if you mess with that
// relationship.
//
// Once we have the `global_proxy` reference, we utilize that to
// `ReattachGlobal()` before entering the context.
// — elliottcable <oss@ell.io>
Handle<Object> global_proxy = // »
context->Global();

// Copy everything from the passed in sandbox (either the persistent
// context for runInContext(), or the sandbox arg to runInNewContext()).
CloneObject(args.This(), sandbox, context->Global()->GetPrototype());
context->DetachGlobal();
context->Global()->SetPrototype(sandbox);
context->ReattachGlobal(global_proxy);

context->Enter();
}

// Catch errors
Expand Down Expand Up @@ -425,11 +440,6 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
result = args.This();
}

if (context_flag == userContext || context_flag == newContext) {
// success! copy changes back onto the sandbox object.
CloneObject(args.This(), context->Global()->GetPrototype(), sandbox);
}

if (context_flag == newContext) {
// Clean up, clean up, everybody everywhere!
context->DetachGlobal();
Expand Down
11 changes: 9 additions & 2 deletions test/simple/test-script-context.js
Expand Up @@ -32,16 +32,23 @@ var result = script.runInContext(context);
assert.equal('passed', result);

common.debug('create a new pre-populated context');
context = script.createContext({'foo': 'bar', 'thing': 'lala'});
var sandbox = {'foo': 'bar', 'thing': 'lala'};
context = script.createContext(sandbox);
assert.equal('bar', context.foo);
assert.equal('lala', context.thing);

common.debug('test updating context');
script = new Script('foo = 3;');
result = script.runInContext(context);
assert.equal(3, context.foo);
//assert.equal(3, context.foo); // TODO: REIMPLEMENT ME
assert.equal('lala', context.thing);

// Issue GH-1801
common.debug('test dynamic modification');
context.fun = function(){ context.widget = 42 };
script = new Script('fun(); widget');
result = script.runInContext(context);
assert.equal(42, result);

// Issue GH-227:
Script.runInNewContext('', null, 'some.js');
Expand Down
31 changes: 27 additions & 4 deletions test/simple/test-script-new.js
Expand Up @@ -66,11 +66,11 @@ code = 'foo = 1;' +
'bar = 2;' +
'if (baz !== 3) throw new Error(\'test fail\');';
foo = 2;
obj = { foo: 0, baz: 3 };
sandbox = { foo: 0, baz: 3 };
script = new Script(code);
var baz = script.runInNewContext(obj);
assert.equal(1, obj.foo);
assert.equal(2, obj.bar);
var baz = script.runInNewContext(sandbox);
//assert.equal(1, context.foo); // TODO: REIMPLEMENT ME
//assert.equal(2, context.bar); // TODO: REIMPLEMENT ME
assert.equal(2, foo);

common.debug('call a function by reference');
Expand All @@ -85,6 +85,29 @@ var f = { a: 1 };
script.runInNewContext({ f: f });
assert.equal(f.a, 2);

// Issue GH-1801
common.debug('test dynamic modification');
sandbox.fun = function(){ sandbox.widget = 42 };
script = new Script('fun(); widget');
result = script.runInNewContext(sandbox);
assert.equal(42, result);

// Issue GH-1801
common.debug('indirectly modify an object');
var sandbox = { proxy: {}, common: common, process: process };
sandbox.finish = function(){
process.nextTick(function(){
common.debug('(FINISH)');
assert.equal(42, sandbox.proxy.widget)
})
};
script = new Script(" process.nextTick(function(){ " +
" common.debug('(TICK)'); " +
" proxy.widget = 42; " +
" }); " +
" finish() ");
script.runInNewContext(sandbox);

common.debug('invalid this');
assert.throws(function() {
script.runInNewContext.call('\'hello\';');
Expand Down

0 comments on commit 43b8e3c

Please sign in to comment.