Преглед изворни кода

Merge pull request #981 from javascript-obfuscator/improvements-08.08.2021

Timofey Kachalov пре 3 година
родитељ
комит
a29505f0aa

+ 2 - 2
src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts

@@ -105,7 +105,7 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
         const stringArrayCallIndexNode: ESTree.Expression = this.stringArrayIndexNodeFactory(stringArrayIndexNodeName)
             .getNode(normalizedIndex);
 
-        NodeMetadata.set(stringArrayCallIndexNode, { replacedLiteral: true });
+        NodeMetadata.set(stringArrayCallIndexNode, { stringArrayCallLiteralNode: true });
 
         const hexadecimalNode: ESTree.Expression = isPositive
             ? stringArrayCallIndexNode
@@ -126,7 +126,7 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
     protected getRc4KeyLiteralNode (decodeKey: string): ESTree.Literal {
         const rc4KeyLiteralNode: ESTree.Literal = NodeFactory.literalNode(decodeKey);
 
-        NodeMetadata.set(rc4KeyLiteralNode, { replacedLiteral: true });
+        NodeMetadata.set(rc4KeyLiteralNode, { stringArrayCallLiteralNode: true });
 
         return rc4KeyLiteralNode;
     }

+ 1 - 1
src/declarations/ESTree.d.ts

@@ -14,7 +14,7 @@ declare module 'estree' {
     }
 
     export interface LiteralNodeMetadata extends BaseNodeMetadata {
-        replacedLiteral?: boolean;
+        stringArrayCallLiteralNode?: boolean;
     }
 
     /**

+ 7 - 0
src/interfaces/utils/IArrayUtils.ts

@@ -24,6 +24,13 @@ export interface IArrayUtils {
      */
     getLastElement <T> (array: T[]): T | undefined;
 
+    /**
+     * @param {T[]} array
+     * @param {number} index
+     * @returns {T | undefined}
+     */
+    getLastElementByIndex <T> (array: T[], index: number): T | undefined;
+
     /**
      * @param array
      * @param times

+ 31 - 10
src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -62,9 +62,9 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     private readonly visitedFunctionNodes: Set<ESTree.Function> = new Set();
 
     /**
-     * @type {Set<TNodeWithStatements>}
+     * @type {WeakMap<TNodeWithStatements, VariableDeclaration>}
      */
