Przeglądaj źródła

Merge pull request #196 from javascript-obfuscator/dead-code-injection-improvements

Dead code injection improvements
Timofey Kachalov 7 lat temu
rodzic
commit
345a1f7dc9

+ 1 - 0
.npmignore

@@ -4,3 +4,4 @@
 coverage
 images
 test/fixtures/compile-performance.js
+test-tmp

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 ===
+v0.14.3
+---
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/195
+
 v0.14.2
 ---
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/181

Plik diff jest za duży
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.14.2",
+  "version": "0.14.3",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 72 - 17
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts

@@ -6,6 +6,7 @@ import * as ESTree from 'estree';
 
 import { TDeadNodeInjectionCustomNodeFactory } from '../../types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory';
 import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
+import { TNodeWithScope } from '../../types/node/TNodeWithScope';
 
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -97,15 +98,62 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {Node} blockStatementNode
+     * @param {Node} targetNode
      * @returns {boolean}
      */
-    private static isValidBlockStatementNode (blockStatementNode: ESTree.Node): boolean {
-        const isProhibitedNode: (node: ESTree.Node) => boolean =
-            (node: ESTree.Node): boolean => NodeGuards.isBreakStatementNode(node) ||
-                NodeGuards.isContinueStatementNode(node) ||
-                NodeGuards.isAwaitExpressionNode(node) ||
-                NodeGuards.isSuperNode(node);
+    private static isProhibitedNodeInsideCollectedBlockStatement (targetNode: ESTree.Node): boolean {
+        return NodeGuards.isBreakStatementNode(targetNode)
+            || NodeGuards.isContinueStatementNode(targetNode)
+            || NodeGuards.isAwaitExpressionNode(targetNode)
+            || NodeGuards.isSuperNode(targetNode);
+    }
+
+    /**
+     * @param {Node} targetNode
+     * @returns {boolean}
+     */
+    private static isScopeHoistingFunctionDeclaration (targetNode: ESTree.Node): boolean {
+        if (!NodeGuards.isFunctionDeclarationNode(targetNode)) {
+            return false;
+        }
+
+        const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(targetNode);
+        const scopeBody: ESTree.Statement[] = !NodeGuards.isSwitchCaseNode(scopeNode)
+            ? <ESTree.Statement[]>scopeNode.body
+            : scopeNode.consequent;
+        const indexInScope: number = scopeBody.indexOf(targetNode);
+
+        if (indexInScope === 0) {
+            return false;
+        }
+
+        const slicedBody: ESTree.Statement[] = scopeBody.slice(0, indexInScope);
+        const hostBlockStatementNode: ESTree.BlockStatement = Nodes.getBlockStatementNode(slicedBody);
+        const functionDeclarationName: string = targetNode.id.name;
+
+        let isScopeHoistedFunctionDeclaration: boolean = false;
+
+        estraverse.traverse(hostBlockStatementNode, {
+            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
+                if (NodeGuards.isIdentifierNode(node) && node.name === functionDeclarationName) {
+                    isScopeHoistedFunctionDeclaration = true;
+
+                    return estraverse.VisitorOption.Break;
+                }
+            }
+        });
+
+        return isScopeHoistedFunctionDeclaration;
+    }
+
+    /**
+     * @param {BlockStatement} blockStatementNode
+     * @returns {boolean}
+     */
+    private static isValidCollectedBlockStatementNode (blockStatementNode: ESTree.BlockStatement): boolean {
+        if (!blockStatementNode.body.length) {
+            return false;
+        }
 
         let nestedBlockStatementsCount: number = 0;
         let isValidBlockStatementNode: boolean = true;
@@ -117,8 +165,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
                 }
 
                 if (
-                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
-                    isProhibitedNode(node)
+                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount
+                    || DeadCodeInjectionTransformer.isProhibitedNodeInsideCollectedBlockStatement(node)
+                    || DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)
                 ) {
                     isValidBlockStatementNode = false;
 
@@ -183,7 +232,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
 
                 let clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(node);
 
-                if (!DeadCodeInjectionTransformer.isValidBlockStatementNode(clonedBlockStatementNode)) {
+                if (!DeadCodeInjectionTransformer.isValidCollectedBlockStatementNode(clonedBlockStatementNode)) {
                     return;
                 }
 
@@ -209,16 +258,21 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
      * @param {NodeGuards} parentNode
      * @returns {NodeGuards | VisitorOption}
      */
-    public transformNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node | estraverse.VisitorOption {
-        if (this.collectedBlockStatementsTotalLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount) {
-            return estraverse.VisitorOption.Break;
-        }
+    public transformNode (
+        blockStatementNode: ESTree.BlockStatement,
+        parentNode: ESTree.Node
+    ): ESTree.Node | estraverse.VisitorOption {
+        const canBreakTraverse: boolean = !this.collectedBlockStatements.length
+            || this.collectedBlockStatementsTotalLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount;
 
-        if (!this.collectedBlockStatements.length) {
+        if (canBreakTraverse) {
             return estraverse.VisitorOption.Break;
         }
 
-        if (this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold) {
+        const isInvalidBlockStatementNode: boolean = !blockStatementNode.body.length
+            || this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold;
+
+        if (isInvalidBlockStatementNode) {
             return blockStatementNode;
         }
 
@@ -233,8 +287,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
         const maxInteger: number = this.collectedBlockStatements.length - 1;
         const randomIndex: number = this.randomGenerator.getRandomInteger(minInteger, maxInteger);
         const randomBlockStatementNode: ESTree.BlockStatement = this.collectedBlockStatements.splice(randomIndex, 1)[0];
+        const isDuplicateBlockStatementNodes: boolean = randomBlockStatementNode === blockStatementNode;
 
-        if (randomBlockStatementNode === blockStatementNode) {
+        if (isDuplicateBlockStatementNodes) {
             return blockStatementNode;
         }
 

+ 24 - 6
test/dev/dev.ts

@@ -6,18 +6,36 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-        var bar = 1;
-        var baz = 2;
         (function(){
-            var bark = bar + baz;
+            function foo () {
+                function inner1 () {}
+                inner1();
+            }
+            function bar () {
+                function inner2 () {}
+                inner2();
+            }
+            function baz () {
+                function inner3 () {}
+                inner3();
+            }
+            function bark () {
+                function inner4 () {}
+                inner4();
+            }
+            function hawk () {
+                function inner5 () {}
+                inner5();
+            }
         })();
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
-            identifiersPrefix: 'foo',
-            identifierNamesGenerator: 'mangled',
-            renameGlobals: true
+            stringArray: true,
+            stringArrayThreshold: 1,
+            deadCodeInjection: true,
+            deadCodeInjectionThreshold: 1
         }
     ).getObfuscatedCode();
 

+ 72 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts

@@ -481,5 +481,77 @@ describe('DeadCodeInjectionTransformer', () => {
                 assert.notEqual(returnIdentifierName, variableDeclarationIdentifierName);
             });
         });
+
+        describe('variant #11 - block statements with empty body', () => {
+            const regExp: RegExp = new RegExp(
+                `function *${variableMatch} *\\(\\) *{ *} *` +
+                `${variableMatch} *\\(\\); *`,
+                'g'
+            );
+            const expectedMatchesLength: number = 5;
+
+            let matchesLength: number = 0;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/block-statement-empty-body.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1
+                    }
+                );
+
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
+
+                if (functionMatches) {
+                    matchesLength = functionMatches.length;
+                }
+            });
+
+            it('shouldn\'t add dead code conditions to the block empty block statements', () => {
+                assert.isAtLeast(matchesLength, expectedMatchesLength);
+            });
+        });
+
+        describe('variant #12 - block statement with scope-hoisting', () => {
+            const regExp: RegExp = new RegExp(
+                `${variableMatch} *\\(\\); *` +
+                `var *${variableMatch} *= *0x2; *` +
+                `function *${variableMatch} *\\(\\) *{ *} *`,
+                'g'
+            );
+            const expectedMatchesLength: number = 5;
+
+            let matchesLength: number = 0;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1
+                    }
+                );
+
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
+
+                if (functionMatches) {
+                    matchesLength = functionMatches.length;
+                }            });
+
+            it('shouldn\'t collect block statements with scope-hoisting', () => {
+                assert.equal(matchesLength, expectedMatchesLength);
+            });
+        });
     });
 });

