diff --git a/CHANGELOG.md b/CHANGELOG.md index 19305ca7..a4bbce6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Change Log v4.1.2 --- * Fix `transformObjectKeys` performance in some edge-cases +* Fix `controlFlowFlattening` + optional chaining compatibility. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1325 * Revert `Improved stringArray calls wrapper templates` commit. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1330 * Don't publish root index.ts files to NPM. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1252 * Update `class-validator` version. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1324 diff --git a/src/custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode.ts b/src/custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode.ts index 1c4fd44e..88f34b03 100644 --- a/src/custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode.ts +++ b/src/custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode.ts @@ -25,6 +25,13 @@ export class CallExpressionFunctionNode extends AbstractCustomNode { @initializable() private expressionArguments!: (ESTree.Expression | ESTree.SpreadElement)[]; + /** + * @type {boolean} + * @private + */ + @initializable() + private isChainExpressionParent!: boolean; + /** * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter @@ -48,9 +55,11 @@ export class CallExpressionFunctionNode extends AbstractCustomNode { /** * @param {(Expression | SpreadElement)[]} expressionArguments + * @param {boolean} isChainExpressionParent */ - public initialize (expressionArguments: (ESTree.Expression | ESTree.SpreadElement)[]): void { + public initialize (expressionArguments: (ESTree.Expression | ESTree.SpreadElement)[], isChainExpressionParent: boolean): void { this.expressionArguments = expressionArguments; + this.isChainExpressionParent = isChainExpressionParent; } /** @@ -82,6 +91,12 @@ export class CallExpressionFunctionNode extends AbstractCustomNode { } } + const callExpression = NodeFactory.callExpressionNode( + calleeIdentifier, + callArguments, + this.isChainExpressionParent + ); + const structure: TStatement = NodeFactory.expressionStatementNode( NodeFactory.functionExpressionNode( [ @@ -90,10 +105,9 @@ export class CallExpressionFunctionNode extends AbstractCustomNode { ], NodeFactory.blockStatementNode([ NodeFactory.returnStatementNode( - NodeFactory.callExpressionNode( - calleeIdentifier, - callArguments - ) + this.isChainExpressionParent + ? NodeFactory.chainExpressionNode(callExpression) + : callExpression ) ]) ) diff --git a/src/node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer.ts b/src/node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer.ts index 61824f68..17e2bf4d 100644 --- a/src/node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer.ts +++ b/src/node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer.ts @@ -66,12 +66,14 @@ export class CallExpressionControlFlowReplacer extends AbstractControlFlowReplac return callExpressionNode; } + const isChainExpressionParent = NodeGuards.isChainExpressionNode(parentNode); + const replacerId: number = callExpressionNode.arguments.length; const callExpressionFunctionCustomNode: ICustomNode> = this.controlFlowCustomNodeFactory(ControlFlowCustomNode.CallExpressionFunctionNode); const expressionArguments: (ESTree.Expression | ESTree.SpreadElement)[] = callExpressionNode.arguments; - callExpressionFunctionCustomNode.initialize(expressionArguments); + callExpressionFunctionCustomNode.initialize(expressionArguments, isChainExpressionParent); const storageKey: string = this.insertCustomNodeToControlFlowStorage( callExpressionFunctionCustomNode, @@ -84,7 +86,7 @@ export class CallExpressionControlFlowReplacer extends AbstractControlFlowReplac controlFlowStorage.getStorageId(), storageKey, callee, - expressionArguments + expressionArguments, ); } diff --git a/src/node/NodeFactory.ts b/src/node/NodeFactory.ts index 534cb9c6..3449f2ef 100644 --- a/src/node/NodeFactory.ts +++ b/src/node/NodeFactory.ts @@ -118,6 +118,20 @@ export class NodeFactory { }; } + /** + * @param {ChainElement} expression + * @return {ChainExpression} + */ + public static chainExpressionNode ( + expression: ESTree.ChainElement, + ): ESTree.ChainExpression { + return { + type: NodeType.ChainExpression, + expression, + metadata: { ignoredNode: false } + }; + } + /** * @param {ESTree.Expression} test * @param {ESTree.Expression} consequent diff --git a/src/node/NodeGuards.ts b/src/node/NodeGuards.ts index 0cec976d..3a4da04b 100644 --- a/src/node/NodeGuards.ts +++ b/src/node/NodeGuards.ts @@ -91,6 +91,14 @@ export class NodeGuards { return node.type === NodeType.CallExpression; } + /** + * @param {Node} node + * @returns {boolean} + */ + public static isChainExpressionNode (node: ESTree.Node): node is ESTree.ChainExpression { + return node.type === NodeType.ChainExpression; + } + /** * @param {Node} node * @returns {boolean} diff --git a/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts b/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts index 10a260bd..a1d0699d 100644 --- a/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts +++ b/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts @@ -229,5 +229,39 @@ describe('CallExpressionControlFlowReplacer', function () { assert.match(obfuscatedCode, controlFlowStorageNodeRegExp); }); }); - }); + + describe('Variant #7 - keep optional chaining operator', () => { + const controlFlowStorageCallRegExp: RegExp = new RegExp( + `${variableMatch}\\['\\w{5}']\\(${variableMatch}, *0x1, *0x2\\);` + ); + const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` + + `'\\w{5}' *: *function *\\(${variableMatch}, *${variableMatch}, *${variableMatch}\\) *\\{` + + `return *${variableMatch}\\?\\.\\(${variableMatch}, *${variableMatch}\\);` + + `\\}` + + ``); + + let obfuscatedCode: string; + + before(() => { + const code: string = readFileAsString(__dirname + '/fixtures/optional-chaining-call.js'); + + obfuscatedCode = JavaScriptObfuscator.obfuscate( + code, + { + ...NO_ADDITIONAL_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ).getObfuscatedCode(); + }); + + it('should replace call expression node with call to control flow storage node', () => { + assert.match(obfuscatedCode, controlFlowStorageCallRegExp); + }); + + it('should wrap call expression into chain expression', () => { + assert.match(obfuscatedCode, controlFlowStorageNodeRegExp); + }); + }); + }); }); diff --git a/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/optional-chaining-call.js b/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/optional-chaining-call.js new file mode 100644 index 00000000..5d579c7f --- /dev/null +++ b/test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/optional-chaining-call.js @@ -0,0 +1,5 @@ +(function () { + const sum = null; + + var variable = sum?.(1, 2); +})();