Przeglądaj źródła

Syncing with master

sanex3339 7 lat temu
rodzic
commit
7af427d586
23 zmienionych plików z 616 dodań i 87 usunięć
  1. 1 0
      .npmignore
  2. 9 0
      CHANGELOG.md
  3. 0 0
      dist/index.js
  4. 5 5
      package.json
  5. 70 0
      src/EsprimaFacade.ts
  6. 2 2
      src/JavaScriptObfuscator.ts
  7. 11 0
      src/declarations/esprima.d.ts
  8. 1 1
      src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts
  9. 48 28
      src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts
  10. 103 24
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  11. 1 1
      src/utils/RandomGenerator.ts
  12. 6 0
      test/declarations/index.d.ts
  13. 26 6
      test/dev/dev.ts
  14. 26 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec.ts
  15. 4 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-binary-expression-return-statement-1.js
  16. 7 0
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/fixtures/multiline-template-literal-binary-expression-return-statement-2.js
  17. 110 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts
  18. 22 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-empty-body.js
  19. 37 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-with-scope-hoisting-1.js
  20. 24 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statement-with-scope-hoisting-2.js
  21. 1 0
      test/index.spec.ts
  22. 80 0
      test/unit-tests/javascript-obfuscator/EsprimaFacade.spec.ts
  23. 22 20
      yarn.lock

+ 1 - 0
.npmignore

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

+ 9 - 0
CHANGELOG.md

@@ -1,5 +1,14 @@
 Change Log
 ===
+v0.14.3
+---
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/195
+* Added code preview to `esprima` error messages.
+
+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

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


+ 5 - 5
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.14.1",
+  "version": "0.14.3",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -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"
   },

+ 70 - 0
src/EsprimaFacade.ts

@@ -0,0 +1,70 @@
+import * as esprima from 'esprima';
+import * as ESTree from 'estree';
+
+import chalk, { Chalk } from 'chalk';
+
+/**
+ * Facade over `esprima` to handle parsing errors and provide more detailed error messages
+ */
+export class EsprimaFacade {
+    /**
+     * @type {Chalk}
+     */
+    private static readonly colorError: Chalk = chalk.red;
+
+    /**
+     * @type {number}
+     */
+    private static readonly nearestSymbolsCount: number = 10;
+
+    /**
+     * @param {string} input
+     * @param {ParseOptions} config
+     * @returns {Program}
+     */
+    public static parseScript (input: string, config: esprima.ParseOptions): ESTree.Program {
+        let lastMeta: esprima.NodeMeta | null = null;
+
+        try {
+            return esprima.parseScript(input, config, (node: ESTree.Node, meta: any) => lastMeta = meta);
+        } catch (error) {
+            return EsprimaFacade.processParsingError(input, error.message, lastMeta);
+        }
+    }
+
+    /**
+     * @param {string} sourceCode
+     * @param {string} errorMessage
+     * @param {"esprima".NodeMeta | null} meta
+     * @returns {never}
+     */
+    private static processParsingError (sourceCode: string, errorMessage: string, meta: esprima.NodeMeta | null): never {
+        if (!meta || !meta.start || !meta.end || !meta.start.column || !meta.end.column) {
+            throw new Error(errorMessage);
+        }
+
+        const lineNumberMatch: RegExpMatchArray | null = errorMessage.match(/Line *(\d*)/);
+
+        if (!lineNumberMatch) {
+            throw new Error(errorMessage);
+        }
+
+        const lineNumber: number = parseInt(lineNumberMatch[1], 10);
+        const sourceCodeLines: string[] = sourceCode.split(/\r?\n/);
+        const errorLine: string | undefined = sourceCodeLines[lineNumber - 1];
+
+        if (!errorLine) {
+            throw new Error(errorMessage);
+        }
+
+        const startErrorIndex: number = Math.max(0, meta.start.column - EsprimaFacade.nearestSymbolsCount);
+        const endErrorIndex: number = Math.min(errorLine.length, meta.end.column + EsprimaFacade.nearestSymbolsCount);
+
+        const formattedPointer: string = EsprimaFacade.colorError('>');
+        const formattedCodeSlice: string = `...${
+            errorLine.substring(startErrorIndex, endErrorIndex).replace(/^\s+/, '')
+        }...`;
+
+        throw new Error(`${errorMessage}\n${formattedPointer} ${formattedCodeSlice}`);
+    }
+}

+ 2 - 2
src/JavaScriptObfuscator.ts

