Browse Source

eslint-scope transformer

sanex3339 5 years ago
parent
commit
ff6b3013a6

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


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


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


+ 2 - 0
package.json

@@ -29,6 +29,7 @@
     "class-validator": "0.11.0",
     "commander": "4.1.0",
     "escodegen": "1.12.1",
+    "eslint-scope": "^5.0.0",
     "estraverse": "4.3.0",
     "eventemitter3": "4.0.0",
     "inversify": "5.0.1",
@@ -45,6 +46,7 @@
     "@types/chai": "4.2.7",
     "@types/chance": "1.0.8",
     "@types/escodegen": "0.0.6",
+    "@types/eslint-scope": "^3.7.0",
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.42",
     "@types/md5": "2.1.33",

+ 5 - 5
src/JavaScriptObfuscator.ts

@@ -50,16 +50,16 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      */
     private static readonly transformersList: NodeTransformer[] = [
         NodeTransformer.BlockStatementControlFlowTransformer,
-        NodeTransformer.ClassDeclarationTransformer,
+        //NodeTransformer.ClassDeclarationTransformer,
         NodeTransformer.CommentsTransformer,
         NodeTransformer.CustomNodesTransformer,
         NodeTransformer.DeadCodeInjectionTransformer,
         NodeTransformer.EvalCallExpressionTransformer,
         NodeTransformer.FunctionControlFlowTransformer,
-        NodeTransformer.CatchClauseTransformer,
-        NodeTransformer.FunctionDeclarationTransformer,
-        NodeTransformer.FunctionTransformer,
-        NodeTransformer.ImportDeclarationTransformer,
+        //NodeTransformer.CatchClauseTransformer,
+        //NodeTransformer.FunctionDeclarationTransformer,
+        //NodeTransformer.FunctionTransformer,
+        //NodeTransformer.ImportDeclarationTransformer,
         NodeTransformer.LabeledStatementTransformer,
         NodeTransformer.LiteralTransformer,
         NodeTransformer.MemberExpressionTransformer,

+ 5 - 0
src/declarations/ESTree.d.ts

@@ -2,6 +2,7 @@
 
 import * as acorn from 'acorn';
 import * as escodegen from 'escodegen';
+import * as eslintScope from 'eslint-scope';
 
 declare module 'estree' {
     export interface BaseNodeMetadata {
@@ -31,6 +32,10 @@ declare module 'estree' {
         metadata?: IdentifierNodeMetadata;
     }
 
+    interface Program extends BaseNode {
+        scope?: eslintScope.Scope | null;
+    }
+
     interface SimpleLiteral extends BaseNode {
         metadata?: LiteralNodeMetadata;
     }

+ 183 - 191
src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts

@@ -1,28 +1,24 @@
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
-import * as estraverse from 'estraverse';
+import * as eslintScope from 'eslint-scope';
 import * as ESTree from 'estree';
+import * as estraverse from 'estraverse';
 
-import { TIdentifierObfuscatingReplacerFactory } from '../../types/container/node-transformers/TIdentifierObfuscatingReplacerFactory';
-import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
-import { TReplaceableIdentifiers } from '../../types/node-transformers/TReplaceableIdentifiers';
-import { TReplaceableIdentifiersNames } from '../../types/node-transformers/TReplaceableIdentifiersNames';
-
-import { IIdentifierObfuscatingReplacer } from '../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IIdentifierObfuscatingReplacer';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
-import { IdentifierObfuscatingReplacer } from "../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer";
-import { NodeType } from '../../enums/node/NodeType';
 import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
-import { NodeLexicalScopeUtils } from '../../node/NodeLexicalScopeUtils';
-import { NodeBlockLexicalScopeUtils } from '../../node/NodeBlockLexicalScopeUtils';
+import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
+import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { NodeMetadata } from '../../node/NodeMetadata';
+import { IdentifierObfuscatingReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer';
+import { IIdentifierObfuscatingReplacer } from '../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IIdentifierObfuscatingReplacer';
+import { TIdentifierObfuscatingReplacerFactory } from '../../types/container/node-transformers/TIdentifierObfuscatingReplacerFactory';
 
 /**
  * replaces:
@@ -37,23 +33,34 @@ import { NodeMetadata } from '../../node/NodeMetadata';
 @injectable()
 export class VariableDeclarationTransformer extends AbstractNodeTransformer {
     /**
-     * @type {IIdentifierObfuscatingReplacer}
+     * @type {eslintScope.AnalysisOptions}
      */
-    private readonly identifierObfuscatingReplacer: IIdentifierObfuscatingReplacer;
+    private static readonly eslintScopeOptions: eslintScope.AnalysisOptions = {
+        ecmaVersion: 10,
+        optimistic: true
+    };
 
     /**
-     * @type {Map<TNodeWithLexicalScope, boolean>}
+     * @type {acorn.Options['sourceType'][]}
      */
-    private readonly lexicalScopesWithObjectPatternWithoutDeclarationMap: Map<TNodeWithLexicalScope, boolean> = new Map();
+    private static readonly sourceTypes: acorn.Options['sourceType'][] = [
+        'script',
+        'module'
+    ];
 
     /**
-     * @type {TReplaceableIdentifiers}
+     * @type {eslintScope.ScopeManager | null}
      */
-    private readonly replaceableIdentifiers: TReplaceableIdentifiers = new Map();
+    private scopeManager: eslintScope.ScopeManager | null = null;
+
+    private readonly identifierObfuscatingReplacer: IIdentifierObfuscatingReplacer;
+
+    private readonly lexicalScopesWithObjectPatternWithoutDeclarationMap: Map<TNodeWithLexicalScope, boolean> = new Map();
 
     /**
      * @param {TIdentifierObfuscatingReplacerFactory} identifierObfuscatingReplacerFactory
      * @param {IRandomGenerator} randomGenerator
+     * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {IOptions} options
      */
     constructor (
@@ -69,6 +76,15 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
         );
     }
 
+    /**
+     * @param {ESTree.Node} node
+     * @returns {boolean}
+     */
+    private static isProhibitedImportSpecifierNode (node: ESTree.Node): boolean {
+        return NodeGuards.isImportSpecifierNode(node)
+            && node.imported.name === node.local.name;
+    }
+
     /**
      * @param {TransformationStage} transformationStage
      * @returns {IVisitor | null}
@@ -78,11 +94,9 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
             case TransformationStage.Obfuscating:
                 return {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                        if (
-                            parentNode
-                            && NodeGuards.isVariableDeclarationNode(node)
-                            && !NodeGuards.isExportNamedDeclarationNode(parentNode)
-                        ) {
+                        if (parentNode && NodeGuards.isProgramNode(node)) {
+                            this.analyzeNode(node, parentNode);
+
                             return this.transformNode(node, parentNode);
                         }
                     }
@@ -94,89 +108,158 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {VariableDeclaration} variableDeclarationNode
-     * @param {NodeGuards} parentNode
-     * @returns {NodeGuards}
+     * @param {Node} node
+     * @param {Node | null} parentNode
+     * @returns {Node}
      */
-    public transformNode (variableDeclarationNode: ESTree.VariableDeclaration, parentNode: ESTree.Node): ESTree.Node {
-        const lexicalScopeNode: TNodeWithLexicalScope | undefined = variableDeclarationNode.kind === 'var'
-            ? NodeLexicalScopeUtils.getLexicalScope(variableDeclarationNode)
-            : NodeBlockLexicalScopeUtils.getLexicalScope(variableDeclarationNode);
+    public analyzeNode (node: ESTree.Node, parentNode: ESTree.Node | null): void | never {
+        const sourceTypeLength: number = VariableDeclarationTransformer.sourceTypes.length;
 
-        if (!lexicalScopeNode) {
-            return variableDeclarationNode;
-        }
+        estraverse.replace(node, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node => {
+                if (node.range) {
+                    return node;
+                }
 
-        const isGlobalDeclaration: boolean = lexicalScopeNode.type === NodeType.Program;
+                node.range = [0, 0];
 
-        if (!this.options.renameGlobals && isGlobalDeclaration) {
-            return variableDeclarationNode;
-        }
+                return node;
+            }
+        });
 
-        const scopeNode: ESTree.Node = variableDeclarationNode.kind === 'var'
-            ? lexicalScopeNode
-            : parentNode;
+        for (let i: number = 0; i < sourceTypeLength; i++) {
+            try {
+                this.scopeManager = eslintScope.analyze(node, {
+                    ...VariableDeclarationTransformer.eslintScopeOptions,
+                    nodejsScope: this.options.target === ObfuscationTarget.Node,
+                    sourceType: VariableDeclarationTransformer.sourceTypes[i]
+                });
 
-        this.storeVariableNames(variableDeclarationNode, lexicalScopeNode, isGlobalDeclaration);
+                return;
+            } catch (error) {
+                if (i < sourceTypeLength - 1) {
+                    continue;
+                }
 
-        // check for cached identifiers for current scope node. If exist - loop through them.
-        if (this.replaceableIdentifiers.has(scopeNode)) {
-            this.replaceScopeCachedIdentifiers(variableDeclarationNode, lexicalScopeNode, scopeNode);
-        } else {
-            this.replaceScopeIdentifiers(scopeNode, lexicalScopeNode);
+                throw new Error(error);
+            }
         }
 
-        return variableDeclarationNode;
+        throw new Error(`Scope analyzing error`);
     }
 
     /**
-     * @param {VariableDeclaration} variableDeclarationNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     * @param {boolean} isGlobalDeclaration
+     * @param {VariableDeclaration} programNode
+     * @param {NodeGuards} parentNode
+     * @returns {NodeGuards}
      */
-    private storeVariableNames (
-        variableDeclarationNode: ESTree.VariableDeclaration,
-        lexicalScopeNode: TNodeWithLexicalScope,
-        isGlobalDeclaration: boolean
-    ): void {
-        this.traverseDeclarationIdentifiers(
-            variableDeclarationNode,
-            (identifierNode: ESTree.Identifier) => {
-                if (
-                    this.isProhibitedVariableName(
-                        identifierNode,
-                        lexicalScopeNode,
-                        variableDeclarationNode
-                    )
-                ) {
+    public transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node {
+        if (!this.scopeManager) {
+            return programNode;
+        }
+
+        const scope: eslintScope.Scope | null = this.scopeManager.acquire(
+            programNode,
+            true
+        );
+
+        if (scope) {
+            this.processScope(scope);
+        }
+
+        return programNode;
+    }
+
+    private processScope(scope: eslintScope.Scope): void {
+        scope?.variables.forEach((variable: eslintScope.Variable) => {
+            if (variable.name === 'arguments') {
+                return;
+            }
+
+            const scope: eslintScope.Scope = (<any>variable).scope;
+            const variableScope: eslintScope.Scope = (<any>variable).scope.variableScope;
+            const isGlobalScope: boolean = variableScope.type === 'global'
+                || variableScope.type === 'module';
+
+            if (!this.options.renameGlobals && isGlobalScope) {
+                const isImportBindingIdentifier: boolean = variable.defs.every((definition) => definition.type === 'ImportBinding');
+                const isCatchClauseIdentifier: boolean = variable.defs.every((definition) => definition.type === 'CatchClause');
+
+                // prevent renaming of import statement and catch clause global identifiers
+                if (!isImportBindingIdentifier && !isCatchClauseIdentifier) {
+                    return;
+                }
+            }
+
+            variable.identifiers.forEach((identifier: ESTree.Identifier) => {
+                if (NodeMetadata.isIgnoredNode(identifier)) {
+                    return;
+                }
+
+                if (this.isReservedName(identifier.name)) {
                     return;
                 }
 
-                if (isGlobalDeclaration) {
-                    this.identifierObfuscatingReplacer.storeGlobalName(identifierNode, lexicalScopeNode);
+                if (identifier.parentNode && !NodeGuards.isReplaceableIdentifierNode(identifier, identifier.parentNode)) {
+                    return;
+                }
+
+                if (identifier.parentNode && VariableDeclarationTransformer.isProhibitedImportSpecifierNode(identifier.parentNode)) {
+                    return;
+                }
+
+                if (this.isProhibitedVariableNameUsedInObjectPatternNode(variable, identifier, <any>scope.variableScope.block)) {
+                    return;
+                }
+
+                let identifierName: string;
+
+                if (
+                    // prevent class name renaming twice for outer scope and for class scope
+                    scope.type === 'class'
+                    && identifier.parentNode
+                    && NodeGuards.isClassDeclarationNode(identifier.parentNode)
+                    && identifier.parentNode.id === identifier
+                ) {
+                    // keep class declaration name
+                    identifierName = identifier.name;
                 } else {
-                    this.identifierObfuscatingReplacer.storeLocalName(identifierNode, lexicalScopeNode);
+                    isGlobalScope
+                        ? this.identifierObfuscatingReplacer.storeGlobalName(
+                            identifier,
+                            <TNodeWithLexicalScope>variableScope.block
+                        )
+                        : this.identifierObfuscatingReplacer.storeLocalName(
+                            identifier,
+                            <TNodeWithLexicalScope>variableScope.block
+                        );
+
+                    const newIdentifierNode: ESTree.Identifier = this.identifierObfuscatingReplacer.replace(
+                        identifier,
+                        <TNodeWithLexicalScope>variableScope.block
+                    );
+
+                    identifier.name = newIdentifierNode.name;
+                    identifierName = identifier.name;
                 }
-            }
-        );
-    }
 
-    /**
-     * @param {Identifier} identifierNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     * @param {VariableDeclaration} hostVariableDeclarationNode
-     * @returns {boolean}
-     */
-    private isProhibitedVariableName (
-        identifierNode: ESTree.Identifier,
-        lexicalScopeNode: TNodeWithLexicalScope,
-        hostVariableDeclarationNode: ESTree.VariableDeclaration
-    ): boolean {
-        return this.isProhibitedVariableNameUsedInObjectPatternNode(
-            identifierNode,
-            lexicalScopeNode,
-            hostVariableDeclarationNode
-        );
+                // rename of function default parameter identifiers
+                (<any>variable).scope.block.defaults?.forEach((node: ESTree.Node) => {
+                    if (NodeGuards.isIdentifierNode(node) && node.name === variable.name) {
+                        node.name = identifierName;
+                    }
+                });
+
+                // rename of references
+                variable.references.forEach((reference: eslintScope.Reference) => {
+                    reference.identifier.name = identifierName;
+                });
+            });
+        });
+
+        for (const childScope of scope.childScopes) {
+            this.processScope(childScope);
+        }
     }
 
     /**
@@ -191,12 +274,13 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
      * @returns {boolean}
      */
     private isProhibitedVariableNameUsedInObjectPatternNode (
+        variable: eslintScope.Variable,
         identifierNode: ESTree.Identifier,
-        lexicalScopeNode: TNodeWithLexicalScope,
-        hostVariableDeclarationNode: ESTree.VariableDeclaration
+        lexicalScopeNode: TNodeWithLexicalScope
     ): boolean {
-        // should transform variable declarations that cannot be reassigned
-        if (hostVariableDeclarationNode.kind === 'const') {
+        const hasVarDefinitions: boolean = variable.defs.some((definition) => (<any>definition).kind === 'var');
+
+        if (!hasVarDefinitions) {
             return false;
         }
 
@@ -251,109 +335,17 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {VariableDeclaration} variableDeclarationNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     * @param {Node} scopeNode
-     */
-    private replaceScopeCachedIdentifiers (
-        variableDeclarationNode: ESTree.VariableDeclaration,
-        lexicalScopeNode: TNodeWithLexicalScope,
-        scopeNode: ESTree.Node
-    ): void {
-        const cachedReplaceableIdentifiersNamesMap: TReplaceableIdentifiersNames =
-            <TReplaceableIdentifiersNames>this.replaceableIdentifiers.get(scopeNode);
-
-        const identifierNames: string[] = [];
-
-        this.traverseDeclarationIdentifiers(variableDeclarationNode, (identifierNode: ESTree.Identifier) => {
-            identifierNames.push(identifierNode.name);
-        });
-
-        identifierNames.forEach((identifierName: string) => {
-            const cachedReplaceableIdentifiers: ESTree.Identifier[] | undefined =
-                cachedReplaceableIdentifiersNamesMap.get(identifierName);
-
-            if (!cachedReplaceableIdentifiers) {
-                return;
-            }
-
-            const cachedReplaceableIdentifierLength: number = cachedReplaceableIdentifiers.length;
-
-            for (let i: number = 0; i < cachedReplaceableIdentifierLength; i++) {
-                const replaceableIdentifier: ESTree.Identifier = cachedReplaceableIdentifiers[i];
-
-                if (identifierName !== replaceableIdentifier.name) {
-                    continue;
-                }
-
-                const newReplaceableIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                    .replace(replaceableIdentifier, lexicalScopeNode);
-
-                replaceableIdentifier.name = newReplaceableIdentifier.name;
-                NodeMetadata.set(replaceableIdentifier, { renamedIdentifier: true });
-            }
-        });
-    }
-
-    /**
-     * @param {Node} scopeNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     */
-    private replaceScopeIdentifiers (scopeNode: ESTree.Node, lexicalScopeNode: TNodeWithLexicalScope): void {
-        const storedReplaceableIdentifiersNamesMap: TReplaceableIdentifiersNames = new Map();
-
-        estraverse.replace(scopeNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): void => {
-                if (
-                    parentNode
-                    && NodeGuards.isReplaceableIdentifierNode(node, parentNode)
-                    && !NodeMetadata.isRenamedIdentifier(node)
-                ) {
-                    const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node, lexicalScopeNode);
-                    const newIdentifierName: string = newIdentifier.name;
-
-                    if (node.name !== newIdentifierName) {
-                        node.name = newIdentifierName;
-                        NodeMetadata.set(node, { renamedIdentifier: true });
-                    } else {
-                        const storedReplaceableIdentifiers: ESTree.Identifier[] =
-                            storedReplaceableIdentifiersNamesMap.get(node.name) || [];
-
-                        storedReplaceableIdentifiers.push(node);
-                        storedReplaceableIdentifiersNamesMap.set(node.name, storedReplaceableIdentifiers);
-                    }
-                }
-            }
-        });
-
-        this.replaceableIdentifiers.set(scopeNode, storedReplaceableIdentifiersNamesMap);
-    }
-
-    /**
-     * @param {VariableDeclaration} variableDeclarationNode
-     * @param {(identifier: Identifier) => void} callback
+     * @param {string} name
+     * @returns {boolean}
      */
-    private traverseDeclarationIdentifiers (
-        variableDeclarationNode: ESTree.VariableDeclaration,
-        callback: (
-            identifier: ESTree.Identifier,
-            variableDeclarator: ESTree.VariableDeclarator
-        ) => void
-    ): void {
-        variableDeclarationNode.declarations
-            .forEach((variableDeclaratorNode: ESTree.VariableDeclarator) => {
-                estraverse.traverse(variableDeclaratorNode.id, {
-                    enter: (node: ESTree.Node) => {
-                        if (NodeGuards.isPropertyNode(node)) {
-                            return estraverse.VisitorOption.Skip;
-                        }
+    private isReservedName (name: string): boolean {
+        if (!this.options.reservedNames.length) {
+            return false;
+        }
 
-                        if (NodeGuards.isIdentifierNode(node)) {
-                            callback(node, variableDeclaratorNode);
-                        }
-                    }
-                });
+        return this.options.reservedNames
+            .some((reservedName: string) => {
+                return new RegExp(reservedName, 'g').exec(name) !== null;
             });
     }
 }

+ 51 - 1
src/node/NodeGuards.ts

@@ -123,6 +123,53 @@ export class NodeGuards {
         return node.type === NodeType.ExportNamedDeclaration;
     }
 
+    /**
+     * @param {Identifier} identifierNode
+     * @param {Node} parentNode
+     * @returns {identifierNode is Identifier}
+     */
+    public static isExportNamedClassDeclarationIdentifierNode (
+        identifierNode: ESTree.Identifier,
+        parentNode: ESTree.Node
+    ): identifierNode is ESTree.Identifier {
+        return NodeGuards.isClassDeclarationNode(parentNode)
+            && parentNode.id === identifierNode
+            && !!parentNode.parentNode
+            && NodeGuards.isExportNamedDeclarationNode(parentNode.parentNode);
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @param {Node} parentNode
+     * @returns {identifierNode is Identifier}
+     */
+    public static isExportNamedFunctionDeclarationIdentifierNode (
+        identifierNode: ESTree.Identifier,
+        parentNode: ESTree.Node
+    ): identifierNode is ESTree.Identifier {
+        return NodeGuards.isFunctionDeclarationNode(parentNode)
+            && parentNode.id === identifierNode
+            && !!parentNode.parentNode
+            && NodeGuards.isExportNamedDeclarationNode(parentNode.parentNode);
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @param {Node} parentNode
+     * @returns {identifierNode is Identifier}
+     */
+    public static isExportNamedVariableDeclarationIdentifierNode (
+        identifierNode: ESTree.Identifier,
+        parentNode: ESTree.Node
+    ): identifierNode is ESTree.Identifier {
+        return NodeGuards.isVariableDeclaratorNode(parentNode)
+            && parentNode.id === identifierNode
+            && !!parentNode.parentNode
+            && NodeGuards.isVariableDeclarationNode(parentNode.parentNode)
+            && !!parentNode.parentNode.parentNode
+            && NodeGuards.isExportNamedDeclarationNode(parentNode.parentNode.parentNode);
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
@@ -404,7 +451,10 @@ export class NodeGuards {
             && !NodeGuards.parentNodeIsPropertyNode(node, parentNode)
             && !NodeGuards.parentNodeIsMemberExpressionNode(node, parentNode)
             && !NodeGuards.parentNodeIsMethodDefinitionNode(node, parentNode)
-            && !NodeGuards.isLabelIdentifierNode(node, parentNode);
+            && !NodeGuards.isLabelIdentifierNode(node, parentNode)
+            && !NodeGuards.isExportNamedClassDeclarationIdentifierNode(node, parentNode)
+            && !NodeGuards.isExportNamedFunctionDeclarationIdentifierNode(node, parentNode)
+            && !NodeGuards.isExportNamedVariableDeclarationIdentifierNode(node, parentNode);
     }
 
     /**

+ 5 - 9
test/dev/dev.ts

@@ -7,18 +7,14 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            var foo = 'foo';
-            var bar = 'bar';
-            var baz = 'baz';
-            var bark = 'bark';
-            
-            console.log(foo, bar, baz, bark);
+            function foo (label) {
+                label:
+                    var s = 1;
+            }
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
-            stringArray: true,
-            stringArrayThreshold: 1,
-            shuffleStringArray: true
+            compact: false
         }
     ).getObfuscatedCode();
 

+ 1 - 1
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec.ts

@@ -99,7 +99,7 @@ describe('ObjectExpressionKeysTransformer', () => {
                 `${variableMatch}\\['foo'] *= *'bar';` +
                 `${variableMatch}\\['baz'] *= *'bark';` +
                 `var *${variableMatch} *= *\\[${variableMatch}];` +
-                ``;
+            ``;
             const regExp: RegExp = new RegExp(match);
 
             let obfuscatedCode: string;

+ 5 - 0
test/functional-tests/node-transformers/obfuscating-transformers/class-declaration-transformer/ClassDeclarationTransformer.spec.ts

@@ -123,6 +123,7 @@ describe('ClassDeclarationTransformer', () => {
         describe('Variant #3: already renamed identifiers shouldn\'t be renamed twice', () => {
             const classDeclarationRegExp: RegExp = /class *d *{/;
             const variableDeclarationsRegExp: RegExp = /let *e, *f, *g, *h;/;
+            const classReferenceRegExp: RegExp = /new d\(\);/;
 
             let obfuscatedCode: string;
 
@@ -145,6 +146,10 @@ describe('ClassDeclarationTransformer', () => {
             it('Match #2: should correctly rename variable declarations', () => {
                 assert.match(obfuscatedCode, variableDeclarationsRegExp);
             });
+
+            it('Match #3: should correctly rename class reference identifier', () => {
+                assert.match(obfuscatedCode, classReferenceRegExp);
+            });
         });
 
         describe('Variant #5: named export', () => {

+ 1 - 0
test/functional-tests/node-transformers/obfuscating-transformers/class-declaration-transformer/fixtures/prevent-renaming-of-renamed-identifiers.js

@@ -2,5 +2,6 @@
     function test() {
         class inner {}
         let a, b, c, d;
+        new inner();
     }
 })();

+ 44 - 26
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts

@@ -11,11 +11,15 @@ describe('FunctionTransformer', () => {
     describe('identifiers transformation inside `FunctionDeclaration` and `FunctionExpression` node body', () => {
         const functionParamIdentifierRegExp: RegExp = /var _0x[a-f0-9]{4,6} *= *function *\((_0x[a-f0-9]{4,6})\) *\{/;
         const functionBodyIdentifierRegExp: RegExp = /console\['log'\]\((_0x[a-f0-9]{4,6})\)/;
-        const variableRegExp: RegExp = /variable *= *0x6;/;
+        const variableDeclarationRegExp: RegExp = /var (_0x[a-f0-9]{4,6}) *= *0x5;/;
+        const variableReferenceRegExp: RegExp = /variable *= *0x6;/;
+        const returnStatementIdentifierRegExp: RegExp = /return (_0x[a-f0-9]{4,6});/;
 
         let obfuscatedCode: string,
             functionParamIdentifierName: string,
-            functionBodyIdentifierName: string;
+            functionBodyIdentifierName: string,
+            variableDeclarationIdentifierName: string,
+            returnStatementIdentifierName: string;
 
         before(() => {
             const code: string = readFileAsString(__dirname + '/fixtures/input.js');
@@ -31,29 +35,43 @@ describe('FunctionTransformer', () => {
                 .match(functionParamIdentifierRegExp);
             const functionBodyIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
                 .match(functionBodyIdentifierRegExp);
+            const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(variableDeclarationRegExp);
+            const returnStatementIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(returnStatementIdentifierRegExp);
 
             functionParamIdentifierName = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
             functionBodyIdentifierName = (<RegExpMatchArray>functionBodyIdentifierMatch)[1];
+            variableDeclarationIdentifierName = (<RegExpMatchArray>variableDeclarationIdentifierMatch)[1];
+            returnStatementIdentifierName = (<RegExpMatchArray>returnStatementIdentifierMatch)[1];
         });
 
-        it('should correctly transform both function parameter identifier and function body identifier with same name', () => {
-            assert.equal(functionParamIdentifierName, functionBodyIdentifierName);
+        it('should generate different names for function parameter identifier and function body identifier with same name', () => {
+            assert.notEqual(functionParamIdentifierName, functionBodyIdentifierName);
+        });
+
+        it('should generate different names for function parameter identifier and variable declaration identifier with same name', () => {
+            assert.notEqual(functionParamIdentifierName, variableDeclarationIdentifierName);
+        });
+
+        it('should correctly transform both variable declaration identifier and return statement identifier with same name', () => {
+            assert.equal(variableDeclarationIdentifierName, returnStatementIdentifierName);
         });
 
         it('shouldn\'t transform other variables in function body', () => {
-            assert.match(obfuscatedCode, variableRegExp);
+            assert.match(obfuscatedCode, variableReferenceRegExp);
         });
     });
 
     describe('function id name obfuscation', () => {
         describe('Variant #1', () => {
             const functionExpressionParamIdentifierRegExp: RegExp = /\(function *\((_0x[a-f0-9]{4,6})\) *\{/;
-            const functionParamIdentifierRegExp: RegExp = /function *(_0x[a-f0-9]{4,6}) *\(\) *\{/;
+            const innerFunctionNameIdentifierRegExp: RegExp = /function *(_0x[a-f0-9]{4,6}) *\(\) *\{/;
             const functionObjectIdentifierRegExp: RegExp = /return new (_0x[a-f0-9]{4,6}) *\(\);/;
 
             let obfuscatedCode: string,
                 functionExpressionParamIdentifierName: string,
-                functionParamIdentifierName: string,
+                innerFunctionNameIdentifierName: string,
                 functionObjectIdentifierName: string;
 
             before(() => {
@@ -68,13 +86,13 @@ describe('FunctionTransformer', () => {
 
                 const functionExpressionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
                     .match(functionExpressionParamIdentifierRegExp);
-                const functionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                    .match(functionParamIdentifierRegExp);
+                const innerFunctionNameIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                    .match(innerFunctionNameIdentifierRegExp);
                 const functionObjectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
                     .match(functionObjectIdentifierRegExp);
 
-                functionParamIdentifierName = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
                 functionExpressionParamIdentifierName = (<RegExpMatchArray>functionExpressionParamIdentifierMatch)[1];
+                innerFunctionNameIdentifierName = (<RegExpMatchArray>innerFunctionNameIdentifierMatch)[1];
                 functionObjectIdentifierName = (<RegExpMatchArray>functionObjectIdentifierMatch)[1];
             });
 
@@ -83,7 +101,7 @@ describe('FunctionTransformer', () => {
             });
 
             it('should correctly transform function parameter identifier', () => {
-                assert.match(obfuscatedCode, functionParamIdentifierRegExp);
+                assert.match(obfuscatedCode, innerFunctionNameIdentifierRegExp);
             });
 
             it('should correctly transform function object parameter identifier', () => {
@@ -91,15 +109,15 @@ describe('FunctionTransformer', () => {
             });
 
             it('should generate same names for function parameter and function object identifiers', () => {
-                assert.equal(functionParamIdentifierName, functionObjectIdentifierName);
+                assert.equal(innerFunctionNameIdentifierName, functionObjectIdentifierName);
             });
 
-            it('should generate same names for function parameter identifiers', () => {
-                assert.equal(functionExpressionParamIdentifierName, functionParamIdentifierName);
+            it('shouldn\'t generate same names for function parameter identifiers', () => {
+                assert.notEqual(functionExpressionParamIdentifierName, innerFunctionNameIdentifierName);
             });
 
-            it('should generate same names for function expression parameter and function object identifiers', () => {
-                assert.equal(functionExpressionParamIdentifierName, functionObjectIdentifierName);
+            it('shouldn\'t generate same names for function expression parameter and function object identifiers', () => {
+                assert.notEqual(functionExpressionParamIdentifierName, functionObjectIdentifierName);
             });
         });
 
@@ -144,8 +162,8 @@ describe('FunctionTransformer', () => {
                 assert.equal(functionIdentifierName, functionObjectIdentifierName);
             });
 
-            it('should generate same names for function id and parameter identifiers', () => {
-                assert.equal(functionIdentifierName, functionParamIdentifierName);
+            it('should\'t generate same names for function id and parameter identifiers', () => {
+                assert.notEqual(functionIdentifierName, functionParamIdentifierName);
             });
         });
 
@@ -243,12 +261,12 @@ describe('FunctionTransformer', () => {
                     assert.equal(functionIdentifierName, functionObjectIdentifierName);
                 });
 
-                it('should generate same names for function parameter and function object identifiers', () => {
-                    assert.equal(functionParamIdentifierName, functionObjectIdentifierName);
+                it('shouldn\'t generate same names for function parameter and function object identifiers', () => {
+                    assert.notEqual(functionParamIdentifierName, functionObjectIdentifierName);
                 });
 
-                it('should generate same names for function id and parameter identifiers', () => {
-                    assert.equal(functionIdentifierName, functionParamIdentifierName);
+                it('shouldn\'t generate same names for function id and parameter identifiers', () => {
+                    assert.notEqual(functionIdentifierName, functionParamIdentifierName);
                 });
             });
         });
@@ -508,16 +526,16 @@ describe('FunctionTransformer', () => {
                 assert.match(obfuscatedCode, functionBodyRegExp);
             });
 
-            it('equal #1: should keep same names for variable declaration identifier and function parameters identifiers', () => {
-                assert.equal(variableDeclarationIdentifierName, functionParameterIdentifierName);
+            it('equal #1:shouldn\'t keep same names for variable declaration identifier and function parameters identifiers', () => {
+                assert.notEqual(variableDeclarationIdentifierName, functionParameterIdentifierName);
             });
 
             it('equal #2: shouldn\'t keep same names for variable declaration identifier and function parameters identifiers', () => {
                 assert.notEqual(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName1);
             });
 
-            it('equal #3: should keep same names for variable declaration identifier and function parameters identifiers', () => {
-                assert.equal(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName2);
+            it('equal #3: shouldn\'t keep same names for variable declaration identifier and function parameters identifiers', () => {
+                assert.notEqual(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName2);
             });
 
             it('equal #4: should keep same names for identifier in first function parameter and default value identifier of second function parameter', () => {

+ 17 - 17
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts

@@ -189,28 +189,28 @@ describe('VariableDeclarationTransformer', () => {
             variableDeclarationIdentifierName = getRegExpMatch(obfuscatedCode, variableDeclarationIdentifierRegExp);
         });
 
-        it('equal #1: should generate same names for identifiers inside inner function and outer function params', () => {
-            assert.equal(outerFunctionParamIdentifierName, consoleLogIdentifierName);
+        it('equal #1: should generate different names for identifiers inside inner function and outer function params', () => {
+            assert.notEqual(outerFunctionParamIdentifierName, consoleLogIdentifierName);
         });
 
-        it('equal #2: should generate same names for identifiers inside inner function and inner function params', () => {
-            assert.equal(outerFunctionParamIdentifierName, innerFunctionParamIdentifierName);
+        it('equal #2: should generate same names for identifiers of outer function params and inner function params', () => {
+            assert.notEqual(outerFunctionParamIdentifierName, innerFunctionParamIdentifierName);
         });
 
         it('equal #1: should correct transform variables inside outer function body', () => {
-            assert.equal(outerFunctionParamIdentifierName, objectIdentifierName);
+            assert.notEqual(outerFunctionParamIdentifierName, objectIdentifierName);
         });
 
         it('equal #2: should correct transform variables inside outer function body', () => {
-            assert.equal(outerFunctionParamIdentifierName, variableDeclarationIdentifierName);
+            assert.notEqual(outerFunctionParamIdentifierName, variableDeclarationIdentifierName);
         });
 
-        it('should correct transform variables inside inner function body', () => {
-            assert.equal(innerFunctionParamIdentifierName, consoleLogIdentifierName);
+        it('equal #3: should correct transform variables inside outer function body', () => {
+            assert.equal(variableDeclarationIdentifierName, objectIdentifierName);
         });
 
-        it('should keep equal names after transformation for variables with same names', () => {
-            assert.equal(variableDeclarationIdentifierName, objectIdentifierName);
+        it('should correct transform variables inside inner function body', () => {
+            assert.equal(innerFunctionParamIdentifierName, consoleLogIdentifierName);
         });
     });
 
@@ -244,20 +244,20 @@ describe('VariableDeclarationTransformer', () => {
             variableDeclarationIdentifierName = getRegExpMatch(obfuscatedCode, variableDeclarationIdentifierRegExp);
         });
 
-        it('match #1: should generate same names for identifiers inside inner function and catch clause param', () => {
-            assert.equal(catchClauseParamIdentifierName, consoleLogIdentifierName);
+        it('match #1: should generate different names for identifiers inside inner function and catch clause param', () => {
+            assert.notEqual(catchClauseParamIdentifierName, consoleLogIdentifierName);
         });
 
-        it('match #2: should generate same names for identifiers inside inner function and catch clause param', () => {
-            assert.equal(catchClauseParamIdentifierName, innerFunctionParamIdentifierName);
+        it('match #2: should generate different names for identifiers inside inner function and catch clause param', () => {
+            assert.notEqual(catchClauseParamIdentifierName, innerFunctionParamIdentifierName);
         });
 
         it('equal #1: should correct transform variables inside catch clause body', () => {
-            assert.equal(catchClauseParamIdentifierName, objectIdentifierName);
+            assert.notEqual(catchClauseParamIdentifierName, objectIdentifierName);
         });
 
         it('equal #2: should correct transform variables inside catch clause body', () => {
-            assert.equal(catchClauseParamIdentifierName, variableDeclarationIdentifierName);
+            assert.notEqual(catchClauseParamIdentifierName, variableDeclarationIdentifierName);
         });
 
         it('should correct transform variables inside inner function body', () => {
@@ -471,7 +471,7 @@ describe('VariableDeclarationTransformer', () => {
             const variableDeclarationRegExp1: RegExp = /var *d *= *0x1;/;
             const variableDeclarationRegExp2: RegExp = /var *e;/;
             const functionDeclarationRegExp: RegExp = /function *f *\(\) *{/;
-            const variableDeclarationRegExp3: RegExp = /var *f *= *function *\(\) *{}/;
+            const variableDeclarationRegExp3: RegExp = /var *g *= *function *\(\) *{}/;
 
             let obfuscatedCode: string;
 

+ 1 - 1
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/variable-call-before-variable-declaration-2.js

@@ -9,4 +9,4 @@
             var t;
         }();
     }
-})();
+})();o

+ 29 - 0
yarn.lock

@@ -196,6 +196,22 @@
   resolved "https://registry.yarnpkg.com/@types/escodegen/-/escodegen-0.0.6.tgz#5230a9ce796e042cda6f086dbf19f22ea330659c"
   integrity sha1-UjCpznluBCzabwhtvxnyLqMwZZw=
 
+"@types/eslint-scope@^3.7.0":
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86"
+  integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==
+  dependencies:
+    "@types/eslint" "*"
+    "@types/estree" "*"
+
+"@types/eslint@*":
+  version "6.1.3"
+  resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-6.1.3.tgz#ec2a66e445a48efaa234020eb3b6e8f06afc9c61"
+  integrity sha512-llYf1QNZaDweXtA7uY6JczcwHmFwJL9TpK3E6sY0B18l6ulDT6VWNMAdEjYccFHiDfxLPxffd8QmSDV4QUUspA==
+  dependencies:
+    "@types/estree" "*"
+    "@types/json-schema" "*"
+
 "@types/[email protected]":
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/@types/estraverse/-/estraverse-0.0.6.tgz#669f7cdf72ab797e6125f8d00fed33d4cf30c221"
@@ -222,6 +238,11 @@
     "@types/minimatch" "*"
     "@types/node" "*"
 
+"@types/json-schema@*":
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
+  integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
+
 "@types/[email protected]":
   version "2.1.33"
   resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.1.33.tgz#8c8dba30df4ad0e92296424f08c4898dd808e8df"
@@ -1597,6 +1618,14 @@ eslint-scope@^4.0.3:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
+eslint-scope@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
+  integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
+  dependencies:
+    esrecurse "^4.1.0"
+    estraverse "^4.1.1"
+
 esm@^3.2.25:
   version "3.2.25"
   resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"

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