From 9bc1baa49b805e4f07423434a8fd663b385f4519 Mon Sep 17 00:00:00 2001 From: XmiliaH Date: Thu, 24 Feb 2022 20:48:40 +0100 Subject: [PATCH 1/2] Bump ecmaVersion to latest --- lib/bridge.js | 158 ++++++++++++++++++++++++++--------------- lib/nodevm.js | 2 +- lib/resolver-compat.js | 2 +- lib/script.js | 2 +- lib/transformer.js | 86 +++++++++++++++++----- lib/vm.js | 4 +- 6 files changed, 173 insertions(+), 81 deletions(-) diff --git a/lib/bridge.js b/lib/bridge.js index f21913a..bae700d 100644 --- a/lib/bridge.js +++ b/lib/bridge.js @@ -311,7 +311,7 @@ function createBridge(otherInit, registerProxy) { try { return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } } @@ -324,7 +324,7 @@ function createBridge(otherInit, registerProxy) { try { ret = otherReflectApply(getter, object, [key]); } catch (e) { - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } } else { ret = desc.value; @@ -338,7 +338,7 @@ function createBridge(otherInit, registerProxy) { try { to[key] = otherFromThis(from[key]); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } return true; } @@ -366,7 +366,7 @@ function createBridge(otherInit, registerProxy) { try { keys = otherReflectOwnKeys(object); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } for (let i = 0; i < keys.length; i++) { const key = keys[i]; // @prim @@ -374,7 +374,7 @@ function createBridge(otherInit, registerProxy) { try { desc = otherSafeGetOwnPropertyDescriptor(object, key); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } if (!desc) continue; if (!desc.configurable) { @@ -440,7 +440,7 @@ function createBridge(otherInit, registerProxy) { try { ret = otherReflectGet(object, key); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } return this.fromOtherWithContext(ret); } @@ -455,7 +455,7 @@ function createBridge(otherInit, registerProxy) { value = otherFromThis(value); return otherReflectSet(object, key, value) === true; } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } } @@ -478,7 +478,7 @@ function createBridge(otherInit, registerProxy) { args = otherFromThisArguments(args); ret = otherReflectApply(object, context, args); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } return thisFromOther(ret); } @@ -491,7 +491,7 @@ function createBridge(otherInit, registerProxy) { args = otherFromThisArguments(args); ret = otherReflectConstruct(object, args); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); } @@ -510,7 +510,7 @@ function createBridge(otherInit, registerProxy) { try { desc = otherSafeGetOwnPropertyDescriptor(object, prop); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); @@ -575,7 +575,7 @@ function createBridge(otherInit, registerProxy) { otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); } } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } if (!otherDesc.configurable) { @@ -608,7 +608,7 @@ function createBridge(otherInit, registerProxy) { try { return otherReflectDeleteProperty(object, prop) === true; } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } } @@ -618,7 +618,7 @@ function createBridge(otherInit, registerProxy) { try { return otherReflectHas(object, key) === true; } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } } @@ -628,7 +628,7 @@ function createBridge(otherInit, registerProxy) { try { if (otherReflectIsExtensible(object)) return true; } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } if (thisReflectIsExtensible(target)) { this.doPreventExtensions(target, object, this); @@ -643,7 +643,7 @@ function createBridge(otherInit, registerProxy) { try { res = otherReflectOwnKeys(object); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } return thisFromOther(res); } @@ -654,7 +654,7 @@ function createBridge(otherInit, registerProxy) { try { if (!otherReflectPreventExtensions(object)) return false; } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } if (thisReflectIsExtensible(target)) { this.doPreventExtensions(target, object, this); @@ -669,7 +669,7 @@ function createBridge(otherInit, registerProxy) { try { res = otherReflectEnumerate(object); } catch (e) { // @other(unsafe) - throw thisFromOther(e); + throw thisFromOtherForThrow(e); } return this.fromOtherWithContext(res); } @@ -817,26 +817,25 @@ function createBridge(otherInit, registerProxy) { const type = typeof other; switch (type) { case 'object': - case 'function': if (other === null) { return null; - } else { - let proto = thisReflectGetPrototypeOf(other); - if (!proto) { - return other; - } - while (proto) { - const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); - if (mapping) { - const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); - if (mapped) return mapped; - return mapping(defaultFactory, other); - } - proto = thisReflectGetPrototypeOf(proto); - } + } + // fallthrough + case 'function': + let proto = thisReflectGetPrototypeOf(other); + if (!proto) { return other; } - + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + return mapping(defaultFactory, other); + } + proto = thisReflectGetPrototypeOf(proto); + } + return other; case 'undefined': case 'string': case 'number': @@ -850,42 +849,40 @@ function createBridge(otherInit, registerProxy) { } } - function thisFromOtherWithFactory(factory, other, proto) { + function thisFromOtherForThrow(other) { for (let loop = 0; loop < 10; loop++) { const type = typeof other; switch (type) { case 'object': - case 'function': if (other === null) { return null; - } else { - const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); - if (mapped) return mapped; - if (proto) { - return thisProxyOther(factory, other, proto); - } + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + let proto; + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) { + return thisProxyOther(defaultFactory, other, null); + } + for (;;) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(defaultFactory, other); try { - proto = otherReflectGetPrototypeOf(other); + proto = otherReflectGetPrototypeOf(proto); } catch (e) { // @other(unsafe) other = e; break; } - if (!proto) { - return thisProxyOther(factory, other, null); - } - while (proto) { - const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); - if (mapping) return mapping(factory, other); - try { - proto = otherReflectGetPrototypeOf(proto); - } catch (e) { // @other(unsafe) - other = e; - break; - } - } - return thisProxyOther(factory, other, thisObjectPrototype); + if (!proto) return thisProxyOther(defaultFactory, other, thisObjectPrototype); } - + break; case 'undefined': case 'string': case 'number': @@ -897,12 +894,55 @@ function createBridge(otherInit, registerProxy) { default: // new, unknown types can be dangerous throw new VMError(`Unknown type '${type}'`); } - factory = defaultFactory; - proto = undefined; } throw new VMError('Exception recursion depth'); } + function thisFromOtherWithFactory(factory, other, proto) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + if (proto) { + return thisProxyOther(factory, other, proto); + } + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!proto) { + return thisProxyOther(factory, other, null); + } + do { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(factory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } while (proto); + return thisProxyOther(factory, other, thisObjectPrototype); + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + function thisFromOtherArguments(args) { // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) const arr = []; diff --git a/lib/nodevm.js b/lib/nodevm.js index e75f9e9..b1236ee 100644 --- a/lib/nodevm.js +++ b/lib/nodevm.js @@ -410,7 +410,7 @@ class NodeVM extends VM { } const prefix = strict ? STRICT_MODULE_PREFIX : MODULE_PREFIX; let scriptCode = this._compiler(code, unresolvedFilename); - scriptCode = transformer(null, scriptCode, false, false).code; + scriptCode = transformer(null, scriptCode, false, false, unresolvedFilename).code; script = new Script(prefix + scriptCode + MODULE_SUFFIX, { __proto__: null, filename: unresolvedFilename, diff --git a/lib/resolver-compat.js b/lib/resolver-compat.js index 86453f0..06cd7e2 100644 --- a/lib/resolver-compat.js +++ b/lib/resolver-compat.js @@ -296,7 +296,7 @@ function resolverFromOptions(vm, options, override, compiler) { return checkedRootPaths.some(path => { if (!filename.startsWith(path)) return false; const len = path.length; - if (filename.length === len) return true; + if (filename.length === len || (len > 0 && path[len-1] === pa.sep)) return true; const sep = filename[len]; return sep === '/' || sep === pa.sep; }); diff --git a/lib/script.js b/lib/script.js index 088fa52..e4c9d9d 100644 --- a/lib/script.js +++ b/lib/script.js @@ -309,7 +309,7 @@ class VMScript { getCompiledCode() { if (!this._compiledCode) { const comp = this._compiler(this._prefix + removeShebang(this._code) + this._suffix, this.filename); - const res = transformer(null, comp, false, false); + const res = transformer(null, comp, false, false, this.filename); this._compiledCode = res.code; this._hasAsync = res.hasAsync; } diff --git a/lib/transformer.js b/lib/transformer.js index 8bb4de1..e336525 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -1,7 +1,6 @@ -const {parse: acornParse} = require('acorn'); +const {Parser: AcornParser, isNewLine: acornIsNewLine, getLineInfo: acornGetLineInfo} = require('acorn'); const {full: acornWalkFull} = require('acorn-walk'); -const {compileFunction} = require('vm'); const INTERNAL_STATE_NAME = 'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL'; @@ -11,7 +10,43 @@ function assertType(node, type) { return node; } -function transformer(args, body, isAsync, isGenerator) { +function makeNiceSyntaxError(message, code, filename, location, tokenizer) { + const loc = acornGetLineInfo(code, location); + let end = location; + while (end < code.length && !acornIsNewLine(code.charCodeAt(end))) { + end++; + } + let markerEnd = tokenizer.end; + if (!markerEnd || markerEnd > end) markerEnd = end; + let markerLen = markerEnd - location; + if (markerLen <= 0) markerLen = 1; + if (message === 'Unexpected token') { + const type = tokenizer.type; + if (type.label === 'name' || type.label === 'privateId') { + message = 'Unexpected identifier'; + } else if (type.label === 'eof') { + message = 'Unexpected end of input'; + } else if (type.label === 'num') { + message = 'Unexpected number'; + } else if (type.label === 'string') { + message = 'Unexpected string'; + } else if (type.label === 'regexp') { + message = 'Unexpected token \'/\''; + markerLen = 1; + } else { + const token = tokenizer.value || type.label; + message = `Unexpected token '${token}'`; + } + } + const error = new SyntaxError(message); + if (!filename) return error; + const line = code.slice(location - loc.column, end); + const marker = line.slice(0, loc.column).replace(/\S/g, ' ') + '^'.repeat(markerLen); + error.stack = `${filename}:${loc.line}\n${line}\n${marker}\n\n${error.stack}`; + return error; +} + +function transformer(args, body, isAsync, isGenerator, filename) { let code; let argsOffset; if (args === null) { @@ -27,17 +62,23 @@ function transformer(args, body, isAsync, isGenerator) { code += '\n})'; } + const parser = new AcornParser({ + __proto__: null, + ecmaVersion: 2022, + allowAwaitOutsideFunction: args === null && isAsync, + allowReturnOutsideFunction: args === null + }, code); let ast; try { - ast = acornParse(code, { - __proto__: null, - ecmaVersion: 2020, - allowAwaitOutsideFunction: args === null && isAsync, - allowReturnOutsideFunction: args === null - }); + ast = parser.parse(); } catch (e) { // Try to generate a nicer error message. - compileFunction(code); + if (e instanceof SyntaxError && e.pos !== undefined) { + let message = e.message; + const match = message.match(/^(.*) \(\d+:\d+\)$/); + if (match) message = match[1]; + e = makeNiceSyntaxError(message, code, filename, e.pos, parser); + } throw e; } @@ -54,8 +95,10 @@ function transformer(args, body, isAsync, isGenerator) { const insertions = []; let hasAsync = false; - const RIGHT = -100; - const LEFT = 100; + const TO_LEFT = -100; + const TO_RIGHT = 100; + + let internStateValiable = undefined; acornWalkFull(ast, (node, state, type) => { if (type === 'CatchClause') { @@ -67,7 +110,7 @@ function transformer(args, body, isAsync, isGenerator) { insertions.push({ __proto__: null, pos: cBody.body[0].start, - order: RIGHT, + order: TO_LEFT, code: `${name}=${INTERNAL_STATE_NAME}.handleException(${name});` }); } @@ -76,24 +119,26 @@ function transformer(args, body, isAsync, isGenerator) { insertions.push({ __proto__: null, pos: node.object.start, - order: RIGHT, + order: TO_LEFT, code: INTERNAL_STATE_NAME + '.wrapWith(' }); insertions.push({ __proto__: null, pos: node.object.end, - order: LEFT, + order: TO_RIGHT, code: ')' }); } else if (type === 'Identifier') { if (node.name === INTERNAL_STATE_NAME) { - throw new SyntaxError('Use of internal vm2 state variable'); + if (internStateValiable === undefined || internStateValiable.start > node.start) { + internStateValiable = node; + } } } else if (type === 'ImportExpression') { insertions.push({ __proto__: null, pos: node.start, - order: LEFT, + order: TO_RIGHT, code: INTERNAL_STATE_NAME + '.' }); } else if (type === 'Function') { @@ -101,6 +146,13 @@ function transformer(args, body, isAsync, isGenerator) { } }); + if (internStateValiable) { + throw makeNiceSyntaxError('Use of internal vm2 state variable', code, filename, internStateValiable.start, { + __proto__: null, + end: internStateValiable.end + }); + } + if (insertions.length === 0) return {__proto__: null, code, hasAsync}; insertions.sort((a, b) => (a.pos == b.pos ? a.order - b.order : a.pos - b.pos)); diff --git a/lib/vm.js b/lib/vm.js index fea6f92..1463e17 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -89,7 +89,7 @@ function checkAsync(allow) { } function transformAndCheck(args, code, isAsync, isGenerator, allowAsync) { - const ret = transformer(args, code, isAsync, isGenerator); + const ret = transformer(args, code, isAsync, isGenerator, undefined); checkAsync(allowAsync || !ret.hasAsync); return ret.code; } @@ -488,7 +488,7 @@ class VM extends EventEmitter { } else { const useFileName = filename || 'vm.js'; let scriptCode = this._compiler(code, useFileName); - const ret = transformer(null, scriptCode, false, false); + const ret = transformer(null, scriptCode, false, false, useFileName); scriptCode = ret.code; checkAsync(this._allowAsync || !ret.hasAsync); // Compile the script here so that we don't need to create a instance of VMScript. From 48a93e580f32ba74a612a8f079caad2bea996216 Mon Sep 17 00:00:00 2001 From: XmiliaH Date: Thu, 24 Feb 2022 21:39:12 +0100 Subject: [PATCH 2/2] Smaller imrovements --- lib/setup-sandbox.js | 3 ++- lib/transformer.js | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/setup-sandbox.js b/lib/setup-sandbox.js index de49fdd..2149cc3 100644 --- a/lib/setup-sandbox.js +++ b/lib/setup-sandbox.js @@ -308,7 +308,8 @@ const withProxy = localObjectFreeze({ const interanState = localObjectFreeze({ __proto__: null, wrapWith(x) { - return new LocalProxy(x, withProxy); + if (x === null || x === undefined) return x; + return new LocalProxy(localObject(x), withProxy); }, handleException: ensureThis, import(what) { diff --git a/lib/transformer.js b/lib/transformer.js index e336525..f33f6ab 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -16,7 +16,7 @@ function makeNiceSyntaxError(message, code, filename, location, tokenizer) { while (end < code.length && !acornIsNewLine(code.charCodeAt(end))) { end++; } - let markerEnd = tokenizer.end; + let markerEnd = tokenizer.start === location ? tokenizer.end : location + 1; if (!markerEnd || markerEnd > end) markerEnd = end; let markerLen = markerEnd - location; if (markerLen <= 0) markerLen = 1; @@ -101,7 +101,11 @@ function transformer(args, body, isAsync, isGenerator, filename) { let internStateValiable = undefined; acornWalkFull(ast, (node, state, type) => { - if (type === 'CatchClause') { + if (type === 'Function') { + if (node.async) hasAsync = true; + } + const nodeType = node.type; + if (nodeType === 'CatchClause') { const param = node.param; if (param) { const name = assertType(param, 'Identifier').name; @@ -115,7 +119,7 @@ function transformer(args, body, isAsync, isGenerator, filename) { }); } } - } else if (type === 'WithStatement') { + } else if (nodeType === 'WithStatement') { insertions.push({ __proto__: null, pos: node.object.start, @@ -128,27 +132,26 @@ function transformer(args, body, isAsync, isGenerator, filename) { order: TO_RIGHT, code: ')' }); - } else if (type === 'Identifier') { + } else if (nodeType === 'Identifier') { if (node.name === INTERNAL_STATE_NAME) { if (internStateValiable === undefined || internStateValiable.start > node.start) { internStateValiable = node; } } - } else if (type === 'ImportExpression') { + } else if (nodeType === 'ImportExpression') { insertions.push({ __proto__: null, pos: node.start, order: TO_RIGHT, code: INTERNAL_STATE_NAME + '.' }); - } else if (type === 'Function') { - if (node.async) hasAsync = true; } }); if (internStateValiable) { throw makeNiceSyntaxError('Use of internal vm2 state variable', code, filename, internStateValiable.start, { __proto__: null, + start: internStateValiable.start, end: internStateValiable.end }); }