@@ -1,7 +1,6 @@
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from './container/ServiceIdentifiers';
 
-import * as esprima from 'esprima';
 import * as escodegen from 'escodegen-wallaby';
 import * as ESTree from 'estree';
 import * as packageJson from 'pjson';
@@ -19,6 +18,7 @@ import { LoggingMessage } from './enums/logger/LoggingMessage';
 import { NodeTransformer } from './enums/node-transformers/NodeTransformer';
 import { TransformationStage } from './enums/node-transformers/TransformationStage';
 
+import { EsprimaFacade } from './EsprimaFacade';
 import { NodeGuards } from './node/NodeGuards';
 
 @injectable()
@@ -134,7 +134,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      * @returns {Program}
      */
     private parseCode (sourceCode: string): ESTree.Program {
-        return esprima.parseScript(sourceCode, {
+        return EsprimaFacade.parseScript(sourceCode, {
             attachComment: true,
             loc: this.options.sourceMap
         });

+ 11 - 0
src/declarations/esprima.d.ts

@@ -3,6 +3,17 @@
 import * as esprima from 'esprima';
 
 declare module 'esprima' {
+    export interface LineMeta {
+        line?: number;
+        column?: number;
+        offset?: number;
+    }
+
+    export interface NodeMeta {
+        start?: LineMeta;
+        end?: LineMeta;
+    }
+
     export interface ParseOptions {
         attachComment?: boolean;
     }

+ 1 - 1
src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts

@@ -35,7 +35,7 @@ export class HexadecimalIdentifierNamesGenerator extends AbstractIdentifierNames
      */
     public generate (): string {
         const rangeMinInteger: number = 10000;
-        const rangeMaxInteger: number = 99999999;
+        const rangeMaxInteger: number = 99_999_999;
         const randomInteger: number = this.randomGenerator.getRandomInteger(rangeMinInteger, rangeMaxInteger);
         const hexadecimalNumber: string = Utils.decToHex(randomInteger);
         const baseIdentifierName: string = hexadecimalNumber.substr(0, HexadecimalIdentifierNamesGenerator.baseIdentifierNameLength);

+ 48 - 28
src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts

@@ -4,6 +4,7 @@ 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';
@@ -51,13 +52,11 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
             case TransformationStage.Converting:
                 return {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                        if (parentNode
-                            && NodeGuards.isExpressionStatementNode(node)
-                            && NodeGuards.isTemplateLiteralNode(node.expression)
-                        ) {
-                            return this.fixEsprimaReturnStatementTemplateLiteralNode(node, node.expression);
+                        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);
                         }
@@ -77,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));
@@ -122,33 +121,54 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {ExpressionStatement} expressionStatementNode
-     * @param {TemplateLiteral} templateLiteralNode
+     * @param {ReturnStatement} returnStatementNode
      * @returns {Node | VisitorOption}
      */
-    private fixEsprimaReturnStatementTemplateLiteralNode (
-        expressionStatementNode: ESTree.ExpressionStatement,
-        templateLiteralNode: ESTree.TemplateLiteral
-    ): ESTree.Node | estraverse.VisitorOption {
-        const previousSiblingStatementNode: TStatement | null = NodeUtils
-            .getPreviousSiblingStatementNode(expressionStatementNode);
+    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;
+        }
 
-        if (
-            !previousSiblingStatementNode
-            || !templateLiteralNode.parentNode
-            || !NodeGuards.isReturnStatementNode(previousSiblingStatementNode)
-            || previousSiblingStatementNode.argument !== null
-        ) {
-            return expressionStatementNode;
+        const nextSiblingStatementNode: TStatement | null = scopeBody[indexInScope + 1];
+
+        if (!nextSiblingStatementNode || !NodeGuards.isExpressionStatementNode(nextSiblingStatementNode)) {
+            return;
         }
 
-        previousSiblingStatementNode.argument = <ESTree.Expression>this.transformNode(
-            templateLiteralNode,
-            templateLiteralNode.parentNode
-        );
+        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;
 
-        NodeUtils.parentizeNode(templateLiteralNode, previousSiblingStatementNode);
+                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 estraverse.VisitorOption.Remove;
+        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;
         }
 

+ 1 - 1
src/utils/RandomGenerator.ts

@@ -70,7 +70,7 @@ export class RandomGenerator implements IRandomGenerator, IInitializable {
             return this.seed + Number(md5Hash.replace(/\D/g, ''));
         };
 
-        this.seed = this.options.seed !== 0 ? this.options.seed : getRandomInteger(0, 999999999);
+        this.seed = this.options.seed !== 0 ? this.options.seed : getRandomInteger(0, 999_999_999);
         this.randomGenerator = new Chance(getSeed());
     }
 

+ 6 - 0
test/declarations/index.d.ts

@@ -0,0 +1,6 @@
+/// <reference path="../../src/declarations/escodegen.d.ts" />
+/// <reference path="../../src/declarations/escodegen-wallaby.d.ts" />
+/// <reference path="../../src/declarations/esprima.d.ts" />
+/// <reference path="../../src/declarations/ESTree.d.ts" />
+/// <reference path="../../src/declarations/js-string-escape.d.ts" />
+/// <reference path="../../src/declarations/threads.d.ts" />

+ 26 - 6
test/dev/dev.ts

@@ -6,18 +6,38 @@ 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 () {
+                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();
 

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

@@ -63,6 +63,32 @@ describe('TemplateLiteralTransformer', () => {
 
             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', () => {

+ 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;
+    }
+}

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

+ 1 - 0
test/index.spec.ts

@@ -17,6 +17,7 @@ import './unit-tests/cli/utils/SourceCodeReader.spec';
 import './unit-tests/decorators/initializable/Initializable.spec';
 import './unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec';
 import './unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec';
+import './unit-tests/javascript-obfuscator/EsprimaFacade.spec';
 import './unit-tests/javascript-obfuscator/JavaScriptObfuscator.spec';
 import './unit-tests/logger/Logger.spec';
 import './unit-tests/node/node-appender/NodeAppender.spec';

+ 80 - 0
test/unit-tests/javascript-obfuscator/EsprimaFacade.spec.ts

@@ -0,0 +1,80 @@
+import { assert } from 'chai';
+import { EsprimaFacade } from '../../../src/EsprimaFacade';
+
+
+describe('EsprimaFacade', () => {
+    describe(`parseScript (input: string, config: esprima.ParseOptions): ESTree.Program`, () => {
+        describe(`\`Unexpected token\` error code preview`, () => {
+            describe('variant #1: 5 lines of code', () => {
+                const sourceCode: string = `` +
+                `var foo = 1;
+                var bar = 2;
+                var baz = 3;,
+                var bark = 4;
+                var hawk = 5;`;
+
+                let testFunc: () => void;
+
+                before(() => {
+                    testFunc = () => EsprimaFacade.parseScript(sourceCode, {});
+                });
+
+                it('should output code preview when `esprima` throws a parse error', () => {
+                    assert.throws(testFunc, /Line 3: Unexpected token ,\n.*\.\.\.var baz = 3;,\.\.\./);
+                });
+            });
+
+            describe('variant #2: 15 lines of code', () => {
+                const sourceCode: string = `` +
+                `var var1 = 1;
+                var var2 = 2;
+                var var3 = 3;
+                var var4 = 4;
+                var var5 = 5;
+                var var6 = 6;
+                var var7 = 7;
+                var var8 = 8;
+                var var9 = 9;
+                var var10 = 10;
+                var foo = 1;
+                var bar = 2;
+                var baz = 3;,
+                var bark = 4;
+                var hawk = 5;`;
+
+                let testFunc: () => void;
+
+                before(() => {
+                    testFunc = () => EsprimaFacade.parseScript(sourceCode, {});
+                });
+
+                it('should output code preview when `esprima` throws a parse error', () => {
+                    assert.throws(testFunc, /Line 13: Unexpected token ,\n.*\.\.\.var baz = 3;,\.\.\./);
+                });
+            });
+        });
+
+        describe(`\`Unexpected identifier\` error code preview`, () => {
+            const sourceCode: string = `` +
+                `function bar () {
+                    var a = 1;
+                }
+                functin baz () {
+                    var a = 1;
+                }
+                function bark () {
+                    var a = 1;
+                }`;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => EsprimaFacade.parseScript(sourceCode, {});
+            });
+
+            it('should output code preview when `esprima` throws a parse error', () => {
+                assert.throws(testFunc, /Line 4: Unexpected identifier\n.*\.\.\.functin baz \(\) {\.\.\./);
+            });
+        });
+    });
+});

+ 22 - 20
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"
@@ -1338,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:
@@ -2587,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"
@@ -3740,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"
@@ -3779,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"
@@ -3821,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"

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