Permalink
Browse files

(api fix:1801 new:1801) `'vm'` module uses sandbox as prototype

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...
1 parent 9fa7480 commit 43b8e3c95647a6d29e6d193daf1ceb659b403420 @ELLIOTTCABLE ELLIOTTCABLE committed Oct 14, 2011
Showing with 56 additions and 16 deletions.
  1. +20 −10 src/node_script.cc
  2. +9 −2 test/simple/test-script-context.js
  3. +27 −4 test/simple/test-script-new.js
View
@@ -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
@@ -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();
@@ -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');
@@ -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');
@@ -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\';');

0 comments on commit 43b8e3c

Please sign in to comment.