-    private readonly hostNodesWithControlFlowNode: Set<TNodeWithStatements> = new Set();
+    private readonly hostNodesWithControlFlowNode: WeakMap<TNodeWithStatements, ESTree.VariableDeclaration> = new WeakMap();
 
     /**
      * @type {TControlFlowReplacerFactory}
@@ -157,8 +157,11 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
             this.controlFlowCustomNodeFactory(ControlFlowCustomNode.ControlFlowStorageNode);
 
         controlFlowStorageCustomNode.initialize(controlFlowStorage);
-        NodeAppender.prepend(hostNode, controlFlowStorageCustomNode.getNode());
-        this.hostNodesWithControlFlowNode.add(hostNode);
+
+        const controlFlowStorageNode: ESTree.VariableDeclaration = this.getControlFlowStorageNode(controlFlowStorage);
+
+        NodeAppender.prepend(hostNode, [controlFlowStorageNode]);
+        this.hostNodesWithControlFlowNode.set(hostNode, controlFlowStorageNode);
 
         NodeUtils.parentizeAst(functionNode);
 
@@ -173,12 +176,11 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         const controlFlowStorage: TControlFlowStorage = this.controlFlowStorageFactory();
 
         if (this.controlFlowData.has(hostNode)) {
-            if (this.hostNodesWithControlFlowNode.has(hostNode)) {
-                if (NodeGuards.isSwitchCaseNode(hostNode)) {
-                    hostNode.consequent.shift();
-                } else {
-                    hostNode.body.shift();
-                }
+            const existingControlFlowStorageNode: ESTree.VariableDeclaration | null =
+                this.hostNodesWithControlFlowNode.get(hostNode) ?? null;
+
+            if (existingControlFlowStorageNode) {
+                NodeAppender.remove(hostNode, existingControlFlowStorageNode);
             }
 
             const hostControlFlowStorage: TControlFlowStorage = <TControlFlowStorage>this.controlFlowData.get(hostNode);
@@ -189,6 +191,25 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         return controlFlowStorage;
     }
 
+    /**
+     * @param {TControlFlowStorage} controlFlowStorage
+     * @returns {VariableDeclaration}
+     */
+    private getControlFlowStorageNode (controlFlowStorage: TControlFlowStorage): ESTree.VariableDeclaration {
+        const controlFlowStorageCustomNode: ICustomNode<TInitialData<ControlFlowStorageNode>> =
+            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.ControlFlowStorageNode);
+
+        controlFlowStorageCustomNode.initialize(controlFlowStorage);
+
+        const controlFlowStorageNode: ESTree.Node = controlFlowStorageCustomNode.getNode()[0];
+
+        if (!NodeGuards.isVariableDeclarationNode(controlFlowStorageNode)) {
+            throw new Error('`controlFlowStorageNode` should contain `VariableDeclaration` node with control flow storage object');
+        }
+
+        return controlFlowStorageNode;
+    }
+
     /**
      * @param {BlockStatement} functionNodeBody
      * @returns {TNodeWithStatements}

+ 9 - 16
src/node-transformers/converting-transformers/SplitStringTransformer.ts

@@ -80,14 +80,14 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
      * @returns {IVisitor | null}
      */
     public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        if (!this.options.splitStrings) {
+            return null;
+        }
+
         switch (nodeTransformationStage) {
             case NodeTransformationStage.Converting:
                 return {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
-                        if (!this.options.splitStrings) {
-                            return;
-                        }
-
                         if (parentNode && NodeGuards.isLiteralNode(node)) {
                             return this.transformNode(node, parentNode);
                         }
@@ -115,7 +115,6 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
         // pass #1: split string on a large chunks with length of `firstPassChunkLength`
         const firstPassChunksNode: ESTree.Node = this.transformLiteralNodeByChunkLength(
             literalNode,
-            parentNode,
             SplitStringTransformer.firstPassChunkLength
         );
 
@@ -123,28 +122,28 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
         const secondPassChunksNode: ESTree.Node = estraverse.replace(firstPassChunksNode, {
             // eslint-disable-next-line @typescript-eslint/no-shadow
             enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isLiteralNode(node)) {
+                if (NodeGuards.isLiteralNode(node)) {
                     return this.transformLiteralNodeByChunkLength(
                         node,
-                        parentNode,
                         this.options.splitStringsChunkLength
                     );
                 }
             }
         });
 
+        NodeUtils.parentizeNode(secondPassChunksNode, parentNode);
+        NodeUtils.parentizeAst(secondPassChunksNode);
+
         return secondPassChunksNode;
     }
 
     /**
      * @param {Literal} literalNode
-     * @param {Node} parentNode
      * @param {number} chunkLength
      * @returns {Node}
      */
     private transformLiteralNodeByChunkLength (
         literalNode: ESTree.Literal,
-        parentNode: ESTree.Node,
         chunkLength: number
     ): ESTree.Node {
         if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
@@ -163,13 +162,7 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
             chunkLength
         );
 
