Browse Source

Syncing with master

sanex3339 7 years ago
parent
commit
92475af89e
20 changed files with 742 additions and 2028 deletions
  1. 1 0
      .npmignore
  2. 12 0
      CHANGELOG.md
  3. 0 1967
      dist/index.js
  4. 6 6
      package.json
  5. 63 1
      src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts
  6. 103 24
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  7. 31 0
      src/node/NodeUtils.ts
  8. 27 5
      test/dev/dev.ts
  9. 70 3
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec.ts
  10. 4 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-binary-expression-return-statement-1.js
  11. 7 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-binary-expression-return-statement-2.js
  12. 4 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-return-statement-1.js
  13. 7 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-return-statement-2.js
  14. 2 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal.js
  15. 110 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts
  16. 22 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-empty-body.js
  17. 37 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-with-scope-hoisting-1.js
  18. 24 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-with-scope-hoisting-2.js
  19. 170 0
      test/unit-tests/node/node-utils/NodeUtils.spec.ts
  20. 42 22
      yarn.lock

+ 1 - 0
.npmignore

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

+ 12 - 0
CHANGELOG.md

@@ -1,5 +1,17 @@
 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
+
+v0.14.1
+---
+* Temporary fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/181
+    
 v0.14.0
 ---
 * **New option:** `identifiersPrefix` sets prefix for all generated identifiers.

File diff suppressed because it is too large
+ 0 - 1967
dist/index.js


+ 6 - 6
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.14.0",
+  "version": "0.14.3",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -19,7 +19,7 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator"
   },
   "dependencies": {
-    "chalk": "2.3.0",
+    "chalk": "2.3.1",
     "chance": "1.0.13",
     "class-validator": "0.8.1",
     "commander": "2.14.1",
@@ -47,7 +47,7 @@
     "@types/md5": "2.1.32",
     "@types/mkdirp": "0.5.2",
     "@types/mocha": "2.2.48",
-    "@types/node": "9.4.5",
+    "@types/node": "9.4.6",
     "@types/rimraf": "2.0.2",
     "@types/sinon": "4.1.3",
     "@types/string-template": "1.0.2",
@@ -60,17 +60,17 @@
     "chai": "4.1.2",
     "coveralls": "3.0.0",
     "istanbul": "1.1.0-alpha.1",
-    "mocha": "5.0.0",
+    "mocha": "5.0.1",
     "pre-commit": "1.2.2",
     "rimraf": "2.6.2",
     "sinon": "4.3.0",
     "threads": "0.10.1",
     "ts-node": "4.1.0",
     "tslint": "5.9.1",
-    "tslint-eslint-rules": "4.1.1",
+    "tslint-eslint-rules": "5.0.0",
     "tslint-language-service": "0.9.8",
     "tslint-webpack-plugin": "1.1.1",
-    "typescript": "2.7.1",
+    "typescript": "2.7.2",
     "webpack": "3.11.0",
     "webpack-node-externals": "1.6.0"
   },

+ 63 - 1
src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts

