In Node 9.3.0
var c = vm.createContext()
c.A = "A"
c.c = c
c.vm = vm
vm.runInContext("A", c)
// 'A'
vm.runInContext("c.A", c)
// 'A'
vm.runInContext("vm.runInContext('A', c)", c)
// Throws TypeError: sandbox argument must have been converted to a context.
vm.runInContext("vm.runInContext('A', vm.createContext(c))", c)
// Throws ReferenceError: A is not defined
/cc @nodejs/vm (which I just created)
something interesting i noticed is that the circular is only found on the second depth inside the vm:
> c
{ A: 'A',
c: [Circular],
vm:
{ Script: [Function: ContextifyScript],
createContext: [Function: createContext],
createScript: [Function: createScript],
runInDebugContext: [Function: runInDebugContext],
runInContext: [Function: runInContext],
runInNewContext: [Function: runInNewContext],
runInThisContext: [Function: runInThisContext],
isContext: [Function: isContext] } }
> vm.runInContext('c', c)
{ A: 'A',
c:
{ A: 'A',
c: [Circular],
vm:
{ Script: [Function: ContextifyScript],
createContext: [Function: createContext],
createScript: [Function: createScript],
runInDebugContext: [Function: runInDebugContext],
runInContext: [Function: runInContext],
runInNewContext: [Function: runInNewContext],
runInThisContext: [Function: runInThisContext],
isContext: [Function: isContext] } },
vm:
{ Script: [Function: ContextifyScript],
createContext: [Function: createContext],
createScript: [Function: createScript],
runInDebugContext: [Function: runInDebugContext],
runInContext: [Function: runInContext],
runInNewContext: [Function: runInNewContext],
runInThisContext: [Function: runInThisContext],
isContext: [Function: isContext] } }
i think this is because the global context is actually represented as a proxy to the object inside the vm, which obviously wouldn't contain the internal slots for the contextify context and such:
https://github.com/nodejs/node/blob/d1ad4a91177808d841f032195d5f7d5e2875ae61/src/node_contextify.cc#L70-L75
@devsnek nailed it. The c on the outside is not the same object as the this on this inside. In fact there are three objects at play here, only two of which are observable:
c, which is created in the top context.
var obj = {};
// obj belongs outside; obj instanceof Object === true
vm.createContext(obj);
// obj's context didn't and cannot change; it just now has an internal pointer to a new context
The this value in the VM context, which is known in the ES spec as the "global this value" and colloquially as the "global proxy". This object is always created inside the VM context, leading to the following result:
var c = vm.createContext();
var globalProxy = vm.runInContext('this', c);
// globalProxy instanceof Object === false
// vm.runInContext('this instanceof Object', c) === true
It is also important to note that this object appears to have all the JS globals like Array, String, eval, etc., while the object returned from vm.createContext does not.
The actual global object of the VM context, that should not be observable from JavaScript. In browsers this separation between global proxy and global object is necessary for cross-origin checks, but V8 implemented contexts in such a way that this separation cannot be disabled. It's just not observable through the VM module because the global proxy forwards all operations on it to the actual global object. The reason why I said "appears to have" above for the global proxy is that these properties actually exist on this object, but are exposed through the global proxy. This object is also created inside the VM context, but we have set up hooks to reflect any changes to that object to the object returned by vm.createContext(). (See src/node_contextify.cc.)
Also, if you are trying to use vm module's methods inside a VM context, you might run into issues like #14757 (which I believe/hope is specific to vm.runInThisContext()) because of language restrictions.
I think you should be able to work around the issue as describe in https://github.com/nodejs/node/issues/855.
Most helpful comment
/cc @nodejs/vm (which I just created)