-        const binaryExpressionNode: ESTree.BinaryExpression =
-            this.transformStringChunksToBinaryExpressionNode(stringChunks);
-
-        NodeUtils.parentizeAst(binaryExpressionNode);
-        NodeUtils.parentizeNode(binaryExpressionNode, parentNode);
-
-        return binaryExpressionNode;
+        return this.transformStringChunksToBinaryExpressionNode(stringChunks);
     }
 
     /**

+ 1 - 1
src/node-transformers/preparing-transformers/MetadataTransformer.ts

@@ -65,7 +65,7 @@ export class MetadataTransformer extends AbstractNodeTransformer {
         NodeMetadata.set(node, { ignoredNode: false });
 
         if (NodeGuards.isLiteralNode(node)) {
-            NodeMetadata.set(node, { replacedLiteral: false });
+            NodeMetadata.set(node, { stringArrayCallLiteralNode: false });
         }
 
         return node;

+ 13 - 12
src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts

@@ -9,11 +9,12 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
-import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
+import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
 export class RenamePropertiesTransformer extends AbstractNodeTransformer {
@@ -106,20 +107,20 @@ export class RenamePropertiesTransformer extends AbstractNodeTransformer {
      * @returns {Node}
      */
     public transformNode (node: ESTree.Node, parentNode: ESTree.Node): ESTree.Node {
-        if (NodeGuards.isPropertyNode(node)) {
-            return this.transformPropertyNode(node);
-        }
+        let propertyNode: ESTree.Node | null = null;
 
-        if (NodeGuards.isPropertyDefinitionNode(node)) {
-            return this.transformPropertyDefinitionNode(node);
-        }
-
-        if (NodeGuards.isMemberExpressionNode(node)) {
-            return this.transformMemberExpressionNode(node);
+        if (NodeGuards.isPropertyNode(node)) {
+            propertyNode = this.transformPropertyNode(node);
+        } else if (NodeGuards.isPropertyDefinitionNode(node)) {
+            propertyNode = this.transformPropertyDefinitionNode(node);
+        } else if (NodeGuards.isMemberExpressionNode(node)) {
+            propertyNode = this.transformMemberExpressionNode(node);
+        } else if (NodeGuards.isMethodDefinitionNode(node)) {
+            propertyNode = this.transformMethodDefinitionNode(node);
         }
 
-        if (NodeGuards.isMethodDefinitionNode(node)) {
-            return this.transformMethodDefinitionNode(node);
+        if (propertyNode) {
+            NodeUtils.parentizeNode(propertyNode, parentNode);
         }
 
         return node;

+ 15 - 8
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -139,7 +139,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
                             this.prepareNode(node);
                         }
 
-                        if (parentNode && NodeGuards.isLiteralNode(node) && !NodeMetadata.isReplacedLiteral(node)) {
+                        if (
+                            parentNode
+                            && NodeGuards.isLiteralNode(node)
+                            && !NodeMetadata.isStringArrayCallLiteralNode(node)
+                        ) {
                             return this.transformNode(node, parentNode);
                         }
                     }
@@ -187,16 +191,19 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         const cacheKey: string = this.literalNodesCacheStorage.buildKey(literalValue, stringArrayStorageItemData);
         const useCachedValue: boolean = this.literalNodesCacheStorage.shouldUseCachedValue(cacheKey, stringArrayStorageItemData);
 
+        let resultNode: ESTree.Node;
+
         if (useCachedValue) {
-            return <ESTree.Node>this.literalNodesCacheStorage.get(cacheKey);
+            const nodeFromCache: ESTree.Node = <ESTree.Node>this.literalNodesCacheStorage.get(cacheKey);
+
+            resultNode = NodeUtils.clone(nodeFromCache);
+        } else {
+            resultNode = stringArrayStorageItemData
+                ? this.getStringArrayCallNode(stringArrayStorageItemData)
+                : literalNode;
+            this.literalNodesCacheStorage.set(cacheKey, resultNode);
         }
 
-        const resultNode: ESTree.Node = stringArrayStorageItemData
-            ? this.getStringArrayCallNode(stringArrayStorageItemData)
-            : literalNode;
-
-        this.literalNodesCacheStorage.set(cacheKey, resultNode);
-
         NodeUtils.parentizeNode(resultNode, parentNode);
 
         return resultNode;

+ 18 - 0
src/node/NodeAppender.ts

@@ -160,6 +160,24 @@ export class NodeAppender {
         ]);
     }
 
+    /**
+     * @param {TNodeWithStatements} nodeWithStatements
+     * @param {Statement} statement
+     */
+    public static remove (nodeWithStatements: TNodeWithStatements, statement: ESTree.Statement): void {
+        const scopeStatements: TStatement[] = NodeAppender.getScopeStatements(nodeWithStatements);
+        const indexInScopeStatement: number = scopeStatements.indexOf(statement);
+
+        if (indexInScopeStatement === -1) {
+            return;
+        }
+
+        const updatedStatements: TStatement[] = [...scopeStatements];
+        updatedStatements.splice(indexInScopeStatement, 1);
+
+        NodeAppender.setScopeStatements(nodeWithStatements, updatedStatements);
+    }
+
     /**
      * @param {TNodeWithStatements} nodeWithStatements
      * @param {TStatement[]} statements

+ 1 - 1
src/node/NodeFactory.ts

@@ -63,7 +63,7 @@ export class NodeFactory {
     public static binaryExpressionNode (
         operator: ESTree.BinaryOperator,
         left: ESTree.Expression,
-        right: ESTree.Expression,
+        right: ESTree.Expression
     ): ESTree.BinaryExpression {
         return {
             type: NodeType.BinaryExpression,

+ 5 - 2
src/node/NodeMetadata.ts

@@ -43,7 +43,10 @@ export class NodeMetadata {
      * @param {Node} literalNode
      * @returns {boolean}
      */