@@ -1,8 +1,12 @@
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
+import { TNodeWithScope } from '../../types/node/TNodeWithScope';
+import { TStatement } from '../../types/node/TStatement';
+
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
@@ -12,6 +16,7 @@ import { TransformationStage } from '../../enums/node-transformers/Transformatio
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { Nodes } from '../../node/Nodes';
+import { NodeUtils } from '../../node/NodeUtils';
 
 /**
  * Transform ES2015 template literals to ES5
@@ -47,6 +52,11 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
             case TransformationStage.Converting:
                 return {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isReturnStatementNode(node) && node.argument === null) {
+                            return this.fixEsprimaReturnStatementTemplateLiteralNode(node);
+                        }
+                    },
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
                         if (parentNode && NodeGuards.isTemplateLiteralNode(node)) {
                             return this.transformNode(node, parentNode);
                         }
@@ -66,7 +76,7 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
     public transformNode (templateLiteralNode: ESTree.TemplateLiteral, parentNode: ESTree.Node): ESTree.Node {
         const templateLiteralExpressions: ESTree.Expression[] = templateLiteralNode.expressions;
 
-        let nodes: (ESTree.Literal | ESTree.Expression)[] = [];
+        let nodes: ESTree.Expression[] = [];
 
         templateLiteralNode.quasis.forEach((templateElement: ESTree.TemplateElement) => {
             nodes.push(Nodes.getLiteralNode(templateElement.value.cooked));
@@ -109,4 +119,56 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
 
         return nodes[0];
     }
+
+    /**
+     * @param {ReturnStatement} returnStatementNode
+     * @returns {Node | VisitorOption}
+     */
+    private fixEsprimaReturnStatementTemplateLiteralNode (returnStatementNode: ESTree.ReturnStatement): ESTree.Node | void {
+        const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(returnStatementNode);
+        const scopeBody: TStatement[] = !NodeGuards.isSwitchCaseNode(scopeNode)
+            ? scopeNode.body
+            : scopeNode.consequent;
+        const indexInScope: number = scopeBody.indexOf(returnStatementNode);
+
+        // in incorrect AST-tree return statement node should be penultimate
+        if (indexInScope !== scopeBody.length - 2) {
+            return;
+        }
+
+        const nextSiblingStatementNode: TStatement | null = scopeBody[indexInScope + 1];
+
+        if (!nextSiblingStatementNode || !NodeGuards.isExpressionStatementNode(nextSiblingStatementNode)) {
+            return;
+        }
+
+        let isSiblingStatementHasTemplateLiteralNode: boolean = false;
+
+        estraverse.traverse(nextSiblingStatementNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): void | estraverse.VisitorOption => {
+                if (!NodeGuards.isTemplateLiteralNode(node)) {
+                    return;
+                }
+
+                isSiblingStatementHasTemplateLiteralNode = true;
+
+                return estraverse.VisitorOption.Break;
+            }
+        });
+
+        if (!isSiblingStatementHasTemplateLiteralNode) {
+            return;
+        }
+
+        returnStatementNode.argument = nextSiblingStatementNode.expression;
+        scopeBody.pop();
+
+        if (!NodeGuards.isSwitchCaseNode(scopeNode)) {
+            scopeNode.body = [...scopeBody];
+        } else {
+            scopeNode.consequent = <ESTree.Statement[]>[...scopeBody];
+        }
+
+        return returnStatementNode;
+    }
 }

+ 103 - 24
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;
 
@@ -130,6 +179,37 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
         return isValidBlockStatementNode;
     }
 
