-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
132 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use strict' | ||
|
||
const { readFileSync } = require('fs') | ||
const fn = readFileSync('./lib/script-handler-runner.js', 'utf-8') | ||
const { createTransferableReference, copyValueFromReference, callFunctionWithArguments, runWithIsolatedContext } = require('./isolate/isolate.js') | ||
const { compileAndRunScript } = require('./isolate/script-runner.js') | ||
const { wrapScript } = require('./isolate/wrap-user-provided-adapter-fn-script.js') | ||
|
||
/** | ||
* @typedef ExecutionError | ||
* @property {string} cause | ||
* @property {number} code | ||
* @property {string} stack | ||
* @property {string} message | ||
*/ | ||
|
||
/** | ||
* @typedef ExecutionResult | ||
* @property {any} result | ||
* @property {Object<String, Array>} logs | ||
* @property {number} durationMillis | ||
* @property {ExecutionError} stack | ||
*/ | ||
|
||
/** | ||
* @param {string} script | ||
* @param {Array} args | ||
* @returns {ExecutionResult} | ||
*/ | ||
async function run(script, args) { | ||
return await runWithIsolatedContext({}, async ({ isolate, context }, collectAndReleaseRefs) => { | ||
await compileAndRunScript({ isolate, context, script }) | ||
const ref = collectAndReleaseRefs(createTransferableReference({ args })) | ||
const fnResult = collectAndReleaseRefs(await callFunctionWithArguments(context, fn, ref)) | ||
|
||
const logs = await copyValueFromReference(fnResult, 'console') | ||
const result = await copyValueFromReference(fnResult, 'result') | ||
const error = await copyValueFromReference(fnResult, 'error') | ||
const durationMillis = await copyValueFromReference(fnResult, 'durationMillis') | ||
|
||
return { result, logs, error, durationMillis } | ||
}) | ||
} | ||
|
||
/** | ||
* | ||
* @param {string} script | ||
* @param {Array} args | ||
*/ | ||
module.exports = async (script, ...args) => { | ||
const wrappedScript = wrapScript(script) | ||
return run(wrappedScript, args) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
'use strict' | ||
|
||
const run = require('../../lib/index.js') | ||
const { expect } = require('chai') | ||
|
||
const allowedPrimitives = ['NaN', 'undefined'] | ||
const allowedArrayTypes = ['BigUint64Array', 'BigInt64Array', 'Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Uint8ClampedArray'] | ||
const allowedTypes = ['BigInt', 'DataView', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Proxy', 'Reflect', 'WeakRef', 'Object', 'Function', 'Array', 'Number', 'Infinity', 'Boolean', 'String', 'Symbol',] | ||
const allowedErrors = ['Error', 'AggregateError', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError'] | ||
const allowedVariables = ['console', 'global', 'globalThis'] | ||
const allowedObjects = ['FinalizationRegistry', 'Math', 'JSON', 'Atomics', 'WebAssembly', 'Intl', 'SharedArrayBuffer', 'ArrayBuffer', 'Date', 'Promise', 'RegExp'] | ||
const allowedFunctions = ['atob', 'btoa', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape', 'eval', 'decodeURI', 'decodeURIComponent', 'isFinite', 'isNaN', 'parseInt', 'parseFloat'] | ||
const allowedGlobals = [...allowedPrimitives, ...allowedErrors, ...allowedTypes, ...allowedArrayTypes, ...allowedObjects, ...allowedFunctions, ...allowedVariables] | ||
const vars = Object.getOwnPropertyNames(global).filter(global => !allowedGlobals.includes(global)) | ||
|
||
describe('FunctionExecutor', () => { | ||
|
||
it('executes a simple script', async () => { | ||
const { result } = await run('return 1+1') | ||
expect(result).to.equal(2) | ||
}) | ||
|
||
it('throws if the return value is a function', async () => { | ||
const { error } = await run('return Date.now') | ||
expect(error.message).to.equal('Function result must not be of type "function"') | ||
}) | ||
|
||
it('throws if the response is a function', async () => { | ||
const { error } = await run('return Symbol("1")') | ||
expect(error.message).to.equal('Function result must not be of type "symbol"') | ||
}) | ||
|
||
it('returns a string result', async () => { | ||
const { result } = await run('return "2"') | ||
expect(result).to.equal("2") | ||
}) | ||
|
||
it('returns a numeric result', async () => { | ||
const { result } = await run('return 2') | ||
expect(result).to.equal(2) | ||
}) | ||
|
||
it('returns a boolean result', async () => { | ||
const { result } = await run('return true') | ||
expect(result).to.equal(true) | ||
}) | ||
|
||
it('returns an object result', async () => { | ||
const { result } = await run('return {hello: {world: "yes"}}') | ||
expect(result).to.deep.equal({ hello: { world: 'yes' } }) | ||
}) | ||
|
||
it('returns an undefined result', async () => { | ||
const { result } = await run('return undefined') | ||
expect(result).to.be.undefined | ||
}) | ||
|
||
it('returns a null result', async () => { | ||
const { result } = await run('return null') | ||
expect(result).to.be.null | ||
}) | ||
|
||
it('provides arguments to the function', async () => { | ||
const { result } = await run('return args', { a: 1 }) | ||
expect(result).to.deep.equal({ a: 1 }) | ||
}) | ||
|
||
it('provides arguments to the function 2', async () => { | ||
const { result } = await run('return args.a + args.b', { a: 1, b: 3 }) | ||
expect(result).to.deep.equal(4) | ||
}) | ||
|
||
vars.forEach((globalVar) => { | ||
it(`does not allow accessing ${globalVar}`, async () => { | ||
const { error } = await run(`return ${globalVar}`) | ||
expect(error.message).to.equal(`${globalVar} is not defined`) | ||
}) | ||
}) | ||
}) |