-    public static isReplacedLiteral (literalNode: ESTree.Literal): boolean {
-        return NodeMetadata.get<ESTree.LiteralNodeMetadata, 'replacedLiteral'>(literalNode, 'replacedLiteral') === true;
+    public static isStringArrayCallLiteralNode (literalNode: ESTree.Literal): boolean {
+        return NodeMetadata.get<
+            ESTree.LiteralNodeMetadata,
+            'stringArrayCallLiteralNode'
+        >(literalNode, 'stringArrayCallLiteralNode') === true;
     }
 }

+ 1 - 3
src/storages/string-array-transformers/VisitedLexicalScopeNodesStackStorage.ts

@@ -43,9 +43,7 @@ export class VisitedLexicalScopeNodesStackStorage extends ArrayStorage <TNodeWit
      * @returns {TNodeWithLexicalScopeStatements | undefined}
      */
     public getPenultimateElement (): TNodeWithLexicalScopeStatements | undefined {
-        const storageLength: number = this.getLength();
-
-        return this.get(storageLength - 2) ?? undefined;
+        return this.arrayUtils.getLastElementByIndex(this.getStorage(), 1);
     }
 
     /**

+ 10 - 1
src/utils/ArrayUtils.ts

@@ -85,9 +85,18 @@ export class ArrayUtils implements IArrayUtils {
      * @returns {T | undefined}
      */
     public getLastElement <T> (array: T[]): T | undefined {
+        return this.getLastElementByIndex(array, 0);
+    }
+
+    /**
+     * @param {T[]} array
+     * @param {number} index
+     * @returns {T | undefined}
+     */
+    public getLastElementByIndex <T> (array: T[], index: number): T | undefined {
         const arrayLength: number = array.length;
 
-        return array[arrayLength - 1] ?? undefined;
+        return array[arrayLength - 1 - index] ?? undefined;
     }
 
     /**

+ 41 - 1
test/unit-tests/node/node-appender/NodeAppender.spec.ts

@@ -13,12 +13,12 @@ import { ICallsGraphAnalyzer } from '../../../../src/interfaces/analyzers/calls-
 import { ICallsGraphData } from '../../../../src/interfaces/analyzers/calls-graph-analyzer/ICallsGraphData';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
+import { removeRangesFromStructure } from '../../../helpers/removeRangesFromStructure';
 
 import { InversifyContainerFacade } from '../../../../src/container/InversifyContainerFacade';
 import { NodeAppender } from '../../../../src/node/NodeAppender';
 import { NodeFactory } from '../../../../src/node/NodeFactory';
 import { NodeUtils } from '../../../../src/node/NodeUtils';
-import { removeRangesFromStructure } from '../../../helpers/removeRangesFromStructure';
 
 /**
  * @param fixturePath
@@ -254,4 +254,44 @@ describe('NodeAppender', () => {
             assert.deepEqual(astTree, expectedAstTree);
         });
     });
+
+    describe('remove', () => {
+        describe('Variant #1: valid index', () => {
+            let astTree: ESTree.Program,
+                expectedAstTree: ESTree.Program;
+
+            before(() => {
+                astTree = convertCodeToAst('/fixtures/remove-node/valid-index.js');
+                expectedAstTree = convertCodeToAst('/fixtures/remove-node/valid-index-expected.js');
+
+                astTree = NodeUtils.parentizeAst(astTree);
+                expectedAstTree = NodeUtils.parentizeAst(expectedAstTree);
+
+                NodeAppender.remove(astTree, <ESTree.Statement>astTree.body[2]);
+            });
+
+            it('should remove given node from a `BlockStatement` node body', () => {
+                assert.deepEqual(astTree, expectedAstTree);
+            });
+        });
+
+        describe('Variant #2: invalid index', () => {
+            let astTree: ESTree.Program,
+                expectedAstTree: ESTree.Program;
+
+            before(() => {
+                astTree = convertCodeToAst('/fixtures/remove-node/invalid-index.js');
+                expectedAstTree = convertCodeToAst('/fixtures/remove-node/invalid-index-expected.js');
+
+                astTree = NodeUtils.parentizeAst(astTree);
+                expectedAstTree = NodeUtils.parentizeAst(expectedAstTree);
+
+                NodeAppender.remove(astTree, <ESTree.Statement>astTree.body[5]);
+            });
+
+            it('should keep `BlockStatement` as is', () => {
+                assert.deepEqual(astTree, expectedAstTree);
+            });
+        });
+    });
 });

+ 4 - 0
test/unit-tests/node/node-appender/fixtures/remove-node/invalid-index-expected.js

@@ -0,0 +1,4 @@
+var foo = 1;
+var bar = 2;
+var baz = 3;
+var bark = 4;

+ 4 - 0
test/unit-tests/node/node-appender/fixtures/remove-node/invalid-index.js

@@ -0,0 +1,4 @@
+var foo = 1;
+var bar = 2;
+var baz = 3;
+var bark = 4;

+ 3 - 0
test/unit-tests/node/node-appender/fixtures/remove-node/valid-index-expected.js

@@ -0,0 +1,3 @@
+var foo = 1;
+var bar = 2;
+var bark = 4;

+ 4 - 0
test/unit-tests/node/node-appender/fixtures/remove-node/valid-index.js

@@ -0,0 +1,4 @@
+var foo = 1;
+var bar = 2;
+var baz = 3;
+var bark = 4;

+ 10 - 7
test/unit-tests/node/node-metadata/NodeMetadata.spec.ts

@@ -11,7 +11,7 @@ describe('NodeMetadata', () => {
     describe('set', () => {
         const expectedMetadata: ESTree.LiteralNodeMetadata = {
             ignoredNode: true,
-            replacedLiteral: true
+            stringArrayCallLiteralNode: true
         };
 
         let node: ESTree.Literal;
@@ -20,7 +20,7 @@ describe('NodeMetadata', () => {
             node = NodeFactory.literalNode('foo');
             NodeMetadata.set(node, {
                 ignoredNode: true,
-                replacedLiteral: true
+                stringArrayCallLiteralNode: true
             })
         });
 
@@ -38,8 +38,11 @@ describe('NodeMetadata', () => {
         before(() => {
             node = NodeFactory.literalNode('foo');
             node.metadata = {};
-            node.metadata.replacedLiteral = true;
-            value = NodeMetadata.get<ESTree.LiteralNodeMetadata, 'replacedLiteral'>(node, 'replacedLiteral');
+            node.metadata.stringArrayCallLiteralNode = true;
+            value = NodeMetadata.get<
+                ESTree.LiteralNodeMetadata,
+                'stringArrayCallLiteralNode'
+            >(node, 'stringArrayCallLiteralNode');
         });
 
         it('should get metadata value of the node', () => {
@@ -83,7 +86,7 @@ describe('NodeMetadata', () => {
         });
     });
 
-    describe('isReplacedLiteral', () => {
+    describe('isStringArrayCallLiteralNode', () => {
         const expectedValue: boolean = true;
 
         let node: ESTree.Literal,
@@ -92,8 +95,8 @@ describe('NodeMetadata', () => {
         before(() => {
             node = NodeFactory.literalNode('foo');
             node.metadata = {};
-            node.metadata.replacedLiteral = true;
-            value = NodeMetadata.isReplacedLiteral(node);
+            node.metadata.stringArrayCallLiteralNode = true;
+            value = NodeMetadata.isStringArrayCallLiteralNode(node);
         });
 
         it('should return metadata value', () => {

+ 62 - 0
test/unit-tests/utils/ArrayUtils.spec.ts

@@ -215,6 +215,68 @@ describe('ArrayUtils', () => {
         });
     });
 
+    describe('getLastElementByIndex', () => {
+        describe('empty array', () => {
+            const array: string[] = [];
+            const expectedLastElement: undefined = undefined;
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = arrayUtils.getLastElementByIndex(array, 1);
+            });
+
+            it('should return undefined if array is empty', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+
+        describe('array length: `1` and index is out of array boundary', () => {
+            const array: string[] = ['foo'];
+            const expectedLastElement: undefined = undefined;
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = arrayUtils.getLastElementByIndex(array, 2);
+            });
+
+            it('should return undefined', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+
+        describe('array length: `3` and index is `0`', () => {
+            const array: string[] = ['foo', 'bar', 'baz'];
+            const expectedLastElement: string = 'baz';
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = arrayUtils.getLastElementByIndex(array, 0);
+            });
+
+            it('should return element with a correct index', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+
+        describe('array length: `3` and index is `1`', () => {
+            const array: string[] = ['foo', 'bar', 'baz'];
+            const expectedLastElement: string = 'bar';
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = arrayUtils.getLastElementByIndex(array, 1);
+            });
+
+            it('should return element with a correct index', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+    });
+
     describe('rotate', () => {
         let array: number[],
             rotatedArray: number[];