+    /**
+     * @param {BlockStatement} blockStatementNode
+     * @returns {boolean}
+     */
+    private static isValidWrappedBlockStatementNode (blockStatementNode: ESTree.BlockStatement): boolean {
+        if (!blockStatementNode.body.length) {
+            return false;
+        }
+
+        let isValidBlockStatementNode: boolean = true;
+
+        estraverse.traverse(blockStatementNode, {
+            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
+                if (DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)) {
+                    isValidBlockStatementNode = false;
+
+                    return estraverse.VisitorOption.Break;
+                }
+            }
+        });
+
+        if (!isValidBlockStatementNode) {
+            return false;
+        }
+
+        const blockScopeOfBlockStatementNode: TNodeWithBlockScope = NodeUtils
+            .getBlockScopesOfNode(blockStatementNode)[0];
+
+        return blockScopeOfBlockStatementNode.type !== NodeType.Program;
+    }
+
     /**
      * @param {TransformationStage} transformationStage
      * @returns {IVisitor | null}
@@ -183,7 +263,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
 
                 let clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(node);
 
-                if (!DeadCodeInjectionTransformer.isValidBlockStatementNode(clonedBlockStatementNode)) {
+                if (!DeadCodeInjectionTransformer.isValidCollectedBlockStatementNode(clonedBlockStatementNode)) {
                     return;
                 }
 
@@ -209,23 +289,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) {
-            return blockStatementNode;
-        }
-
-        const blockScopeOfBlockStatementNode: TNodeWithBlockScope = NodeUtils
-            .getBlockScopesOfNode(blockStatementNode)[0];
-
-        if (blockScopeOfBlockStatementNode.type === NodeType.Program) {
+        if (
+            this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold
+            || !DeadCodeInjectionTransformer.isValidWrappedBlockStatementNode(blockStatementNode)
+        ) {
             return blockStatementNode;
         }
 
@@ -233,8 +311,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;
         }
 

+ 31 - 0
src/node/NodeUtils.ts

@@ -71,6 +71,22 @@ export class NodeUtils {
         return NodeUtils.getBlockScopesOfNodeRecursive(targetNode);
     }
 
+    /**
+     * @param {Statement} node
+     * @returns {TStatement | null}
+     */
+    public static getNextSiblingStatementNode (node: ESTree.Statement): TStatement | null {
+        return NodeUtils.getSiblingStatementNodeByOffset(node, 1);
+    }
+
+    /**
+     * @param {Statement} node
+     * @returns {TStatement | null}
+     */
+    public static getPreviousSiblingStatementNode (node: ESTree.Statement): TStatement | null {
+        return NodeUtils.getSiblingStatementNodeByOffset(node, -1);
+    }
+
     /**
      * @param {NodeGuards} node
      * @returns {TNodeWithScope}
@@ -208,4 +224,19 @@ export class NodeUtils {
 
         return blockScopes;
     }
+
+    /**
+     * @param {Statement} node
+     * @param {number} offset
+     * @returns {TStatement | null}
+     */
+    private static getSiblingStatementNodeByOffset (node: ESTree.Statement, offset: number): TStatement | null {
+        const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(node);
+        const scopeBody: TStatement[] = !NodeGuards.isSwitchCaseNode(scopeNode)
+            ? scopeNode.body
+            : scopeNode.consequent;
+        const indexInScope: number = scopeBody.indexOf(node);
+
+        return scopeBody[indexInScope + offset] || null;
+    }
 }

+ 27 - 5
test/dev/dev.ts

@@ -7,18 +7,40 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         (function(){
-            var foo = 'foo'; var bar = 'bar'; var bark = foo + bar,; var baz = 'baz';
+            function foo () {
+                var a = 1;
+                inner1();
+                var b = 2;
+                function inner1 () {}
+                var c = 3;
+            }
+            function bar () {
+                var a = 1;
+            }
+            function baz () {
+                var a = 1;
+            }
+            function bark () {
+                var a = 1;
+            }
+            function hawk () {
+                var a = 1;
+            }
+            function eagle () {
+                var a = 1;
+            }
         })();
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
-            identifiersPrefix: 'foo',
-            identifierNamesGenerator: 'mangled',
-            renameGlobals: true
+            stringArray: true,
+            stringArrayThreshold: 1,
+            deadCodeInjection: true,
+            deadCodeInjectionThreshold: 1
         }
     ).getObfuscatedCode();
 
     console.log(obfuscatedCode);
     console.log(eval(obfuscatedCode));
-})();
+})();

+ 70 - 3
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec.ts

@@ -24,7 +24,74 @@ describe('TemplateLiteralTransformer', () => {
         });
     });
 
-    describe('variant #1: simple template literal with expression only', () => {
+    describe('variant #2: multiline template literals', () => {
+        it('variant #1: should transform es6 multiline template literal to es5', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiline-template-literal.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
+            assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'foo\\x0abar';$/);
+        });
+
+        it('variant #2: should transform es6 multiline template literal inside return statement', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiline-template-literal-return-statement-1.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
+            assert.match(obfuscationResult.getObfuscatedCode(),  /{ *return *'foo\\x0abar'; *}$/);
+        });
+
+        it('variant #3: should transform es6 multiline template literal inside return statement', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiline-template-literal-return-statement-2.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
+            assert.match(obfuscationResult.getObfuscatedCode(),  /case *!!\[] *: *return *'foo\\x0abar'; *} *}$/);
+        });
+
+        it('variant #4: should transform es6 multiline template literal inside binary expression inside return statement', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiline-template-literal-binary-expression-return-statement-1.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
+            assert.match(obfuscationResult.getObfuscatedCode(),  /{ *return *'foo\\x0abar' *\+ *0x1; *}$/);
+        });
+
+        it('variant #5: should transform es6 multiline template literal inside binary expression inside return statement', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiline-template-literal-binary-expression-return-statement-2.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
+            assert.match(obfuscationResult.getObfuscatedCode(),  /case *!!\[] *: *return *'foo\\x0abar' *\+ *0x1; *} *}$/);
+        });
+    });
+
+    describe('variant #3: simple template literal with expression only', () => {
         it('should transform es6 template literal to es5 and add empty literal node before expression node', () => {
             const code: string = readFileAsString(__dirname + '/fixtures/expression-only.js');
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
@@ -39,7 +106,7 @@ describe('TemplateLiteralTransformer', () => {
         });
     });
 
-    describe('variant #3: literal node inside expression', () => {
+    describe('variant #4: literal node inside expression', () => {
         it('should transform es6 template literal to es5', () => {
             const code: string = readFileAsString(__dirname + '/fixtures/literal-inside-expression.js');
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
@@ -54,7 +121,7 @@ describe('TemplateLiteralTransformer', () => {
         });
     });
 
-    describe('variant #4: multiple expressions', () => {
+    describe('variant #5: multiple expressions', () => {
         it('should transform es6 template literal to es5', () => {
             const code: string = readFileAsString(__dirname + '/fixtures/multiple-expressions.js');
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(

+ 4 - 0
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-binary-expression-return-statement-1.js

@@ -0,0 +1,4 @@
+function foo() {
+    return `foo
+bar` + 1;
+}

+ 7 - 0
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-binary-expression-return-statement-2.js

@@ -0,0 +1,7 @@
+function hi() {
+    switch (true) {
+        case true:
+            return `foo
+bar` + 1;
+    }
+}

+ 4 - 0
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-return-statement-1.js

@@ -0,0 +1,4 @@
+function foo() {
+    return `foo
+bar`;
+}

+ 7 - 0
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-return-statement-2.js

@@ -0,0 +1,7 @@
+function hi() {
+    switch (true) {
+        case true:
+            return `foo
+bar`;
+    }
+}

+ 2 - 0
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal.js

@@ -0,0 +1,2 @@
+var test = `foo
+bar`;

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

@@ -481,5 +481,115 @@ 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', () => {
+            describe('variant #1: collecting of block statements', () => {
+                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-1.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);
+                });
+            });
+
+            describe('variant #2: wrapping of block statements in dead code conditions', () => {
+                const regExp: RegExp = new RegExp(
+                    `function *${variableMatch} *\\(\\) *{ *` +
+                        `var *${variableMatch} *= *0x1; *` +
+                        `${variableMatch} *\\(\\); *` +
+                        `var *${variableMatch} *= *0x2; *` +
+                        `function *${variableMatch} *\\(\\) *{ *} *` +
+                        `var *${variableMatch} *= *0x3; *` +
+                    `}`,
+                    'g'
+                );
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting-2.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            deadCodeInjection: true,
+                            deadCodeInjectionThreshold: 1
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('shouldn\'t wrap block statements in dead code conditions', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
     });
 });

+ 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-1.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;
+    }
+})();

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

@@ -0,0 +1,24 @@
+(function(){
+    function foo () {
+        var a = 1;
+        inner1();
+        var b = 2;
+        function inner1 () {}
+        var c = 3;
+    }
+    function bar () {
+        var a = 1;
+    }
+    function baz () {
+        var a = 1;
+    }
+    function bark () {
+        var a = 1;
+    }
+    function hawk () {
+        var a = 1;
+    }
+    function eagle () {
+        var a = 1;
+    }
+})();

+ 170 - 0
test/unit-tests/node/node-utils/NodeUtils.spec.ts

@@ -294,6 +294,176 @@ describe('NodeUtils', () => {
         });
     });
 
+    describe('getNextSiblingStatementNode (node: ESTree.Statement): TStatement | null', () => {
+        describe('variant #1: block statement node as scope node', () => {
+                let statementNode1: ESTree.Statement,
+                statementNode2: ESTree.Statement,
+                statementNode3: ESTree.Statement;
+
+            before(() => {
+                statementNode1 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('a')
+                );
+                statementNode2 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('b')
+                );
+                statementNode3 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('c')
+                );
+
+                const blockStatementNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
+                    statementNode1,
+                    statementNode2,
+                    statementNode3
+                ]);
+
+                statementNode1.parentNode = blockStatementNode;
+                statementNode2.parentNode = blockStatementNode;
+                statementNode3.parentNode = blockStatementNode;
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getNextSiblingStatementNode(statementNode1), statementNode2);
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getNextSiblingStatementNode(statementNode2), statementNode3);
+            });
+
+            it('should return `null` if given statement node is last node in the scope', () => {
+                assert.deepEqual(NodeUtils.getNextSiblingStatementNode(statementNode3), null);
+            });
+        });
+
+        describe('variant #2: switch case node as scope node', () => {
+            let statementNode1: ESTree.Statement,
+                statementNode2: ESTree.Statement,
+                statementNode3: ESTree.Statement;
+
+            before(() => {
+                statementNode1 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('a')
+                );
+                statementNode2 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('b')
+                );
+                statementNode3 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('c')
+                );
+
+                const switchCaseNode: ESTree.SwitchCase = Nodes.getSwitchCaseNode(
+                    Nodes.getLiteralNode(true),
+                    [
+                        statementNode1,
+                        statementNode2,
+                        statementNode3
+                    ]
+                );
+
+                statementNode1.parentNode = switchCaseNode;
+                statementNode2.parentNode = switchCaseNode;
+                statementNode3.parentNode = switchCaseNode;
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getNextSiblingStatementNode(statementNode1), statementNode2);
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getNextSiblingStatementNode(statementNode2), statementNode3);
+            });
+
+            it('should return `null` if given statement node is last node in the scope', () => {
+                assert.deepEqual(NodeUtils.getNextSiblingStatementNode(statementNode3), null);
+            });
+        });
+    });
+
+    describe('getPreviousSiblingStatementNode (node: ESTree.Statement): TStatement | null', () => {
+        describe('variant #1: block statement node as scope node', () => {
+            let statementNode1: ESTree.Statement,
+                statementNode2: ESTree.Statement,
+                statementNode3: ESTree.Statement;
+
+            before(() => {
+                statementNode1 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('a')
+                );
+                statementNode2 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('b')
+                );
+                statementNode3 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('c')
+                );
+
+                const blockStatementNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
+                    statementNode1,
+                    statementNode2,
+                    statementNode3
+                ]);
+
+                statementNode1.parentNode = blockStatementNode;
+                statementNode2.parentNode = blockStatementNode;
+                statementNode3.parentNode = blockStatementNode;
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getPreviousSiblingStatementNode(statementNode1), null);
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getPreviousSiblingStatementNode(statementNode2), statementNode1);
+            });
+
+            it('should return `null` if given statement node is last node in the scope', () => {
+                assert.deepEqual(NodeUtils.getPreviousSiblingStatementNode(statementNode3), statementNode2);
+            });
+        });
+
+        describe('variant #2: switch case node as scope node', () => {
+            let statementNode1: ESTree.Statement,
+                statementNode2: ESTree.Statement,
+                statementNode3: ESTree.Statement;
+
+            before(() => {
+                statementNode1 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('a')
+                );
+                statementNode2 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('b')
+                );
+                statementNode3 = Nodes.getExpressionStatementNode(
+                    Nodes.getIdentifierNode('c')
+                );
+
+                const switchCaseNode: ESTree.SwitchCase = Nodes.getSwitchCaseNode(
+                    Nodes.getLiteralNode(true),
+                    [
+                        statementNode1,
+                        statementNode2,
+                        statementNode3
+                    ]
+                );
+
+                statementNode1.parentNode = switchCaseNode;
+                statementNode2.parentNode = switchCaseNode;
+                statementNode3.parentNode = switchCaseNode;
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getPreviousSiblingStatementNode(statementNode1), null);
+            });
+
+            it('should return next sibling statement node', () => {
+                assert.deepEqual(NodeUtils.getPreviousSiblingStatementNode(statementNode2), statementNode1);
+            });
+
+            it('should return `null` if given statement node is last node in the scope', () => {
+                assert.deepEqual(NodeUtils.getPreviousSiblingStatementNode(statementNode3), statementNode2);
+            });
+        });
+    });
+
     describe('getScopeOfNode (node: ESTree.Node): TNodeWithScope | null', () => {
         let functionDeclarationBlockStatementNode: ESTree.BlockStatement,
             ifStatementBlockStatementNode1: ESTree.BlockStatement,

+ 42 - 22
yarn.lock

@@ -72,9 +72,9 @@
   version "8.0.53"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
 
-"@types/[email protected].5":
-  version "9.4.5"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.5.tgz#d2a90c634208173d1b1a0a6ba9f1df3de62edcf5"
+"@types/[email protected].6":
+  version "9.4.6"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.6.tgz#d8176d864ee48753d053783e4e463aec86b8d82e"
 
 "@types/[email protected]":
   version "2.0.2"
@@ -168,7 +168,7 @@ ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
-ansi-styles@^3.1.0:
+ansi-styles@^3.1.0, ansi-styles@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
   dependencies:
@@ -969,7 +969,15 @@ [email protected], chalk@^1.0.0, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
[email protected], chalk@^2.1.0, chalk@^2.3.0:
[email protected]:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
+  dependencies:
+    ansi-styles "^3.2.0"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.2.0"
+
+chalk@^2.1.0, chalk@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
   dependencies:
@@ -1330,7 +1338,7 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-doctrine@^0.7.2:
[email protected]:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523"
   dependencies:
@@ -1839,6 +1847,10 @@ has-flag@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
 
+has-flag@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+
 has-unicode@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2575,9 +2587,9 @@ [email protected], [email protected], "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0:
   dependencies:
     minimist "0.0.8"
 
[email protected].0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.0.tgz#cccac988b0bc5477119cba0e43de7af6d6ad8f4e"
[email protected].1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.1.tgz#759b62c836b0732382a62b6b1fb245ec1bc943ac"
   dependencies:
     browser-stdout "1.3.0"
     commander "2.11.0"
@@ -3603,6 +3615,12 @@ supports-color@^5.1.0:
   dependencies:
     has-flag "^2.0.0"
 
+supports-color@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
+  dependencies:
+    has-flag "^3.0.0"
+
 tapable@^0.2.5, tapable@^0.2.7:
   version "0.2.8"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
@@ -3722,17 +3740,17 @@ [email protected]:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
 
-tslib@^1.0.0, tslib@^1.7.1, tslib@^1.8.0:
+tslib@^1.7.1, tslib@^1.8.0:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac"
 
-tslint-eslint-rules@4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-4.1.1.tgz#7c30e7882f26bc276bff91d2384975c69daf88ba"
+tslint-eslint-rules@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.0.0.tgz#ba46e32168137b7b6f73c2121a29d57abea032a6"
   dependencies:
-    doctrine "^0.7.2"
-    tslib "^1.0.0"
-    tsutils "^1.4.0"
+    doctrine "0.7.2"
+    tslib "1.9.0"
+    tsutils "2.8.0"
 
 [email protected]:
   version "0.9.8"
@@ -3761,9 +3779,11 @@ [email protected]:
     tslib "^1.8.0"
     tsutils "^2.12.1"
 
-tsutils@^1.4.0:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0"
[email protected]:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.0.tgz#0160173729b3bf138628dd14a1537e00851d814a"
+  dependencies:
+    tslib "^1.7.1"
 
 tsutils@^2.12.1:
   version "2.12.2"
@@ -3803,9 +3823,9 @@ typedarray@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
[email protected].1:
-  version "2.7.1"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359"
[email protected].2:
+  version "2.7.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836"
 
 uglify-js@^2.6, uglify-js@^2.8.29:
   version "2.8.29"

Some files were not shown because too many files changed in this diff