mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Implement vm.compileFunction and fix some node:vm tests (#18285)
This commit is contained in:
@@ -630,6 +630,7 @@ namespace ERR {
|
||||
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value)
|
||||
{
|
||||
auto message = Message::ERR_INVALID_ARG_TYPE(throwScope, globalObject, arg_name, expected_type, val_actual_value);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message));
|
||||
return {};
|
||||
}
|
||||
@@ -641,6 +642,7 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO
|
||||
auto arg_name = jsString->view(globalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
auto message = Message::ERR_INVALID_ARG_TYPE(throwScope, globalObject, arg_name, expected_type, val_actual_value);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message));
|
||||
return {};
|
||||
}
|
||||
@@ -649,10 +651,13 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO
|
||||
JSC::EncodedJSValue INVALID_ARG_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
ASCIILiteral type = String(arg_name).contains('.') ? "property"_s : "argument"_s;
|
||||
WTF::StringBuilder builder;
|
||||
builder.append("The \""_s);
|
||||
builder.append(arg_name);
|
||||
builder.append("\" argument must be an instance of "_s);
|
||||
builder.append("\" "_s);
|
||||
builder.append(type);
|
||||
builder.append(" must be an instance of "_s);
|
||||
builder.append(expected_type);
|
||||
builder.append(". Received "_s);
|
||||
determineSpecificType(vm, globalObject, builder, val_actual_value);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#include "headers-handwritten.h"
|
||||
#include "BunClientData.h"
|
||||
#include <JavaScriptCore/CallFrame.h>
|
||||
#include <JavaScriptCore/Nodes.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
|
||||
@@ -700,7 +700,6 @@ JSC::EncodedJSValue V::validateObject(JSC::ThrowScope& scope, JSC::JSGlobalObjec
|
||||
template JSC::EncodedJSValue V::validateInteger<size_t>(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max, size_t* out);
|
||||
template JSC::EncodedJSValue V::validateInteger<ssize_t>(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max, ssize_t* out);
|
||||
template JSC::EncodedJSValue V::validateInteger<uint32_t>(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max, uint32_t* out);
|
||||
|
||||
template JSC::EncodedJSValue V::validateInteger<int32_t>(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, ASCIILiteral name, JSC::JSValue min, JSC::JSValue max, int32_t* out);
|
||||
template JSC::EncodedJSValue V::validateInteger<size_t>(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, ASCIILiteral name, JSC::JSValue min, JSC::JSValue max, size_t* out);
|
||||
template JSC::EncodedJSValue V::validateInteger<ssize_t>(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, ASCIILiteral name, JSC::JSValue min, JSC::JSValue max, ssize_t* out);
|
||||
|
||||
@@ -604,7 +604,7 @@ WTF::String Bun::formatStackTrace(
|
||||
|
||||
if (!sourceURLForFrame.isEmpty()) {
|
||||
sb.append(sourceURLForFrame);
|
||||
if (displayLine.zeroBasedInt() > 0) {
|
||||
if (displayLine.zeroBasedInt() > 0 || displayColumn.zeroBasedInt() > 0) {
|
||||
sb.append(':');
|
||||
sb.append(displayLine.oneBasedInt());
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding");
|
||||
|
||||
const ObjectFreeze = Object.freeze;
|
||||
|
||||
const { createContext, isContext, Script, runInNewContext, runInThisContext } = vm;
|
||||
const { createContext, isContext, Script, runInNewContext, runInThisContext, compileFunction } = vm;
|
||||
|
||||
function runInContext(code, context, options) {
|
||||
return new Script(code, options).runInContext(context);
|
||||
@@ -15,9 +15,6 @@ function createScript(code, options) {
|
||||
return new Script(code, options);
|
||||
}
|
||||
|
||||
function compileFunction() {
|
||||
throwNotImplemented("node:vm compileFunction");
|
||||
}
|
||||
function measureMemory() {
|
||||
throwNotImplemented("node:vm measureMemory");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
// Tests that vm.createScript and runInThisContext correctly handle errors
|
||||
// thrown by option property getters.
|
||||
// See https://github.com/nodejs/node/issues/12369.
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const execFile = require('child_process').execFile;
|
||||
|
||||
const scripts = [];
|
||||
|
||||
['filename', 'cachedData', 'produceCachedData', 'lineOffset', 'columnOffset']
|
||||
.forEach((prop) => {
|
||||
scripts.push(`vm.createScript('', {
|
||||
get ${prop} () {
|
||||
throw new Error('xyz');
|
||||
}
|
||||
})`);
|
||||
});
|
||||
|
||||
['breakOnSigint', 'timeout', 'displayErrors']
|
||||
.forEach((prop) => {
|
||||
scripts.push(`vm.createScript('').runInThisContext({
|
||||
get ${prop} () {
|
||||
throw new Error('xyz');
|
||||
}
|
||||
})`);
|
||||
});
|
||||
|
||||
scripts.forEach((script, i) => {
|
||||
const node = process.execPath;
|
||||
execFile(node, [ '-e', script ], common.mustCall((err, stdout, stderr) => {
|
||||
assert(typeof Bun === 'undefined' ? stderr.includes('Error: xyz') : stderr.includes('error: xyz'), 'createScript crashes');
|
||||
}));
|
||||
});
|
||||
123
test/js/node/test/parallel/test-vm-context.js
Normal file
123
test/js/node/test/parallel/test-vm-context.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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.
|
||||
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
const Script = vm.Script;
|
||||
let script = new Script('"passed";');
|
||||
|
||||
// Run in a new empty context
|
||||
let context = vm.createContext();
|
||||
let result = script.runInContext(context);
|
||||
assert.strictEqual(result, 'passed');
|
||||
|
||||
// Create a new pre-populated context
|
||||
context = vm.createContext({ 'foo': 'bar', 'thing': 'lala' });
|
||||
assert.strictEqual(context.foo, 'bar');
|
||||
assert.strictEqual(context.thing, 'lala');
|
||||
|
||||
// Test updating context
|
||||
script = new Script('foo = 3;');
|
||||
result = script.runInContext(context);
|
||||
assert.strictEqual(context.foo, 3);
|
||||
assert.strictEqual(context.thing, 'lala');
|
||||
|
||||
// Issue GH-227:
|
||||
assert.throws(() => {
|
||||
vm.runInNewContext('', null, 'some.js');
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError'
|
||||
});
|
||||
|
||||
// Issue GH-1140:
|
||||
// Test runInContext signature
|
||||
let gh1140Exception;
|
||||
try {
|
||||
vm.runInContext('throw new Error()', context, 'expected-filename.js');
|
||||
} catch (e) {
|
||||
gh1140Exception = e;
|
||||
assert.match(e.stack, /expected-filename/);
|
||||
}
|
||||
// This is outside of catch block to confirm catch block ran.
|
||||
assert.strictEqual(gh1140Exception.toString(), 'Error');
|
||||
|
||||
const nonContextualObjectError = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: /must be of type object/
|
||||
};
|
||||
const contextifiedObjectError = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: /The "contextifiedObject" argument must be an vm\.Context/
|
||||
};
|
||||
|
||||
let i = 0;
|
||||
[
|
||||
[undefined, nonContextualObjectError],
|
||||
[null, nonContextualObjectError],
|
||||
[0, nonContextualObjectError],
|
||||
[0.0, nonContextualObjectError],
|
||||
['', nonContextualObjectError],
|
||||
[{}, contextifiedObjectError],
|
||||
[[], contextifiedObjectError],
|
||||
].forEach((e) => {
|
||||
assert.throws(() => { script.runInContext(e[0]); }, e[1]);
|
||||
assert.throws(() => { vm.runInContext('', e[0]); }, e[1]);
|
||||
});
|
||||
|
||||
// Issue GH-693:
|
||||
// Test RegExp as argument to assert.throws
|
||||
script = vm.createScript('const assert = require(\'assert\'); assert.throws(' +
|
||||
'function() { throw "hello world"; }, /hello/);',
|
||||
'some.js');
|
||||
script.runInNewContext({ require });
|
||||
|
||||
// Issue GH-7529
|
||||
script = vm.createScript('delete b');
|
||||
let ctx = {};
|
||||
Object.defineProperty(ctx, 'b', { configurable: false });
|
||||
ctx = vm.createContext(ctx);
|
||||
assert.strictEqual(script.runInContext(ctx), false);
|
||||
|
||||
// Error on the first line of a module should have the correct line and column
|
||||
// number.
|
||||
{
|
||||
let stack = null;
|
||||
assert.throws(() => {
|
||||
vm.runInContext(' throw new Error()', context, {
|
||||
filename: 'expected-filename.js',
|
||||
lineOffset: 32,
|
||||
columnOffset: 123
|
||||
});
|
||||
}, (err) => {
|
||||
stack = err.stack;
|
||||
return /^ \^/m.test(stack) &&
|
||||
typeof Bun === 'undefined' ? /expected-filename\.js:33:131/.test(stack) : /expected-filename\.js:32:139/.test(stack);
|
||||
}, `stack not formatted as expected: ${stack}`);
|
||||
}
|
||||
|
||||
// https://github.com/nodejs/node/issues/6158
|
||||
ctx = new Proxy({}, {});
|
||||
assert.strictEqual(typeof vm.runInNewContext('String', ctx), 'function');
|
||||
46
test/js/node/test/parallel/test-vm-is-context.js
Normal file
46
test/js/node/test/parallel/test-vm-is-context.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.
|
||||
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
for (const valToTest of [
|
||||
'string', null, undefined, 8.9, Symbol('sym'), true,
|
||||
]) {
|
||||
assert.throws(() => {
|
||||
vm.isContext(valToTest);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError'
|
||||
});
|
||||
}
|
||||
|
||||
assert.strictEqual(vm.isContext({}), false);
|
||||
assert.strictEqual(vm.isContext([]), false);
|
||||
|
||||
assert.strictEqual(vm.isContext(vm.createContext()), true);
|
||||
assert.strictEqual(vm.isContext(vm.createContext([])), true);
|
||||
|
||||
const sandbox = { foo: 'bar' };
|
||||
vm.createContext(sandbox);
|
||||
assert.strictEqual(vm.isContext(sandbox), true);
|
||||
94
test/js/node/test/parallel/test-vm-options-validation.js
Normal file
94
test/js/node/test/parallel/test-vm-options-validation.js
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
const invalidArgType = {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
};
|
||||
|
||||
const outOfRange = {
|
||||
name: 'RangeError',
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
};
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', 42);
|
||||
}, invalidArgType);
|
||||
|
||||
[null, {}, [1], 'bad', true].forEach((value) => {
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { lineOffset: value });
|
||||
}, invalidArgType);
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { columnOffset: value });
|
||||
}, invalidArgType);
|
||||
});
|
||||
|
||||
[0.1, 2 ** 32].forEach((value) => {
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { lineOffset: value });
|
||||
}, outOfRange);
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { columnOffset: value });
|
||||
}, outOfRange);
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { lineOffset: Number.MAX_SAFE_INTEGER });
|
||||
}, outOfRange);
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { columnOffset: Number.MAX_SAFE_INTEGER });
|
||||
}, outOfRange);
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { filename: 123 });
|
||||
}, invalidArgType);
|
||||
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { produceCachedData: 1 });
|
||||
}, invalidArgType);
|
||||
|
||||
[[0], {}, true, 'bad', 42].forEach((value) => {
|
||||
assert.throws(() => {
|
||||
new vm.Script('void 0', { cachedData: value });
|
||||
}, invalidArgType);
|
||||
});
|
||||
|
||||
{
|
||||
const script = new vm.Script('void 0');
|
||||
const sandbox = vm.createContext();
|
||||
|
||||
function assertErrors(options, errCheck) {
|
||||
assert.throws(() => {
|
||||
script.runInThisContext(options);
|
||||
}, errCheck);
|
||||
|
||||
assert.throws(() => {
|
||||
script.runInContext(sandbox, options);
|
||||
}, errCheck);
|
||||
|
||||
assert.throws(() => {
|
||||
script.runInNewContext({}, options);
|
||||
}, errCheck);
|
||||
}
|
||||
|
||||
[/*null,*/ 'bad', 42].forEach((value) => {
|
||||
assertErrors(value, invalidArgType);
|
||||
});
|
||||
// [{}, [1], 'bad', null].forEach((value) => {
|
||||
// assertErrors({ timeout: value }, invalidArgType);
|
||||
// });
|
||||
// [-1, 0, NaN].forEach((value) => {
|
||||
// assertErrors({ timeout: value }, outOfRange);
|
||||
// });
|
||||
// [{}, [1], 'bad', 1, null].forEach((value) => {
|
||||
// assertErrors({ displayErrors: value }, invalidArgType);
|
||||
// assertErrors({ breakOnSigint: value }, invalidArgType);
|
||||
// });
|
||||
}
|
||||
105
test/js/node/test/parallel/test-vm-run-in-new-context.js
Normal file
105
test/js/node/test/parallel/test-vm-run-in-new-context.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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.
|
||||
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
if (typeof globalThis.gc !== 'function')
|
||||
assert.fail('Run this test with --expose-gc');
|
||||
|
||||
// Run a string
|
||||
const result = vm.runInNewContext('\'passed\';');
|
||||
assert.strictEqual(result, 'passed');
|
||||
|
||||
// Thrown error
|
||||
assert.throws(() => {
|
||||
vm.runInNewContext('throw new Error(\'test\');');
|
||||
}, /^Error: test$/);
|
||||
|
||||
globalThis.hello = 5;
|
||||
vm.runInNewContext('hello = 2');
|
||||
assert.strictEqual(globalThis.hello, 5);
|
||||
|
||||
|
||||
// Pass values in and out
|
||||
globalThis.code = 'foo = 1;' +
|
||||
'bar = 2;' +
|
||||
'if (baz !== 3) throw new Error(\'test fail\');';
|
||||
globalThis.foo = 2;
|
||||
globalThis.obj = { foo: 0, baz: 3 };
|
||||
/* eslint-disable no-unused-vars */
|
||||
const baz = vm.runInNewContext(globalThis.code, globalThis.obj);
|
||||
/* eslint-enable no-unused-vars */
|
||||
assert.strictEqual(globalThis.obj.foo, 1);
|
||||
assert.strictEqual(globalThis.obj.bar, 2);
|
||||
assert.strictEqual(globalThis.foo, 2);
|
||||
|
||||
// Call a function by reference
|
||||
function changeFoo() { globalThis.foo = 100; }
|
||||
vm.runInNewContext('f()', { f: changeFoo });
|
||||
assert.strictEqual(globalThis.foo, 100);
|
||||
|
||||
// Modify an object by reference
|
||||
const f = { a: 1 };
|
||||
vm.runInNewContext('f.a = 2', { f });
|
||||
assert.strictEqual(f.a, 2);
|
||||
|
||||
// Use function in context without referencing context
|
||||
const fn = vm.runInNewContext('(function() { obj.p = {}; })', { obj: {} });
|
||||
globalThis.gc();
|
||||
fn();
|
||||
// Should not crash
|
||||
|
||||
const filename = 'test_file.vm';
|
||||
for (const arg of [filename, { filename }]) {
|
||||
// Verify that providing a custom filename works.
|
||||
const code = 'throw new Error("foo");';
|
||||
|
||||
assert.throws(() => {
|
||||
vm.runInNewContext(code, {}, arg);
|
||||
}, (err) => {
|
||||
const lines = err.stack.split('\n');
|
||||
|
||||
assert.strictEqual(lines[0].trim(), `${filename}:1`);
|
||||
if (typeof Bun === 'undefined') {
|
||||
assert.strictEqual(lines[1].trim(), code);
|
||||
// Skip lines[2] and lines[3]. They're just a ^ and blank line.
|
||||
assert.strictEqual(lines[4].trim(), 'Error: foo');
|
||||
assert.strictEqual(lines[5].trim(), `at ${filename}:1:7`);
|
||||
} else {
|
||||
assert.strictEqual(lines[1].trim(), 'Error: foo');
|
||||
assert.strictEqual(lines[2].trim(), `at ${filename}:1:16`);
|
||||
}
|
||||
// The rest of the stack is uninteresting.
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
common.allowGlobals(
|
||||
globalThis.hello,
|
||||
globalThis.code,
|
||||
globalThis.foo,
|
||||
globalThis.obj
|
||||
);
|
||||
20
test/js/node/test/parallel/test-vm-strict-assign.js
Normal file
20
test/js/node/test/parallel/test-vm-strict-assign.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const vm = require('vm');
|
||||
|
||||
// https://github.com/nodejs/node/issues/10223
|
||||
const ctx = vm.createContext();
|
||||
|
||||
// Define x with writable = false.
|
||||
vm.runInContext('Object.defineProperty(this, "x", { value: 42 })', ctx);
|
||||
assert.strictEqual(ctx.x, 42);
|
||||
assert.strictEqual(vm.runInContext('x', ctx), 42);
|
||||
|
||||
vm.runInContext('x = 0', ctx); // Does not throw but x...
|
||||
assert.strictEqual(vm.runInContext('x', ctx), 42); // ...should be unaltered.
|
||||
|
||||
assert.throws(() => vm.runInContext('"use strict"; x = 0', ctx),
|
||||
typeof Bun === 'undefined' ? /Cannot assign to read only property 'x'/ : /Attempted to assign to readonly property\./);
|
||||
assert.strictEqual(vm.runInContext('x', ctx), 42);
|
||||
29
test/js/node/test/parallel/test-vm-syntax-error-stderr.js
Normal file
29
test/js/node/test/parallel/test-vm-syntax-error-stderr.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const wrong_script = fixtures.path('keys/rsa_cert.crt');
|
||||
|
||||
const p = typeof Bun === 'undefined' ? child_process.spawn(process.execPath, [
|
||||
'-e',
|
||||
'require(process.argv[1]);',
|
||||
wrong_script,
|
||||
]) : child_process.spawn(process.execPath, [wrong_script]);
|
||||
|
||||
p.stdout.on('data', common.mustNotCall());
|
||||
|
||||
let output = '';
|
||||
|
||||
p.stderr.on('data', (data) => output += data);
|
||||
|
||||
p.stderr.on('end', common.mustCall(() => {
|
||||
if (typeof Bun === 'undefined') {
|
||||
assert.match(output, /BEGIN CERT/);
|
||||
assert.match(output, /^\s+\^/m);
|
||||
assert.match(output, /Invalid left-hand side expression in prefix operation/);
|
||||
} else {
|
||||
assert.match(output, /Expected ";" but found "CERTIFICATE"/);
|
||||
}
|
||||
}));
|
||||
@@ -65,6 +65,177 @@ describe("Script", () => {
|
||||
message: "Class constructor Script cannot be invoked without 'new'",
|
||||
});
|
||||
});
|
||||
|
||||
describe("compileFunction()", () => {
|
||||
const vm = require("vm");
|
||||
// Security tests
|
||||
test("Template literal attack should not break out of sandbox", () => {
|
||||
const before = globalThis.hacked;
|
||||
try {
|
||||
const result = vm.compileFunction("return `\n`; globalThis.hacked = true; //")();
|
||||
expect(result).toBe("\n");
|
||||
expect(globalThis.hacked).toBe(before);
|
||||
} catch (e) {
|
||||
// If it throws, that's also acceptable as long as it didn't modify globalThis
|
||||
expect(globalThis.hacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
test("Comment-based attack should not break out of sandbox", () => {
|
||||
const before = globalThis.commentHacked;
|
||||
try {
|
||||
const result = vm.compileFunction("return 1; /* \n */ globalThis.commentHacked = true; //")();
|
||||
expect(result).toBe(1);
|
||||
expect(globalThis.commentHacked).toBe(before);
|
||||
} catch (e) {
|
||||
expect(globalThis.commentHacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
test("Function constructor abuse should be contained", () => {
|
||||
try {
|
||||
const result = vm.compileFunction("return (function(){}).constructor('return process')();")();
|
||||
// If it doesn't throw, it should at least not return the actual process object
|
||||
expect(result).not.toBe(process);
|
||||
} catch (e) {
|
||||
// Throwing is also acceptable
|
||||
expect(e).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test("Regex literal attack should not break out of sandbox", () => {
|
||||
const before = globalThis.regexHacked;
|
||||
try {
|
||||
const result = vm.compileFunction("return /\n/; globalThis.regexHacked = true; //")();
|
||||
expect(result instanceof RegExp).toBe(true);
|
||||
expect(result.toString()).toBe("/\n/");
|
||||
expect(globalThis.regexHacked).toBe(before);
|
||||
} catch (e) {
|
||||
expect(globalThis.regexHacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
test("String escape sequence attack should not break out of sandbox", () => {
|
||||
const before = globalThis.stringHacked;
|
||||
try {
|
||||
const result = vm.compileFunction("return '\\\n'; globalThis.stringHacked = true; //")();
|
||||
expect(result).toBe("\n");
|
||||
expect(globalThis.stringHacked).toBe(before);
|
||||
} catch (e) {
|
||||
expect(globalThis.stringHacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
test("Arguments access attack should be contained", () => {
|
||||
try {
|
||||
const result = vm.compileFunction("return (function(){return arguments.callee.caller})();")();
|
||||
// If it doesn't throw, it should at least not return a function
|
||||
expect(typeof result !== "function").toBe(true);
|
||||
} catch (e) {
|
||||
// Throwing is also acceptable
|
||||
expect(e).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test("With statement attack should not modify Object.prototype", () => {
|
||||
const originalToString = Object.prototype.toString;
|
||||
const before = globalThis.withHacked;
|
||||
|
||||
const parsingContext = vm.createContext({});
|
||||
|
||||
try {
|
||||
vm.compileFunction(
|
||||
"with(Object.prototype) { toString = function() { globalThis.withHacked = true; }; } return 'test';",
|
||||
[],
|
||||
{
|
||||
parsingContext,
|
||||
},
|
||||
)();
|
||||
|
||||
// Check that Object.prototype.toString wasn't modified
|
||||
expect(Object.prototype.toString).toBe(originalToString);
|
||||
expect(globalThis.withHacked).toBe(before);
|
||||
} catch (e) {
|
||||
// If it throws, also check that nothing was modified
|
||||
expect(Object.prototype.toString).toBe(originalToString);
|
||||
expect(globalThis.withHacked).toBe(before);
|
||||
} finally {
|
||||
// Restore just in case
|
||||
Object.prototype.toString = originalToString;
|
||||
}
|
||||
});
|
||||
|
||||
test("Eval attack should be contained", () => {
|
||||
const before = globalThis.evalHacked;
|
||||
|
||||
const parsingContext = vm.createContext({});
|
||||
|
||||
try {
|
||||
vm.compileFunction("return eval('globalThis.evalHacked = true;');", [], { parsingContext })();
|
||||
expect(globalThis.evalHacked).toBe(before);
|
||||
} catch (e) {
|
||||
expect(globalThis.evalHacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
// Additional tests for other potential vulnerabilities
|
||||
|
||||
test("Octal escape sequence attack should not break out", () => {
|
||||
const before = globalThis.octalHacked;
|
||||
|
||||
try {
|
||||
const result = vm.compileFunction("return '\\012'; globalThis.octalHacked = true; //")();
|
||||
expect(result).toBe("\n");
|
||||
expect(globalThis.octalHacked).toBe(before);
|
||||
} catch (e) {
|
||||
expect(globalThis.octalHacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
test("Unicode escape sequence attack should not break out", () => {
|
||||
const before = globalThis.unicodeHacked;
|
||||
|
||||
try {
|
||||
const result = vm.compileFunction("return '\\u000A'; globalThis.unicodeHacked = true; //")();
|
||||
expect(result).toBe("\n");
|
||||
expect(globalThis.unicodeHacked).toBe(before);
|
||||
} catch (e) {
|
||||
expect(globalThis.unicodeHacked).toBe(before);
|
||||
}
|
||||
});
|
||||
|
||||
test("Attempted syntax error injection should be caught", () => {
|
||||
expect(() => {
|
||||
vm.compileFunction("});\n\n(function() {\nconsole.log(1);\n})();\n\n(function() {");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("Attempted prototype pollution should be contained", () => {
|
||||
const originalHasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
try {
|
||||
vm.compileFunction("Object.prototype.polluted = true; return 'done';")();
|
||||
expect(Object.prototype.polluted).toBeUndefined();
|
||||
} catch (e) {
|
||||
// Throwing is acceptable
|
||||
} finally {
|
||||
// Clean up just in case
|
||||
delete Object.prototype.polluted;
|
||||
Object.prototype.hasOwnProperty = originalHasOwnProperty;
|
||||
}
|
||||
});
|
||||
|
||||
test("Attempted global object access should be contained", () => {
|
||||
try {
|
||||
const result = vm.compileFunction("return this;")();
|
||||
// The "this" inside the function should not be the global object
|
||||
expect(result).not.toBe(globalThis);
|
||||
} catch (e) {
|
||||
// Throwing is also acceptable
|
||||
expect(e).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
type TestRunInContextArg =
|
||||
@@ -468,11 +639,12 @@ throw new Error("hello");
|
||||
}
|
||||
|
||||
expect(err!.stack!.replaceAll("\r\n", "\n").replaceAll(import.meta.path, "<this-url>")).toMatchInlineSnapshot(`
|
||||
"Error: hello
|
||||
at hellohello.js:2:16
|
||||
at runInNewContext (unknown)
|
||||
at <anonymous> (<this-url>:459:5)"
|
||||
`);
|
||||
"evalmachine.<anonymous>:2
|
||||
Error: hello
|
||||
at hellohello.js:2:16
|
||||
at runInNewContext (unknown)
|
||||
at <anonymous> (<this-url>:630:5)"
|
||||
`);
|
||||
});
|
||||
|
||||
test("can get sourceURL inside node:vm", () => {
|
||||
@@ -491,14 +663,14 @@ hello();
|
||||
);
|
||||
|
||||
expect(err.replaceAll("\r\n", "\n").replaceAll(import.meta.path, "<this-url>")).toMatchInlineSnapshot(`
|
||||
"4 | return Bun.inspect(new Error("hello"));
|
||||
^
|
||||
error: hello
|
||||
at hello (hellohello.js:4:24)
|
||||
at hellohello.js:7:6
|
||||
at <anonymous> (<this-url>:479:15)
|
||||
"
|
||||
`);
|
||||
"4 | return Bun.inspect(new Error("hello"));
|
||||
^
|
||||
error: hello
|
||||
at hello (hellohello.js:4:24)
|
||||
at hellohello.js:7:6
|
||||
at <anonymous> (<this-url>:651:15)
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
test("eval sourceURL is correct", () => {
|
||||
@@ -515,12 +687,12 @@ hello();
|
||||
`,
|
||||
);
|
||||
expect(err.replaceAll("\r\n", "\n").replaceAll(import.meta.path, "<this-url>")).toMatchInlineSnapshot(`
|
||||
"4 | return Bun.inspect(new Error("hello"));
|
||||
^
|
||||
error: hello
|
||||
at hello (hellohello.js:4:24)
|
||||
at eval (hellohello.js:7:6)
|
||||
at <anonymous> (<this-url>:505:15)
|
||||
"
|
||||
`);
|
||||
"4 | return Bun.inspect(new Error("hello"));
|
||||
^
|
||||
error: hello
|
||||
at hello (hellohello.js:4:24)
|
||||
at eval (hellohello.js:7:6)
|
||||
at <anonymous> (<this-url>:677:15)
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user