+ 22 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-empty-body.js

@@ -0,0 +1,22 @@
+(function(){
+    function foo () {
+        function inner1 () {}
+        inner1();
+    }
+    function bar () {
+        function inner2 () {}
+        inner2();
+    }
+    function baz () {
+        function inner3 () {}
+        inner3();
+    }
+    function bark () {
+        function inner4 () {}
+        inner4();
+    }
+    function hawk () {
+        function inner5 () {}
+        inner5();
+    }
+})();

+ 37 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-with-scope-hoisting.js

@@ -0,0 +1,37 @@
+(function(){
+    function foo () {
+        var a = 1;
+        inner1();
+        var b = 2;
+        function inner1 () {}
+        var c = 3;
+    }
+    function bar () {
+        var a = 1;
+        inner2();
+        var b = 2;
+        function inner2 () {}
+        var c = 3;
+    }
+    function baz () {
+        var a = 1;
+        inner3();
+        var b = 2;
+        function inner3 () {}
+        var c = 3;
+    }
+    function bark () {
+        var a = 1;
+        inner4();
+        var b = 2;
+        function inner4 () {}
+        var c = 3;
+    }
+    function hawk () {
+        var a = 1;
+        inner5();
+        var b = 2;
+        function inner5 () {}
+        var c = 3;
+    }
+})();

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików