From 8f0aff4ab2b08a201690812e52aba1f77da6f8c1 Mon Sep 17 00:00:00 2001 From: miherlosev Date: Mon, 4 Apr 2022 11:50:39 +0300 Subject: [PATCH 1/2] initial --- .../code-instrumentation/properties/index.ts | 9 +++-- src/processing/script/index.ts | 5 ++- src/processing/script/node-builder.ts | 16 ++++++-- .../transformers/computed-property-get.ts | 2 +- .../script/transformers/property-get.ts | 2 +- .../process-script-test.js | 40 +++++++++++++++++-- test/server/script-processor-test.js | 38 ++++++++++++++---- 7 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/client/sandbox/code-instrumentation/properties/index.ts b/src/client/sandbox/code-instrumentation/properties/index.ts index a7bf32e83..92ae20096 100644 --- a/src/client/sandbox/code-instrumentation/properties/index.ts +++ b/src/client/sandbox/code-instrumentation/properties/index.ts @@ -87,12 +87,15 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase { } }; - private static _propertyGetter (owner: any, propName: any) { - if (isNullOrUndefined(owner)) + private static _propertyGetter (owner: any, propName: any, optional = false) { + if (isNullOrUndefined(owner) && !optional) PropertyAccessorsInstrumentation._error(`Cannot read property '${propName}' of ${inaccessibleTypeToStr(owner)}`); if (typeof propName === 'string' && shouldInstrumentProperty(propName)) { - if (!WindowSandbox.isProxyObject(owner) && PropertyAccessorsInstrumentation._ACCESSORS[propName].condition(owner)) + if (optional && isNullOrUndefined(owner)) + return void 0; + + else if (!WindowSandbox.isProxyObject(owner) && PropertyAccessorsInstrumentation._ACCESSORS[propName].condition(owner)) return PropertyAccessorsInstrumentation._ACCESSORS[propName].get(owner); } diff --git a/src/processing/script/index.ts b/src/processing/script/index.ts index fdf615152..872275d86 100644 --- a/src/processing/script/index.ts +++ b/src/processing/script/index.ts @@ -16,7 +16,8 @@ const OBJECT_RE = /^\s*\{.*\}\s*$/; const TRAILING_SEMICOLON_RE = /;\s*$/; const OBJECT_WRAPPER_RE = /^\s*\((.*)\);\s*$/; const SOURCEMAP_RE = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)/gm; -const PROCESSED_SCRIPT_RE = new RegExp([ + +const PROCESSED_SCRIPT_RE = new RegExp([ reEscape(INSTRUCTION.getLocation), reEscape(INSTRUCTION.setLocation), reEscape(INSTRUCTION.getProperty), @@ -30,7 +31,7 @@ const PROCESSED_SCRIPT_RE = new RegExp([ const PARSING_OPTIONS = { allowReturnOutsideFunction: true, allowImportExportEverywhere: true, - ecmaVersion: 11 + ecmaVersion: 13 }; // Code pre/post-processing diff --git a/src/processing/script/node-builder.ts b/src/processing/script/node-builder.ts index 3c3423ced..205ade8aa 100644 --- a/src/processing/script/node-builder.ts +++ b/src/processing/script/node-builder.ts @@ -170,16 +170,24 @@ export function createMethodCallWrapper (owner: Expression, method: Literal, arg return createSimpleCallExpression(callMethodIdentifier, [owner, method, methodArgsArray]); } -export function createPropertyGetWrapper (propertyName: string, owner: Expression): CallExpression { +export function createPropertyGetWrapper (propertyName: string, owner: Expression, optional = false): CallExpression { const getPropertyIdentifier = createIdentifier(INSTRUCTION.getProperty); + const args = [owner, createSimpleLiteral(propertyName)]; - return createSimpleCallExpression(getPropertyIdentifier, [owner, createSimpleLiteral(propertyName)]); + if (optional) + args.push(createSimpleLiteral(optional)); + + return createSimpleCallExpression(getPropertyIdentifier, args); } -export function createComputedPropertyGetWrapper (property: Expression, owner: Expression): CallExpression { +export function createComputedPropertyGetWrapper (property: Expression, owner: Expression, optional = false): CallExpression { const getPropertyIdentifier = createIdentifier(INSTRUCTION.getProperty); + const args = [owner, property]; + + if (optional) + args.push(createSimpleLiteral(optional)); - return createSimpleCallExpression(getPropertyIdentifier, [owner, property]); + return createSimpleCallExpression(getPropertyIdentifier, args); } export function createComputedPropertySetWrapper (property: Expression, owner: Expression, value: Expression): CallExpression { diff --git a/src/processing/script/transformers/computed-property-get.ts b/src/processing/script/transformers/computed-property-get.ts index 3da02aa33..7a4ea7c3e 100644 --- a/src/processing/script/transformers/computed-property-get.ts +++ b/src/processing/script/transformers/computed-property-get.ts @@ -55,7 +55,7 @@ const transformer: Transformer = { return true; }, - run: node => createComputedPropertyGetWrapper(node.property, node.object as Expression) + run: node => createComputedPropertyGetWrapper(node.property, node.object as Expression, node.optional) }; export default transformer; diff --git a/src/processing/script/transformers/property-get.ts b/src/processing/script/transformers/property-get.ts index 6c061f0a0..201510b93 100644 --- a/src/processing/script/transformers/property-get.ts +++ b/src/processing/script/transformers/property-get.ts @@ -57,7 +57,7 @@ const transformer: Transformer = { }, // eslint-disable-next-line - run: node => createPropertyGetWrapper((node.property as Identifier).name, node.object as Expression) + run: node => createPropertyGetWrapper((node.property as Identifier).name, node.object as Expression, node.optional) }; export default transformer; diff --git a/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js b/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js index 40cfd2eed..cda5a1822 100644 --- a/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js +++ b/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js @@ -91,7 +91,7 @@ test('the eval method should switch its own context to global', function () { strictEqual(execScript('window.t3="globalScope";(function(ev){ var t3 = "localScope"; return ev("t3"); })(eval)'), 'globalScope'); }); -test('the script processor should process eval\'s global', function () { +test("the script processor should process eval's global", function () { var link = document.createElement('a'); var testUrl = 'http://host/index.html'; @@ -102,9 +102,9 @@ test('the script processor should process eval\'s global', function () { strictEqual(execScript('var ev = eval; ev("test.href")'), testUrl); }); -module('destructuring'); - if (!browserUtils.isIE11) { + module('destructuring'); + var defaultRestArrayStr = 'var ' + scriptHeader.add('') .replace(/[\s\S]+(__rest\$Array\s*=\s*function[^}]+})[\s\S]+/g, '$1'); var defaultArrayFromStr = 'var ' + scriptHeader.add('') @@ -224,4 +224,38 @@ if (!browserUtils.isIE11) { test('should process script arg', function () { strictEqual(execScript('({ a: b } = { a: null }).a'), null); }); + + module('others'); + + test('optional chaining', function () { + var testCases = [ + { + src: 'var obj = null; window.optionChainingResult = obj?.href;', + expected: void 0, + }, + { + src: 'var obj = { href: "123" }; window.optionChainingResult = obj?.href;', + expected: '123', + }, + { + src: 'var obj = null; window.optionChainingResult = obj?.["href"];', + expected: void 0, + }, + { + src: 'var obj = { href: "123" }; window.optionChainingResult = obj?.["href"];', + expected: '123', + } + ]; + + for (var i = 0; i < testCases.length; i++) { + var testCase = testCases[i]; + + eval(processScript(testCase.src)); + + strictEqual(window.optionChainingResult, testCase.expected); + + delete window.optionChainingResult; + delete window.obj; + } + }); } diff --git a/test/server/script-processor-test.js b/test/server/script-processor-test.js index 6d4ab51db..b151ff8d3 100644 --- a/test/server/script-processor-test.js +++ b/test/server/script-processor-test.js @@ -1,11 +1,8 @@ -const expect = require('chai').expect; -const multiline = require('multiline'); -const processScript = require('../../lib/processing/script').processScript; -const isScriptProcessed = require('../../lib/processing/script').isScriptProcessed; -const HEADER = require('../../lib/processing/script/header').HEADER; -const SCRIPT_PROCESSING_START_COMMENT = require('../../lib/processing/script/header').SCRIPT_PROCESSING_START_COMMENT; -const INSTRUMENTED_PROPERTIES = require('../../lib/processing/script/instrumented').PROPERTIES; - +const { expect } = require('chai'); +const multiline = require('multiline'); +const { processScript, isScriptProcessed } = require('../../lib/processing/script'); +const { HEADER, SCRIPT_PROCESSING_START_COMMENT } = require('../../lib/processing/script/header'); +const { PROPERTIES: INSTRUMENTED_PROPERTIES } = require('../../lib/processing/script/instrumented'); const ACORN_UNICODE_PATCH_WARNING = multiline(function () {/* ATTENTION! If this test fails, this may happen because you have updated acorn. @@ -822,6 +819,31 @@ describe('Script processor', () => { ]); }); + it('Should process optional chaining', () => { + testPropertyProcessing([ + { + src: 'obj?.{0}', + expected: '__get$(obj,"{0}",true)', + }, + { + src: 'obj?.["{0}"]', + expected: '__get$(obj,"{0}",true)', + }, + { + src: 'obj?.{0}?.{0}', + expected: '__get$(__get$(obj,"{0}",true),"{0}",true)' + }, + { + src: 'arr?.[0]', + expected: 'arr?.[0]', + }, + { + src: 'obj?.{0}?.method(args)', + expected: '__get$(obj,"{0}",true)?.method(args)', + }, + ]); + }); + describe('Destructuring', () => { it('object pattern declaration', () => { testProcessing([ From 4e5aa688a516f0ac648148a66e81858e74a14ba4 Mon Sep 17 00:00:00 2001 From: miherlosev Date: Tue, 5 Apr 2022 11:59:29 +0300 Subject: [PATCH 2/2] update acorn-hammerhead --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23aa9d6f3..fb65d2ee4 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "url": "git+https://github.com/DevExpress/testcafe-hammerhead.git" }, "dependencies": { - "acorn-hammerhead": "0.5.0", + "acorn-hammerhead": "0.6.1", "asar": "^2.0.1", "bowser": "1.6.0", "brotli": "^1.3.1",