vm: Copy missing properties from context
This addresses a current shortcoming of the V8 SetNamedPropertyHandler function. It does not provide a way to intercept Object.defineProperty(..) calls. As a result, these properties are not copied onto the contextified sandbox when a new global property is added via either a function declaration or a Object.defineProperty(global, ...) call. Note that any function declarations or Object.defineProperty() globals that are created asynchronously (in a setTimeout, callback, etc.) will happen AFTER the call to copy properties, and thus not be caught. The way to properly fix this is to add some sort of a Object::SetNamedDefinePropertyHandler() function that takes a callback, which receives the property name and property descriptor as arguments. Luckily, such situations are rare, and asynchronously-added globals weren't supported by Node's VM module until 0.12 anyway. But, this should be fixed properly in V8, and this copy function should be removed once there is a better way. Fix #6416
This commit is contained in:
parent
4c0195e034
commit
3c5ea410ca
@ -96,6 +96,79 @@ class ContextifyContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// XXX(isaacs): This function only exists because of a shortcoming of
|
||||||
|
// the V8 SetNamedPropertyHandler function.
|
||||||
|
//
|
||||||
|
// It does not provide a way to intercept Object.defineProperty(..)
|
||||||
|
// calls. As a result, these properties are not copied onto the
|
||||||
|
// contextified sandbox when a new global property is added via either
|
||||||
|
// a function declaration or a Object.defineProperty(global, ...) call.
|
||||||
|
//
|
||||||
|
// Note that any function declarations or Object.defineProperty()
|
||||||
|
// globals that are created asynchronously (in a setTimeout, callback,
|
||||||
|
// etc.) will happen AFTER the call to copy properties, and thus not be
|
||||||
|
// caught.
|
||||||
|
//
|
||||||
|
// The way to properly fix this is to add some sort of a
|
||||||
|
// Object::SetNamedDefinePropertyHandler() function that takes a callback,
|
||||||
|
// which receives the property name and property descriptor as arguments.
|
||||||
|
//
|
||||||
|
// Luckily, such situations are rare, and asynchronously-added globals
|
||||||
|
// weren't supported by Node's VM module until 0.12 anyway. But, this
|
||||||
|
// should be fixed properly in V8, and this copy function should be
|
||||||
|
// removed once there is a better way.
|
||||||
|
void CopyProperties() {
|
||||||
|
HandleScope scope(node_isolate);
|
||||||
|
|
||||||
|
Local<Context> context = PersistentToLocal(env()->isolate(), context_);
|
||||||
|
Local<Object> global = context->Global()->GetPrototype()->ToObject();
|
||||||
|
Local<Object> sandbox = PersistentToLocal(env()->isolate(), sandbox_);
|
||||||
|
|
||||||
|
Local<Function> clone_property_method;
|
||||||
|
|
||||||
|
Local<Array> names = global->GetOwnPropertyNames();
|
||||||
|
int length = names->Length();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Local<String> key = names->Get(i)->ToString();
|
||||||
|
bool has = sandbox->HasOwnProperty(key);
|
||||||
|
if (!has) {
|
||||||
|
// Could also do this like so:
|
||||||
|
//
|
||||||
|
// PropertyAttribute att = global->GetPropertyAttributes(key_v);
|
||||||
|
// Local<Value> val = global->Get(key_v);
|
||||||
|
// sandbox->ForceSet(key_v, val, att);
|
||||||
|
//
|
||||||
|
// However, this doesn't handle ES6-style properties configured with
|
||||||
|
// Object.defineProperty, and that's exactly what we're up against at
|
||||||
|
// this point. ForceSet(key,val,att) only supports value properties
|
||||||
|
// with the ES3-style attribute flags (DontDelete/DontEnum/ReadOnly),
|
||||||
|
// which doesn't faithfully capture the full range of configurations
|
||||||
|
// that can be done using Object.defineProperty.
|
||||||
|
if (clone_property_method.IsEmpty()) {
|
||||||
|
Local<String> code = FIXED_ONE_BYTE_STRING(node_isolate,
|
||||||
|
"(function cloneProperty(source, key, target) {\n"
|
||||||
|
" try {\n"
|
||||||
|
" var desc = Object.getOwnPropertyDescriptor(source, key);\n"
|
||||||
|
" if (desc.value === source) desc.value = target;\n"
|
||||||
|
" Object.defineProperty(target, key, desc);\n"
|
||||||
|
" } catch (e) {\n"
|
||||||
|
" // Catch sealed properties errors\n"
|
||||||
|
" }\n"
|
||||||
|
"})");
|
||||||
|
|
||||||
|
Local<String> fname = FIXED_ONE_BYTE_STRING(node_isolate,
|
||||||
|
"binding:script");
|
||||||
|
Local<Script> script = Script::Compile(code, fname);
|
||||||
|
clone_property_method = Local<Function>::Cast(script->Run());
|
||||||
|
assert(clone_property_method->IsFunction());
|
||||||
|
}
|
||||||
|
Local<Value> args[] = { global, key, sandbox };
|
||||||
|
clone_property_method->Call(global, ARRAY_SIZE(args), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// This is an object that just keeps an internal pointer to this
|
// This is an object that just keeps an internal pointer to this
|
||||||
// ContextifyContext. It's passed to the NamedPropertyHandler. If we
|
// ContextifyContext. It's passed to the NamedPropertyHandler. If we
|
||||||
// pass the main JavaScript context object we're embedded in, then the
|
// pass the main JavaScript context object we're embedded in, then the
|
||||||
@ -421,6 +494,7 @@ class ContextifyScript : public WeakObject {
|
|||||||
display_errors,
|
display_errors,
|
||||||
args,
|
args,
|
||||||
try_catch);
|
try_catch);
|
||||||
|
contextify_context->CopyProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t GetTimeoutArg(const FunctionCallbackInfo<Value>& args,
|
static int64_t GetTimeoutArg(const FunctionCallbackInfo<Value>& args,
|
||||||
|
47
test/simple/test-vm-function-declaration.js
Normal file
47
test/simple/test-vm-function-declaration.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var vm = require('vm');
|
||||||
|
var o = vm.createContext({ console: console });
|
||||||
|
|
||||||
|
// This triggers the setter callback in node_contextify.cc
|
||||||
|
var code = 'var a = function() {};\n';
|
||||||
|
|
||||||
|
// but this does not, since function decls are defineProperties,
|
||||||
|
// not simple sets.
|
||||||
|
code += 'function b(){}\n';
|
||||||
|
|
||||||
|
// Grab the global b function as the completion value, to ensure that
|
||||||
|
// we are getting the global function, and not some other thing
|
||||||
|
code += '(function(){return this})().b;\n'
|
||||||
|
|
||||||
|
var res = vm.runInContext(code, o, 'test');
|
||||||
|
|
||||||
|
assert.equal(typeof res, 'function', 'result should be function');
|
||||||
|
assert.equal(res.name, 'b', 'res should be named b');
|
||||||
|
assert.equal(typeof o.a, 'function', 'a should be function');
|
||||||
|
assert.equal(typeof o.b, 'function', 'b should be function');
|
||||||
|
assert.equal(res, o.b, 'result should be global b function');
|
||||||
|
|
||||||
|
console.log('ok');
|
46
test/simple/test-vm-global-define-property.js
Normal file
46
test/simple/test-vm-global-define-property.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var vm = require('vm');
|
||||||
|
|
||||||
|
var code =
|
||||||
|
'Object.defineProperty(this, "f", {\n' +
|
||||||
|
' get: function() { return x; },\n' +
|
||||||
|
' set: function(k) { x = k; },\n' +
|
||||||
|
' configurable: true,\n' +
|
||||||
|
' enumerable: true\n' +
|
||||||
|
'});\n' +
|
||||||
|
'g = f;\n' +
|
||||||
|
'f;\n';
|
||||||
|
|
||||||
|
var x = {};
|
||||||
|
var o = vm.createContext({ console: console, x: x });
|
||||||
|
|
||||||
|
var res = vm.runInContext(code, o, 'test');
|
||||||
|
|
||||||
|
assert(res);
|
||||||
|
assert.equal(typeof res, 'object');
|
||||||
|
assert.equal(res, x);
|
||||||
|
assert.equal(o.f, res);
|
||||||
|
assert.deepEqual(Object.keys(o), ['console', 'x', 'g', 'f']);
|
Loading…
x
Reference in New Issue
Block a user