Skip to content

Commit 9c110d8

Browse files
domenicbnoordhuis
authored andcommitted
vm: add isContext; prevent double-contextifying
Previously, calling `vm.createContext(o)` repeatedly on the same `o` would cause new C++ `ContextifyContext`s to be created and stored on `o`, while the previous resident went off into leaked-memory limbo. Now, repeatedly trying to contextify a sandbox will do nothing after the first time. To detect this, an independently-useful `vm.isContext(sandbox)` export was added.
1 parent a3bf3d1 commit 9c110d8

File tree

4 files changed

+72
-3
lines changed

4 files changed

+72
-3
lines changed

lib/vm.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var util = require('util');
2828
// - runInThisContext()
2929
// - runInContext(sandbox, [timeout])
3030
// - makeContext(sandbox)
31+
// - isContext(sandbox)
3132
// From this we build the entire documented API.
3233

3334
Script.prototype.runInNewContext = function(initSandbox, timeout, disp) {
@@ -44,10 +45,11 @@ exports.createScript = function(code, filename, disp) {
4445
exports.createContext = function(initSandbox) {
4546
if (util.isUndefined(initSandbox)) {
4647
initSandbox = {};
48+
} else if (binding.isContext(initSandbox)) {
49+
return initSandbox;
4750
}
4851

4952
binding.makeContext(initSandbox);
50-
5153
return initSandbox;
5254
};
5355

@@ -65,3 +67,5 @@ exports.runInThisContext = function(code, filename, timeout, disp) {
6567
var script = exports.createScript(code, filename, disp);
6668
return script.runInThisContext(timeout, disp);
6769
};
70+
71+
exports.isContext = binding.isContext;

src/node_contextify.cc

+22-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class ContextifyContext {
119119
data_wrapper_ctor.Reset(node_isolate, function_template->GetFunction());
120120

121121
NODE_SET_METHOD(target, "makeContext", MakeContext);
122+
NODE_SET_METHOD(target, "isContext", IsContext);
122123
}
123124

124125

@@ -130,11 +131,31 @@ class ContextifyContext {
130131
}
131132
Local<Object> sandbox = args[0].As<Object>();
132133

134+
Local<String> hidden_name =
135+
FIXED_ONE_BYTE_STRING(node_isolate, "_contextifyHidden");
136+
137+
// Don't allow contextifying a sandbox multiple times.
138+
assert(sandbox->GetHiddenValue(hidden_name).IsEmpty());
139+
133140
ContextifyContext* context = new ContextifyContext(sandbox);
134141
Local<External> hidden_context = External::New(context);
142+
sandbox->SetHiddenValue(hidden_name, hidden_context);
143+
}
144+
145+
146+
static void IsContext(const FunctionCallbackInfo<Value>& args) {
147+
HandleScope scope(node_isolate);
148+
149+
if (!args[0]->IsObject()) {
150+
ThrowTypeError("sandbox must be an object");
151+
return;
152+
}
153+
Local<Object> sandbox = args[0].As<Object>();
154+
135155
Local<String> hidden_name =
136156
FIXED_ONE_BYTE_STRING(node_isolate, "_contextifyHidden");
137-
sandbox->SetHiddenValue(hidden_name, hidden_context);
157+
158+
args.GetReturnValue().Set(!sandbox->GetHiddenValue(hidden_name).IsEmpty());
138159
}
139160

140161

test/simple/test-vm-create-context-arg.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ var vm = require('vm');
2525

2626
assert.throws(function() {
2727
var ctx = vm.createContext('string is not supported');
28-
});
28+
}, TypeError);
2929

3030
assert.doesNotThrow(function() {
3131
var ctx = vm.createContext({ a: 1 });
3232
ctx = vm.createContext([0, 1, 2, 3]);
3333
});
34+
35+
assert.doesNotThrow(function() {
36+
var sandbox = {};
37+
vm.createContext(sandbox);
38+
vm.createContext(sandbox);
39+
});

test/simple/test-vm-is-context.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
var common = require('../common');
23+
var assert = require('assert');
24+
var vm = require('vm');
25+
26+
assert.throws(function() {
27+
vm.isContext('string is not supported');
28+
}, TypeError);
29+
30+
assert.strictEqual(vm.isContext({}), false);
31+
assert.strictEqual(vm.isContext([]), false);
32+
33+
assert.strictEqual(vm.isContext(vm.createContext()), true);
34+
assert.strictEqual(vm.isContext(vm.createContext([])), true);
35+
36+
var sandbox = { foo: 'bar' };
37+
vm.createContext(sandbox);
38+
assert.strictEqual(vm.isContext(sandbox), true);

0 commit comments

Comments
 (0)