├── native-example ├── .gitignore ├── package.json ├── binding.gyp ├── README.md ├── usage.js └── example.cc ├── isolated-vm.js ├── tests ├── wasm-test.wasm ├── resource-name-crash.js ├── cpp-exceptions.js ├── idle-gc.js ├── timeout.js ├── double-ref.js ├── heap-limit-high.js ├── invalid-context.js ├── uint32.js ├── timer-free.js ├── no-name-callback.js ├── busy-dispose.js ├── disposed-isolate-context.js ├── heap-limit-array-spread.js ├── isolate-in-isolate.js ├── lib │ └── v8-version.js ├── self-async-dispose.js ├── context-leak.js ├── context-spam.js ├── parser-overflow.js ├── orphaned-isolate-gc.js ├── recursive-sync.js ├── ignored-async.js ├── sync-allowed-in-same-isolate.js ├── thrown-exception.js ├── dispose-while-running.js ├── cleanup-race.js ├── module-throws.js ├── run.js ├── heap-limit-json-abuse.js ├── large-strings.js ├── worker-terminate.js ├── uv-awaken-from-non-default.js ├── async-rentry-deadlock.js ├── catastrophic-error.js ├── module-meta.js ├── worker-reload.js ├── microtask-timeout.js ├── syntax-lines.js ├── wasm-test.js ├── apply-sync-promise.js ├── context-release.js ├── heap-stats.js ├── manual │ └── cb-hang.js ├── hrtime.js ├── sync-error-deadlock.js ├── heap-limit-copy-into.js ├── cross-context-instance.js ├── heap-limit-incremental-sweep.js ├── immutable.js ├── timeout-fn.js ├── dispose-race.js ├── heap-limit-async.js ├── neverending-gc.js ├── release-option.js ├── deadlock.js ├── array-close-to-limit.js ├── heap-limit-nested.js ├── workers-threads.js ├── promise-error.js ├── shared-array-buffer.js ├── async-rentry.js ├── cpu-wall-timer.js ├── cached-data.js ├── transfer-options.js ├── reference.js ├── array-buffer-copy.js ├── cpu-profiler.js └── external-copy-strings.js ├── .prebuildrc ├── include.js ├── .gitignore ├── .dockerignore ├── .npmignore ├── .vscode ├── tasks.json └── launch.json ├── Dockerfile.alpine ├── Dockerfile.debian ├── src ├── isolate │ ├── node_wrapper.h │ ├── transferable.h │ ├── v8_inspector_wrapper.h │ ├── v8_version.h │ ├── platform_delegate.cc │ ├── specific.h │ ├── runnable.h │ ├── allocator.h │ ├── stack_trace.h │ ├── util.h │ ├── generic │ │ ├── read_option.h │ │ ├── array.h │ │ └── error.h │ ├── platform_delegate.h │ ├── external.h │ ├── holder.h │ ├── inspector.h │ ├── holder.cc │ ├── cpu_profile_manager.h │ ├── functor_runners.h │ ├── strings.h │ ├── allocator_nortti.cc │ └── three_phase_task.h ├── module │ ├── lib_handle.h │ ├── session_handle.h │ ├── script_handle.h │ ├── context_handle.h │ ├── transferable.h │ ├── native_module_handle.h │ ├── callback.h │ ├── isolate_handle.h │ ├── external_copy_handle.h │ ├── module_handle.h │ ├── lib_handle.cc │ ├── reference_handle.h │ ├── script_handle.cc │ ├── evaluation.h │ ├── evaluation.cc │ └── native_module_handle.cc ├── external_copy │ ├── error.h │ ├── string.h │ ├── serializer_nortti.cc │ ├── serializer.cc │ ├── external_copy.h │ └── string.cc ├── lib │ ├── thread_pool.h │ ├── timer.h │ ├── covariant.h │ ├── suspend.h │ └── thread_pool.cc └── isolated_vm.h ├── LICENSE ├── CHANGELOG.md ├── .clang-tidy ├── package.json ├── ISSUE_TEMPLATE.md ├── .github └── workflows │ ├── build.yml │ └── prebuild.yml ├── test.js ├── inspector-example.js └── binding.gyp /native-example/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /isolated-vm.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./out/isolated_vm').ivm; 2 | -------------------------------------------------------------------------------- /tests/wasm-test.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laverdet/isolated-vm/HEAD/tests/wasm-test.wasm -------------------------------------------------------------------------------- /.prebuildrc: -------------------------------------------------------------------------------- 1 | { 2 | "target": [ 3 | "22.17.0", 4 | "24.4.0" 5 | ], 6 | "strip": true 7 | } 8 | -------------------------------------------------------------------------------- /include.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | console.log(path.join(__dirname, 'src')); 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | out/ 5 | .cache/ 6 | compile_commands.json 7 | /prebuilds/ 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .dockerignore 4 | Dockerfile* 5 | examples 6 | node_modules 7 | out 8 | prebuilds 9 | spec 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test.js 2 | tests 3 | out/ 4 | build/ 5 | prebuild/ 6 | .github/ 7 | .prebuildrc 8 | .travis.yml 9 | .vscode/ 10 | *.md 11 | -------------------------------------------------------------------------------- /tests/resource-name-crash.js: -------------------------------------------------------------------------------- 1 | const { Isolate } = require('isolated-vm'); 2 | const isolate = new Isolate({ memoryLimit: 8 }); 3 | const ctx = isolate.createContextSync(); 4 | ctx.eval('~'.repeat(1e6)).catch(() => console.log('pass')); 5 | -------------------------------------------------------------------------------- /tests/cpp-exceptions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const ivm = require('isolated-vm'); 3 | const assert = require('assert'); 4 | assert.throws(() => ivm.ExternalCopy({})); 5 | assert.throws(() => new ivm.Isolate('hello')); 6 | console.log('pass'); 7 | -------------------------------------------------------------------------------- /tests/idle-gc.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const isolate = new ivm.Isolate({ memoryLimit: 128 }); 3 | const context = isolate.createContextSync(); 4 | const jail = context.global; 5 | setTimeout(() => console.log('pass'), 15000); 6 | -------------------------------------------------------------------------------- /tests/timeout.js: -------------------------------------------------------------------------------- 1 | let ivm = require("isolated-vm"); 2 | let isolate = new ivm.Isolate; 3 | 4 | let context; 5 | isolate.createContext().then(val => context = val); 6 | setTimeout(function() { 7 | console.log(context ? 'pass' : 'fail'); 8 | }, 100); 9 | -------------------------------------------------------------------------------- /tests/double-ref.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let ref = new ivm.Reference('hello'); 4 | (new ivm.Reference(function(a, b) { 5 | a.release(); 6 | b.deref(); 7 | console.log('pass'); 8 | })).applySync(undefined, [ ref, ref ]); 9 | -------------------------------------------------------------------------------- /tests/heap-limit-high.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | 3 | let isolate = new ivm.Isolate({ memoryLimit: 10 * 1024 * 1024 }); 4 | let context = isolate.createContextSync(); 5 | 6 | isolate.compileScriptSync(``).runSync(context); 7 | console.log('pass'); 8 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ { 4 | "label": "node-gyp: build", 5 | "type": "shell", 6 | "command": "node-gyp", 7 | "args": [ "build", "-j", "8" ], 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | } 12 | } ] 13 | } -------------------------------------------------------------------------------- /Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine3.20 AS build 2 | WORKDIR /isolated-vm 3 | RUN apk add g++ make python3 4 | COPY . . 5 | RUN npm install --ignore-scripts 6 | RUN MAKEFLAGS=-j$(nproc) npx -y prebuild 7 | 8 | FROM scratch 9 | COPY --from=build /isolated-vm/prebuilds . 10 | -------------------------------------------------------------------------------- /tests/invalid-context.js: -------------------------------------------------------------------------------- 1 | let ivm = require('isolated-vm'); 2 | let isolate1 = new ivm.Isolate(); 3 | let isolate2 = new ivm.Isolate(); 4 | 5 | try { 6 | isolate1.compileScriptSync('1').runSync(isolate2.createContextSync()); 7 | } catch (err) { 8 | console.log('pass'); 9 | } 10 | -------------------------------------------------------------------------------- /tests/uint32.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const ivm = require('isolated-vm'); 3 | const assert = require('assert'); 4 | let isolate = new ivm.Isolate; 5 | let context = isolate.createContextSync(); 6 | assert.equal(0xdeadbeef, isolate.compileScriptSync('0xdeadbeef').runSync(context)); 7 | console.log('pass'); 8 | -------------------------------------------------------------------------------- /tests/timer-free.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | for (let ii = 0; ii < 500; ++ii) { 4 | const isolate = new ivm.Isolate(); 5 | const context = isolate.createContextSync(); 6 | isolate.compileScriptSync('1').runSync(context, { timeout: 100 }); 7 | isolate.dispose(); 8 | } 9 | console.log('pass'); 10 | -------------------------------------------------------------------------------- /native-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "native-example", 3 | "version": "1.0.0", 4 | "main": "build/Release/isolated-native-example", 5 | "scripts": { 6 | "install": "node-gyp rebuild --release" 7 | }, 8 | "license": "ISC", 9 | "gypfile": true, 10 | "dependencies": { 11 | "nan": "^2.10.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/no-name-callback.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const js = `Function.prototype.bind(Function.call, Object.prototype.hasOwnProperty);`; 3 | const isolate = new ivm.Isolate(); 4 | const context = isolate.createContextSync(); 5 | const script = isolate.compileScriptSync(js); 6 | script.runSync(context); 7 | console.log('pass'); 8 | -------------------------------------------------------------------------------- /tests/busy-dispose.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | let task = isolate.createContext(); 5 | isolate.dispose(); 6 | function done() { 7 | try { 8 | isolate.dispose(); 9 | } catch (err) { 10 | console.log('pass'); 11 | } 12 | } 13 | task.then(done); 14 | task.catch(done); 15 | -------------------------------------------------------------------------------- /tests/disposed-isolate-context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | let isolate = new ivm.Isolate; 5 | let context = isolate.createContextSync(); 6 | let script = isolate.compileScriptSync('1'); 7 | isolate.dispose(); 8 | try { 9 | script.runSync(context); 10 | } catch (err) { 11 | console.log('pass'); 12 | } 13 | -------------------------------------------------------------------------------- /tests/heap-limit-array-spread.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate({ memoryLimit: 128 }); 4 | let context = isolate.createContextSync(); 5 | assert.throws(function() { 6 | isolate.compileScriptSync(`[...'.'.repeat(1e9)]; undefined`).runSync(context); 7 | }); 8 | console.log('pass'); 9 | -------------------------------------------------------------------------------- /tests/isolate-in-isolate.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | 3 | const isolate = new ivm.Isolate; 4 | const context = isolate.createContextSync(); 5 | context.global.setSync('ivm', ivm); 6 | context.eval(` 7 | const isolate = new ivm.Isolate; 8 | isolate.createContext(); 9 | `).then(() => console.log('pass'), err => console.error(err)); 10 | -------------------------------------------------------------------------------- /Dockerfile.debian: -------------------------------------------------------------------------------- 1 | FROM node:22-bullseye-slim AS build 2 | WORKDIR /isolated-vm 3 | RUN apt-get update && \ 4 | apt-get install -y --no-install-recommends \ 5 | g++ \ 6 | make \ 7 | python3 8 | COPY . . 9 | RUN npm install --ignore-scripts 10 | RUN MAKEFLAGS=-j$(nproc) npx -y prebuild 11 | FROM scratch 12 | COPY --from=build /isolated-vm/prebuilds . 13 | -------------------------------------------------------------------------------- /tests/lib/v8-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | V8_AT_LEAST(major, minor, patch) { 4 | let versions = process.versions.v8.split(/\./g).map(Number); 5 | return (major < versions[0]) || 6 | (major === versions[0] && minor < versions[1]) || 7 | (major === versions[0] && minor === versions[1] && patch <= versions[2]); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/self-async-dispose.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | 3 | const isolate = new ivm.Isolate(); 4 | const context = isolate.createContextSync(); 5 | context.global.setSync("getProm", () => {}, { reference: true }); 6 | context.eval("getProm.applySync()").catch(() => console.log('pass')); 7 | context.release(); 8 | process.nextTick(() => {}); 9 | isolate.dispose(); 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ { 4 | "name": "Launch", 5 | "type": "lldb", 6 | "request": "launch", 7 | "program": "node_g", 8 | "args": [ "--no-node-snapshot", "${input:entry}" ], 9 | } ], 10 | "inputs": [ { 11 | "id": "entry", 12 | "type": "promptString", 13 | "default": ".js", 14 | "description": "File name", 15 | } ], 16 | } 17 | -------------------------------------------------------------------------------- /src/isolate/node_wrapper.h: -------------------------------------------------------------------------------- 1 | // node.h has some deprecated v8 calls which blow up with warnings 2 | #pragma once 3 | #ifdef __clang__ 4 | #pragma clang system_header 5 | #include 6 | #elif __GNUC__ 7 | #pragma GCC system_header 8 | #include 9 | #elif _MSC_VER 10 | #pragma warning(push, 0) 11 | #include 12 | #pragma warning(pop) 13 | #else 14 | #include 15 | #endif 16 | -------------------------------------------------------------------------------- /src/isolate/transferable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace ivm { 5 | 6 | class Transferable { 7 | public: 8 | Transferable() = default; 9 | Transferable(const Transferable&) = delete; 10 | auto operator= (const Transferable&) = delete; 11 | virtual ~Transferable() = default; 12 | virtual auto TransferIn() -> v8::Local = 0; 13 | }; 14 | 15 | } // namespace ivm 16 | -------------------------------------------------------------------------------- /tests/context-leak.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let snapshot = ivm.Isolate.createSnapshot([ 4 | { code: "function fn(){};" } 5 | ]); 6 | let isolate = new ivm.Isolate({ memoryLimit: 32, snapshot }); 7 | for (let ii = 0; ii < 500; ++ii) { 8 | let context = isolate.createContextSync(); 9 | let global = context.global; 10 | context.release(); 11 | } 12 | console.log('pass'); 13 | -------------------------------------------------------------------------------- /tests/context-spam.js: -------------------------------------------------------------------------------- 1 | let ivm = require('isolated-vm'); 2 | 3 | let isolate = new ivm.Isolate({ memoryLimit: 32 }); 4 | try { 5 | while (true) { 6 | isolate.createContextSync(); 7 | } 8 | } catch (err) {} 9 | 10 | (async function () { 11 | let isolate = new ivm.Isolate({ memoryLimit: 32 }); 12 | while (true) { 13 | await isolate.createContext(); 14 | } 15 | }()).catch(() => console.log('pass')); 16 | -------------------------------------------------------------------------------- /tests/parser-overflow.js: -------------------------------------------------------------------------------- 1 | const { V8_AT_LEAST } = require('./lib/v8-version'); 2 | const assert = require('assert'); 3 | const ivm = require('isolated-vm'); 4 | 5 | const isolate = new ivm.Isolate; 6 | const context = isolate.createContextSync(); 7 | context.evalSync('eval("1".repeat(40).repeat(1e3))'); 8 | assert.throws(() => context.evalSync('eval("1".repeat(40).repeat(1e7))')); 9 | console.log('pass'); 10 | -------------------------------------------------------------------------------- /tests/orphaned-isolate-gc.js: -------------------------------------------------------------------------------- 1 | // node-args: --expose-gc 2 | const ivm = require('isolated-vm'); 3 | let isolate = Array(1000).fill().map(() => new ivm.Isolate()); 4 | gc(); 5 | let rss1 = process.memoryUsage().rss; 6 | isolate = null; 7 | gc(); 8 | let rss2 = process.memoryUsage().rss; 9 | if (rss1 - rss2 > 20 * 1024 * 1024) { 10 | console.log('pass'); 11 | } else { 12 | console.log(`${rss1} - ${rss2} = ${rss1 - rss2}`); 13 | } 14 | -------------------------------------------------------------------------------- /tests/recursive-sync.js: -------------------------------------------------------------------------------- 1 | let ivm = require('isolated-vm'); 2 | let isolate = new ivm.Isolate; 3 | let context = isolate.createContextSync(); 4 | let global = context.global; 5 | 6 | function recur1() { 7 | return global.getSync('recur2')(); 8 | } 9 | 10 | global.setSync('recur1', new ivm.Reference(recur1)); 11 | 12 | console.log(isolate.compileScriptSync(` 13 | function recur2() { 14 | return 'pass'; 15 | } 16 | recur1.applySync(undefined, []); 17 | `).runSync(context)); 18 | -------------------------------------------------------------------------------- /src/isolate/v8_inspector_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "node_wrapper.h" 3 | #include "./v8_version.h" 4 | #if V8_AT_LEAST(13, 6, 233) 5 | #include "v8_inspector/nodejs_v24.0.0.h" 6 | #elif V8_AT_LEAST(12, 4, 254) 7 | #include "v8_inspector/nodejs_v22.0.0.h" 8 | #elif V8_AT_LEAST(11, 3, 244) 9 | #include "v8_inspector/nodejs_v20.0.0.h" 10 | #elif V8_AT_LEAST(10, 2, 154) 11 | #include "v8_inspector/nodejs_v18.3.0.h" 12 | #else 13 | #include "v8_inspector/nodejs_v18.0.0.h" 14 | #endif 15 | -------------------------------------------------------------------------------- /tests/ignored-async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | (async function() { 5 | let context = await isolate.createContext(); 6 | let script = await isolate.compileScript('foo + 1 == bar ? "pass" : "fail"'); 7 | let global = context.global; 8 | if (global.setIgnored('foo', 1) || global.setIgnored('bar', 2)) { 9 | console.log('fail'); 10 | } 11 | console.log(await script.run(context)); 12 | }()).catch(console.error); 13 | 14 | -------------------------------------------------------------------------------- /tests/sync-allowed-in-same-isolate.js: -------------------------------------------------------------------------------- 1 | // Create a new isolate limited to 128MB 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | 5 | let context = isolate.createContextSync(); 6 | let jail = context.global; 7 | jail.setSync('context', context); 8 | jail.setSync('isolate', isolate); 9 | isolate.compileScriptSync('new '+ function() { 10 | if (isolate.compileScriptSync('1').runSync(context) !== 1) { 11 | throw new Error; 12 | } 13 | }).runSync(context); 14 | console.log('pass'); 15 | 16 | -------------------------------------------------------------------------------- /tests/thrown-exception.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const cp = require('child_process'); 3 | const arg = '--the-arg'; 4 | if (process.argv[2] === arg) { 5 | const isolate = new ivm.Isolate; 6 | isolate.compileScriptSync('*'); 7 | } else { 8 | cp.execFile(process.execPath, [ '--no-node-snapshot', __filename, arg ], { stdio: 'ignore' }, function(err, stdout, stderr) { 9 | if (err.code !== 1 || err.signal || err.killed) { 10 | console.log(err); 11 | } else { 12 | console.log('pass'); 13 | } 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /native-example/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'isolated-native-example', 5 | 'cflags_cc!': [ '-fno-exceptions' ], 6 | 'xcode_settings': { 7 | 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 8 | }, 9 | 'msvs_settings': { 10 | 'VCCLCompilerTool': { 11 | 'ExceptionHandling': '1', 12 | }, 13 | }, 14 | 'include_dirs': [ 15 | ' { 5 | const isolate = new ivm.Isolate; 6 | const context = await isolate.createContext(); 7 | await context.evalClosure( 8 | 'wait = ms => $0.applySync(undefined, [ ms ], { result: { promise: true } })', 9 | [ ms => new Promise(resolve => setTimeout(resolve, ms)) ], 10 | { arguments: { reference: true } }, 11 | ); 12 | await context.eval('wait(100)'); 13 | process.nextTick(() => { 14 | gc(); 15 | console.log('pass'); 16 | }); 17 | })(); 18 | -------------------------------------------------------------------------------- /tests/module-throws.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | function rejects(fn) { 3 | return fn().then(() => { throw new Error('Promise did not reject') }).catch(() => 0); 4 | } 5 | 6 | (async function() { 7 | const isolate = new ivm.Isolate(); 8 | const context = await isolate.createContext(); 9 | const module = await isolate.compileModule(`import foo from 'foo'`); 10 | await rejects(() => module.instantiate(context, () => Promise.reject(new Error))); 11 | await rejects(() => module.evaluate()); 12 | console.log('pass'); 13 | })().catch(console.error); 14 | -------------------------------------------------------------------------------- /tests/run.js: -------------------------------------------------------------------------------- 1 | // For some reason this simple case triggered a bug that was due to unexpected ordering of static 2 | // member destructors. 3 | let ivm = require('isolated-vm'); 4 | let isolate = new ivm.Isolate; 5 | let context = isolate.createContextSync(); 6 | let script = isolate.compileScriptSync('1'); 7 | script.runSync(context, { release: true }); 8 | try { 9 | script.runSync(context); 10 | } catch (err) { 11 | let script = isolate.compileScriptSync('1'); 12 | script.release(); 13 | try { 14 | script.runSync(context); 15 | } catch (err) { 16 | console.log('pass'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/isolate/v8_version.h: -------------------------------------------------------------------------------- 1 | #include "node_wrapper.h" 2 | #define V8_AT_LEAST(major, minor, patch) (\ 3 | V8_MAJOR_VERSION > (major) || \ 4 | (V8_MAJOR_VERSION == (major) && V8_MINOR_VERSION > (minor)) || \ 5 | (V8_MAJOR_VERSION == (major) && V8_MINOR_VERSION == (minor) && V8_BUILD_NUMBER >= (patch)) \ 6 | ) 7 | 8 | #ifdef NODE_MODULE_VERSION 9 | #define NODE_MODULE_OR_V8_AT_LEAST(nodejs, v8_major, v8_minor, v8_build) (NODE_MODULE_VERSION >= (nodejs)) 10 | #else 11 | #define NODE_MODULE_OR_V8_AT_LEAST(nodejs, v8_major, v8_minor, v8_build) (V8_AT_LEAST(v8_major, v8_minor, v8_build)) 12 | #endif 13 | -------------------------------------------------------------------------------- /tests/heap-limit-json-abuse.js: -------------------------------------------------------------------------------- 1 | let ivm = require('isolated-vm'); 2 | let isolate = new ivm.Isolate({ memoryLimit: 32 }); 3 | let context = isolate.createContextSync(); 4 | let global = context.global; 5 | let count = Math.floor(1024 * 1024 * 25 / 12); 6 | global.setSync('blob', new ivm.ExternalCopy({ val: `[${Array(count).fill('"aa"').join(',')}]` }).copyInto()); 7 | 8 | try { 9 | isolate.compileScriptSync('new '+ function() { 10 | let storage = []; 11 | for (;;) { 12 | storage.push(JSON.parse(blob.val)); 13 | } 14 | }).runSync(context); 15 | } catch (err) { 16 | console.log('pass'); 17 | } 18 | -------------------------------------------------------------------------------- /tests/large-strings.js: -------------------------------------------------------------------------------- 1 | if (process.platform === 'win32') { 2 | console.log('pass'); 3 | return; 4 | } 5 | const ivm = require('isolated-vm'); 6 | const isolate = new ivm.Isolate; 7 | const context = isolate.createContextSync(); 8 | const timeout = setTimeout(() => { 9 | console.log('fail'); 10 | process.exit(1); 11 | }, 1000); 12 | (async function() { 13 | try { 14 | await context.eval('/1+2/.test("1".repeat(1024 ** 2 * 127));', { timeout: 100 }); 15 | console.log('did not fail'); 16 | } catch (err) {} 17 | clearTimeout(timeout); 18 | console.log('pass'); 19 | })().catch(console.error); 20 | 21 | -------------------------------------------------------------------------------- /tests/worker-terminate.js: -------------------------------------------------------------------------------- 1 | if (true || process.versions.modules < 72) { 2 | // Disabled 3 | console.log('pass'); 4 | process.exit(); 5 | } 6 | const ivm = require('isolated-vm'); 7 | const { Worker, isMainThread, parentPort } = require('worker_threads'); 8 | if (isMainThread) { 9 | const worker = new Worker(__filename); 10 | worker.on('message', () => { worker.terminate() }); 11 | process.on('exit', () => console.log('pass')); 12 | } else { 13 | const isolate = new ivm.Isolate; 14 | const context = isolate.createContextSync(); 15 | context.eval('for(;;);'); 16 | parentPort.postMessage(''); 17 | } 18 | -------------------------------------------------------------------------------- /tests/uv-awaken-from-non-default.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const assert = require('assert'); 3 | 4 | (async () => { 5 | const isolate = new ivm.Isolate; 6 | const context = isolate.createContextSync(); 7 | context.global.set('isolate', isolate); 8 | context.evalIgnored('const d = Date.now(); while (Date.now() < d + 500); isolate.dispose();'); 9 | await new Promise(resolve => setTimeout(resolve, 250)); 10 | try { 11 | await isolate.createContext(); 12 | } catch (err) { 13 | assert.ok(/disposed/.test(err.message)); 14 | } 15 | console.log('pass'); 16 | })().catch(console.log); 17 | -------------------------------------------------------------------------------- /native-example/README.md: -------------------------------------------------------------------------------- 1 | `isolated-vm` supports loading native compiled modules. In this directory is an example module to 2 | get you started. Your code must be isolate-aware, context-aware, and thread-safe. Using `nan` 3 | [Native Abstractions for Node] as demonstrated here will get you most of the way there since all 4 | those functions are isolate and context-aware. Sometimes existing native modules can be modified 5 | to support `isolated-vm` with very few changes at all. 6 | 7 | You should *not* use libuv in `isolated-vm`, except in the default isolate. Also, asynchronous 8 | callbacks are not supported except for the default isolate. 9 | -------------------------------------------------------------------------------- /tests/async-rentry-deadlock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | function makeIsolate() { 5 | let isolate = new ivm.Isolate; 6 | let context = isolate.createContextSync(); 7 | let global = context.global; 8 | return { isolate, context, global }; 9 | } 10 | 11 | { 12 | // Basic test.. tests unlocking 13 | let env = makeIsolate(); 14 | env.global.setSync('fn', new ivm.Reference(function() {})); 15 | env.isolate.compileScriptSync('fn.applySync(undefined, []);').runSync(env.context); 16 | 17 | let d = Date.now(); 18 | while (Date.now() < d + 5); 19 | env.global.setSync('dummy', 1); 20 | } 21 | 22 | console.log('pass'); 23 | -------------------------------------------------------------------------------- /tests/catastrophic-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Uhh this one you kind of have to just test yourself. Comment out the `process.exit` and check 3 | // activity monitor to see total CPU 4 | const ivm = require('isolated-vm'); 5 | console.log('pass'); 6 | process.exit(); 7 | 8 | for (const run of [ 9 | context => context.eval('ivm.lib.testHang()', { timeout: 100 }), 10 | context => context.eval('ivm.lib.testOOM()'), 11 | ]) { 12 | const isolate = new ivm.Isolate({ 13 | memoryLimit: 128, 14 | onCatastrophicError: message => { 15 | console.log(message); 16 | }, 17 | }); 18 | const context = isolate.createContextSync(); 19 | context.global.setSync('ivm', ivm); 20 | run(context); 21 | } 22 | -------------------------------------------------------------------------------- /tests/module-meta.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const assert = require('assert'); 3 | 4 | (async function() { 5 | const isolate = new ivm.Isolate(); 6 | const context = isolate.createContextSync(); 7 | 8 | assert.throws(() => 9 | isolate.compileModuleSync('export default import.meta.url', { meta: meta => { meta.url = 'fail' } })); 10 | 11 | console.log(context.evalClosureSync(` 12 | const module = $0.compileModuleSync('export default import.meta.url', { meta: meta => { meta.url = 'pass' } }); 13 | module.instantiateSync($1, () => {}); 14 | module.evaluateSync(); 15 | return module.namespace.deref().default; 16 | `, [ isolate, context ])); 17 | })().catch(console.error); 18 | -------------------------------------------------------------------------------- /tests/worker-reload.js: -------------------------------------------------------------------------------- 1 | const { Worker } = require('worker_threads'); 2 | 3 | if (process.platform === 'win32') { 4 | // unloading the module segfaults on Windows, skip for now 5 | console.log('pass'); 6 | process.exit(); 7 | } 8 | 9 | (async function () { 10 | for (let i = 0; i < 2; i++) { 11 | const worker = new Worker("require('isolated-vm')", { eval: true }); 12 | await new Promise((resolve, reject) => { 13 | worker.on('exit', code => code ? reject(new Error(`Worker exited with code: ${code}`)) : resolve()); 14 | worker.on('error', error => reject(error)); 15 | }); 16 | } 17 | console.log('pass') 18 | })().catch(console.error); 19 | -------------------------------------------------------------------------------- /tests/microtask-timeout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | (async function() { 5 | let context = await isolate.createContext(); 6 | let script = await isolate.compileScript(` 7 | function bad() { 8 | Promise.resolve().then(function() { 9 | bad(); 10 | for(;;); 11 | }); 12 | } 13 | bad(); 14 | for(;;); 15 | `); 16 | try { 17 | await script.run(context, { timeout: 100 }); 18 | } catch (err) { 19 | if (!/timed out/.test(err.message)) { 20 | throw err; 21 | } 22 | } 23 | // This flushes the microtask queue so if any remain from above this would spin forever 24 | // Broken (!!) 25 | // await isolate.createContext(); 26 | console.log('pass'); 27 | })().catch(console.error); 28 | -------------------------------------------------------------------------------- /tests/syntax-lines.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | function check(name, fn) { 5 | try { 6 | fn(); 7 | } catch (err) { 8 | if (err.message.indexOf(name) === -1) { 9 | console.log(err); 10 | console.log('fail', name); 11 | throw new Error; 12 | } 13 | } 14 | } 15 | [ 16 | [ 'test1', () => isolate.compileScriptSync('**', { filename: 'test1.js' }) ], 17 | [ 'test2', () => ivm.Isolate.createSnapshot([ { code: '**', filename: 'test2.js' } ]) ], 18 | [ 'isolated-vm', () => ivm.Isolate.createSnapshot([ { code: '1' } ], '**') ], 19 | [ 'test3', () => isolate.compileModuleSync('**', { filename: 'test3.js' }) ], 20 | ].map(val => check(val[0], val[1])); 21 | console.log('pass'); 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Marcel Laverdet 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /src/isolate/platform_delegate.cc: -------------------------------------------------------------------------------- 1 | #include "platform_delegate.h" 2 | 3 | namespace ivm { 4 | namespace { 5 | PlatformDelegate delegate; 6 | } 7 | 8 | void PlatformDelegate::InitializeDelegate() { 9 | auto context = v8::Isolate::GetCurrent()->GetCurrentContext(); 10 | auto* node_platform = node::GetMultiIsolatePlatform(node::GetCurrentEnvironment(context)); 11 | delegate = PlatformDelegate{node_platform}; 12 | } 13 | 14 | void PlatformDelegate::RegisterIsolate(v8::Isolate* isolate, node::IsolatePlatformDelegate* isolate_delegate) { 15 | delegate.node_platform->RegisterIsolate(isolate, isolate_delegate); 16 | } 17 | 18 | void PlatformDelegate::UnregisterIsolate(v8::Isolate* isolate) { 19 | delegate.node_platform->UnregisterIsolate(isolate); 20 | } 21 | 22 | } // namespace ivm 23 | -------------------------------------------------------------------------------- /tests/wasm-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('pass'); 3 | // Spooky error! 4 | /* 5 | const ivm = require('isolated-vm'); 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const binary = new Uint8Array(fs.readFileSync(path.join(__dirname, 'wasm-test.wasm'))); 9 | const wasm = new WebAssembly.Module(binary); 10 | 11 | const isolate = new ivm.Isolate; 12 | const context = isolate.createContextSync(); 13 | context.global.setSync('wasm', wasm, { copy: true }); 14 | const result = context.evalSync(` 15 | const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }); 16 | const wasmExports = (new WebAssembly.Instance(wasm, { env: { memory } })).exports; 17 | wasmExports.foo(); 18 | `); 19 | if (result === 123) { 20 | console.log('pass'); 21 | } 22 | */ 23 | -------------------------------------------------------------------------------- /tests/apply-sync-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | (async function() { 4 | const isolate = new ivm.Isolate; 5 | const context = await isolate.createContext(); 6 | 7 | await context.evalClosure( 8 | 'run = () => $0.applySyncPromise(null, [])', 9 | [ 10 | async () => { 11 | isolate.dispose(); 12 | await new Promise(resolve => setTimeout(resolve, 0)); 13 | } 14 | ], 15 | { arguments: { reference: true } }, 16 | ); 17 | 18 | const run = await context.global.get('run', { reference: true }); 19 | try { 20 | await run.apply(null, []); 21 | } catch (err) { 22 | if (err.message !== 'Isolate was disposed during execution') { 23 | throw err; 24 | } 25 | } 26 | console.log('pass'); 27 | }()).catch(console.log); -------------------------------------------------------------------------------- /tests/context-release.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | 3 | (async () => { 4 | let result = ''; 5 | const isolate = new ivm.Isolate; 6 | const context = await isolate.createContext(); 7 | await context.evalClosure(` 8 | save = value => $0.applySync(undefined, [ value ]); 9 | run = () => $1.applySync(undefined, [], { result: { promise: true } }); 10 | `, [ 11 | val => result = val, 12 | () => new Promise(resolve => setTimeout(resolve, 100)), 13 | ], 14 | { arguments: { reference: true } }, 15 | ); 16 | 17 | const job = context.evalSync(` 18 | (async () => { 19 | save('fail'); 20 | await run(); 21 | save('pass'); 22 | })(); 23 | `, { promise: true }, 24 | ); 25 | context.release(); 26 | await job; 27 | console.log(result); 28 | })().catch(console.error); 29 | -------------------------------------------------------------------------------- /tests/heap-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | let size1 = isolate.getHeapStatisticsSync(); 5 | let context = isolate.createContextSync(); 6 | let global = context.global; 7 | global.setSync('global', global.derefInto()); 8 | let script = isolate.compileScriptSync(` 9 | global.data = new Uint8Array(1024); 10 | global.garbage = []; 11 | for (let ii = 0; ii < 100000; ++ii) { 12 | garbage.push(Math.random()); 13 | } 14 | `); 15 | script.runSync(context); 16 | let size2 = isolate.getHeapStatisticsSync(); 17 | if (size1.total_heap_size === size2.total_heap_size) { 18 | console.log('heap didn\'t increase?'); 19 | } else if (size2.externally_allocated_size !== 1024) { 20 | console.log('externally allocated size is wrong'); 21 | } else { 22 | console.log('pass'); 23 | } 24 | -------------------------------------------------------------------------------- /src/module/lib_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | #include "transferable.h" 7 | #include "transferable.h" 8 | 9 | namespace ivm { 10 | 11 | class LibHandle : public TransferableHandle { 12 | private: 13 | class LibTransferable : public Transferable { 14 | public: 15 | auto TransferIn() -> v8::Local final; 16 | }; 17 | 18 | auto Hrtime(v8::MaybeLocal maybe_diff) -> v8::Local; 19 | auto PrivateSymbol(v8::MaybeLocal maybe_name) -> v8::Local; 20 | auto TestHang() -> v8::Local; 21 | auto TestOOM() -> v8::Local; 22 | 23 | public: 24 | static auto Definition() -> v8::Local; 25 | auto TransferOut() -> std::unique_ptr final; 26 | }; 27 | 28 | } // namespace ivm 29 | -------------------------------------------------------------------------------- /src/isolate/specific.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace ivm { 6 | namespace detail { 7 | extern std::atomic IsolateSpecificSize; 8 | } 9 | 10 | /** 11 | * Like thread_local data, but specific to an Isolate instead. 12 | */ 13 | template 14 | class IsolateSpecific { 15 | template 16 | friend class IsolateSpecific; 17 | 18 | public: 19 | IsolateSpecific() = default; 20 | 21 | template 22 | auto Deref(Functor callback) -> v8::Local; 23 | 24 | private: 25 | size_t key{detail::IsolateSpecificSize++}; 26 | union HandleConvert { 27 | explicit HandleConvert(v8::Local data) : data{data} {} 28 | v8::Local data; 29 | v8::Local value; 30 | }; 31 | }; 32 | 33 | } // namespace ivm 34 | 35 | #include "environment.h" 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v4.3.0 2 | - v8 inspector API fixed in nodejs v16.x 3 | - `release` method added to `Module` 4 | 5 | ## v4.2.0 6 | - `accessors` option added to `reference.get()` 7 | 8 | ## v4.1.0 9 | - Support for nodejs v16.x 10 | - `onCatastrophicError` added 11 | - Fix for `null` error thrown from callback 12 | 13 | ## v4.0.0 14 | - `Callback` class addeed. 15 | - When possible, `reference.get()` will return a function delegate instead of a `Reference`. 16 | - `reference.get()` will no longer return inherited properties by default. 17 | - `result` property on `eval` and `evalClosure` has been removed. The result is now just the return 18 | value. 19 | - All `isolated-vm` class prototypes, and most instances are frozen. 20 | - `isolate.cpuTime` and `isolate.wallTime` now return bigints. 21 | - Proxies and accessors are no longer tolerated via `reference.get`, and related functions. 22 | -------------------------------------------------------------------------------- /tests/manual/cb-hang.js: -------------------------------------------------------------------------------- 1 | // Hangs within a couple of minutes 2 | // https://github.com/laverdet/isolated-vm/issues/321 3 | const ivm = require('isolated-vm'); 4 | 5 | const vm = new ivm.Isolate({ 6 | memoryLimit: 2048, 7 | }); 8 | 9 | const cb = new ivm.Callback(() => '123'); 10 | 11 | async function run() { 12 | const context = await vm.createContext(); 13 | 14 | await context.global.set('cb', cb); 15 | 16 | const result = await context.eval('cb()', { 17 | timeout: 5000, 18 | }); 19 | 20 | context.release(); 21 | 22 | return result; 23 | } 24 | 25 | const loop = () => { 26 | let current = 0; 27 | const max = 1000; 28 | for (let i = 0; i < max; i++) { 29 | run().then(() => { 30 | console.log(++current); 31 | 32 | if (current === max) { 33 | setTimeout(loop, 500); 34 | } 35 | }); 36 | } 37 | }; 38 | loop(); 39 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'bugprone-*,clang-*,hicpp-*,misc-*,modernize-*,performance-*,readability-*,-bugprone-easily-swappable-parameters,-hicpp-avoid-c-arrays,-hicpp-no-array-decay,-hicpp-no-malloc,-hicpp-signed-bitwise,-hicpp-use-override,-misc-const-correctness,-misc-no-recursion,-misc-non-private-member-variables-in-classes,-modernize-avoid-c-arrays,-modernize-use-nodiscard,-readability-else-after-return,-readability-magic-numbers,-readability-function-cognitive-complexity' 3 | HeaderFilterRegex: 'src/*' 4 | CheckOptions: 5 | - key: readability-identifier-length.MinimumVariableNameLength 6 | value: '2' 7 | - key: readability-identifier-length.MinimumParameterNameLength 8 | value: '2' 9 | - key: hicpp-special-member-functions.AllowMissingMoveFunctions 10 | value: '1' 11 | - key: hicpp-special-member-functions.AllowSoleDefaultDtor 12 | value: '1' 13 | ... 14 | -------------------------------------------------------------------------------- /src/isolate/runnable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace ivm { 5 | // ivm::Runnable serves the same role as v8::Task but instances of Runnable live and die without v8 6 | // so it might not actually make sense to use a v8 container for the job. An alias is used to avoid 7 | // the adapter which would be needed in the case where a Runnable actually does need to be passed 8 | // off to v8. 9 | using Runnable = v8::Task; 10 | /* 11 | class Runnable { 12 | public: 13 | virtual ~Runnable() = default; 14 | virtual void Run() = 0; 15 | }; 16 | 17 | class TaskHolder : public Runnable { 18 | private: 19 | std::unique_ptr task; 20 | public: 21 | explicit TaskHolder(v8::Task* task) : task{task} {} 22 | explicit TaskHolder(std::unique_ptr task) : task{std::move(task)} {} 23 | TaskHolder(TaskHolder task) noexcept : task{std::move(task.task)} {} 24 | void Run() final { 25 | task->Run(); 26 | } 27 | }; 28 | */ 29 | } // namespace ivm 30 | -------------------------------------------------------------------------------- /src/external_copy/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "external_copy.h" 3 | #include "./string.h" 4 | 5 | namespace ivm { 6 | 7 | /** 8 | * Make a special case for errors so if someone throws then a similar error will come out the other 9 | * side. 10 | */ 11 | class ExternalCopyError : public ExternalCopy { 12 | friend class ExternalCopy; 13 | public: 14 | enum class ErrorType { Error, RangeError, ReferenceError, SyntaxError, TypeError, CustomError }; 15 | 16 | ExternalCopyError( 17 | ErrorType error_type, 18 | ExternalCopyString name, 19 | ExternalCopyString message, 20 | ExternalCopyString stack 21 | ); 22 | ExternalCopyError(ErrorType error_type, const char* message, const std::string& stack = ""); 23 | 24 | auto CopyInto(bool transfer_in = false) -> v8::Local final; 25 | 26 | private: 27 | ErrorType error_type; 28 | ExternalCopyString name; 29 | ExternalCopyString message; 30 | ExternalCopyString stack; 31 | }; 32 | 33 | } // namespace ivm 34 | -------------------------------------------------------------------------------- /src/isolate/allocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "v8_version.h" 5 | 6 | namespace ivm { 7 | 8 | class LimitedAllocator : public v8::ArrayBuffer::Allocator { 9 | private: 10 | class IsolateEnvironment& env; 11 | size_t limit; 12 | size_t v8_heap; 13 | size_t next_check; 14 | int failures = 0; 15 | 16 | public: 17 | auto Check(size_t length) -> bool; 18 | explicit LimitedAllocator(class IsolateEnvironment& env, size_t limit); 19 | auto Allocate(size_t length) -> void* final; 20 | auto AllocateUninitialized(size_t length) -> void* final; 21 | void Free(void* data, size_t length) final; 22 | 23 | // This is used by ExternalCopy when an ArrayBuffer is transferred. The memory is not freed but 24 | // we should no longer count it against the isolate 25 | void AdjustAllocatedSize(ptrdiff_t length); 26 | auto GetFailureCount() const -> int; 27 | void Track(v8::Local handle, size_t size); 28 | }; 29 | 30 | } // namespace ivm 31 | -------------------------------------------------------------------------------- /tests/hrtime.js: -------------------------------------------------------------------------------- 1 | // Create a new isolate limited to 128MB 2 | let ivm = require('isolated-vm'); 3 | let isolate = new ivm.Isolate; 4 | let context = isolate.createContextSync(); 5 | let jail = context.global; 6 | jail.setSync('lib', ivm.lib); 7 | isolate.compileScriptSync('new '+ function() { 8 | let time1 = lib.hrtime(); 9 | let time2 = lib.hrtime(); 10 | if (time1[0] === time2[0]) { 11 | if (time1[1] >= time2[1]) { 12 | throw new Error('nanoseconds wrong'); 13 | } 14 | } else if (time1[0] > time2[0]) { 15 | throw new Error('seconds wrong'); 16 | } 17 | let diff = lib.hrtime(lib.hrtime()); 18 | if (diff[0] !== 0 || diff[1] === 0) { 19 | throw new Error('diff doesnt work?'); 20 | } 21 | }).runSync(context); 22 | let time1 = ivm.lib.hrtime(); 23 | let time2 = process.hrtime(); 24 | let diff = (time2[0] * 1e9 + time2[1]) - (time1[0] * 1e9 + time1[1]); 25 | if (diff <= 0 || diff > 5000000) { // 5ms 26 | console.log('doesnt match process.hrtime', diff); 27 | } 28 | console.log('pass'); 29 | 30 | -------------------------------------------------------------------------------- /tests/sync-error-deadlock.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | 3 | const isolate = new ivm.Isolate(); 4 | 5 | const task = async () => { 6 | const context = await isolate.createContext(); 7 | 8 | await context.global.set("getProm", () => { 9 | throw new Error('test') 10 | }, { reference: true }); 11 | 12 | await context.global.set("pass", () => { 13 | console.log('pass'); 14 | }, { reference: true }); 15 | 16 | 17 | timeout = setTimeout(() => { 18 | console.error('Deadlock check time out'); 19 | process.exitCode = 1; 20 | process.exit(1); 21 | }, 1000); 22 | 23 | await context.eval(` 24 | try { 25 | getProm.applySync(); 26 | } catch { 27 | pass.applySync(); 28 | } 29 | `); 30 | 31 | clearTimeout(timeout); 32 | 33 | context.release(); 34 | } 35 | 36 | task().catch(e => { 37 | console.error(e); 38 | process.exitCode = 1; 39 | process.exit(); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/heap-limit-copy-into.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | let copies = [ 5 | new ivm.ExternalCopy(Array(1024 * 25.5).fill().map(() => 'aa')), // 1mb strings 6 | new ivm.ExternalCopy(Array(1024 * 128).fill().map(() => {})), // 1mb objects 7 | new ivm.ExternalCopy(Array(1024 * 25.5 * 8).fill().map(() => 'aa')), // 8mb strings 8 | new ivm.ExternalCopy(Array(1024 * 128 * 8).fill().map(() => {})), // 8mb objects 9 | new ivm.ExternalCopy(Array(1024 * 25.5 * 32).fill().map(() => 'aa')), // 32mb strings 10 | new ivm.ExternalCopy(Array(1024 * 128 * 32).fill().map(() => {})), // 32mb objects 11 | ]; 12 | 13 | for (let memoryLimit of [ 8, 32, 64, 128 ]) { 14 | for (let copy of copies) { 15 | let isolate = new ivm.Isolate({ memoryLimit }); 16 | let context = isolate.createContextSync(); 17 | let global = context.global; 18 | try { 19 | for (let ii = 0; ii < 256; ++ii) { 20 | global.setSync(ii, copy.copyInto()); 21 | } 22 | } catch (err) {} 23 | } 24 | } 25 | 26 | console.log('pass'); 27 | -------------------------------------------------------------------------------- /src/isolate/stack_trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "class_handle.h" 4 | #include 5 | 6 | namespace ivm { 7 | 8 | /** 9 | * v8::StackTrace doesn't inherit from v8::Value so it can't be set as a property of an object which 10 | * is what we need to do to pass stack traces in an efficent way. So this gets around that. 11 | */ 12 | class StackTraceHolder : public ClassHandle { 13 | private: 14 | static void AppendFileLocation(v8::Isolate* isolate, v8::Local frame, std::stringstream& ss); 15 | public: 16 | v8::Global stack_trace; 17 | explicit StackTraceHolder(v8::Local stack_handle); 18 | static auto Definition() -> v8::Local; 19 | static void AttachStack(v8::Local error, v8::Local stack); 20 | static void ChainStack(v8::Local error, v8::Local stack); 21 | static auto RenderSingleStack(v8::Local stack_trace) -> std::string; 22 | }; 23 | 24 | } // namespace ivm 25 | -------------------------------------------------------------------------------- /tests/cross-context-instance.js: -------------------------------------------------------------------------------- 1 | let ivm = require('isolated-vm'); 2 | let isolate = new ivm.Isolate; 3 | 4 | function makeContext() { 5 | let context = isolate.createContextSync(); 6 | let global = context.global; 7 | global.setSync('ivm', ivm); 8 | isolate.compileScriptSync(` 9 | function makeReference(ref) { 10 | return new ivm.Reference(ref); 11 | } 12 | function isReference(ref) { 13 | return ref instanceof ivm.Reference; 14 | } 15 | `).runSync(context); 16 | return { 17 | makeReference: global.getSync('makeReference'), 18 | isReference: global.getSync('isReference'), 19 | }; 20 | } 21 | 22 | let context1 = makeContext(); 23 | let context2 = makeContext(); 24 | [ context1, context2 ].forEach(context => { 25 | if (!context.isReference(new ivm.Reference({}))) { 26 | console.log('fail1'); 27 | } 28 | if (!context.isReference(context.makeReference({}))) { 29 | console.log('fail2'); 30 | } 31 | }); 32 | if (context1.isReference(context2.makeReference({}).derefInto())) { 33 | console.log('fail3'); 34 | } 35 | console.log('pass'); 36 | -------------------------------------------------------------------------------- /src/external_copy/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "external_copy.h" 3 | #include 4 | #include 5 | 6 | namespace ivm { 7 | 8 | class ExternalCopyString final : public ExternalCopy { 9 | public: 10 | ExternalCopyString() = default; 11 | explicit ExternalCopyString(v8::Local string); 12 | explicit ExternalCopyString(const char* string); 13 | explicit ExternalCopyString(const std::string& string); 14 | ExternalCopyString(const ExternalCopyString&) = delete; 15 | ExternalCopyString(ExternalCopyString&& that) = default; 16 | ~ExternalCopyString() final = default; 17 | auto operator= (const ExternalCopyString&) -> ExternalCopyString& = delete; 18 | auto operator= (ExternalCopyString&& that) noexcept -> ExternalCopyString& = default; 19 | 20 | explicit operator bool() const { return static_cast(value); } 21 | auto CopyInto(bool transfer_in = false) -> v8::Local final; 22 | 23 | private: 24 | std::shared_ptr> value; 25 | bool one_byte = false; 26 | }; 27 | 28 | } // namespace ivm 29 | -------------------------------------------------------------------------------- /src/module/session_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "isolate/class_handle.h" 4 | #include "isolate/inspector.h" 5 | #include 6 | 7 | namespace ivm { 8 | 9 | /** 10 | * This handle is given to the client and encapsulates an inspector session. Non-transferable. 11 | */ 12 | class SessionHandle : public ClassHandle { 13 | private: 14 | std::shared_ptr session; 15 | 16 | public: 17 | using DontFreezePrototype = void; 18 | using DontFreezeInstance = void; 19 | explicit SessionHandle(IsolateEnvironment& isolate); 20 | static auto Definition() -> v8::Local; 21 | 22 | void CheckDisposed(); 23 | auto DispatchProtocolMessage(v8::Local message) -> v8::Local; 24 | auto Dispose() -> v8::Local; 25 | auto OnNotificationGetter() -> v8::Local; 26 | void OnNotificationSetter(v8::Local value); 27 | auto OnResponseGetter() -> v8::Local; 28 | void OnResponseSetter(v8::Local value); 29 | }; 30 | 31 | } // namespace ivm 32 | -------------------------------------------------------------------------------- /tests/heap-limit-incremental-sweep.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | 4 | { 5 | const isolate = new ivm.Isolate(); 6 | const context = isolate.createContextSync(); 7 | context.global.setSync('ivm', ivm); 8 | 9 | const func = isolate.compileScriptSync('new ivm.Reference(function test(data) { return data.length; })').runSync(context); 10 | let counter = 0; 11 | 12 | for (let i = 0; i < 500; i++) { 13 | counter += func.applySync(null, [new ivm.ExternalCopy('x'.repeat(400000)).copyInto()]); 14 | } 15 | 16 | isolate.dispose(); 17 | } 18 | 19 | { 20 | const isolate = new ivm.Isolate(); 21 | const context = isolate.createContextSync(); 22 | context.global.setSync('ivm', ivm); 23 | 24 | const func = isolate.compileScriptSync('new ivm.Reference(function test(data) { return data.text.length; })').runSync(context); 25 | let counter = 0; 26 | 27 | for (let i = 0; i < 500; i++) { 28 | counter += func.applySync(null, [new ivm.ExternalCopy({ text: 'x'.repeat(400000) }).copyInto()]); 29 | } 30 | 31 | isolate.dispose(); 32 | } 33 | 34 | console.log('pass'); 35 | -------------------------------------------------------------------------------- /tests/immutable.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const { strictEqual, throws } = require('assert'); 3 | 4 | ivm.ExternalCopy = () => {}; 5 | Object.getPrototypeOf(ivm).ExternalCopy = () => {}; 6 | 7 | { 8 | ivm.ExternalCopy.prototype.copy = 1; 9 | const ref = new ivm.ExternalCopy(1); 10 | throws(() => Object.setPrototypeOf(ref, {})); 11 | strictEqual(ref.copy(), 1); 12 | strictEqual(Object.getPrototypeOf(ref), ivm.ExternalCopy.prototype); 13 | } 14 | 15 | { 16 | const isolate = new ivm.Isolate; 17 | const context = isolate.createContextSync(); 18 | const result = context.evalClosureSync(` 19 | $0.__proto__.copy = 1; 20 | return $0.copy(); 21 | `, [ new ivm.ExternalCopy(1) ]); 22 | strictEqual(result, 1); 23 | } 24 | 25 | { 26 | const isolate = new ivm.Isolate; 27 | const context = isolate.createContextSync(); 28 | const result = context.evalClosureSync(` 29 | $0.ExternalCopy = () => {}; 30 | $0.__proto__.ExternalCopy = () => {}; 31 | return new $0.ExternalCopy(1).copy(); 32 | `, [ ivm ]); 33 | strictEqual(result, 1); 34 | } 35 | 36 | console.log('pass'); 37 | -------------------------------------------------------------------------------- /tests/timeout-fn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | (async function() { 5 | let isolate = new ivm.Isolate; 6 | let context = isolate.createContextSync(); 7 | let global = context.global; 8 | let time = Date.now(); 9 | global.setSync('global', global.derefInto()); 10 | isolate.compileScriptSync('global.run = () => { function the_stack() { for(;;); }; the_stack(); }').runSync(context); 11 | let run = global.getSync('run', { reference: true }); 12 | let uhoh = false; 13 | let timeout = setTimeout(function() { 14 | uhoh = true; 15 | }, 500); 16 | try { 17 | run.applySync(undefined, [], { timeout: 20 }); 18 | } catch (err) { 19 | if (!/the_stack/.test(err.stack)) { 20 | console.log('missing stack'); 21 | } 22 | } 23 | 24 | try { 25 | await run.apply(undefined, [], { timeout: 20 }); 26 | console.log('what?'); 27 | } catch (err) { 28 | if (!/the_stack/.test(err.stack)) { 29 | console.log('missing stack'); 30 | } 31 | console.log(uhoh ? 'uhoh ' + (Date.now() - time) : 'pass'); 32 | clearTimeout(timeout); 33 | } 34 | })().catch(console.error); 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isolated-vm", 3 | "version": "6.0.2", 4 | "description": "Access to multiple isolates", 5 | "main": "isolated-vm.js", 6 | "types": "isolated-vm.d.ts", 7 | "engines": { 8 | "node": ">=22.0.0" 9 | }, 10 | "scripts": { 11 | "install": "prebuild-install || (node-gyp rebuild --release -j max && node-gyp clean)", 12 | "rebuild": "node-gyp rebuild --release -j max", 13 | "lint": "find src -name '*.cc' | xargs -n1 clang-tidy", 14 | "test": "node test.js" 15 | }, 16 | "dependencies": { 17 | "prebuild-install": "^7.1.3" 18 | }, 19 | "devDependencies": { 20 | "isolated-vm": ".", 21 | "prebuild": "^13.0.1" 22 | }, 23 | "binary": { 24 | "module_path": "out" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/laverdet/isolated-vm.git" 29 | }, 30 | "author": "https://github.com/laverdet/", 31 | "license": "ISC", 32 | "gypfile": true, 33 | "bugs": { 34 | "url": "https://github.com/laverdet/isolated-vm/issues" 35 | }, 36 | "homepage": "https://github.com/laverdet/isolated-vm#readme" 37 | } 38 | -------------------------------------------------------------------------------- /src/module/script_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "isolate/remote_handle.h" 3 | #include "transferable.h" 4 | #include "transferable.h" 5 | #include 6 | #include 7 | 8 | namespace ivm { 9 | 10 | class ContextHandle; 11 | 12 | class ScriptHandle : public TransferableHandle { 13 | public: 14 | using DontFreezeInstance = void; 15 | 16 | explicit ScriptHandle(RemoteHandle script); 17 | static auto Definition() -> v8::Local; 18 | 19 | auto TransferOut() -> std::unique_ptr final; 20 | 21 | auto Release() -> v8::Local; 22 | template 23 | auto Run(ContextHandle& context_handle, v8::MaybeLocal maybe_options) -> v8::Local; 24 | 25 | private: 26 | class ScriptHandleTransferable : public Transferable { 27 | public: 28 | explicit ScriptHandleTransferable(RemoteHandle script); 29 | auto TransferIn() -> v8::Local final; 30 | private: 31 | RemoteHandle script; 32 | }; 33 | 34 | RemoteHandle script; 35 | }; 36 | 37 | } // namespace ivm 38 | -------------------------------------------------------------------------------- /tests/dispose-race.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | function make() { 5 | let isolate = new ivm.Isolate; 6 | let context = isolate.createContextSync(); 7 | let global = context.global; 8 | let script = isolate.compileScriptSync('isolate.dispose()'); 9 | return { isolate, context, global, script }; 10 | } 11 | 12 | function race() { 13 | return new Promise(function(resolve) { 14 | let isolate1 = make(); 15 | let isolate2 = make(); 16 | 17 | isolate1.global.setSync('handle', isolate2.global); 18 | isolate1.global.setSync('isolate', isolate2.isolate); 19 | isolate2.global.setSync('handle', isolate1.global); 20 | isolate2.global.setSync('isolate', isolate1.isolate); 21 | 22 | let promises = [ 23 | isolate1.script.run(isolate1.context), 24 | isolate2.script.run(isolate2.context), 25 | ]; 26 | let ii = promises.length; 27 | function update() { 28 | if (--ii === 0) { 29 | resolve(); 30 | } 31 | } 32 | promises.forEach(promise => promise.then(update).catch(update)); 33 | }); 34 | } 35 | 36 | Promise.all([ Array(100).fill().map(() => race()) ]).then(() => console.log('pass')); 37 | -------------------------------------------------------------------------------- /src/module/context_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "isolate/remote_handle.h" 3 | #include "transferable.h" 4 | #include 5 | #include 6 | 7 | namespace ivm { 8 | 9 | class ContextHandle : public TransferableHandle { 10 | public: 11 | ContextHandle(RemoteHandle context, RemoteHandle global); 12 | static auto Definition() -> v8::Local; 13 | auto TransferOut() -> std::unique_ptr final; 14 | 15 | auto GetContext() const -> RemoteHandle; 16 | auto GlobalGetter() -> v8::Local; 17 | auto Release() -> v8::Local; 18 | 19 | template 20 | auto Eval(v8::Local code, v8::MaybeLocal maybe_options) -> v8::Local; 21 | 22 | template 23 | auto EvalClosure( 24 | v8::Local code, 25 | v8::Maybe maybe_arguments, 26 | v8::MaybeLocal maybe_options 27 | ) -> v8::Local; 28 | 29 | private: 30 | RemoteHandle context; 31 | RemoteHandle global; 32 | RemoteHandle global_reference; 33 | }; 34 | 35 | } // namespace ivm 36 | -------------------------------------------------------------------------------- /tests/heap-limit-async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | // This is our code which will blow the v8 heap 5 | let src = ` 6 | let storage = []; 7 | let s = 'x'; 8 | while (true) { 9 | s += Math.random(); 10 | storage.push(s); 11 | wait.apply(null, [ s.length ]); 12 | } 13 | `; 14 | 15 | // First run is to figure out about how long it takes 16 | let time = Date.now(); 17 | let isolate = new ivm.Isolate({ memoryLimit: 16 }); 18 | let context = isolate.createContextSync(); 19 | context.global.setSync('wait', new ivm.Reference(function() {})); 20 | isolate.compileScriptSync(src).run(context).catch(function() { 21 | let timeTaken = Date.now() - time; 22 | 23 | // Now run again and ensure each `wait` finishes after the isolate is destroyed 24 | let runUntil = Date.now() + timeTaken * 2; 25 | let isolate = new ivm.Isolate({ memoryLimit: 16 }); 26 | let context = isolate.createContextSync(); 27 | context.global.setSync('wait', new ivm.Reference(function(a) { 28 | while (Date.now() < runUntil); 29 | })); 30 | isolate.compileScriptSync(src).run(context).catch(function(err) { 31 | if (/disposed/.test(err)) { 32 | setTimeout(() => console.log('pass'), 100); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/lib/thread_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ivm { 10 | 11 | class thread_pool_t { 12 | public: 13 | using entry_t = void(bool, void*); 14 | class affinity_t { 15 | friend thread_pool_t; 16 | std::unordered_set ids; 17 | unsigned previous = std::numeric_limits::max(); 18 | }; 19 | 20 | explicit thread_pool_t(size_t desired_size) noexcept : desired_size{desired_size} {} 21 | thread_pool_t(const thread_pool_t&) = delete; 22 | ~thread_pool_t() { resize(0); } 23 | auto operator= (const thread_pool_t&) = delete; 24 | 25 | void exec(affinity_t& affinity, entry_t* entry, void* param); 26 | void resize(size_t size); 27 | 28 | private: 29 | auto new_thread(std::lock_guard& /*lock*/) -> size_t; 30 | 31 | struct thread_data_t { 32 | std::thread thread; 33 | std::condition_variable cv; 34 | entry_t* entry = nullptr; 35 | void* param = nullptr; 36 | bool should_exit = false; 37 | }; 38 | 39 | size_t desired_size; 40 | size_t rr = 0; 41 | std::mutex mutex; 42 | std::deque thread_data; 43 | }; 44 | 45 | } // namespace ivm 46 | -------------------------------------------------------------------------------- /src/lib/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace ivm { 6 | 7 | /** 8 | * isolated-vm could start timers from different threads which libuv isn't really cut out for, so 9 | * I'm rolling my own here. The goal of the library is to have atomic timers without spawning a new 10 | * thread for each timer. 11 | */ 12 | struct timer_data_t; 13 | class timer_t { 14 | public: 15 | using callback_t = std::function; 16 | 17 | // Runs a callback unless the `timer_t` destructor is called. 18 | timer_t(uint32_t ms, void** holder, const callback_t& callback); 19 | timer_t(uint32_t ms, const callback_t& callback) : timer_t{ms, nullptr, callback} {} 20 | timer_t(const timer_t&) = delete; 21 | ~timer_t(); 22 | auto operator= (const timer_t&) = delete; 23 | 24 | // Runs a callback in `ms` with no `timer_t` object. 25 | static void wait_detached(uint32_t ms, const callback_t& callback); 26 | // Invoked from callbacks when they are done scheduling and may need to wait 27 | static void chain(void* ptr); 28 | // Pause/unpause timer callbacks 29 | static void pause(void*& holder); 30 | static void resume(void*& holder); 31 | 32 | private: 33 | std::shared_ptr data; 34 | }; 35 | 36 | } // namespace ivm 37 | -------------------------------------------------------------------------------- /tests/neverending-gc.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | function getWall(isolate) { 3 | return Number(isolate.wallTime) / 1e6; 4 | } 5 | 6 | (async function() { 7 | const isolate = new ivm.Isolate; 8 | const context = await isolate.createContext(); 9 | await context.eval('let heap = [];'); 10 | const run = await context.eval(`function run() { 11 | let str = ""; 12 | for (let ii = 0; ii < 1000; ++ii) { 13 | str += Math.random(); 14 | } 15 | return str; 16 | }; run`); 17 | const wallTime = getWall(isolate); 18 | for (let ii = 0; ii < 1000; ++ii) { 19 | await run(); 20 | } 21 | const time1 = getWall(isolate) - wallTime; 22 | while (true) { 23 | await context.eval('heap.push(run())'); 24 | const heap = await isolate.getHeapStatistics(); 25 | if (heap.heap_size_limit < heap.used_heap_size * 1.2) { 26 | break; 27 | } 28 | } 29 | const wallTime2 = getWall(isolate); 30 | for (let ii = 0; ii < 1000; ++ii) { 31 | await run(); 32 | } 33 | const time2 = getWall(isolate) - wallTime2; 34 | if (time1 * 6 > time2) { 35 | console.log('pass'); 36 | } else { 37 | console.log('fail'); 38 | } 39 | const timer = setTimeout(() => { 40 | console.log('hung'); 41 | process.exit(1); 42 | }, 1000); 43 | timer.unref(); 44 | })().catch(console.error); 45 | 46 | -------------------------------------------------------------------------------- /tests/release-option.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | let foo = {}; 5 | let bar = 'bar'; 6 | let fnRef = new ivm.Reference(function(a, b) { 7 | if (a !== foo) { 8 | throw new Error('ref is wrong'); 9 | } 10 | if (b !== bar) { 11 | throw new Error('copy is wrong'); 12 | } 13 | }); 14 | 15 | let ref = new ivm.Reference(foo); 16 | let copy = new ivm.ExternalCopy(bar); 17 | 18 | let tmp1 = ref.derefInto({ release: true }); 19 | let tmp2 = copy.copyInto({ release: true }); 20 | 21 | fnRef.applySync(undefined, [ tmp1, tmp2 ]); 22 | 23 | try { 24 | fnRef.applySync(undefined, [ tmp1 ]); 25 | console.log('no error'); 26 | } catch (err) { 27 | if (!/only be used once/.test(err.message)) { 28 | throw err; 29 | } 30 | } 31 | 32 | try { 33 | fnRef.applySync(undefined, [ tmp2 ]); 34 | console.log('no error'); 35 | } catch (err) { 36 | if (!/only be used once/.test(err.message)) { 37 | throw err; 38 | } 39 | } 40 | 41 | try { 42 | ref.deref(); 43 | console.log('no error'); 44 | } catch (err) { 45 | if (!/released/.test(err.message)) { 46 | throw err; 47 | } 48 | } 49 | 50 | try { 51 | copy.copy(); 52 | console.log('no error'); 53 | } catch (err) { 54 | if (!/released/.test(err.message)) { 55 | throw err; 56 | } 57 | } 58 | 59 | console.log('pass'); 60 | -------------------------------------------------------------------------------- /src/isolate/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "generic/error.h" 7 | 8 | namespace ivm { 9 | 10 | /** 11 | * Easy strings 12 | */ 13 | inline auto v8_string(const char* string) -> v8::Local { 14 | return Unmaybe(v8::String::NewFromOneByte(v8::Isolate::GetCurrent(), (const uint8_t*)string, v8::NewStringType::kNormal)); // NOLINT 15 | } 16 | 17 | inline auto v8_symbol(const char* string) -> v8::Local { 18 | return Unmaybe(v8::String::NewFromOneByte(v8::Isolate::GetCurrent(), (const uint8_t*)string, v8::NewStringType::kInternalized)); // NOLINT 19 | } 20 | 21 | /** 22 | * Shorthand dereference of Persistent to Local 23 | */ 24 | template 25 | auto Deref(const v8::Persistent& handle) -> v8::Local { 26 | return v8::Local::New(v8::Isolate::GetCurrent(), handle); 27 | } 28 | 29 | template 30 | auto Deref(const v8::Global& handle) -> v8::Local { 31 | return v8::Local::New(v8::Isolate::GetCurrent(), handle); 32 | } 33 | 34 | } 35 | 36 | #include "remote_handle.h" 37 | 38 | namespace ivm { 39 | 40 | template 41 | auto Deref(const RemoteHandle& handle) -> v8::Local { 42 | return handle.Deref(); 43 | } 44 | 45 | } // namespace ivm 46 | -------------------------------------------------------------------------------- /tests/deadlock.js: -------------------------------------------------------------------------------- 1 | // Create a new isolate limited to 128MB 2 | let ivm = require('isolated-vm'); 3 | 4 | let isolates = Array(2).fill().map(function() { 5 | let isolate = new ivm.Isolate; 6 | let context = isolate.createContextSync(); 7 | let jail = context.global; 8 | jail.setSync('global', jail.derefInto()); 9 | isolate.compileScriptSync('new '+ function() { 10 | global.random = function() { 11 | return Math.random(); 12 | }; 13 | global.hammer = function(fnRef) { 14 | for (let ii = 0; ii < 100; ++ii) { 15 | fnRef.applySync(undefined, []); 16 | } 17 | }; 18 | }).runSync(context); 19 | return { isolate, jail }; 20 | }); 21 | 22 | let timeout; 23 | (async function() { 24 | timeout = setTimeout(function() { 25 | console.log('test is deadlocked please kill'); 26 | process.exit(1); 27 | setTimeout(function() { 28 | // This doesn't seem to work?? 29 | process.kill(process.pid, 'SIGKILL'); 30 | }, 100); 31 | }, 1000); 32 | await Promise.all([ 33 | isolates[0].jail.getSync('hammer').apply(undefined, [ isolates[1].jail.getSync('random') ]), 34 | isolates[1].jail.getSync('hammer').apply(undefined, [ isolates[0].jail.getSync('random') ]), 35 | ]); 36 | console.log('strange pass?'); 37 | })().catch(function() { 38 | console.log('pass'); 39 | clearTimeout(timeout); 40 | }); 41 | -------------------------------------------------------------------------------- /native-example/usage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | 4 | // You *must* require the extension in nodejs first. 5 | require('.'); 6 | 7 | // `NativeModule` constructor takes a full path to a native module. `require.resolve` handles that 8 | // for us, and finds the module path from `package.json`. 9 | const example = new ivm.NativeModule(require.resolve('.')); 10 | 11 | // Create an isolate like normal 12 | let isolate = new ivm.Isolate; 13 | let context = isolate.createContextSync(); 14 | let global = context.global; 15 | 16 | // Now you can load this module into the isolate. `create` or `createSync` call `InitForContext` in 17 | // C++ which returns a reference to this module for a specific context. The return value is an 18 | // instance of `ivm.Reference` which is just the same object that you would get by doing 19 | // `require('native-example')` in plain nodejs. The compiled native module is also compatible with 20 | // plain nodejs as well. 21 | global.setSync('module', example.createSync(context).derefInto()); 22 | 23 | // Create unsafe log function 24 | global.setSync('log', new ivm.Reference(function(...args) { 25 | console.log(...args); 26 | })); 27 | 28 | // Now we can test the function 29 | let script = isolate.compileScriptSync(`module.timeout(function() { 30 | log.apply(0, [ "Timeout triggered" ]); 31 | }, 1000);`); 32 | console.log("Before runSync"); 33 | script.runSync(context); 34 | console.log("After runSync"); 35 | // logs: 123 36 | -------------------------------------------------------------------------------- /tests/array-close-to-limit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | const memoryLimit = 32; 4 | function makeIsolate(memoryLimit) { 5 | let isolate = new ivm.Isolate({ memoryLimit }); 6 | return { isolate, context: isolate.createContextSync() }; 7 | } 8 | 9 | // Figure out how much junk gets us to the memory limit 10 | let env = makeIsolate(memoryLimit); 11 | let count; 12 | env.context.global.setSync('update', new ivm.Reference(function(ii) { 13 | let heap = env.isolate.getHeapStatisticsSync(); 14 | if (heap.used_heap_size < memoryLimit * 1024 * 1024) { 15 | count = ii; 16 | } 17 | })); 18 | 19 | try { 20 | env.isolate.compileScriptSync(` 21 | let list = []; 22 | for (let ii = 0;; ++ii) { 23 | list.push({}); 24 | if (ii % 1000 === 0) { 25 | update.applySync(undefined, [ ii ]); 26 | } 27 | } 28 | `).runSync(env.context); 29 | } catch (err) {}; 30 | 31 | // Try to allocate an array while garbage can be collected 32 | let env2 = makeIsolate(memoryLimit); 33 | env2.context.global.setSync('count', count); 34 | env2.context.global.setSync('update', new ivm.Reference(function() {})); 35 | env2.isolate.compileScriptSync(` 36 | let list = []; 37 | for (let ii = 0; ii < count; ++ii) { 38 | list.push({}); 39 | if (ii % 1000 === 0) { 40 | // Called to better match GC of previous run 41 | update.applySync(undefined, [ ii ]); 42 | } 43 | } 44 | list = undefined; 45 | new Uint8Array(8 * 1024 * 1024); 46 | `).runSync(env2.context); 47 | console.log('pass'); 48 | -------------------------------------------------------------------------------- /src/module/transferable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "isolate/class_handle.h" 3 | #include "isolate/transferable.h" 4 | #include 5 | #include 6 | 7 | namespace ivm { 8 | 9 | class TransferableHandle : public ClassHandle { 10 | public: 11 | static auto Definition() -> v8::Local { 12 | return MakeClass("Transferable", nullptr); 13 | } 14 | 15 | virtual auto TransferOut() -> std::unique_ptr = 0; 16 | }; 17 | 18 | class TransferOptions { 19 | public: 20 | enum class Type { None, Copy, ExternalCopy, Reference, DeepReference }; 21 | 22 | TransferOptions() = default; 23 | explicit TransferOptions(Type fallback) : fallback{fallback} {}; 24 | explicit TransferOptions(v8::Local options, Type fallback = Type::None); 25 | explicit TransferOptions(v8::MaybeLocal maybe_options, Type fallback = Type::None); 26 | 27 | auto operator==(const TransferOptions& that) const -> bool { 28 | return type == that.type && fallback == that.fallback && promise == that.promise; 29 | } 30 | 31 | Type type = Type::None; 32 | Type fallback = Type::None; 33 | bool promise = false; 34 | 35 | private: 36 | void ParseOptions(v8::Local options); 37 | }; 38 | 39 | auto OptionalTransferOut(v8::Local value, TransferOptions options = {}) -> std::unique_ptr; 40 | auto TransferOut(v8::Local value, TransferOptions options = {}) -> std::unique_ptr; 41 | 42 | } // namespace ivm 43 | -------------------------------------------------------------------------------- /tests/heap-limit-nested.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | function makeIsolate() { 4 | let isolate = new ivm.Isolate({ memoryLimit: 16 }); 5 | let context = isolate.createContextSync(); 6 | let jail = context.global; 7 | jail.setSync('context', context); 8 | jail.setSync('isolate', isolate); 9 | jail.setSync('global', jail.derefInto()); 10 | return { isolate, context, global }; 11 | } 12 | 13 | (async function() { 14 | try { 15 | let env = makeIsolate(); 16 | let script = await env.isolate.compileScript('new '+ function() { 17 | // Now we're inside the isolate 18 | let script2 = isolate.compileScriptSync('new '+ function() { 19 | let stash = []; 20 | for (;;) { 21 | stash.push({}); 22 | } 23 | }).runSync(context); 24 | }); 25 | await script.run(env.context); 26 | console.log('fail1'); 27 | } catch (err) { 28 | if (!/disposed/.test(err)) throw err; 29 | } 30 | 31 | // Repeat but with applySync() 32 | try { 33 | let env = makeIsolate(); 34 | let script = await env.isolate.compileScript('new '+ function() { 35 | // Now we're inside the isolate 36 | global.sabotage = function() { 37 | let stash = []; 38 | for (;;) { 39 | stash.push({}); 40 | } 41 | } 42 | let ref = context.global.getSync('sabotage'); 43 | ref(); 44 | }); 45 | await script.run(env.context); 46 | console.log('fail2'); 47 | } catch (err) { 48 | if (!/disposed/.test(err)) throw err; 49 | } 50 | 51 | console.log('pass'); 52 | 53 | }()).catch(console.error); 54 | -------------------------------------------------------------------------------- /src/lib/covariant.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace detail { 4 | 5 | template 6 | void destructor_wrapper(Base* value) { 7 | static_cast(value)->~Type(); 8 | } 9 | 10 | template 11 | struct contains_type { 12 | static constexpr bool value = 13 | std::is_same::value || contains_type::value; 14 | }; 15 | 16 | template 17 | struct contains_type : std::is_same {}; 18 | 19 | } // namespace detail 20 | 21 | template 22 | class in_place {}; 23 | 24 | template 25 | class covariant_t { 26 | public: 27 | template 28 | explicit covariant_t(in_place /*tag*/, Args&&... args) : dtor{&::detail::destructor_wrapper} { 29 | static_assert(::detail::contains_type::value, "Instantiated constructor must inherit from `Base`"); 30 | new(&storage) Ctor(std::forward(args)...); 31 | } 32 | 33 | ~covariant_t() { 34 | dtor(base()); 35 | } 36 | 37 | covariant_t(const covariant_t&) = delete; 38 | auto operator=(const covariant_t&) = delete; 39 | 40 | auto operator*() -> auto& { return *base(); } 41 | auto operator*() const -> auto& { return *base(); } 42 | auto operator->() { return base(); } 43 | auto operator->() const { return base(); } 44 | 45 | private: 46 | auto base() { return static_cast(static_cast(&storage)); } 47 | 48 | std::aligned_union_t<1, Storage...> storage; 49 | void(*dtor)(Base*); 50 | }; 51 | -------------------------------------------------------------------------------- /tests/workers-threads.js: -------------------------------------------------------------------------------- 1 | if (process.versions.modules < 72) { 2 | console.log('pass'); 3 | process.exit(); 4 | } 5 | const ivm = require('isolated-vm'); 6 | const assert = require('assert'); 7 | const { Worker, isMainThread, parentPort } = require('worker_threads'); 8 | 9 | const kIsolateCount = 100; 10 | const kWorkerCount = 10; 11 | if (isMainThread) { 12 | Promise.all(Array(kWorkerCount).fill().map(async (_, ii) => { 13 | const worker = new Worker(__filename); 14 | const exit = new Promise((resolve, reject) => { 15 | worker.on('exit', code => code ? reject(new Error(`Worker exitted with code: ${code}`)) : resolve()); 16 | worker.on('error', error => reject(error)); 17 | }); 18 | const results = []; 19 | worker.on('message', message => results.push(...message)); 20 | await exit; 21 | assert(results.length == kIsolateCount); 22 | for (let ii = 0; ii < kIsolateCount; ++ii) { 23 | assert(results.includes(`pass${ii}`)); 24 | } 25 | return true; 26 | })).then(results => { 27 | if (results.every(value => value)) { 28 | console.log('pass'); 29 | } 30 | }); 31 | } else { 32 | Promise.all(Array(kIsolateCount).fill().map(async (_, ii) => { 33 | const isolate = new ivm.Isolate; 34 | const context = await isolate.createContext(); 35 | const logs = []; 36 | await context.global.set('log', new ivm.Reference(str => logs.push(str))); 37 | await context.global.set('ii', ii); 38 | const script = await isolate.compileScript('log.apply(null, [`pass${ii}`])'); 39 | await script.run(context); 40 | parentPort.postMessage(logs); 41 | })).catch(err => console.error(err)); 42 | } 43 | -------------------------------------------------------------------------------- /src/module/native_module_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "transferable.h" 8 | #include "context_handle.h" 9 | #include "transferable.h" 10 | 11 | namespace ivm { 12 | 13 | class NativeModule { 14 | private: 15 | using init_t = void(*)(v8::Isolate *, v8::Local, v8::Local); 16 | uv_lib_t lib; 17 | init_t init; 18 | 19 | public: 20 | explicit NativeModule(const std::string& filename); 21 | NativeModule(const NativeModule&) = delete; 22 | auto operator= (const NativeModule&) -> NativeModule& = delete; 23 | ~NativeModule(); 24 | void InitForContext(v8::Isolate* isolate, v8::Local context, v8::Local target); 25 | }; 26 | 27 | class NativeModuleHandle : public TransferableHandle { 28 | private: 29 | class NativeModuleTransferable : public Transferable { 30 | private: 31 | std::shared_ptr module; 32 | public: 33 | explicit NativeModuleTransferable(std::shared_ptr module); 34 | auto TransferIn() -> v8::Local final; 35 | }; 36 | 37 | std::shared_ptr module; 38 | 39 | template 40 | auto Create(ContextHandle& context_handle) -> v8::Local; 41 | 42 | public: 43 | explicit NativeModuleHandle(std::shared_ptr module); 44 | static auto Definition() -> v8::Local; 45 | static auto New(v8::Local value) -> std::unique_ptr; 46 | auto TransferOut() -> std::unique_ptr final; 47 | }; 48 | 49 | } // namespace ivm 50 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ## Is Your Question Already Answered? 13 | - [ ] I have read the [Frequently Asked Question](https://github.com/laverdet/isolated-vm?tab=readme-ov-file#frequently-asked-question) in the README. My question is not the Frequently Asked Question. 14 | 15 | ## Personal Diagnostics 16 | Please answer the following questions: 17 | 18 | #### JavaScript includes a `setTimeout` function: 19 | - [ ] Yes 20 | - [ ] No 21 | 22 | #### Functions are a type of primitive value in JavaScript: 23 | - [ ] Yes 24 | - [ ] No 25 | 26 | #### Objects can be shared between isolates: 27 | - [ ] Yes 28 | - [ ] No 29 | 30 | ## The Code 31 | - [ ] This code will parse and evaluate if I put it into a file called `main.mjs` and then run `node main.mjs`. 32 | 33 | ```js 34 | // Replace this code with the code which demonstrates what you are asking about 35 | import ivm from "isolated-vm"; 36 | const isolate = new ivm.Isolate(); 37 | const context = await isolate.createContext(); 38 | console.log(await context.eval('"hello world"')); 39 | ``` 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-and-test: 7 | name: ${{ matrix.os }} / Node ${{ matrix.node }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ ubuntu-latest, macos-latest, windows-latest ] 12 | node: [ 22, 24 ] 13 | fail-fast: false 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js ${{ matrix.node }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node }} 20 | - name: Add msbuild to PATH 21 | if: matrix.os == 'windows-latest' 22 | uses: microsoft/setup-msbuild@v2 23 | - name: Install node-gyp 24 | if: matrix.os == 'windows-latest' 25 | run: | 26 | npm install --global node-gyp@latest 27 | - name: Build 28 | run: | 29 | npm install --build-from-source 30 | - name: Test 31 | uses: nick-fields/retry@v3 32 | with: 33 | max_attempts: 3 34 | retry_on: error 35 | timeout_minutes: 6 36 | command: | 37 | npm test 38 | 39 | build-and-test-alpine: 40 | runs-on: ubuntu-latest 41 | strategy: 42 | matrix: 43 | node: [ 22, 24 ] 44 | fail-fast: false 45 | container: node:${{ matrix.node }}-alpine 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: install build deps 49 | run: | 50 | apk add g++ make python3 51 | - name: Build 52 | run: | 53 | npm install --build-from-source 54 | - name: Test 55 | run: | 56 | npm test 57 | timeout-minutes: 4 58 | -------------------------------------------------------------------------------- /src/isolate/generic/read_option.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "handle_cast.h" 4 | 5 | namespace ivm { 6 | namespace detail { 7 | 8 | struct ParamRequired : std::exception {}; 9 | 10 | template 11 | inline auto ReadOptionImpl(v8::MaybeLocal maybe_options, Property&& property, Default default_fn) { 12 | HandleCastArguments arguments; 13 | auto property_handle = HandleCast>(std::forward(property), arguments); 14 | try { 15 | v8::Local options; 16 | if (maybe_options.ToLocal(&options)) { 17 | v8::Local value = Unmaybe(options->Get(arguments.context, property_handle)); 18 | if (value->IsNullOrUndefined()) { 19 | return default_fn(); 20 | } 21 | return HandleCast(value, arguments); 22 | } else { 23 | return default_fn(); 24 | } 25 | } catch (const ParamIncorrect& ex) { 26 | throw RuntimeTypeError{ 27 | std::string{"`"}+ *v8::String::Utf8Value{arguments.isolate, property_handle}+ "` must be "+ ex.type 28 | }; 29 | } catch (const ParamRequired&) { 30 | throw RuntimeTypeError( 31 | std::string{"`"}+ *v8::String::Utf8Value{arguments.isolate, property_handle}+ "` is required"); 32 | } 33 | } 34 | 35 | } // namespace detail 36 | 37 | template 38 | auto ReadOption(Options options, Property&& property) { 39 | return detail::ReadOptionImpl(options, std::forward(property), []() { throw detail::ParamRequired(); }); 40 | } 41 | 42 | template 43 | auto ReadOption(Options options, Property&& property, Type default_value) { 44 | return detail::ReadOptionImpl(options, std::forward(property), [&]() { return std::move(default_value); }); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /tests/promise-error.js: -------------------------------------------------------------------------------- 1 | // node-args: --expose-gc 2 | 3 | const ivm = require('isolated-vm'); 4 | const assert = require('assert'); 5 | const isolate = new ivm.Isolate; 6 | const context = isolate.createContextSync(); 7 | (async function() { 8 | assert.throws(() => 9 | isolate.compileScriptSync('Promise.resolve().then(() => { throw new Error("hello") })').runSync(context), 10 | /hello/); 11 | 12 | await assert.rejects( 13 | (await isolate.compileScript('Promise.resolve().then(() => { throw new Error("hello") })')).run(context), 14 | /hello/); 15 | 16 | await assert.doesNotReject( 17 | context.eval('Promise.race([ 1, 2 ])', { promise: true })); 18 | 19 | 20 | await assert.rejects(() => 21 | context.evalSync(`Promise.reject(new Error('hello'))`, { promise: true }), 22 | /hello/); 23 | 24 | assert.doesNotThrow(() => 25 | context.evalSync('Promise.reject(new Error(1)).catch(() => {})')); 26 | 27 | assert.doesNotThrow(() => context.evalSync(` 28 | (async () => { 29 | try { 30 | await new Promise((_, reject) => reject(new Error('abc'))); 31 | } catch (err) { 32 | // ignore 33 | } 34 | })(); 35 | `)); 36 | 37 | await assert.doesNotReject(async () => { 38 | const isolate = new ivm.Isolate; 39 | for (let i = 0; i < 20; i++) { 40 | const context = await isolate.createContext(); 41 | await context.eval('const value = new Uint8Array(1024 * 1024 * 16); Promise.reject();').catch(() => {}); 42 | context.release(); 43 | global.gc(); 44 | } 45 | }); 46 | 47 | try { 48 | context.evalSync(` 49 | function innerFunction() { 50 | throw new Error("hello"); 51 | } 52 | Promise.resolve().then(innerFunction); 53 | `); 54 | assert.ok(false); 55 | } catch (err) { 56 | assert.ok(/innerFunction/.test(err.stack)); 57 | } 58 | 59 | console.log('pass'); 60 | })().catch(console.error); 61 | -------------------------------------------------------------------------------- /.github/workflows/prebuild.yml: -------------------------------------------------------------------------------- 1 | name: Add native binaries to release 2 | 3 | on: 4 | push: 5 | tags: ['*'] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | prebuild: 10 | name: ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ macos-latest, windows-latest ] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Use nodejs 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: latest 21 | - name: Add msbuild to PATH 22 | if: matrix.os == 'windows-latest' 23 | uses: microsoft/setup-msbuild@v2 24 | - name: Install node-gyp 25 | if: matrix.os == 'windows-latest' 26 | run: | 27 | npm install --global node-gyp@latest 28 | - name: Dependencies 29 | run: | 30 | npm install --ignore-scripts 31 | - name: Build 32 | run: | 33 | npx -y prebuild 34 | - name: Upload 35 | run: | 36 | npx -y prebuild --upload-all ${{ secrets.UPLOAD_TOKEN }} 37 | env: 38 | MAKEFLAGS: -j4 39 | 40 | prebuild-linux: 41 | name: ${{ matrix.os }} 42 | runs-on: ubuntu-latest 43 | strategy: 44 | matrix: 45 | os: [ debian, alpine ] 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: Use nodejs 49 | uses: actions/setup-node@v4 50 | with: 51 | node-version: latest 52 | - name: Set up QEMU 53 | uses: docker/setup-qemu-action@v3 54 | - name: Set up Docker Buildx 55 | uses: docker/setup-buildx-action@v3 56 | - name: Build 57 | run: | 58 | docker buildx build --platform linux/amd64,linux/arm64 . -f Dockerfile.${{ matrix.os }} --output prebuilds 59 | - name: Upload 60 | run: | 61 | npx -y prebuild --upload-all ${{ secrets.UPLOAD_TOKEN }} 62 | -------------------------------------------------------------------------------- /src/module/callback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "transferable.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ivm { 9 | 10 | /** 11 | * Holds a reference to a function in an arbitrary isolate 12 | */ 13 | class CallbackHandle : public TransferableHandle { 14 | public: 15 | template 16 | explicit CallbackHandle(Args&&... args) : data{std::forward(args)...} {} 17 | 18 | static auto Definition() -> v8::Local; 19 | static auto New(v8::Local fn, v8::MaybeLocal maybe_options) -> 20 | std::unique_ptr; 21 | auto TransferOut() -> std::unique_ptr final; 22 | 23 | void Release(); 24 | 25 | struct InvokeData { 26 | enum class Apply { Async, Ignored, Sync }; 27 | RemoteHandle callback; 28 | RemoteHandle context; 29 | Apply apply{}; 30 | }; 31 | 32 | struct Data : public InvokeData { 33 | Data() = default; 34 | 35 | template 36 | Data(std::optional name, int length, Args&&... args) : 37 | InvokeData{std::forward(args)...}, 38 | name{std::move(name)}, length{length} {} 39 | 40 | std::optional name; 41 | int length = 0; 42 | }; 43 | 44 | private: 45 | Data data; 46 | }; 47 | 48 | /** 49 | * Internal transferable handle for callback 50 | */ 51 | class CallbackTransferable : public Transferable { 52 | public: 53 | explicit CallbackTransferable(CallbackHandle::Data& data); 54 | explicit CallbackTransferable(v8::Local data); 55 | CallbackTransferable(v8::Local fn, v8::Local context); 56 | 57 | auto TransferIn() -> v8::Local final; 58 | 59 | private: 60 | static void Invoke(const v8::FunctionCallbackInfo& info); 61 | CallbackHandle::Data data; 62 | }; 63 | 64 | } // namespace ivm 65 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | let fs = require('fs'); 3 | let spawn = require('child_process').spawn; 4 | let path = require('path'); 5 | 6 | let ret = 0; 7 | let passCount = 0, failCount = 0; 8 | function runTest(test, cb) { 9 | // Copy env variables 10 | let env = {}; 11 | for (let ii in process.env) { 12 | env[ii] = process.env[ii]; 13 | } 14 | env.NODE_PATH = __dirname; 15 | 16 | // Get extra args 17 | let args = [ '--no-node-snapshot' ]; 18 | let testPath = path.join('tests', test); 19 | let content = fs.readFileSync(testPath, 'utf8'); 20 | let match = /node-args: *(.+)/.exec(content); 21 | if (match) { 22 | args.push(...match[1].split(/ /g)); 23 | } 24 | args.push(testPath); 25 | 26 | // Launch process 27 | let proc = spawn(process.execPath, args, { env }); 28 | proc.stdout.setEncoding('utf8'); 29 | proc.stderr.setEncoding('utf8'); 30 | 31 | let stdout = '', stderr = ''; 32 | proc.stdout.on('data', function(data) { 33 | stdout += data; 34 | }); 35 | proc.stderr.on('data', function(data) { 36 | stderr += data; 37 | }); 38 | proc.stdin.end(); 39 | 40 | // Wait for completion 41 | process.stderr.write(`${test}: `); 42 | proc.on('exit', function(code, signal) { 43 | if (stdout !== 'pass\n' || stderr !== '') { 44 | ++failCount; 45 | ret = 1; 46 | console.error( 47 | `*fail*\n`+ 48 | `code: ${code} signal: ${signal}\n`+ 49 | `stderr: ${stderr}\n`+ 50 | `stdout: ${stdout}` 51 | ); 52 | } else if (code !== 0) { 53 | ++failCount; 54 | ret = 1; 55 | console.error(`fail (${code})`); 56 | } else { 57 | ++passCount; 58 | console.error(`pass`); 59 | } 60 | cb(); 61 | }); 62 | } 63 | 64 | let cb = function() { 65 | console.log('\nCompleted: '+ passCount+ ' passed, '+ failCount+ ' failed.'); 66 | process.exit(ret); 67 | }; 68 | fs.readdirSync('./tests').sort().reverse().forEach(function(file) { 69 | if (/\.js$/.test(file)) { 70 | cb = new function(cb) { 71 | return function(err) { 72 | if (err) return cb(err); 73 | runTest(file, cb); 74 | }; 75 | }(cb); 76 | } 77 | }); 78 | cb(); 79 | -------------------------------------------------------------------------------- /inspector-example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let WebSocket = require('ws'); 3 | let ivm = require('./isolated-vm'); 4 | 5 | /** 6 | * IMPORTANT: Allowing untrusted users to access the v8 inspector will almost certainly result in a 7 | * security vulnerability. Access to these endpoints should be restricted. 8 | */ 9 | 10 | // Launch an infinite loop in another thread 11 | let isolate = new ivm.Isolate({ inspector: true }); 12 | (async function() { 13 | let context = await isolate.createContext({ inspector: true }); 14 | const inspector = isolate.createInspectorSession(); 15 | inspector.dispatchProtocolMessage('{"id":1,"method":"Debugger.enable"}'); 16 | await context.eval('/* break on script start */debugger;'); 17 | inspector.dispose(); 18 | let script = await isolate.compileScript('console.log("hello world")', { filename: 'example.js' }); 19 | await script.run(context); 20 | }()).catch(console.error); 21 | 22 | // Create an inspector channel on port 10000 23 | let wss = new WebSocket.Server({ port: 10000 }); 24 | 25 | wss.on('connection', function(ws) { 26 | // Dispose inspector session on websocket disconnect 27 | let channel = isolate.createInspectorSession(); 28 | function dispose() { 29 | try { 30 | channel.dispose(); 31 | } catch (err) {} 32 | } 33 | ws.on('error', dispose); 34 | ws.on('close', dispose); 35 | 36 | // Relay messages from frontend to backend 37 | ws.on('message', function(message) { 38 | console.log('<', message.toString()) 39 | try { 40 | channel.dispatchProtocolMessage(String(message)); 41 | } catch (err) { 42 | // This happens if inspector session was closed unexpectedly 43 | ws.close(); 44 | } 45 | }); 46 | 47 | // Relay messages from backend to frontend 48 | function send(message) { 49 | console.log('>', message.toString()) 50 | try { 51 | ws.send(message); 52 | } catch (err) { 53 | dispose(); 54 | } 55 | } 56 | channel.onResponse = (callId, message) => send(message); 57 | channel.onNotification = send; 58 | }); 59 | console.log('Inspector: devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:10000'); 60 | -------------------------------------------------------------------------------- /src/isolated_vm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef _WIN32 3 | #define ISOLATED_VM_MODULE extern "C" __declspec(dllexport) 4 | #else 5 | #define ISOLATED_VM_MODULE extern "C" 6 | #endif 7 | 8 | #include "isolate/environment.h" 9 | #include "isolate/holder.h" 10 | #include "isolate/remote_handle.h" 11 | #include "isolate/runnable.h" 12 | #include 13 | 14 | namespace isolated_vm { 15 | using Runnable = ivm::Runnable; 16 | // ^ The only thing you need to know: `virtual void Run() = 0` 17 | 18 | class IsolateHolder { 19 | private: 20 | std::shared_ptr holder; 21 | explicit IsolateHolder(std::shared_ptr holder) : holder{std::move(holder)} { 22 | ivm::LockedScheduler::IncrementUvRefForIsolate(holder); 23 | } 24 | 25 | public: 26 | IsolateHolder(const IsolateHolder& that) : holder{that.holder} { 27 | ivm::LockedScheduler::IncrementUvRefForIsolate(holder); 28 | } 29 | 30 | IsolateHolder(IsolateHolder&& that) noexcept : holder{std::move(that.holder)} { 31 | } 32 | 33 | ~IsolateHolder() { 34 | if (holder) { 35 | ivm::LockedScheduler::DecrementUvRefForIsolate(holder); 36 | } 37 | } 38 | 39 | auto operator=(const IsolateHolder&) -> IsolateHolder& = default; 40 | auto operator=(IsolateHolder&&) -> IsolateHolder& = default; 41 | 42 | static auto GetCurrent() -> IsolateHolder { 43 | return IsolateHolder{ivm::IsolateEnvironment::GetCurrentHolder()}; 44 | } 45 | 46 | void ScheduleTask(std::unique_ptr runnable) { 47 | holder->ScheduleTask(std::move(runnable), false, true, false); 48 | } 49 | 50 | void Release() { 51 | holder.reset(); 52 | } 53 | }; 54 | 55 | template 56 | class RemoteHandle { 57 | private: 58 | std::shared_ptr> handle; 59 | 60 | public: 61 | explicit RemoteHandle(v8::Local handle) : handle(std::make_shared>(handle)) {} 62 | 63 | auto operator*() const { 64 | return handle->Deref(); 65 | } 66 | 67 | void Release() { 68 | handle.reset(); 69 | } 70 | }; 71 | } // namespace isolated_vm 72 | -------------------------------------------------------------------------------- /src/module/isolate_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "isolate/generic/array.h" 3 | #include "transferable.h" 4 | #include 5 | #include 6 | 7 | namespace ivm { 8 | 9 | /** 10 | * Reference to a v8 isolate 11 | */ 12 | class IsolateHandle : public TransferableHandle { 13 | private: 14 | std::shared_ptr isolate; 15 | 16 | class IsolateHandleTransferable : public Transferable { 17 | private: 18 | std::shared_ptr isolate; 19 | public: 20 | explicit IsolateHandleTransferable(std::shared_ptr isolate); 21 | auto TransferIn() -> v8::Local final; 22 | }; 23 | 24 | public: 25 | explicit IsolateHandle(std::shared_ptr isolate); 26 | static auto Definition() -> v8::Local; 27 | static auto New(v8::MaybeLocal maybe_options) -> std::unique_ptr; 28 | auto TransferOut() -> std::unique_ptr final; 29 | 30 | template auto CreateContext(v8::MaybeLocal maybe_options) -> v8::Local; 31 | template auto CompileScript(v8::Local code_handle, v8::MaybeLocal maybe_options) -> v8::Local; 32 | template auto CompileModule(v8::Local code_handle, v8::MaybeLocal maybe_options) -> v8::Local; 33 | 34 | auto CreateInspectorSession() -> v8::Local; 35 | auto Dispose() -> v8::Local; 36 | template auto GetHeapStatistics() -> v8::Local; 37 | auto GetCpuTime() -> v8::Local; 38 | auto GetWallTime() -> v8::Local; 39 | auto StartCpuProfiler(v8::Local title) -> v8::Local; 40 | template auto StopCpuProfiler(v8::Local title) -> v8::Local; 41 | 42 | auto GetReferenceCount() -> v8::Local; 43 | auto IsDisposedGetter() -> v8::Local; 44 | static auto CreateSnapshot(ArrayRange script_handles, v8::MaybeLocal warmup_handle) -> v8::Local; 45 | }; 46 | 47 | } // namespace ivm 48 | -------------------------------------------------------------------------------- /src/isolate/platform_delegate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "lib/lockable.h" 3 | #include "node_wrapper.h" 4 | #include "v8_version.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ivm { 10 | 11 | // Normalize this interface from v8 12 | class TaskRunner : public v8::TaskRunner { 13 | public: 14 | // Methods for v8::TaskRunner 15 | void PostTaskImpl(std::unique_ptr task, const v8::SourceLocation& location) override = 0; 16 | void PostDelayedTaskImpl(std::unique_ptr task, double delay_in_seconds, const v8::SourceLocation& location) override = 0; 17 | void PostIdleTaskImpl(std::unique_ptr /*task*/, const v8::SourceLocation& /*location*/) final { std::terminate(); } 18 | // Can't be final because symbol is also used in IsolatePlatformDelegate 19 | auto IdleTasksEnabled() -> bool override { return false; }; 20 | auto NonNestableTasksEnabled() const -> bool final { return true; } 21 | // void PostNonNestableDelayedTask(std::unique_ptr /*task*/, double /*delay_in_seconds*/) final { std::terminate(); } 22 | auto NonNestableDelayedTasksEnabled() const -> bool final { return false; } 23 | }; 24 | 25 | class PlatformDelegate { 26 | public: 27 | PlatformDelegate() = default; 28 | explicit PlatformDelegate(node::MultiIsolatePlatform* node_platform) : node_platform{node_platform} {} 29 | PlatformDelegate(const PlatformDelegate&) = delete; 30 | PlatformDelegate(PlatformDelegate&&) = delete; 31 | ~PlatformDelegate() = default; // NOLINT(modernize-use-override) -- this only sometimes inherits from v8::Platform 32 | 33 | auto operator=(const PlatformDelegate&) = delete; 34 | auto operator=(PlatformDelegate&& delegate) noexcept -> PlatformDelegate& { 35 | node_platform = std::exchange(delegate.node_platform, nullptr); 36 | return *this; 37 | } 38 | 39 | static void InitializeDelegate(); 40 | static void RegisterIsolate(v8::Isolate* isolate, node::IsolatePlatformDelegate* isolate_delegate); 41 | static void UnregisterIsolate(v8::Isolate* isolate); 42 | 43 | node::MultiIsolatePlatform* node_platform = nullptr; 44 | }; 45 | 46 | } // namespace ivm 47 | -------------------------------------------------------------------------------- /src/isolate/external.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "environment.h" 3 | #include "util.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ivm { 9 | namespace detail { 10 | 11 | template 12 | class ExternalHolder { 13 | public: 14 | ExternalHolder(const ExternalHolder&) = delete; 15 | ~ExternalHolder() = default; 16 | auto operator=(const ExternalHolder&) = delete; 17 | 18 | template 19 | static auto New(Args&&... args) { 20 | // Allocate heap memory for ExternalHolder (don't construct) 21 | ExternalHolder* that = std::allocator{}.allocate(1); 22 | // Create a new v8::External referencing the unconstructed holder 23 | auto external = v8::External::New(v8::Isolate::GetCurrent(), &that->data); 24 | // Construct the holder in-place 25 | new(that) ExternalHolder(external, std::forward(args)...); 26 | // Setup weak callbacks (technically could throw) 27 | that->handle.SetWeak(reinterpret_cast(that), WeakCallbackV8, v8::WeakCallbackType::kParameter); 28 | IsolateEnvironment::GetCurrent().AddWeakCallback(&that->handle, WeakCallback, that); 29 | // Return local external handle 30 | return external; 31 | } 32 | 33 | private: 34 | template 35 | explicit ExternalHolder(v8::Local handle, Args&&... args) : 36 | handle{v8::Isolate::GetCurrent(), handle}, data{std::forward(args)...} {} 37 | 38 | static void WeakCallback(void* param) { 39 | auto* that = static_cast(param); 40 | auto& isolate = IsolateEnvironment::GetCurrent(); 41 | isolate.RemoveWeakCallback(&that->handle); 42 | that->handle.Reset(); 43 | delete that; 44 | } 45 | 46 | static void WeakCallbackV8(const v8::WeakCallbackInfo& info) { 47 | WeakCallback(info.GetParameter()); 48 | } 49 | 50 | v8::Persistent handle; 51 | Type data; 52 | }; 53 | 54 | } // namespace detail 55 | 56 | /** 57 | * Returns a v8::External with attached handle deletion 58 | */ 59 | template 60 | auto MakeExternal(Args&&... args) { 61 | return detail::ExternalHolder::New(std::forward(args)...); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /tests/shared-array-buffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | 4 | let buffer = new SharedArrayBuffer(1024 * 1024); 5 | let array = new Uint8Array(buffer); 6 | 7 | function runSync(env, fn) { 8 | env.isolate.compileScriptSync('new '+ fn).runSync(env.context); 9 | } 10 | 11 | function makeIsolate() { 12 | let isolate = new ivm.Isolate; 13 | let context = isolate.createContextSync(); 14 | let global = context.global; 15 | return { isolate, context, global }; 16 | } 17 | let env = makeIsolate(); 18 | 19 | // Check initial transfer 20 | env.global.setSync('buffer', new ivm.ExternalCopy(buffer).copyInto({ release: true })); 21 | env.global.setSync('ivm', ivm); 22 | 23 | if (env.isolate.getHeapStatisticsSync().externally_allocated_size !== 1024 * 1024) { 24 | console.log('size wrong'); 25 | } 26 | 27 | // Check that buffer transfer + externalize size adjustments work 28 | let env2 = makeIsolate(); 29 | env.global.setSync('global2', env2.global); 30 | env2.global.setSync('ivm', ivm); 31 | runSync(env, function() { 32 | global2.setSync('buffer', new ivm.ExternalCopy(buffer).copyInto({ release: true })); 33 | }); 34 | 35 | if ( 36 | env.isolate.getHeapStatisticsSync().externally_allocated_size !== 1024 * 1024 || 37 | env2.isolate.getHeapStatisticsSync().externally_allocated_size !== 1024 * 1024 38 | ) { 39 | console.log('size wrong2'); 40 | } 41 | 42 | // Make sure we're using the same array 43 | env.global.setSync('array', new ivm.ExternalCopy(array).copyInto({ release: true })); 44 | runSync(env, function() { 45 | array[0] = 123; 46 | }); 47 | runSync(env2, function() { 48 | let foo = new Uint8Array(buffer); 49 | foo[1] = 234; 50 | buf2 = new SharedArrayBuffer(1024 * 1024); 51 | new ivm.ExternalCopy(buf2); // externalize 52 | }); 53 | 54 | if (array[0] !== 123 || array[1] !== 234) { 55 | console.log('not shared'); 56 | } 57 | 58 | if (env.isolate.getHeapStatisticsSync().externally_allocated_size !== 1024 * 1024 * 2) { 59 | // This isn't really a good thing but I can't really think of an efficient way to fix it 60 | console.log('size wrong3'); 61 | } 62 | if (env2.isolate.getHeapStatisticsSync().externally_allocated_size !== 1024 * 1024 * 2) { 63 | console.log('size wrong4'); 64 | } 65 | 66 | console.log('pass'); 67 | -------------------------------------------------------------------------------- /src/module/external_copy_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "transferable.h" 4 | #include 5 | 6 | namespace ivm { 7 | 8 | class ExternalCopy; 9 | 10 | class ExternalCopyHandle final : public TransferableHandle { 11 | public: 12 | class ExternalCopyTransferable : public Transferable { 13 | private: 14 | std::shared_ptr value; 15 | 16 | public: 17 | explicit ExternalCopyTransferable(std::shared_ptr value); 18 | auto TransferIn() -> v8::Local final; 19 | }; 20 | 21 | std::shared_ptr value; 22 | 23 | void CheckDisposed() const; 24 | 25 | explicit ExternalCopyHandle(std::shared_ptr value); 26 | ExternalCopyHandle(const ExternalCopyHandle&) = delete; 27 | auto operator= (const ExternalCopyHandle&) -> ExternalCopyHandle& = delete; 28 | ~ExternalCopyHandle() final; 29 | static auto Definition() -> v8::Local; 30 | auto TransferOut() -> std::unique_ptr final; 31 | 32 | static auto New(v8::Local value, v8::MaybeLocal maybe_options) -> std::unique_ptr; 33 | static auto TotalExternalSizeGetter() -> v8::Local; 34 | auto Copy(v8::MaybeLocal maybe_options) -> v8::Local; 35 | auto CopyInto(v8::MaybeLocal maybe_options) -> v8::Local; 36 | auto Release() -> v8::Local; 37 | auto GetValue() const -> std::shared_ptr { return value; } 38 | 39 | private: 40 | int size = 0; 41 | }; 42 | 43 | class ExternalCopyIntoHandle : public TransferableHandle { 44 | private: 45 | class ExternalCopyIntoTransferable : public Transferable { 46 | private: 47 | std::shared_ptr value; 48 | bool transfer_in; 49 | 50 | public: 51 | explicit ExternalCopyIntoTransferable(std::shared_ptr value, bool transfer_in); 52 | auto TransferIn() -> v8::Local final; 53 | }; 54 | 55 | std::shared_ptr value; 56 | bool transfer_in; 57 | 58 | public: 59 | explicit ExternalCopyIntoHandle(std::shared_ptr value, bool transfer_in); 60 | static auto Definition() -> v8::Local; 61 | auto TransferOut() -> std::unique_ptr final; 62 | }; 63 | 64 | } // namespace ivm 65 | -------------------------------------------------------------------------------- /tests/async-rentry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ivm = require('isolated-vm'); 3 | let fs = require('fs'); 4 | 5 | let isolate = new ivm.Isolate; 6 | let context = isolate.createContextSync(); 7 | let global = context.global; 8 | let fnPromiseRef; 9 | global.setSync('fnPromise', fnPromiseRef = new ivm.Reference(function() { 10 | return new Promise(function(resolve, reject) { 11 | fs.readFile(__filename, 'utf8', function(err, val) { 12 | err ? reject(err) : resolve(val); 13 | }); 14 | }); 15 | })); 16 | global.setSync('fnSync', new ivm.Reference(function() { 17 | return 'hello'; 18 | })); 19 | 20 | try { 21 | fnPromiseRef.applySyncPromise(undefined, []); 22 | console.log('applySyncPromise is not allowed'); 23 | } catch (err) { 24 | if (!/default thread/.test(err)) { 25 | console.log('strange error'); 26 | } 27 | } 28 | 29 | isolate.compileScriptSync(`function recursive() { 30 | throw new Error('passed the test'); 31 | }`).runSync(context); 32 | let recursiveFn = global.getSync('recursive'); 33 | 34 | (async function() { 35 | let script = await isolate.compileScript('fnPromise.applySyncPromise(undefined, [], {})'); 36 | let value = await script.run(context); 37 | if (/hello123/.test(value)) { 38 | try { 39 | console.log(recursiveFn()); 40 | } catch (err) { 41 | if (/passed the test/.test(err)) { 42 | console.log('pass'); 43 | } 44 | } 45 | } 46 | 47 | let script2 = await isolate.compileScript('fnSync.applySync(undefined, [])'); 48 | if (await script2.run(context) !== 'hello') { 49 | console.log('nevermind it didnt pass'); 50 | } 51 | })().catch(console.error); 52 | 53 | // Test slow promise timeout 54 | global.setSync('slowPromise', new ivm.Reference(function() { 55 | return new Promise(function(resolve) { 56 | setTimeout(function() { 57 | resolve(); 58 | }, 50); 59 | }); 60 | })); 61 | isolate.compileScriptSync('slowPromise.applySyncPromise(undefined, [])').run(context, { timeout: 1 }).catch(() => 0); 62 | 63 | // Test dead promise (This causes a memory leak! Don't do this!) 64 | // Disabled test because `timeout` is now paused when the isolate is not active. 65 | /* 66 | global.setSync('deadPromise', new ivm.Reference(function() { 67 | return new Promise(() => {}); 68 | })); 69 | isolate.compileScriptSync('deadPromise.applySyncPromise(undefined, [])').run(context, { timeout: 1 }).catch(() => 0); 70 | */ 71 | -------------------------------------------------------------------------------- /src/isolate/holder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "platform_delegate.h" 3 | #include "runnable.h" 4 | #include "v8_version.h" 5 | #include "lib/lockable.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace ivm { 12 | 13 | class IsolateEnvironment; 14 | 15 | class IsolateDisposeWait { 16 | public: 17 | void IsolateDidDispose(); 18 | void Join(); 19 | 20 | private: 21 | lockable_t is_disposed{false}; 22 | }; 23 | 24 | class IsolateHolder { 25 | friend IsolateEnvironment; 26 | 27 | public: 28 | explicit IsolateHolder(std::shared_ptr isolate) : 29 | isolate{std::move(isolate)} {} 30 | IsolateHolder(const IsolateHolder&) = delete; 31 | ~IsolateHolder() = default; 32 | auto operator=(const IsolateHolder&) = delete; 33 | 34 | static auto GetCurrent() -> std::shared_ptr; 35 | 36 | auto Dispose() -> bool; 37 | void Release(); 38 | auto GetIsolate() -> std::shared_ptr; 39 | void ScheduleTask(std::unique_ptr task, bool run_inline, bool wake_isolate, bool handle_task = false); 40 | 41 | private: 42 | lockable_t> isolate; 43 | }; 44 | 45 | // This needs to be separate from IsolateHolder because v8 holds references to this indefinitely and 46 | // we don't want it keeping the isolate alive. 47 | class IsolateTaskRunner final : public TaskRunner { 48 | public: 49 | explicit IsolateTaskRunner(const std::shared_ptr& isolate) : weak_env{isolate} {} 50 | IsolateTaskRunner(const IsolateTaskRunner&) = delete; 51 | ~IsolateTaskRunner() final = default; 52 | auto operator=(const IsolateTaskRunner&) = delete; 53 | 54 | // Methods for v8::TaskRunner 55 | void PostTaskImpl(std::unique_ptr task, const v8::SourceLocation& /*location*/) final; 56 | void PostDelayedTaskImpl(std::unique_ptr task, double delay_in_seconds, const v8::SourceLocation& /*location*/) final; 57 | void PostNonNestableTaskImpl(std::unique_ptr task, const v8::SourceLocation& location) final { 58 | #if V8_AT_LEAST(13, 3, 241) 59 | PostTask(std::move(task), location); 60 | #else 61 | PostTask(std::move(task)); 62 | #endif 63 | } 64 | 65 | private: 66 | std::weak_ptr weak_env; 67 | }; 68 | 69 | } // namespace ivm 70 | -------------------------------------------------------------------------------- /tests/cpu-wall-timer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const kTimeout = 100; 3 | const kTimeoutRange = 115; 4 | const kError = 0.98; 5 | let ivm = require('isolated-vm'); 6 | let isolate = new ivm.Isolate; 7 | let context = isolate.createContextSync(); 8 | 9 | function spin(timeout) { 10 | let d = Date.now() + timeout + 2; 11 | while (Date.now() < d); 12 | } 13 | 14 | function hrToFloat(hr) { 15 | return Number(hr) / 1e6; 16 | } 17 | 18 | function checkRange(val, min, max) { 19 | return val <= max / kError && val >= min * kError; 20 | } 21 | 22 | // Synchronous timers 23 | context.global.setSync('defaultIsolateSpin', new ivm.Reference(spin)); 24 | context.global.setSync('recurse', new ivm.Reference(function(code) { 25 | isolate.compileScriptSync(code).runSync(context); 26 | })); 27 | isolate.compileScriptSync(''+ spin).runSync(context); 28 | isolate.compileScriptSync(` 29 | spin(${kTimeout}); 30 | defaultIsolateSpin.applySync(undefined, [ ${kTimeout} ]); 31 | recurse.applySync(undefined, [ 'spin(${kTimeout});' ]); 32 | `).runSync(context); 33 | 34 | if (!checkRange(hrToFloat(isolate.cpuTime), kTimeout * 2, kTimeoutRange * 2)) { 35 | console.log('cpu time wrong'); 36 | } 37 | if (!checkRange(hrToFloat(isolate.wallTime), kTimeout * 3, kTimeoutRange * 3)) { 38 | console.log('wall time wrong'); 39 | } 40 | 41 | // Asynchronous time 42 | let initialCpu = hrToFloat(isolate.cpuTime); 43 | let lastCpu; 44 | let initialWall = hrToFloat(isolate.wallTime); 45 | isolate.compileScriptSync(`spin(${kTimeout}); defaultIsolateSpin.applySync(undefined, [ ${kTimeout} ] );`).run(context); 46 | let d = Date.now() + kTimeoutRange * 2; 47 | let passedCpu = false; 48 | while (Date.now() < d && !passedCpu) { 49 | let cpuTime = hrToFloat(isolate.cpuTime); 50 | // Ensures cpu is ticking up while the spin() loop runs in another thread 51 | if (lastCpu === undefined) { 52 | lastCpu = cpuTime; 53 | } else if (lastCpu !== cpuTime) { 54 | passedCpu = true; 55 | } 56 | } 57 | if (!passedCpu) { 58 | console.log('Async CPU time not updating'); 59 | } 60 | 61 | d = Date.now() + kTimeout * 4; 62 | let interval = setInterval(function() { 63 | if (Date.now() > d) { 64 | clearInterval(interval); 65 | } 66 | if (checkRange(hrToFloat(isolate.wallTime) - initialWall, kTimeout * 2, kTimeoutRange * 2)) { 67 | clearInterval(interval); 68 | if (checkRange(hrToFloat(isolate.cpuTime) - initialCpu, kTimeout, kTimeoutRange)) { 69 | console.log('pass'); 70 | } 71 | } 72 | }, 10); 73 | -------------------------------------------------------------------------------- /src/module/module_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "isolate/holder.h" 6 | #include "isolate/remote_handle.h" 7 | #include "transferable.h" 8 | 9 | namespace ivm { 10 | 11 | struct ModuleInfo { 12 | // Underlying data on the module. Some information is stored outside of v8 so there is a separate 13 | // struct to hold this data, which is then referenced by any number of handles. 14 | friend struct InstantiateRunner; 15 | friend class ModuleHandle; 16 | enum class LinkStatus { None, Linking, Linked }; 17 | std::mutex mutex; 18 | class ModuleLinker* linker = nullptr; 19 | LinkStatus link_status = LinkStatus::None; 20 | int identity_hash; 21 | std::vector dependency_specifiers; 22 | std::unordered_map> resolutions; 23 | RemoteHandle handle; 24 | RemoteHandle context_handle; 25 | RemoteHandle global_namespace; 26 | RemoteHandle meta_callback; 27 | explicit ModuleInfo(v8::Local handle); 28 | ModuleInfo(const ModuleInfo&) = delete; 29 | auto operator=(const ModuleInfo&) = delete; 30 | ~ModuleInfo(); 31 | }; 32 | 33 | class ModuleHandle : public TransferableHandle { 34 | private: 35 | class ModuleHandleTransferable : public Transferable { 36 | private: 37 | std::shared_ptr info; 38 | public: 39 | explicit ModuleHandleTransferable(std::shared_ptr info); 40 | auto TransferIn() -> v8::Local final; 41 | }; 42 | 43 | std::shared_ptr info; 44 | 45 | public: 46 | using DontFreezeInstance = void; 47 | 48 | explicit ModuleHandle(std::shared_ptr info); 49 | 50 | static auto Definition() -> v8::Local; 51 | auto TransferOut() -> std::unique_ptr final; 52 | 53 | auto GetDependencySpecifiers() -> v8::Local; 54 | auto GetInfo() const -> std::shared_ptr; 55 | auto Release() -> v8::Local; 56 | 57 | auto Instantiate(class ContextHandle& context_handle, v8::Local callback) -> v8::Local; 58 | auto InstantiateSync(class ContextHandle& context_handle, v8::Local callback) -> v8::Local; 59 | 60 | template 61 | auto Evaluate(v8::MaybeLocal maybe_options) -> v8::Local; 62 | 63 | auto GetNamespace() -> v8::Local; 64 | 65 | static void InitializeImportMeta(v8::Local context, v8::Local module, v8::Local meta); 66 | }; 67 | 68 | } // namespace ivm 69 | -------------------------------------------------------------------------------- /src/external_copy/serializer_nortti.cc: -------------------------------------------------------------------------------- 1 | #include "serializer.h" 2 | #include "isolate/functor_runners.h" 3 | #include "module/transferable.h" 4 | 5 | /** 6 | * This file is compiled *without* runtime type information, which matches the nodejs binary and 7 | * allows the serializer delegates to resolve correctly. 8 | */ 9 | 10 | using namespace v8; 11 | 12 | namespace ivm::detail { 13 | 14 | void SerializerDelegate::ThrowDataCloneError(Local message) { 15 | Isolate::GetCurrent()->ThrowException(Exception::TypeError(message)); 16 | } 17 | 18 | auto SerializerDelegate::GetSharedArrayBufferId( 19 | Isolate* /*isolate*/, Local shared_array_buffer) -> Maybe { 20 | auto result = Nothing(); 21 | detail::RunBarrier([&]() { 22 | transferables.emplace_back(std::make_unique(shared_array_buffer)); 23 | result = Just(transferables.size() - 1); 24 | }); 25 | return result; 26 | } 27 | 28 | auto SerializerDelegate::GetWasmModuleTransferId( 29 | Isolate* /*isolate*/, Local module) -> Maybe { 30 | auto result = Just(wasm_modules.size()); 31 | wasm_modules.emplace_back(module->GetCompiledModule()); 32 | return result; 33 | } 34 | 35 | auto SerializerDelegate::WriteHostObject(Isolate* /*isolate*/, Local object) -> Maybe { 36 | auto result = Nothing(); 37 | detail::RunBarrier([&]() { 38 | serializer->WriteUint32(transferables.size()); 39 | transferables.emplace_back(TransferOut(object)); 40 | result = Just(true); 41 | }); 42 | return result; 43 | } 44 | 45 | auto DeserializerDelegate::ReadHostObject(Isolate* /*isolate*/) -> MaybeLocal { 46 | MaybeLocal result; 47 | detail::RunBarrier([&]() { 48 | uint32_t ii; 49 | assert(deserializer->ReadUint32(&ii)); 50 | result = transferables[ii]->TransferIn().As(); 51 | }); 52 | return result; 53 | } 54 | 55 | auto DeserializerDelegate::GetSharedArrayBufferFromId( 56 | Isolate* /*isolate*/, uint32_t clone_id) -> MaybeLocal { 57 | MaybeLocal result; 58 | detail::RunBarrier([&]() { 59 | result = transferables[clone_id]->TransferIn().As(); 60 | }); 61 | return result; 62 | } 63 | 64 | auto DeserializerDelegate::GetWasmModuleFromId( 65 | Isolate* isolate, uint32_t transfer_id) -> MaybeLocal { 66 | MaybeLocal result; 67 | detail::RunBarrier([&]() { 68 | result = WasmModuleObject::FromCompiledModule(isolate, wasm_modules[transfer_id]); 69 | }); 70 | return result; 71 | } 72 | 73 | } // namespace ivm::detail 74 | -------------------------------------------------------------------------------- /tests/cached-data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | const assert = require('assert'); 4 | const { V8_AT_LEAST } = require('./lib/v8-version'); 5 | const src = Array(20000).fill().map((_, ii) => `function a${ii}(){}`).join(';'); 6 | 7 | // Each isolate seems to have some internal source cache so each of these tests run in a new 8 | // isolate instead of reusing the same one for multiple tests 9 | 10 | { 11 | // Try compilation with no flags and ensure nothing extra is returned 12 | const isolate = new ivm.Isolate; 13 | const script = isolate.compileScriptSync(src); 14 | assert.strictEqual(script.cachedData, undefined); 15 | assert.strictEqual(script.cachedDataRejected, undefined); 16 | } 17 | 18 | { 19 | // Try broken cache data and produce fallback 20 | const isolate = new ivm.Isolate; 21 | const script = isolate.compileScriptSync(src, { cachedData: new ivm.ExternalCopy(Buffer.from('garbage').buffer), produceCachedData: true }); 22 | assert.ok(script.cachedData); 23 | assert.strictEqual(script.cachedDataRejected, true); 24 | } 25 | 26 | let cachedData; 27 | { 28 | // Produce some cached data 29 | const isolate = new ivm.Isolate; 30 | const script = isolate.compileScriptSync(src, { produceCachedData: true }); 31 | assert.ok(script.cachedData); 32 | assert.strictEqual(script.cachedDataRejected, undefined); 33 | cachedData = script.cachedData; 34 | 35 | } 36 | 37 | { 38 | // Time compilation with no cached data 39 | let d = Date.now(); 40 | const timeout = 200; 41 | let count = 0; 42 | do { 43 | const isolate = new ivm.Isolate; 44 | const script = isolate.compileScriptSync(src); 45 | ++count; 46 | } while (Date.now() < d + timeout); 47 | 48 | // Compare to compilation with cached data 49 | d = Date.now(); 50 | for (let ii = 0; ii < count; ++ii) { 51 | const isolate = new ivm.Isolate; 52 | const script = isolate.compileScriptSync(src, { cachedData }); 53 | assert.ok(!script.cachedDataRejected); 54 | } 55 | if (Date.now() - d > timeout / 2) { 56 | console.log('cached data is suspiciously slow'); 57 | } 58 | } 59 | 60 | // Check module cached data 61 | { 62 | const cachedData = (() => { 63 | const isolate = new ivm.Isolate; 64 | const module = isolate.compileModuleSync(src, { produceCachedData: true }); 65 | assert.ok(module.cachedData); 66 | assert.strictEqual(module.cachedDataRejected, undefined); 67 | return module.cachedData; 68 | })(); 69 | const isolate = new ivm.Isolate; 70 | const module = isolate.compileModuleSync(src, { cachedData }); 71 | assert.strictEqual(module.cachedData, undefined); 72 | assert.ok(!module.cachedDataRejected); 73 | } 74 | 75 | console.log('pass'); 76 | -------------------------------------------------------------------------------- /native-example/example.cc: -------------------------------------------------------------------------------- 1 | // Example module w/ async function 2 | 3 | #include // You don't have to use nan, but it's supported 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // `using namespace` is omitted to better show which interfaces exist where. 10 | 11 | // `Runnable` is used for async function calls. It is possible that `Run` will never actually be 12 | // called, in the case the isolate is disposed. You should make sure to cleanup in the destructor 13 | // instead of `Run`. 14 | struct TimeoutCallback : public isolated_vm::Runnable { 15 | // `RemoteHandle` should be used instead of `v8::Persistent` or `nan::Persistent` 16 | isolated_vm::RemoteHandle context; 17 | isolated_vm::RemoteHandle fn; 18 | 19 | TimeoutCallback( 20 | isolated_vm::RemoteHandle context, 21 | isolated_vm::RemoteHandle fn 22 | ) : 23 | context(std::move(context)), 24 | fn(std::move(fn)) {} 25 | 26 | void Run() override { 27 | // `v8::Locker` and `v8::HandleScope` are already set up and the isolate is entered. All that is 28 | // left to do is enter a context. 29 | v8::Context::Scope context_scope(*context); 30 | v8::Local local_fn = *fn; 31 | v8::Local argv[0]; 32 | Nan::Call(local_fn, v8::Local::Cast(Nan::Undefined()), 0, argv); 33 | } 34 | }; 35 | 36 | NAN_METHOD(timeout) { 37 | isolated_vm::IsolateHolder isolate_holder = isolated_vm::IsolateHolder::GetCurrent(); 38 | isolated_vm::RemoteHandle context(v8::Isolate::GetCurrent()->GetCurrentContext()); 39 | isolated_vm::RemoteHandle fn(v8::Local::Cast(info[0])); 40 | 41 | uint32_t ms = Nan::To(info[1]).FromJust(); 42 | std::thread timeout_thread([=]() mutable { 43 | // Note that in this closure it is not safe to call into v8! The only thing you can do is 44 | // schedule a task. 45 | std::this_thread::sleep_for(std::chrono::milliseconds(ms)); 46 | isolate_holder.ScheduleTask(std::make_unique(std::move(context), std::move(fn))); 47 | }); 48 | timeout_thread.detach(); 49 | 50 | info.GetReturnValue().Set(Nan::Undefined()); 51 | } 52 | 53 | ISOLATED_VM_MODULE void InitForContext(v8::Isolate* isolate, v8::Local context, v8::Local target) { 54 | Nan::Set(target, Nan::New("timeout").ToLocalChecked(), Nan::GetFunction(Nan::New(timeout)).ToLocalChecked()); 55 | } 56 | 57 | NAN_MODULE_INIT(init) { 58 | v8::Isolate* isolate = v8::Isolate::GetCurrent(); 59 | InitForContext(isolate, isolate->GetCurrentContext(), target); 60 | } 61 | NODE_MODULE(native, init); 62 | -------------------------------------------------------------------------------- /src/isolate/inspector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "lib/lockable.h" 4 | #include "runnable.h" 5 | #include "v8_inspector_wrapper.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace ivm { 12 | class IsolateEnvironment; 13 | class InspectorSession; 14 | 15 | /** 16 | * This handles communication to the v8 backend. There is only one of these per isolate, but many 17 | * sessions may connect to this agent. 18 | * 19 | * This class combines the role of both V8Inspector and V8InspectorClient into a single class. 20 | */ 21 | class InspectorAgent : public v8_inspector::V8InspectorClient { 22 | friend class InspectorSession; 23 | private: 24 | IsolateEnvironment& isolate; 25 | std::unique_ptr inspector; 26 | std::condition_variable cv; 27 | std::mutex mutex; 28 | lockable_t> active_sessions; 29 | bool running = false; 30 | bool terminated = false; 31 | 32 | auto ConnectSession(InspectorSession& session) -> std::unique_ptr; 33 | void SessionDisconnected(InspectorSession& session); 34 | void SendInterrupt(std::unique_ptr task); 35 | 36 | public: 37 | explicit InspectorAgent(IsolateEnvironment& isolate); 38 | ~InspectorAgent() override; 39 | InspectorAgent(const InspectorAgent&) = delete; 40 | auto operator= (const InspectorAgent&) -> InspectorAgent& = delete; 41 | void runMessageLoopOnPause(int context_group_id) final; 42 | void quitMessageLoopOnPause() final; 43 | void ContextCreated(v8::Local context, const std::string& name); 44 | void ContextDestroyed(v8::Local context); 45 | void Terminate(); 46 | }; 47 | 48 | /** 49 | * Individual session to the v8 inspector agent. This probably relays messages from a websocket to 50 | * the v8 backend. To use the class you need to implement the abstract methods defined in Channel. 51 | * 52 | * This class combines the role of both V8Inspector::Channel and V8InspectorSession into a single 53 | * class. 54 | */ 55 | class InspectorSession : public v8_inspector::V8Inspector::Channel { 56 | friend class InspectorAgent; 57 | private: 58 | InspectorAgent& agent; 59 | std::shared_ptr session; 60 | std::mutex mutex; 61 | public: 62 | explicit InspectorSession(IsolateEnvironment& isolate); 63 | ~InspectorSession() override; 64 | InspectorSession(const InspectorSession&) = delete; 65 | auto operator= (const InspectorSession&) -> InspectorSession& = delete; 66 | void Disconnect(); 67 | void DispatchBackendProtocolMessage(std::vector message); 68 | }; 69 | 70 | } // namespace ivm 71 | -------------------------------------------------------------------------------- /tests/transfer-options.js: -------------------------------------------------------------------------------- 1 | const ivm = require('isolated-vm'); 2 | const assert = require('assert'); 3 | 4 | const isolate = new ivm.Isolate; 5 | const context = isolate.createContextSync(); 6 | const global = context.global; 7 | global.setSync('global', global.derefInto()); 8 | 9 | const value = { foo: 'bar' }; 10 | 11 | global.setSync('test', value, { reference: true }); 12 | assert.equal(global.getSync('test').deref(), value); 13 | 14 | global.setSync('test', value, { copy: true }); 15 | assert.deepEqual(global.getSync('test', { copy: true }), value); 16 | assert.deepEqual(global.getSync('test', { externalCopy: true }).copy(), value); 17 | global.getSync('test', { copy: true, promise: true }).then(resolved => assert.deepEqual(resolved, value)).then(resolved); 18 | assert.deepEqual(context.evalSync('test', { copy: true }), value); 19 | 20 | const fn = context.evalSync('(function foo(arg1) { return arg1; })', { reference: true }); 21 | assert.deepEqual(fn.applySync(undefined, [ value ], { arguments: { copy: true }, result: { copy: true } }), value); 22 | 23 | const closureResult = context.evalClosureSync('return [ $0, $1 ];', [ value, value ], { arguments: { copy: true }, result: { copy: true } }); 24 | assert.deepEqual(closureResult[0], closureResult[1]); 25 | assert.deepEqual(closureResult[0], value); 26 | 27 | const promise = isolate.compileScriptSync( 28 | 'global.promise = new Promise(resolve => { global.resolve = resolve; })' 29 | ).runSync(context, { copy: true, promise: true }); 30 | global.getSync('promise', { copy: true, promise: true }).then(resolved => assert.deepEqual(value, resolved)).then(resolved); 31 | global.getSync('resolve', { reference: true }).applySync(undefined, [ value ], { arguments: { copy: true } }); 32 | promise.then(resolved => assert.deepEqual(value, resolved)).then(resolved); 33 | 34 | assert.throws(() => context.evalClosureSync(`return {}`)); 35 | 36 | const delegatedPromise = context.evalClosureSync( 37 | `return $0.apply(undefined, [], { result: { promise: true, copy: true }})`, 38 | [ () => new Promise(resolve => process.nextTick(() => resolve(value))) ], 39 | { arguments: { reference: true }, result: { promise: true, copy: true } }); 40 | delegatedPromise.then(resolved => assert.deepEqual(resolved, value)).then(resolved); 41 | 42 | context.evalClosure( 43 | 'return $0.applySyncPromise()', 44 | [ async() => new ivm.ExternalCopy(value).copyInto() ], { arguments: { reference: true }, result: { copy: true }} 45 | ).then(result => assert.deepEqual(result, value)); 46 | 47 | let ii = 0; 48 | function resolved() { 49 | if (++ii === 4) { 50 | isolate.compileScriptSync('new Promise(() => {})').runSync(context, { promise: true }).catch(() => { 51 | // abandoned 52 | console.log('pass'); 53 | }); 54 | isolate.dispose(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/isolate/holder.cc: -------------------------------------------------------------------------------- 1 | #include "holder.h" 2 | #include "environment.h" 3 | #include "scheduler.h" 4 | #include "util.h" 5 | #include "lib/timer.h" 6 | #include 7 | 8 | namespace ivm { 9 | 10 | void IsolateDisposeWait::IsolateDidDispose() { 11 | *is_disposed.write() = true; 12 | is_disposed.notify_all(); 13 | } 14 | 15 | void IsolateDisposeWait::Join() { 16 | auto lock = is_disposed.read(); 17 | while (!*lock) { 18 | lock.wait(); 19 | } 20 | } 21 | 22 | auto IsolateHolder::GetCurrent() -> std::shared_ptr { 23 | return IsolateEnvironment::GetCurrentHolder(); 24 | } 25 | 26 | auto IsolateHolder::Dispose() -> bool { 27 | auto ref = std::exchange(*isolate.write(), {}); 28 | if (ref) { 29 | ref->Terminate(); 30 | ref.reset(); 31 | return true; 32 | } else { 33 | return false; 34 | } 35 | } 36 | 37 | void IsolateHolder::Release() { 38 | auto ref = std::exchange(*isolate.write(), {}); 39 | ref.reset(); 40 | } 41 | 42 | auto IsolateHolder::GetIsolate() -> std::shared_ptr { 43 | return *isolate.read(); 44 | } 45 | 46 | void IsolateHolder::ScheduleTask(std::unique_ptr task, bool run_inline, bool wake_isolate, bool handle_task) { 47 | auto ref = *isolate.read(); 48 | if (ref) { 49 | if (run_inline && Executor::MayRunInlineTasks(*ref)) { 50 | task->Run(); 51 | return; 52 | } 53 | auto lock = ref->scheduler->Lock(); 54 | if (handle_task) { 55 | lock->handle_tasks.push(std::move(task)); 56 | } else { 57 | lock->tasks.push(std::move(task)); 58 | } 59 | if (wake_isolate) { 60 | lock->WakeIsolate(std::move(ref)); 61 | } 62 | } 63 | } 64 | 65 | // Methods for v8::TaskRunner 66 | void IsolateTaskRunner::PostTaskImpl(std::unique_ptr task, const v8::SourceLocation& /*location*/) { 67 | auto env = weak_env.lock(); 68 | if (env) { 69 | env->GetScheduler().Lock()->tasks.push(std::move(task)); 70 | } 71 | } 72 | 73 | void IsolateTaskRunner::PostDelayedTaskImpl(std::unique_ptr task, double delay_in_seconds, const v8::SourceLocation& /*location*/) { 74 | // wait_detached erases the type of the lambda into a std::function which must be 75 | // copyable. The unique_ptr is stored in a shared pointer so ownership can be handled correctly. 76 | auto shared_task = std::make_shared>(std::move(task)); 77 | auto weak_env = this->weak_env; 78 | timer_t::wait_detached(static_cast(delay_in_seconds * 1000), [shared_task, weak_env](void* next) { 79 | auto env = weak_env.lock(); 80 | if (env) { 81 | // Don't wake the isolate, this will just run the next time the isolate is doing something 82 | env->GetScheduler().Lock()->tasks.push(std::move(*shared_task)); 83 | } 84 | timer_t::chain(next); 85 | }); 86 | } 87 | 88 | } // namespace ivm 89 | -------------------------------------------------------------------------------- /src/external_copy/serializer.cc: -------------------------------------------------------------------------------- 1 | #include "serializer.h" 2 | #include "isolate/allocator.h" 3 | 4 | using namespace v8; 5 | namespace ivm { 6 | 7 | /** 8 | * ExternalCopySerialized implementation 9 | */ 10 | ExternalCopySerialized::ExternalCopySerialized(Local value, ArrayRange transfer_list) : 11 | BaseSerializer{[&](ValueSerializer& serializer, Local context) { 12 | // Mark ArrayBuffers as transferred, but don't actually transfer yet otherwise it will invalidate 13 | // array views before they are transferred 14 | int ii = 0; 15 | for (auto handle : transfer_list) { 16 | if (handle->IsArrayBuffer()) { 17 | serializer.TransferArrayBuffer(ii++, handle.As()); 18 | } else { 19 | throw RuntimeTypeError("Non-ArrayBuffer passed in `transferList`"); 20 | } 21 | } 22 | 23 | // Serialize object and save 24 | serializer.WriteHeader(); 25 | Unmaybe(serializer.WriteValue(context, value)); 26 | }} { 27 | 28 | // Transfer ArrayBuffers 29 | for (auto handle : transfer_list) { 30 | array_buffers.emplace_back(ExternalCopyArrayBuffer::Transfer(handle.As())); 31 | } 32 | } 33 | 34 | auto ExternalCopySerialized::CopyInto(bool transfer_in) -> Local { 35 | Local value; 36 | 37 | Deserialize([&](ValueDeserializer& deserializer, Local context) { 38 | // Transfer ArrayBuffers 39 | for (unsigned ii = 0; ii < array_buffers.size(); ++ii) { 40 | deserializer.TransferArrayBuffer(ii, array_buffers[ii]->CopyIntoCheckHeap(transfer_in).As()); 41 | } 42 | 43 | // Deserialize object 44 | Unmaybe(deserializer.ReadHeader(context)); 45 | return deserializer.ReadValue(context).ToLocal(&value); 46 | }); 47 | 48 | return value; 49 | } 50 | 51 | /** 52 | * SerializedVector implementation 53 | */ 54 | SerializedVector::SerializedVector(const v8::FunctionCallbackInfo& info) : 55 | BaseSerializer([&](ValueSerializer& serializer, Local context) { 56 | int length = info.Length(); 57 | serializer.WriteUint32(length); 58 | for (int ii = 0; ii < length; ++ii) { 59 | Unmaybe(serializer.WriteValue(context, info[ii])); 60 | } 61 | }) {} 62 | 63 | auto SerializedVector::CopyIntoAsVector() -> std::vector> { 64 | std::vector> result; 65 | Deserialize([&](ValueDeserializer& deserializer, Local context) { 66 | // Read length 67 | uint32_t length; 68 | Unmaybe(deserializer.ReadHeader(context)); 69 | if (!deserializer.ReadUint32(&length)) { 70 | throw RuntimeGenericError("Invalid arguments payload"); 71 | } 72 | 73 | // Read arguments 74 | result.resize(length); 75 | for (unsigned ii = 0; ii < length; ++ii) { 76 | if (!deserializer.ReadValue(context).ToLocal(&result[ii])) { 77 | return false; 78 | } 79 | } 80 | return true; 81 | }); 82 | return result; 83 | } 84 | 85 | } // namespace ivm 86 | -------------------------------------------------------------------------------- /src/module/lib_handle.cc: -------------------------------------------------------------------------------- 1 | #include "lib_handle.h" 2 | #include 3 | 4 | using namespace v8; 5 | using std::unique_ptr; 6 | 7 | namespace ivm { 8 | 9 | /** 10 | * Stateless transferable interface 11 | */ 12 | auto LibHandle::LibTransferable::TransferIn() -> Local { 13 | return ClassHandle::NewInstance(); 14 | } 15 | 16 | /** 17 | * ivm.lib API container 18 | */ 19 | auto LibHandle::Definition() -> Local { 20 | return Inherit(MakeClass( 21 | "Lib", nullptr, 22 | "hrtime", MemberFunction{}, 23 | "privateSymbol", MemberFunction{}, 24 | "testHang", MemberFunction{}, 25 | "testOOM", MemberFunction{} 26 | )); 27 | } 28 | 29 | auto LibHandle::TransferOut() -> unique_ptr { 30 | return std::make_unique(); 31 | } 32 | 33 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 34 | auto LibHandle::Hrtime(MaybeLocal maybe_diff) -> Local { 35 | Isolate* isolate = Isolate::GetCurrent(); 36 | Local context = isolate->GetCurrentContext(); 37 | uint64_t time = uv_hrtime(); 38 | constexpr auto kNanos = (uint64_t)1e9; 39 | Local diff; 40 | if (maybe_diff.ToLocal(&diff)) { 41 | if (diff->Length() != 2) { 42 | throw RuntimeTypeError("hrtime diff must be 2-length array"); 43 | } 44 | uint64_t time_diff = Unmaybe(diff->Get(context, 0)).As()->Value() * kNanos + Unmaybe(diff->Get(context, 1)).As()->Value(); 45 | time -= time_diff; 46 | } 47 | Local ret = Array::New(isolate, 2); 48 | Unmaybe(ret->Set(context, 0, Uint32::New(isolate, (uint32_t)(time / kNanos)))); 49 | Unmaybe(ret->Set(context, 1, Uint32::New(isolate, (uint32_t)(time - (time / kNanos) * kNanos)))); 50 | return ret; 51 | } 52 | 53 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 54 | auto LibHandle::PrivateSymbol(MaybeLocal maybe_name) -> Local { 55 | Local name{}; 56 | if (maybe_name.ToLocal(&name)) { /* nothing */ } 57 | auto symbol = Private::New(Isolate::GetCurrent(), name); 58 | return *reinterpret_cast*>(&symbol); 59 | } 60 | 61 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 62 | auto LibHandle::TestHang() -> Local { 63 | auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(60); 64 | while (std::chrono::steady_clock::now() < deadline) {} 65 | return Undefined(Isolate::GetCurrent()); 66 | } 67 | 68 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 69 | auto LibHandle::TestOOM() -> Local { 70 | Isolate* isolate = Isolate::GetCurrent(); 71 | for (;;) { 72 | Array::New(isolate, 128); 73 | } 74 | return {}; 75 | } 76 | 77 | } // namespace ivm 78 | -------------------------------------------------------------------------------- /src/lib/suspend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Linux 4 | #ifdef __linux__ 5 | #include 6 | namespace ivm { 7 | class thread_suspend_handle { 8 | public: 9 | thread_suspend_handle() : 10 | id{pthread_self()}, 11 | prev{std::exchange(instance(), this)} {} 12 | 13 | ~thread_suspend_handle() { 14 | std::exchange(instance(), prev); 15 | } 16 | 17 | thread_suspend_handle(const thread_suspend_handle&) = delete; 18 | auto operator=(thread_suspend_handle&) = delete; 19 | 20 | void suspend() { 21 | // Called from another thread 22 | invoked = true; 23 | pthread_kill(id, SIGRTMIN); 24 | } 25 | 26 | struct initialize { 27 | initialize() { 28 | // Set process-wide signal handler 29 | struct sigaction handler; 30 | memset(&handler, '\0', sizeof(handler)); 31 | handler.sa_handler = callback; 32 | sigemptyset(&handler.sa_mask); 33 | assert(sigaction(SIGRTMIN, &handler, nullptr) == 0); 34 | } 35 | }; 36 | 37 | private: 38 | static void callback(int) { 39 | if (instance() != nullptr && instance()->invoked == true) { 40 | while (true) { 41 | using namespace std::chrono_literals; 42 | std::this_thread::sleep_for(100s); 43 | } 44 | } 45 | } 46 | 47 | static thread_suspend_handle*& instance() { 48 | static thread_local thread_suspend_handle* instance = nullptr; 49 | return instance; 50 | } 51 | 52 | pthread_t id; 53 | thread_suspend_handle* prev; 54 | bool invoked = false; 55 | }; 56 | } // namespace ivm 57 | 58 | // macOS 59 | #elif __APPLE__ 60 | #include 61 | namespace ivm { 62 | class thread_suspend_handle { 63 | public: 64 | thread_suspend_handle() : id{mach_thread_self()} {} 65 | ~thread_suspend_handle() = default; 66 | thread_suspend_handle(const thread_suspend_handle&) = delete; 67 | auto operator=(thread_suspend_handle&) = delete; 68 | void suspend() const { thread_suspend(id); } 69 | struct initialize {}; 70 | private: 71 | mach_port_t id; 72 | }; 73 | } // namespace ivm 74 | 75 | // Windows 76 | #elif _WIN32 77 | #include 78 | namespace ivm { 79 | class thread_suspend_handle { 80 | public: 81 | thread_suspend_handle() : id{GetCurrentThread()} {} 82 | ~thread_suspend_handle() = default; 83 | thread_suspend_handle(const thread_suspend_handle&) = delete; 84 | auto operator=(thread_suspend_handle&) = delete; 85 | void suspend() const { SuspendThread(id); } 86 | struct initialize {}; 87 | private: 88 | HANDLE id; 89 | }; 90 | } // namespace ivm 91 | 92 | // Fallback [no-op] 93 | #else 94 | namespace ivm { 95 | class thread_suspend_handle { 96 | public: 97 | thread_suspend_handle() {} 98 | ~thread_suspend_handle() = default; 99 | thread_suspend_handle(const thread_suspend_handle&) = delete; 100 | auto operator=(thread_suspend_handle&) = delete; 101 | void suspend() const {} 102 | struct initialize {}; 103 | }; 104 | } // namespace ivm 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /src/isolate/cpu_profile_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "executor.h" 8 | #include "v8-profiler.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace ivm { 18 | 19 | class IVMCpuProfile { 20 | public: 21 | explicit IVMCpuProfile(const std::shared_ptr& cpuProfile); 22 | ~IVMCpuProfile() = default; 23 | 24 | class CallFrame { 25 | public: 26 | explicit CallFrame(const v8::CpuProfileNode* node); 27 | ~CallFrame() = default; 28 | 29 | auto ToJSObject(v8::Isolate *iso) -> v8::Local; 30 | private: 31 | const char* function_name; 32 | const char* url; 33 | int script_id; 34 | int line_number; 35 | int column_number; 36 | }; 37 | 38 | class ProfileNode { 39 | public: 40 | explicit ProfileNode(const v8::CpuProfileNode* node); 41 | ~ProfileNode() = default; 42 | 43 | auto ToJSObject(v8::Isolate *iso) -> v8::Local; 44 | private: 45 | unsigned int hit_count; 46 | unsigned int node_id; 47 | const char* bailout_reason; 48 | std::vector children; 49 | CallFrame call_frame; 50 | }; 51 | 52 | auto GetStartTime() const -> int64_t { return start_time; } 53 | 54 | auto ToJSObject(v8::Isolate *iso) -> v8::Local; 55 | 56 | 57 | private: 58 | std::thread::id profile_thread; 59 | int64_t start_time; 60 | int64_t end_time; 61 | int64_t samples_count; 62 | std::vector samples; 63 | std::vector timestamps; 64 | std::vector profileNodes; 65 | 66 | auto GetTidValue(v8::Isolate *iso) -> v8::Local; 67 | 68 | auto BuildCpuProfile(v8::Isolate *iso) -> v8::Local; 69 | 70 | void FlatNodes(const v8::CpuProfileNode* node, std::vector* nodes) { // NOLINT(misc-no-recursion) 71 | nodes->emplace_back(node); 72 | const int childrenCount = node->GetChildrenCount(); 73 | 74 | for (int index = 0; index < childrenCount; ++index) { 75 | FlatNodes(node->GetChild(index), nodes); 76 | } 77 | } 78 | 79 | }; 80 | 81 | class CpuProfileManager { 82 | public: 83 | CpuProfileManager(); 84 | void StartProfiling(const char* title); 85 | auto StopProfiling(const char* title) -> std::vector; 86 | auto IsProfiling() -> bool; 87 | auto InjestCpuProfile(const std::shared_ptr& cpuProfile) -> void; 88 | 89 | CpuProfileManager(const CpuProfileManager&) = delete; 90 | auto operator= (const CpuProfileManager) = delete; 91 | ~CpuProfileManager() = default; 92 | private: 93 | void CleanProfiles(); 94 | 95 | std::set profile_titles{}; 96 | std::list profile_items{}; 97 | std::unordered_map::iterator> profile_begin_ptrs{}; 98 | std::mutex mutex; 99 | }; 100 | } -------------------------------------------------------------------------------- /src/lib/thread_pool.cc: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | #include 3 | 4 | namespace ivm { 5 | 6 | void thread_pool_t::exec(affinity_t& affinity, entry_t* entry, void* param) { 7 | std::lock_guard lock{mutex}; 8 | 9 | // First try to use an old thread 10 | unsigned thread = std::numeric_limits::max(); 11 | if (affinity.previous < thread_data.size() && thread_data[affinity.previous].entry == nullptr) { 12 | thread = affinity.previous; 13 | } else { 14 | for (auto ii = affinity.ids.begin(); ii != affinity.ids.end(); ) { 15 | if (*ii >= thread_data.size()) { 16 | ii = affinity.ids.erase(ii); 17 | continue; 18 | } 19 | if (thread_data[*ii].entry == nullptr) { 20 | affinity.previous = thread = *ii; 21 | break; 22 | } 23 | ++ii; 24 | } 25 | } 26 | 27 | if (thread == std::numeric_limits::max()) { 28 | if (desired_size > thread_data.size()) { 29 | // Thread pool hasn't yet reached `desired_size`, so we can make a new thread 30 | thread = new_thread(lock); 31 | affinity.previous = thread; 32 | affinity.ids.insert(thread); 33 | } else { 34 | // Now try to re-use a non-busy thread 35 | size_t offset = rr++; 36 | for (size_t ii = 0; ii < thread_data.size(); ++ii) { 37 | size_t jj = (rr + ii + offset) % thread_data.size(); 38 | if (thread_data[jj].entry == nullptr) { 39 | thread = jj; 40 | affinity.previous = thread; 41 | affinity.ids.insert(thread); 42 | break; 43 | } 44 | } 45 | 46 | if (thread == std::numeric_limits::max()) { 47 | // All threads are busy and pool is full, just run this in a new thread 48 | std::thread tmp_thread{[=]() { entry(false, param); }}; 49 | tmp_thread.detach(); 50 | return; 51 | } 52 | } 53 | } 54 | 55 | thread_data[thread].entry = entry; 56 | thread_data[thread].param = param; 57 | thread_data[thread].cv.notify_one(); 58 | } 59 | 60 | void thread_pool_t::resize(size_t size) { 61 | std::unique_lock lock{mutex}; 62 | desired_size = size; 63 | if (thread_data.size() > desired_size) { 64 | for (size_t ii = desired_size; ii < thread_data.size(); ++ii) { 65 | thread_data[ii].should_exit = true; 66 | thread_data[ii].cv.notify_one(); 67 | } 68 | lock.unlock(); 69 | for (size_t ii = desired_size; ii < thread_data.size(); ++ii) { 70 | thread_data[ii].thread.join(); 71 | } 72 | thread_data.resize(desired_size); 73 | } 74 | } 75 | 76 | auto thread_pool_t::new_thread(std::lock_guard& /*lock*/) -> size_t { 77 | thread_data.emplace_back(); 78 | auto& data = thread_data.back(); 79 | data.thread = std::thread{[this, &data]() { 80 | std::unique_lock lock{mutex}; 81 | while (!data.should_exit) { 82 | if (data.entry == nullptr) { 83 | data.cv.wait(lock); 84 | } else { 85 | entry_t* entry = data.entry; 86 | void* param = data.param; 87 | lock.unlock(); 88 | entry(true, param); 89 | lock.lock(); 90 | data.entry = nullptr; 91 | data.param = nullptr; 92 | } 93 | } 94 | }}; 95 | return thread_data.size() - 1; 96 | } 97 | 98 | } // namespace ivm 99 | -------------------------------------------------------------------------------- /tests/reference.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ivm = require('isolated-vm'); 3 | const { strictEqual, throws } = require('assert'); 4 | let trap = false; 5 | 6 | { 7 | // Set up inheritance 8 | const foo = { foo: 1 }; 9 | const bar = Object.create(foo); 10 | bar.bar = 2; 11 | const etc = Object.create(bar); 12 | etc.etc = bar; 13 | 14 | { 15 | // Test without inheritance 16 | const ref = new ivm.Reference(etc); 17 | strictEqual(ref.getSync('bar'), undefined); 18 | strictEqual(ref.getSync('etc').getSync('foo'), undefined); 19 | strictEqual(ref.getSync('etc').getSync('bar'), 2); 20 | ref.setSync('prop', 1); 21 | strictEqual(ref.getSync('prop'), 1); 22 | ref.deleteSync('prop'); 23 | strictEqual(ref.getSync('prop'), undefined); 24 | } 25 | 26 | { 27 | // Test with inheritance 28 | const ref = new ivm.Reference(etc, { unsafeInherit: true }); 29 | strictEqual(ref.getSync('bar'), 2); 30 | strictEqual(ref.getSync('etc').getSync('foo'), 1); 31 | strictEqual(ref.getSync('etc').getSync('bar'), 2); 32 | ref.getSync('etc').setSync('prop', 1); 33 | ref.setSync('prop', 2); 34 | strictEqual(ref.getSync('prop'), 2); 35 | ref.deleteSync('prop'); 36 | strictEqual(ref.getSync('prop'), 1); 37 | } 38 | } 39 | 40 | { 41 | // Set up getter / setters 42 | let setter = 0; 43 | const foo = { 44 | get str() { trap = true; return 'got' }, 45 | set str(_) { trap = true }, 46 | }; 47 | Object.defineProperty(foo, 0, { 48 | get() { trap = true; return 'got' }, 49 | set() { trap = true }, 50 | }); 51 | 52 | { 53 | // Test plain accessors 54 | const ref = new ivm.Reference(foo); 55 | throws(() => ref.getSync('str')); 56 | throws(() => ref.getSync(0)); 57 | throws(() => ref.setSync(0, undefined)); 58 | } 59 | 60 | { 61 | // Test accessors + inheritance 62 | const ref = new ivm.Reference(Object.create(foo), { unsafeInherit: true }); 63 | throws(() => ref.getSync('str')); 64 | ref.setSync('str', undefined); 65 | throws(() => ref.getSync(0)); 66 | } 67 | } 68 | 69 | { 70 | // Set up evil proxy 71 | const val = { prop: 1 }; 72 | const prox = new Proxy(val, { 73 | get() { trap = true }, 74 | set() { trap = true }, 75 | getOwnPropertyDescriptor() { trap = true }, 76 | getPrototypeOf() { trap = true }, 77 | has() { trap = true }, 78 | ownKeys() { trap = true }, 79 | }); 80 | const inherited = Object.create(prox); 81 | 82 | { 83 | // Test proxy 84 | const ref = new ivm.Reference(prox); 85 | throws(() => ref.getSync('prop')); 86 | } 87 | 88 | { 89 | // Test inherited proxy 90 | const ref = new ivm.Reference(inherited); 91 | throws(() => ref.getSync('prop'),); 92 | } 93 | 94 | { 95 | // Test deep inherited proxy 96 | const ref = new ivm.Reference(inherited, { unsafeInherit: true }); 97 | throws(() => ref.getSync('prop')); 98 | } 99 | } 100 | 101 | { 102 | // Test Array (numeric indices) 103 | const val = [ 1, 2, 3 ]; 104 | Object.defineProperty(val, 1, { get() { trap = true; return 'got' }}); 105 | const ref = new ivm.Reference(val); 106 | strictEqual(ref.getSync(0), 1); 107 | throws(() => ref.getSync(1)); 108 | } 109 | 110 | { 111 | // Test ArrayBuffer (numeric indices) 112 | const val = new Uint8Array([ 1, 2, 3 ]); 113 | const ref = new ivm.Reference(val); 114 | strictEqual(ref.getSync(0), 1); 115 | } 116 | 117 | if (trap) { 118 | console.log('fail'); 119 | } 120 | console.log('pass'); 121 | -------------------------------------------------------------------------------- /src/module/reference_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "isolate/remote_handle.h" 3 | #include "transferable.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ivm { 10 | namespace detail { 11 | 12 | /** 13 | * Holds common data for ReferenceHandle and ReferenceHandleTransferable 14 | */ 15 | class ReferenceData { 16 | friend class AccessorRunner; 17 | public: 18 | enum class TypeOf { Null, Undefined, Number, String, Boolean, Object, Function }; 19 | 20 | explicit ReferenceData(v8::Local value, bool inherit = false); 21 | ReferenceData( 22 | std::shared_ptr isolate, 23 | RemoteHandle reference, 24 | RemoteHandle context, 25 | TypeOf type_of, 26 | bool accessors, 27 | bool inherit 28 | ); 29 | 30 | protected: 31 | std::shared_ptr isolate; 32 | RemoteHandle reference; 33 | RemoteHandle context; 34 | TypeOf type_of; 35 | bool accessors; 36 | bool inherit; 37 | }; 38 | 39 | } // namespace detail 40 | 41 | /** 42 | * This will be a reference to any v8 Value in any isolate. 43 | */ 44 | class ReferenceHandle : public TransferableHandle, public detail::ReferenceData { 45 | friend class ApplyRunner; 46 | friend class CopyRunner; 47 | friend class AccessorRunner; 48 | friend class GetRunner; 49 | public: 50 | using TypeOf = detail::ReferenceData::TypeOf; 51 | 52 | template 53 | explicit ReferenceHandle(Args&&... args) : ReferenceData{std::forward(args)...} {} 54 | 55 | static auto Definition() -> v8::Local; 56 | static auto New(v8::Local value, v8::MaybeLocal options) 57 | -> std::unique_ptr; 58 | auto TransferOut() -> std::unique_ptr final; 59 | 60 | auto Deref(v8::MaybeLocal maybe_options) -> v8::Local; 61 | auto DerefInto(v8::MaybeLocal maybe_options) -> v8::Local; 62 | auto Release() -> v8::Local; 63 | auto TypeOfGetter() -> v8::Local; 64 | 65 | template 66 | auto Apply( 67 | v8::MaybeLocal recv_handle, 68 | v8::Maybe maybe_arguments, 69 | v8::MaybeLocal maybe_options 70 | ) -> v8::Local; 71 | 72 | template 73 | auto Copy() -> v8::Local; 74 | 75 | template 76 | auto Get( 77 | v8::Local key_handle, 78 | v8::MaybeLocal maybe_options 79 | ) -> v8::Local; 80 | 81 | template 82 | auto Delete(v8::Local key_handle) -> v8::Local; 83 | 84 | template 85 | auto Set( 86 | v8::Local key_handle, 87 | v8::Local val_handle, 88 | v8::MaybeLocal maybe_options 89 | ) -> v8::Local; 90 | 91 | private: 92 | void CheckDisposed() const; 93 | }; 94 | 95 | /** 96 | * Instances of this turn into a ReferenceHandle when they are transferred in 97 | */ 98 | class ReferenceHandleTransferable : public Transferable, public detail::ReferenceData { 99 | public: 100 | template 101 | explicit ReferenceHandleTransferable(Args&&... args) : ReferenceData{std::forward(args)...} {} 102 | 103 | auto TransferIn() -> v8::Local final; 104 | }; 105 | 106 | } // namespace ivm 107 | -------------------------------------------------------------------------------- /src/module/script_handle.cc: -------------------------------------------------------------------------------- 1 | #include "isolate/run_with_timeout.h" 2 | #include "isolate/three_phase_task.h" 3 | #include "context_handle.h" 4 | #include "script_handle.h" 5 | 6 | using namespace v8; 7 | 8 | namespace ivm { 9 | 10 | /** 11 | * ScriptHandle implementation 12 | */ 13 | ScriptHandle::ScriptHandle(RemoteHandle script) : 14 | script{std::move(script)} {} 15 | 16 | auto ScriptHandle::Definition() -> Local { 17 | return Inherit(MakeClass( 18 | "Script", nullptr, 19 | "release", MemberFunction{}, 20 | "run", MemberFunction), &ScriptHandle::Run<1>>{}, 21 | "runIgnored", MemberFunction), &ScriptHandle::Run<2>>{}, 22 | "runSync", MemberFunction), &ScriptHandle::Run<0>>{} 23 | )); 24 | } 25 | 26 | auto ScriptHandle::TransferOut() -> std::unique_ptr { 27 | return std::make_unique(script); 28 | } 29 | 30 | auto ScriptHandle::Release() -> Local { 31 | script = {}; 32 | return Undefined(Isolate::GetCurrent()); 33 | } 34 | 35 | /* 36 | * Run this script in a given context 37 | */ 38 | struct RunRunner /* lol */ : public ThreePhaseTask { 39 | RunRunner( 40 | RemoteHandle& script, 41 | ContextHandle& context_handle, 42 | MaybeLocal maybe_options 43 | ) : context{context_handle.GetContext()} { 44 | // Sanity check 45 | if (!script) { 46 | throw RuntimeGenericError("Script has been released"); 47 | } 48 | if (script.GetIsolateHolder() != context.GetIsolateHolder()) { 49 | throw RuntimeGenericError("Invalid context"); 50 | } 51 | 52 | // Parse options 53 | bool release = false; 54 | Local options; 55 | if (maybe_options.ToLocal(&options)) { 56 | release = ReadOption(options, StringTable::Get().release, false); 57 | timeout_ms = ReadOption(options, StringTable::Get().timeout, 0); 58 | } 59 | if (release) { 60 | this->script = std::move(script); 61 | } else { 62 | this->script = script; 63 | } 64 | transfer_options = TransferOptions{maybe_options}; 65 | } 66 | 67 | void Phase2() final { 68 | // Enter script's context and run it 69 | Local context_local = Deref(context); 70 | Context::Scope context_scope{context_local}; 71 | Local