Sfoglia il codice sorgente

Literal replacer refactoring

sanex3339 5 anni fa
parent
commit
b764844696
33 ha cambiato i file con 529 aggiunte e 252 eliminazioni
  1. 0 0
      dist/index.browser.js
  2. 0 0
      dist/index.cli.js
  3. 0 0
      dist/index.js
  4. 115 0
      src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts
  5. 1 0
      src/container/ServiceIdentifiers.ts
  6. 7 0
      src/container/modules/analyzers/AnalyzersModule.ts
  7. 1 1
      src/custom-nodes/string-array-nodes/StringArrayNode.ts
  8. 1 1
      src/interfaces/IEncodedValue.d.ts
  9. 17 0
      src/interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer.d.ts
  10. 4 4
      src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IIdentifierObfuscatingReplacer.d.ts
  11. 6 2
      src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IObfuscatingReplacer.d.ts
  12. 0 4
      src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IStringArrayIndexData.d.ts
  13. 7 1
      src/interfaces/storages/IMapStorage.d.ts
  14. 6 0
      src/interfaces/storages/string-array-storage/IStringArrayStorageItem.d.ts
  15. 2 2
      src/node-transformers/obfuscating-transformers/CatchClauseTransformer.ts
  16. 4 4
      src/node-transformers/obfuscating-transformers/ClassDeclarationTransformer.ts
  17. 4 4
      src/node-transformers/obfuscating-transformers/FunctionDeclarationTransformer.ts
  18. 3 3
      src/node-transformers/obfuscating-transformers/FunctionTransformer.ts
  19. 3 3
      src/node-transformers/obfuscating-transformers/ImportDeclarationTransformer.ts
  20. 2 2
      src/node-transformers/obfuscating-transformers/LabeledStatementTransformer.ts
  21. 21 4
      src/node-transformers/obfuscating-transformers/LiteralTransformer.ts
  22. 4 4
      src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts
  23. 2 2
      src/node-transformers/obfuscating-transformers/obfuscating-replacers/AbstractObfuscatingReplacer.ts
  24. 21 15
      src/node-transformers/obfuscating-transformers/obfuscating-replacers/identifier-obfuscating-replacers/BaseIdentifierObfuscatingReplacer.ts
  25. 9 3
      src/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/BooleanLiteralObfuscatingReplacer.ts
  26. 15 9
      src/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/NumberLiteralObfuscatingReplacer.ts
  27. 36 148
      src/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/StringLiteralObfuscatingReplacer.ts
  28. 10 2
      src/storages/MapStorage.ts
  29. 126 12
      src/storages/string-array/StringArrayStorage.ts
  30. 4 2
      src/types/storages/TStringArrayStorage.d.ts
  31. 4 3
      test/dev/dev.ts
  32. 57 15
      test/functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec.ts
  33. 37 2
      test/unit-tests/storages/MapStorage.spec.ts

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


+ 115 - 0
src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts

@@ -0,0 +1,115 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TStringArrayStorage } from '../../types/storages/TStringArrayStorage';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
+import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
+
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeMetadata } from '../../node/NodeMetadata';
+
+/**
+ * Adds values of literal nodes to the string array storage
+ */
+@injectable()
+export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
+    /**
+     * @type {number}
+     */
+    private static readonly minimumLengthForStringArray: number = 3;
+
+    /**
+     * @type {IOptions}
+     */
+    private readonly options: IOptions;
+
+    /**
+     * @type {randomGenerator}
+     */
+    private readonly randomGenerator: IRandomGenerator;
+
+    /**
+     * @type {TStringArrayStorage}
+     */
+    private readonly stringArrayStorage: TStringArrayStorage;
+
+    /**
+     * @type {Map<ESTree.Literal, IStringArrayStorageItemData>}
+     */
+    private readonly stringArrayStorageData: Map<ESTree.Literal, IStringArrayStorageItemData> = new Map();
+
+    /**
+     * @param {TStringArrayStorage} stringArrayStorage
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.TStringArrayStorage) stringArrayStorage: TStringArrayStorage,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+    ) {
+        this.stringArrayStorage = stringArrayStorage;
+        this.randomGenerator = randomGenerator;
+        this.options = options;
+    }
+
+    /**
+     * @param {Program} astTree
+     */
+    public analyze (astTree: ESTree.Program): void {
+        estraverse.traverse(astTree, {
+            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
+                if (NodeMetadata.isIgnoredNode(node)) {
+                    return estraverse.VisitorOption.Skip;
+                }
+
+                if (!NodeGuards.isLiteralNode(node)) {
+                    return;
+                }
+
+                this.analyzeLiteralNode(node);
+            }
+        });
+    }
+
+    /**
+     * @param {Literal} literalNode
+     * @returns {IStringArrayStorageItemData | undefined}
+     */
+    public getItemDataForLiteralNode (literalNode: ESTree.Literal): IStringArrayStorageItemData | undefined {
+        return this.stringArrayStorageData.get(literalNode);
+    }
+
+    /**
+     * @param {Literal} literalNode
+     */
+    private analyzeLiteralNode (literalNode: ESTree.Literal): void {
+        if (typeof literalNode.value !== 'string') {
+            return;
+        }
+
+        if (!this.shouldAddValueToStringArray(literalNode.value)) {
+            return;
+        }
+
+        this.stringArrayStorageData.set(
+            literalNode,
+            this.stringArrayStorage.getOrThrow(literalNode.value)
+        );
+    }
+
+    /**
+     * @param {string} value
+     * @returns {boolean}
+     */
+    private shouldAddValueToStringArray (value: string): boolean {
+        return value.length >= StringArrayStorageAnalyzer.minimumLengthForStringArray
+            && this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold;
+    }
+}

+ 1 - 0
src/container/ServiceIdentifiers.ts

@@ -41,6 +41,7 @@ export enum ServiceIdentifiers {
     IRandomGenerator = 'IRandomGenerator',
     ISourceCode = 'ISourceCode',
     ISourceMapCorrector = 'ISourceMapCorrector',
+    IStringArrayStorageAnalyzer = 'IStringArrayStorageAnalyzer',
     ITransformersRunner = 'ITransformersRunner',
     Newable__ICustomNode = 'Newable<ICustomNode>',
     Newable__TControlFlowStorage = 'Newable<TControlFlowStorage>',

+ 7 - 0
src/container/modules/analyzers/AnalyzersModule.ts

@@ -5,6 +5,7 @@ import { ServiceIdentifiers } from '../../ServiceIdentifiers';
 import { ICalleeDataExtractor } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeDataExtractor';
 import { ICallsGraphAnalyzer } from '../../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphAnalyzer';
 import { IPrevailingKindOfVariablesAnalyzer } from '../../../interfaces/analyzers/calls-graph-analyzer/IPrevailingKindOfVariablesAnalyzer';
+import { IStringArrayStorageAnalyzer } from '../../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 
 import { CalleeDataExtractor } from '../../../enums/analyzers/calls-graph-analyzer/CalleeDataExtractor';
 import { CallsGraphAnalyzer } from '../../../analyzers/calls-graph-analyzer/CallsGraphAnalyzer';
@@ -12,6 +13,7 @@ import { FunctionDeclarationCalleeDataExtractor } from '../../../analyzers/calls
 import { FunctionExpressionCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor';
 import { ObjectExpressionCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor';
 import { PrevailingKindOfVariablesAnalyzer } from '../../../analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer';
+import { StringArrayStorageAnalyzer } from '../../../analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer';
 
 export const analyzersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // calls graph analyzer
@@ -24,6 +26,11 @@ export const analyzersModule: interfaces.ContainerModule = new ContainerModule((
         .to(PrevailingKindOfVariablesAnalyzer)
         .inSingletonScope();
 
+    // string array storage analyzer
+    bind<IStringArrayStorageAnalyzer>(ServiceIdentifiers.IStringArrayStorageAnalyzer)
+        .to(StringArrayStorageAnalyzer)
+        .inSingletonScope();
+
     // callee data extractors
     bind<ICalleeDataExtractor>(ServiceIdentifiers.ICalleeDataExtractor)
         .to(FunctionDeclarationCalleeDataExtractor)

+ 1 - 1
src/custom-nodes/string-array-nodes/StringArrayNode.ts

@@ -72,7 +72,7 @@ export class StringArrayNode extends AbstractCustomNode {
      * @returns {TStatement[]}
      */
     public getNode (): TStatement[] {
-        (<StringArrayStorage>this.stringArrayStorage).rotateArray(this.stringArrayRotateValue);
+        (<StringArrayStorage>this.stringArrayStorage).rotateStorage(this.stringArrayRotateValue);
 
         return super.getNode();
     }

+ 1 - 1
src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IEncodedValue.d.ts → src/interfaces/IEncodedValue.d.ts

@@ -1,4 +1,4 @@
 export interface IEncodedValue {
     encodedValue: string;
-    key: string | null;
+    decodeKey: string | null;
 }

+ 17 - 0
src/interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer.d.ts

@@ -0,0 +1,17 @@
+import * as ESTree from 'estree';
+
+import { IAnalyzer } from '../IAnalyzer';
+import { IStringArrayStorageItemData } from '../../storages/string-array-storage/IStringArrayStorageItem';
+
+export interface IStringArrayStorageAnalyzer extends IAnalyzer<void> {
+    /**
+     * @param {Program} astTree
+     */
+    analyze (astTree: ESTree.Program): void;
+
+    /**
+     * @param {Literal} literalNode
+     * @returns {IStringArrayStorageItemData | undefined}
+     */
+    getItemDataForLiteralNode (literalNode: ESTree.Literal): IStringArrayStorageItemData | undefined;
+}

+ 4 - 4
src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IIdentifierObfuscatingReplacer.d.ts

@@ -6,16 +6,16 @@ import { IObfuscatingReplacer } from './IObfuscatingReplacer';
 
 export interface IIdentifierObfuscatingReplacer extends IObfuscatingReplacer <ESTree.Identifier> {
     /**
-     * @param {string} nodeValue
+     * @param {Identifier} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
-    storeGlobalName (nodeValue: string, lexicalScopeNode: TNodeWithLexicalScope): void;
+    storeGlobalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void;
 
     /**
-     * @param {string} nodeValue
+     * @param {Identifier} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
-    storeLocalName (nodeValue: string, lexicalScopeNode: TNodeWithLexicalScope): void;
+    storeLocalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void;
 
     /**
      * @param {string} name

+ 6 - 2
src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IObfuscatingReplacer.d.ts

@@ -4,10 +4,14 @@ import { TNodeWithLexicalScope } from '../../../../types/node/TNodeWithLexicalSc
 
 export interface IObfuscatingReplacer <T = ESTree.Node> {
     /**
-     * @param {SimpleLiteral["value"]} nodeValue
+     * @param {Node} node
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      * @param {number} nodeIdentifier
      * @returns {T}
      */
-    replace (nodeValue: ESTree.SimpleLiteral['value'], lexicalScopeNode?: TNodeWithLexicalScope, nodeIdentifier?: number): T;
+    replace (
+        node: ESTree.Node,
+        lexicalScopeNode?: TNodeWithLexicalScope,
+        nodeIdentifier?: number
+    ): T;
 }

+ 0 - 4
src/interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IStringArrayIndexData.d.ts

@@ -1,4 +0,0 @@
-export interface IStringArrayIndexData {
-    fromCache: boolean;
-    index: string;
-}

+ 7 - 1
src/interfaces/storages/IMapStorage.d.ts

@@ -1,11 +1,17 @@
 import { IInitializable } from '../IInitializable';
 
 export interface IMapStorage <K, V> extends IInitializable {
+    /**
+     * @param {K} key
+     * @returns {V | undefined}
+     */
+    get (key: K): V | undefined;
+
     /**
      * @param {K} key
      * @returns {V}
      */
-    get (key: K): V;
+    getOrThrow (key: K): V;
 
     /**
      * @param {V} value

+ 6 - 0
src/interfaces/storages/string-array-storage/IStringArrayStorageItem.d.ts

@@ -0,0 +1,6 @@
+import { IEncodedValue } from '../../IEncodedValue';
+
+export interface IStringArrayStorageItemData extends IEncodedValue {
+    index: number;
+    value: string;
+}

+ 2 - 2
src/node-transformers/obfuscating-transformers/CatchClauseTransformer.ts

@@ -100,7 +100,7 @@ export class CatchClauseTransformer extends AbstractNodeTransformer {
         lexicalScopeNode: TNodeWithLexicalScope
     ): void {
         if (catchClauseNode.param && NodeGuards.isIdentifierNode(catchClauseNode.param)) {
-            this.identifierObfuscatingReplacer.storeLocalName(catchClauseNode.param.name, lexicalScopeNode);
+            this.identifierObfuscatingReplacer.storeLocalName(catchClauseNode.param, lexicalScopeNode);
         }
     }
 
@@ -116,7 +116,7 @@ export class CatchClauseTransformer extends AbstractNodeTransformer {
             enter: (node: ESTree.Node, parentNode: ESTree.Node | null): void => {
                 if (parentNode && NodeGuards.isReplaceableIdentifierNode(node, parentNode)) {
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
                     const newIdentifierName: string = newIdentifier.name;
 
                     if (node.name !== newIdentifierName) {

+ 4 - 4
src/node-transformers/obfuscating-transformers/ClassDeclarationTransformer.ts

@@ -130,9 +130,9 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
         isGlobalDeclaration: boolean
     ): void {
         if (isGlobalDeclaration) {
-            this.identifierObfuscatingReplacer.storeGlobalName(classDeclarationNode.id.name, lexicalScopeNode);
+            this.identifierObfuscatingReplacer.storeGlobalName(classDeclarationNode.id, lexicalScopeNode);
         } else {
-            this.identifierObfuscatingReplacer.storeLocalName(classDeclarationNode.id.name, lexicalScopeNode);
+            this.identifierObfuscatingReplacer.storeLocalName(classDeclarationNode.id, lexicalScopeNode);
         }
     }
 
@@ -159,7 +159,7 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
         for (let i: number = 0; i < cachedReplaceableIdentifierLength; i++) {
             const replaceableIdentifier: ESTree.Identifier = cachedReplaceableIdentifiers[i];
             const newReplaceableIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                .replace(replaceableIdentifier.name, lexicalScopeNode);
+                .replace(replaceableIdentifier, lexicalScopeNode);
 
             replaceableIdentifier.name = newReplaceableIdentifier.name;
             NodeMetadata.set(replaceableIdentifier, { renamedIdentifier: true });
@@ -180,7 +180,7 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
                     && !NodeMetadata.isRenamedIdentifier(node)
                 ) {
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
                     const newIdentifierName: string = newIdentifier.name;
 
                     if (node.name !== newIdentifierName) {

+ 4 - 4
src/node-transformers/obfuscating-transformers/FunctionDeclarationTransformer.ts

@@ -130,9 +130,9 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
         isGlobalDeclaration: boolean
     ): void {
         if (isGlobalDeclaration) {
-            this.identifierObfuscatingReplacer.storeGlobalName(functionDeclarationNode.id.name, lexicalScopeNode);
+            this.identifierObfuscatingReplacer.storeGlobalName(functionDeclarationNode.id, lexicalScopeNode);
         } else {
-            this.identifierObfuscatingReplacer.storeLocalName(functionDeclarationNode.id.name, lexicalScopeNode);
+            this.identifierObfuscatingReplacer.storeLocalName(functionDeclarationNode.id, lexicalScopeNode);
         }
     }
 
@@ -159,7 +159,7 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
         for (let i: number = 0; i < cachedReplaceableIdentifierLength; i++) {
             const replaceableIdentifier: ESTree.Identifier = cachedReplaceableIdentifiers[i];
             const newReplaceableIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                .replace(replaceableIdentifier.name, lexicalScopeNode);
+                .replace(replaceableIdentifier, lexicalScopeNode);
 
             replaceableIdentifier.name = newReplaceableIdentifier.name;
             NodeMetadata.set(replaceableIdentifier, { renamedIdentifier: true });
@@ -181,7 +181,7 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
                     && !NodeMetadata.isRenamedIdentifier(node)
                 ) {
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
                     const newIdentifierName: string = newIdentifier.name;
 
                     if (node.name !== newIdentifierName) {

+ 3 - 3
src/node-transformers/obfuscating-transformers/FunctionTransformer.ts

@@ -154,13 +154,13 @@ export class FunctionTransformer extends AbstractNodeTransformer {
                 }
 
                 if (NodeGuards.isAssignmentPatternNode(node) && NodeGuards.isIdentifierNode(node.left)) {
-                    this.identifierObfuscatingReplacer.storeLocalName(node.left.name, lexicalScopeNode);
+                    this.identifierObfuscatingReplacer.storeLocalName(node.left, lexicalScopeNode);
 
                     return estraverse.VisitorOption.Skip;
                 }
 
                 if (NodeGuards.isIdentifierNode(node)) {
-                    this.identifierObfuscatingReplacer.storeLocalName(node.name, lexicalScopeNode);
+                    this.identifierObfuscatingReplacer.storeLocalName(node, lexicalScopeNode);
                 }
             }
         };
@@ -212,7 +212,7 @@ export class FunctionTransformer extends AbstractNodeTransformer {
                     }
 
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
                     const newIdentifierName: string = newIdentifier.name;
 
                     if (node.name !== newIdentifierName) {

+ 3 - 3
src/node-transformers/obfuscating-transformers/ImportDeclarationTransformer.ts

@@ -126,7 +126,7 @@ export class ImportDeclarationTransformer extends AbstractNodeTransformer {
                 return;
             }
 
-            this.identifierObfuscatingReplacer.storeGlobalName(importSpecifierNode.local.name, lexicalScopeNode);
+            this.identifierObfuscatingReplacer.storeGlobalName(importSpecifierNode.local, lexicalScopeNode);
         });
     }
 
@@ -139,7 +139,7 @@ export class ImportDeclarationTransformer extends AbstractNodeTransformer {
 
         cachedReplaceableIdentifiers.forEach((replaceableIdentifier: ESTree.Identifier) => {
             const newReplaceableIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                .replace(replaceableIdentifier.name, lexicalScopeNode);
+                .replace(replaceableIdentifier, lexicalScopeNode);
 
             if (replaceableIdentifier.name !== newReplaceableIdentifier.name) {
                 replaceableIdentifier.name = newReplaceableIdentifier.name;
@@ -162,7 +162,7 @@ export class ImportDeclarationTransformer extends AbstractNodeTransformer {
                     && !NodeMetadata.isRenamedIdentifier(node)
                 ) {
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
                     const newIdentifierName: string = newIdentifier.name;
 
                     if (node.name !== newIdentifierName) {

+ 2 - 2
src/node-transformers/obfuscating-transformers/LabeledStatementTransformer.ts

@@ -106,7 +106,7 @@ export class LabeledStatementTransformer extends AbstractNodeTransformer {
         labeledStatementNode: ESTree.LabeledStatement,
         lexicalScopeNode: TNodeWithLexicalScope
     ): void {
-        this.identifierObfuscatingReplacer.storeLocalName(labeledStatementNode.label.name, lexicalScopeNode);
+        this.identifierObfuscatingReplacer.storeLocalName(labeledStatementNode.label, lexicalScopeNode);
     }
 
     /**
@@ -121,7 +121,7 @@ export class LabeledStatementTransformer extends AbstractNodeTransformer {
             enter: (node: ESTree.Node, parentNode: ESTree.Node | null): void => {
                 if (parentNode && NodeGuards.isLabelIdentifierNode(node, parentNode)) {
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
 
                     node.name = newIdentifier.name;
                 }

+ 21 - 4
src/node-transformers/obfuscating-transformers/LiteralTransformer.ts

@@ -7,6 +7,7 @@ import { TLiteralObfuscatingReplacerFactory } from '../../types/container/node-t
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { LiteralObfuscatingReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/LiteralObfuscatingReplacer';
@@ -24,20 +25,28 @@ export class LiteralTransformer extends AbstractNodeTransformer {
      */
     private readonly literalObfuscatingReplacerFactory: TLiteralObfuscatingReplacerFactory;
 
+    /**
+     * @type {IStringArrayStorageAnalyzer}
+     */
+    private readonly stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer;
+
     /**
      * @param {TLiteralObfuscatingReplacerFactory} literalObfuscatingReplacerFactory
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
+     * @param {IStringArrayStorageAnalyzer} stringArrayStorageAnalyzer
      */
     constructor (
         @inject(ServiceIdentifiers.Factory__IObfuscatingReplacer)
             literalObfuscatingReplacerFactory: TLiteralObfuscatingReplacerFactory,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.IStringArrayStorageAnalyzer) stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer
     ) {
         super(randomGenerator, options);
 
         this.literalObfuscatingReplacerFactory = literalObfuscatingReplacerFactory;
+        this.stringArrayStorageAnalyzer = stringArrayStorageAnalyzer;
     }
 
     /**
@@ -49,6 +58,10 @@ export class LiteralTransformer extends AbstractNodeTransformer {
             case TransformationStage.Obfuscating:
                 return {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (NodeGuards.isProgramNode(node)) {
+                            this.analyzeNode(node);
+                        }
+
                         if (parentNode && NodeGuards.isLiteralNode(node) && !NodeMetadata.isReplacedLiteral(node)) {
                             return this.transformNode(node, parentNode);
                         }
@@ -60,6 +73,10 @@ export class LiteralTransformer extends AbstractNodeTransformer {
         }
     }
 
+    public analyzeNode (programNode: ESTree.Program): void {
+        this.stringArrayStorageAnalyzer.analyze(programNode);
+    }
+
     /**
      * @param {Literal} literalNode
      * @param {NodeGuards} parentNode
@@ -76,21 +93,21 @@ export class LiteralTransformer extends AbstractNodeTransformer {
             case 'boolean':
                 newLiteralNode = this.literalObfuscatingReplacerFactory(
                     LiteralObfuscatingReplacer.BooleanLiteralObfuscatingReplacer
-                ).replace(literalNode.value);
+                ).replace(literalNode);
 
                 break;
 
             case 'number':
                 newLiteralNode = this.literalObfuscatingReplacerFactory(
                     LiteralObfuscatingReplacer.NumberLiteralObfuscatingReplacer
-                ).replace(literalNode.value);
+                ).replace(literalNode);
 
                 break;
 
             case 'string':
                 newLiteralNode = this.literalObfuscatingReplacerFactory(
                     LiteralObfuscatingReplacer.StringLiteralObfuscatingReplacer
-                ).replace(literalNode.value);
+                ).replace(literalNode);
 
                 break;
 

+ 4 - 4
src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts

@@ -153,9 +153,9 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
                 }
 
                 if (isGlobalDeclaration) {
-                    this.identifierObfuscatingReplacer.storeGlobalName(identifierNode.name, lexicalScopeNode);
+                    this.identifierObfuscatingReplacer.storeGlobalName(identifierNode, lexicalScopeNode);
                 } else {
-                    this.identifierObfuscatingReplacer.storeLocalName(identifierNode.name, lexicalScopeNode);
+                    this.identifierObfuscatingReplacer.storeLocalName(identifierNode, lexicalScopeNode);
                 }
             }
         );
@@ -287,7 +287,7 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
                 }
 
                 const newReplaceableIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                    .replace(replaceableIdentifier.name, lexicalScopeNode);
+                    .replace(replaceableIdentifier, lexicalScopeNode);
 
                 replaceableIdentifier.name = newReplaceableIdentifier.name;
                 NodeMetadata.set(replaceableIdentifier, { renamedIdentifier: true });
@@ -310,7 +310,7 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
                     && !NodeMetadata.isRenamedIdentifier(node)
                 ) {
                     const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer
-                        .replace(node.name, lexicalScopeNode);
+                        .replace(node, lexicalScopeNode);
                     const newIdentifierName: string = newIdentifier.name;
 
                     if (node.name !== newIdentifierName) {

+ 2 - 2
src/node-transformers/obfuscating-transformers/obfuscating-replacers/AbstractObfuscatingReplacer.ts

@@ -25,9 +25,9 @@ export abstract class AbstractObfuscatingReplacer implements IObfuscatingReplace
     }
 
     /**
-     * @param {SimpleLiteral["value"]} nodeValue
+     * @param {Node} node
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      * @returns {Node}
      */
-    public abstract replace (nodeValue: ESTree.SimpleLiteral['value'], lexicalScopeNode?: TNodeWithLexicalScope): ESTree.Node;
+    public abstract replace (node: ESTree.Node, lexicalScopeNode?: TNodeWithLexicalScope): ESTree.Node;
 }

+ 21 - 15
src/node-transformers/obfuscating-transformers/obfuscating-replacers/identifier-obfuscating-replacers/BaseIdentifierObfuscatingReplacer.ts

@@ -40,35 +40,39 @@ export class BaseIdentifierObfuscatingReplacer extends AbstractObfuscatingReplac
     }
 
     /**
-     * @param {string} nodeValue
+     * @param {Identifier} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      * @returns {Identifier}
      */
-    public replace (nodeValue: string, lexicalScopeNode: TNodeWithLexicalScope): ESTree.Identifier {
+    public replace (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): ESTree.Identifier {
+        let identifierName: string = identifierNode.name;
+
         if (this.blockScopesMap.has(lexicalScopeNode)) {
             const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
 
-            if (namesMap.has(nodeValue)) {
-                nodeValue = <string>namesMap.get(nodeValue);
+            if (namesMap.has(identifierName)) {
+                identifierName = <string>namesMap.get(identifierName);
             }
         }
 
-        return NodeFactory.identifierNode(nodeValue);
+        return NodeFactory.identifierNode(identifierName);
     }
 
     /**
      * Store `nodeName` of global identifiers as key in map with random name as value.
      * Reserved name will be ignored.
      *
-     * @param {string} nodeName
+     * @param {Node} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
-    public storeGlobalName (nodeName: string, lexicalScopeNode: TNodeWithLexicalScope): void {
-        if (this.isReservedName(nodeName)) {
+    public storeGlobalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void {
+        const identifierName: string = identifierNode.name;
+
+        if (this.isReservedName(identifierName)) {
             return;
         }
 
-        const identifierName: string = this.identifierNamesGenerator.generateWithPrefix();
+        const newIdentifierName: string = this.identifierNamesGenerator.generateWithPrefix();
 
         if (!this.blockScopesMap.has(lexicalScopeNode)) {
             this.blockScopesMap.set(lexicalScopeNode, new Map());
@@ -76,22 +80,24 @@ export class BaseIdentifierObfuscatingReplacer extends AbstractObfuscatingReplac
 
         const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
 
-        namesMap.set(nodeName, identifierName);
+        namesMap.set(identifierName, newIdentifierName);
     }
 
     /**
      * Store `nodeName` of local identifier as key in map with random name as value.
      * Reserved name will be ignored.
      *
-     * @param {string} nodeName
+     * @param {Identifier} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
-    public storeLocalName (nodeName: string, lexicalScopeNode: TNodeWithLexicalScope): void {
-        if (this.isReservedName(nodeName)) {
+    public storeLocalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void {
+        const identifierName: string = identifierNode.name;
+
+        if (this.isReservedName(identifierName)) {
             return;
         }
 
-        const identifierName: string = this.identifierNamesGenerator.generate();
+        const newIdentifierName: string = this.identifierNamesGenerator.generate();
 
         if (!this.blockScopesMap.has(lexicalScopeNode)) {
             this.blockScopesMap.set(lexicalScopeNode, new Map());
@@ -99,7 +105,7 @@ export class BaseIdentifierObfuscatingReplacer extends AbstractObfuscatingReplac
 
         const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
 
-        namesMap.set(nodeName, identifierName);
+        namesMap.set(identifierName, newIdentifierName);
     }
 
     /**

+ 9 - 3
src/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/BooleanLiteralObfuscatingReplacer.ts

@@ -40,11 +40,17 @@ export class BooleanLiteralObfuscatingReplacer extends AbstractObfuscatingReplac
     }
 
     /**
-     * @param {boolean} nodeValue
+     * @param {SimpleLiteral} literalNode
      * @returns {Node}
      */
-    public replace (nodeValue: boolean): ESTree.Node {
-        return nodeValue
+    public replace (literalNode: ESTree.SimpleLiteral): ESTree.Node {
+        const literalValue: ESTree.SimpleLiteral['value'] = literalNode.value;
+
+        if (typeof literalValue !== 'boolean') {
+            throw new Error('`BooleanLiteralObfuscatingReplacer` should accept only literals with `boolean` value');
+        }
+
+        return literalValue
             ? BooleanLiteralObfuscatingReplacer.getTrueUnaryExpressionNode()
             : BooleanLiteralObfuscatingReplacer.getFalseUnaryExpressionNode();
     }

+ 15 - 9
src/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/NumberLiteralObfuscatingReplacer.ts

@@ -27,24 +27,30 @@ export class NumberLiteralObfuscatingReplacer extends AbstractObfuscatingReplace
     }
 
     /**
-     * @param {number} nodeValue
+     * @param {SimpleLiteral} literalNode
      * @returns {Node}
      */
-    public replace (nodeValue: number): ESTree.Node {
+    public replace (literalNode: ESTree.SimpleLiteral): ESTree.Node {
+        const literalValue: ESTree.SimpleLiteral['value'] = literalNode.value;
+
+        if (typeof literalValue !== 'number') {
+            throw new Error('`NumberLiteralObfuscatingReplacer` should accept only literals with `number` value');
+        }
+
         let rawValue: string;
 
-        if (this.numberLiteralCache.has(nodeValue)) {
-            rawValue = <string>this.numberLiteralCache.get(nodeValue);
+        if (this.numberLiteralCache.has(literalValue)) {
+            rawValue = <string>this.numberLiteralCache.get(literalValue);
         } else {
-            if (!NumberUtils.isCeil(nodeValue)) {
-                rawValue = String(nodeValue);
+            if (!NumberUtils.isCeil(literalValue)) {
+                rawValue = String(literalValue);
             } else {
-                rawValue = `${Utils.hexadecimalPrefix}${NumberUtils.toHex(nodeValue)}`;
+                rawValue = `${Utils.hexadecimalPrefix}${NumberUtils.toHex(literalValue)}`;
             }
 
-            this.numberLiteralCache.set(nodeValue, rawValue);
+            this.numberLiteralCache.set(literalValue, rawValue);
         }
 
-        return NodeFactory.literalNode(nodeValue, rawValue);
+        return NodeFactory.literalNode(literalValue, rawValue);
     }
 }

+ 36 - 148
src/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/StringLiteralObfuscatingReplacer.ts

@@ -5,12 +5,10 @@ import * as ESTree from 'estree';
 
 import { TStringArrayStorage } from '../../../../types/storages/TStringArrayStorage';
 
-import { ICryptUtils } from '../../../../interfaces/utils/ICryptUtils';
-import { IEncodedValue } from '../../../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IEncodedValue';
 import { IEscapeSequenceEncoder } from '../../../../interfaces/utils/IEscapeSequenceEncoder';
 import { IOptions } from '../../../../interfaces/options/IOptions';
-import { IRandomGenerator } from '../../../../interfaces/utils/IRandomGenerator';
-import { IStringArrayIndexData } from '../../../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IStringArrayIndexData';
+import { IStringArrayStorageAnalyzer } from '../../../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
+import { IStringArrayStorageItemData } from '../../../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
 
 import { StringArrayEncoding } from '../../../../enums/StringArrayEncoding';
 
@@ -22,26 +20,6 @@ import { Utils } from '../../../../utils/Utils';
 
 @injectable()
 export class StringLiteralObfuscatingReplacer extends AbstractObfuscatingReplacer {
-    /**
-     * @type {number}
-     */
-    private static readonly minimumLengthForStringArray: number = 3;
-
-    /**
-     * @type {number}
-     */
-    private static readonly rc4KeyLength: number = 4;
-
-    /**
-     * @type {number}
-     */
-    private static readonly rc4KeysCount: number = 50;
-
-    /**
-     * @type {ICryptUtils}
-     */
-    private readonly cryptUtils: ICryptUtils;
-
     /**
      * @type {IEscapeSequenceEncoder}
      */
@@ -53,55 +31,33 @@ export class StringLiteralObfuscatingReplacer extends AbstractObfuscatingReplace
     private readonly nodesCache: Map <string, ESTree.Node> = new Map();
 
     /**
-     * @type {IRandomGenerator}
-     */
-    private readonly randomGenerator: IRandomGenerator;
-
-    /**
-     * @type {string[]}
-     */
-    private readonly rc4Keys: string[];
-
-    /**
-     * @type {Map<string, string>}
+     * @type {IStringArrayStorageAnalyzer}
      */
-    private readonly stringLiteralHexadecimalIndexCache: Map <string, string> = new Map();
+    private readonly stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer;
 
     /**
-     * @type {TStringArrayStorage}
+     * @type {string}
      */
-    private readonly stringArrayStorage: TStringArrayStorage;
+    private readonly stringArrayStorageCallsWrapperName: string;
 
     /**
      * @param {TStringArrayStorage} stringArrayStorage
+     * @param {IStringArrayStorageAnalyzer} stringArrayStorageAnalyzer
      * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
-     * @param {IRandomGenerator} randomGenerator
-     * @param {ICryptUtils} cryptUtils
      * @param {IOptions} options
      */
     constructor (
         @inject(ServiceIdentifiers.TStringArrayStorage) stringArrayStorage: TStringArrayStorage,
+        @inject(ServiceIdentifiers.IStringArrayStorageAnalyzer) stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer,
         @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder,
-        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
-        super(
-            options
-        );
+        super(options);
 
-        this.stringArrayStorage = stringArrayStorage;
+        this.stringArrayStorageAnalyzer = stringArrayStorageAnalyzer;
         this.escapeSequenceEncoder = escapeSequenceEncoder;
-        this.randomGenerator = randomGenerator;
-        this.cryptUtils = cryptUtils;
-
-        this.rc4Keys = this.randomGenerator.getRandomGenerator()
-            .n(
-                () => this.randomGenerator.getRandomGenerator().string({
-                    length: StringLiteralObfuscatingReplacer.rc4KeyLength
-                }),
-                StringLiteralObfuscatingReplacer.rc4KeysCount
-            );
+
+        this.stringArrayStorageCallsWrapperName = stringArrayStorage.getStorageId().split('|')[1];
     }
 
     /**
@@ -129,88 +85,32 @@ export class StringLiteralObfuscatingReplacer extends AbstractObfuscatingReplace
     }
 
     /**
-     * @param {string} nodeValue
+     * @param {SimpleLiteral} literalNode
      * @returns {Node}
      */
-    public replace (nodeValue: string): ESTree.Node {
-        const useStringArray: boolean = this.canUseStringArray(nodeValue);
-        const cacheKey: string = `${nodeValue}-${String(useStringArray)}`;
-        const useCacheValue: boolean = this.nodesCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.Rc4;
+    public replace (literalNode: ESTree.SimpleLiteral): ESTree.Node {
+        const literalValue: ESTree.SimpleLiteral['value'] = literalNode.value;
 
-        if (useCacheValue) {
-            return <ESTree.Node>this.nodesCache.get(cacheKey);
+        if (typeof literalValue !== 'string') {
+            throw new Error('`StringLiteralObfuscatingReplacer` should accept only literals with `string` value');
         }
 
-        const resultNode: ESTree.Node = useStringArray
-            ? this.replaceWithStringArrayCallNode(nodeValue)
-            : this.replaceWithLiteralNode(nodeValue);
+        const stringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.stringArrayStorageAnalyzer
+            .getItemDataForLiteralNode(literalNode);
+        const cacheKey: string = `${literalValue}-${Boolean(stringArrayStorageItemData)}`;
+        const useCachedValue: boolean = this.nodesCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.Rc4;
 
-        this.nodesCache.set(cacheKey, resultNode);
-
-        return resultNode;
-    }
-
-    /**
-     * @param {string} nodeValue
-     * @returns {boolean}
-     */
-    private canUseStringArray (nodeValue: string): boolean {
-        return (
-            this.options.stringArray &&
-            nodeValue.length >= StringLiteralObfuscatingReplacer.minimumLengthForStringArray &&
-            this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold
-        );
-    }
-
-    /**
-     * @param {string} value
-     * @param {number} stringArrayStorageLength
-     * @returns {IStringArrayIndexData}
-     */
-    private getStringArrayHexadecimalIndex (value: string, stringArrayStorageLength: number): IStringArrayIndexData {
-        if (this.stringLiteralHexadecimalIndexCache.has(value)) {
-            return {
-                fromCache: true,
-                index: <string>this.stringLiteralHexadecimalIndexCache.get(value)
-            };
+        if (useCachedValue) {
+            return <ESTree.Node>this.nodesCache.get(cacheKey);
         }
 
-        const hexadecimalRawIndex: string = NumberUtils.toHex(stringArrayStorageLength);
-        const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${hexadecimalRawIndex}`;
-
-        this.stringLiteralHexadecimalIndexCache.set(value, hexadecimalIndex);
-
-        return {
-            fromCache: false,
-            index: hexadecimalIndex
-        };
-    }
-
-    /**
-     * @param {string} value
-     * @returns {IEncodedValue}
-     */
-    private getEncodedValue (value: string): IEncodedValue {
-        let encodedValue: string;
-        let key: string | null = null;
+        const resultNode: ESTree.Node = stringArrayStorageItemData
+            ? this.replaceWithStringArrayCallNode(stringArrayStorageItemData)
+            : this.replaceWithLiteralNode(literalValue);
 
-        switch (this.options.stringArrayEncoding) {
-            case StringArrayEncoding.Rc4:
-                key = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys);
-                encodedValue = this.cryptUtils.btoa(this.cryptUtils.rc4(value, key));
-
-                break;
-
-            case StringArrayEncoding.Base64:
-                encodedValue = this.cryptUtils.btoa(value);
-
-                break;
-
-            default:
-                encodedValue = value;
-        }
+        this.nodesCache.set(cacheKey, resultNode);
 
-        return { encodedValue, key };
+        return resultNode;
     }
 
     /**
@@ -224,36 +124,24 @@ export class StringLiteralObfuscatingReplacer extends AbstractObfuscatingReplace
     }
 
     /**
-     * @param {string} value
+     * @param {IStringArrayStorageItemData} stringArrayStorageItemData
      * @returns {Node}
      */
-    private replaceWithStringArrayCallNode (value: string): ESTree.Node {
-        const { encodedValue, key }: IEncodedValue = this.getEncodedValue(value);
-        const escapedValue: string = this.escapeSequenceEncoder.encode(encodedValue, this.options.unicodeEscapeSequence);
-
-        const stringArrayStorageLength: number = this.stringArrayStorage.getLength();
-        const stringArrayStorageCallsWrapperName: string = this.stringArrayStorage.getStorageId().split('|')[1];
-
-        const { fromCache, index }: IStringArrayIndexData = this.getStringArrayHexadecimalIndex(
-            escapedValue,
-            stringArrayStorageLength
-        );
-
-        if (!fromCache) {
-            this.stringArrayStorage.set(stringArrayStorageLength, escapedValue);
-        }
+    private replaceWithStringArrayCallNode (stringArrayStorageItemData: IStringArrayStorageItemData): ESTree.Node {
+        const { index, decodeKey } = stringArrayStorageItemData;
 
+        const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${NumberUtils.toHex(index)}`;
         const callExpressionArgs: (ESTree.Expression | ESTree.SpreadElement)[] = [
-            StringLiteralObfuscatingReplacer.getHexadecimalLiteralNode(index)
+            StringLiteralObfuscatingReplacer.getHexadecimalLiteralNode(hexadecimalIndex)
         ];
 
-        if (key) {
+        if (decodeKey) {
             callExpressionArgs.push(StringLiteralObfuscatingReplacer.getRc4KeyLiteralNode(
-                this.escapeSequenceEncoder.encode(key, this.options.unicodeEscapeSequence)
+                this.escapeSequenceEncoder.encode(decodeKey, this.options.unicodeEscapeSequence)
             ));
         }
 
-        const stringArrayIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(stringArrayStorageCallsWrapperName);
+        const stringArrayIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.stringArrayStorageCallsWrapperName);
 
         // prevent obfuscation of this identifier
         NodeMetadata.set(stringArrayIdentifierNode, { renamedIdentifier: true });

+ 10 - 2
src/storages/MapStorage.ts

@@ -49,12 +49,20 @@ export abstract class MapStorage <K, V> implements IMapStorage <K, V> {
         this.storageId = this.randomGenerator.getRandomString(6);
     }
 
+    /**
+     * @param {K} key
+     * @returns {V | undefined}
+     */
+    public get (key: K): V | undefined {
+        return this.storage.get(key);
+    }
+
     /**
      * @param {K} key
      * @returns {V}
      */
-    public get (key: K): V {
-        const value: V | undefined = this.storage.get(key);
+    public getOrThrow (key: K): V {
+        const value: V | undefined = this.get(key);
 
         if (!value) {
             throw new Error(`No value found in map storage with key \`${key}\``);

+ 126 - 12
src/storages/string-array/StringArrayStorage.ts

@@ -1,17 +1,33 @@
 import { inject, injectable, postConstruct } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
-import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
-import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
-
 import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
+
+import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
+import { ICryptUtils } from '../../interfaces/utils/ICryptUtils';
+import { IEncodedValue } from '../../interfaces/IEncodedValue';
+import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
 
-import { ArrayStorage } from '../ArrayStorage';
+import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
+
+import { MapStorage } from '../MapStorage';
 
 @injectable()
-export class StringArrayStorage extends ArrayStorage <string> {
+export class StringArrayStorage extends MapStorage <string, IStringArrayStorageItemData> {
+    /**
+     * @type {number}
+     */
+    private static readonly rc4KeyLength: number = 4;
+
+    /**
+     * @type {number}
+     */
+    private static readonly rc4KeysCount: number = 50;
+
     /**
      * @type {number}
      */
@@ -22,28 +38,57 @@ export class StringArrayStorage extends ArrayStorage <string> {
      */
     private readonly arrayUtils: IArrayUtils;
 
+    /**
+     * @type {ICryptUtils}
+     */
+    private readonly cryptUtils: ICryptUtils;
+
+    /**
+     * @type {IEscapeSequenceEncoder}
+     */
+    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
+
     /**
      * @type {IIdentifierNamesGenerator}
      */
     private readonly identifierNamesGenerator: IIdentifierNamesGenerator;
 
+    /**
+     * @type {string[]}
+     */
+    private readonly rc4Keys: string[];
+
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {IArrayUtils} arrayUtils
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
+     * @param {ICryptUtils} cryptUtils
+     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
      */
     constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
         @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
+        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder
     ) {
         super(randomGenerator, options);
 
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
         this.arrayUtils = arrayUtils;
+        this.cryptUtils = cryptUtils;
+        this.escapeSequenceEncoder = escapeSequenceEncoder;
+
+        this.rc4Keys = this.randomGenerator.getRandomGenerator()
+            .n(
+                () => this.randomGenerator.getRandomGenerator().string({
+                    length: StringArrayStorage.rc4KeyLength
+                }),
+                StringArrayStorage.rc4KeysCount
+            );
     }
 
     @postConstruct()
@@ -61,18 +106,87 @@ export class StringArrayStorage extends ArrayStorage <string> {
     }
 
     /**
-     * @param {number} rotationValue
+     * @param {string} value
+     * @returns {IStringArrayStorageItemData}
+     */
+    public get (value: string): IStringArrayStorageItemData {
+        return this.getOrSetIfDoesNotExist(value);
+    }
+
+    /**
+     * @param {number} rotationAmount
      */
-    public rotateArray (rotationValue: number): void {
-        this.storage = this.arrayUtils.rotate(this.storage, rotationValue);
+    public rotateStorage (rotationAmount: number): void {
+        this.storage = new Map(
+            this.arrayUtils.rotate(
+                Array.from(this.storage.entries()),
+                rotationAmount
+            )
+        );
     }
 
     /**
      * @returns {string}
      */
     public toString (): string {
-        return this.storage.map((value: string) => {
-            return `'${value}'`;
-        }).toString();
+        return Array
+            .from(this.storage.values())
+            .map((item: IStringArrayStorageItemData) => {
+                return `'${this.escapeSequenceEncoder.encode(
+                    item.encodedValue,
+                    this.options.unicodeEscapeSequence
+                )}'`;
+            }).toString();
+    }
+
+    /**
+     * @param {string} value
+     * @returns {IStringArrayStorageItemData}
+     */
+    private getOrSetIfDoesNotExist (value: string): IStringArrayStorageItemData {
+        const { encodedValue, decodeKey }: IEncodedValue = this.getEncodedValue(value);
+        const storedStringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.storage.get(encodedValue);
+
+        if (storedStringArrayStorageItemData) {
+            return storedStringArrayStorageItemData;
+        }
+
+        const stringArrayStorageItemData: IStringArrayStorageItemData = {
+            encodedValue,
+            decodeKey,
+            value,
+            index: this.getLength()
+        };
+
+        this.storage.set(encodedValue, stringArrayStorageItemData);
+
+        return stringArrayStorageItemData;
+    }
+
+    /**
+     * @param {string} value
+     * @returns {IEncodedValue}
+     */
+    private getEncodedValue (value: string): IEncodedValue {
+        let encodedValue: string;
+        let decodeKey: string | null = null;
+
+        switch (this.options.stringArrayEncoding) {
+            case StringArrayEncoding.Rc4:
+                decodeKey = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys);
+                encodedValue = this.cryptUtils.btoa(this.cryptUtils.rc4(value, decodeKey));
+
+                break;
+
+            case StringArrayEncoding.Base64:
+                encodedValue = this.cryptUtils.btoa(value);
+
+                break;
+
+            default:
+                encodedValue = value;
+        }
+
+        return { encodedValue, decodeKey };
     }
 }

+ 4 - 2
src/types/storages/TStringArrayStorage.d.ts

@@ -1,3 +1,5 @@
-import { IArrayStorage } from '../../interfaces/storages/IArrayStorage';
+import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
 
-export type TStringArrayStorage = IArrayStorage <string>;
+import { IMapStorage } from '../../interfaces/storages/IMapStorage';
+
+export type TStringArrayStorage = IMapStorage <string, IStringArrayStorageItemData>;

+ 4 - 3
test/dev/dev.ts

@@ -7,9 +7,10 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            class Foo {
-                'bar'() {}
-            }
+            var foo = 'foo';
+            var bar = 'bar';
+            var baz = 'baz';
+            var bark = 'bark';
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,

+ 57 - 15
test/functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec.ts

@@ -6,6 +6,7 @@ import { StringArrayEncoding } from '../../../../../src/enums/StringArrayEncodin
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
+import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
@@ -214,26 +215,67 @@ describe('LiteralTransformer', () => {
         });
 
         describe('Variant #9: rc4 encoding', () => {
-            const regExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
+            describe('Variant #1: single string literal', () => {
+                const regExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
 
-            let obfuscatedCode: string;
+                let obfuscatedCode: string;
 
-            before(() => {
-                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
 
-                obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                    code,
-                    {
-                        ...NO_ADDITIONAL_NODES_PRESET,
-                        stringArray: true,
-                        stringArrayEncoding: StringArrayEncoding.Rc4,
-                        stringArrayThreshold: 1
-                    }
-                ).getObfuscatedCode();
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayEncoding: StringArrayEncoding.Rc4,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should replace literal node value with value from string array encoded using rc4', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
             });
 
-            it('should replace literal node value with value from string array encoded using rc4', () => {
-                assert.match(obfuscatedCode, regExp);
+            describe('Variant #2: multiple string literals', () => {
+                const variableRegExp1: RegExp = /var *test *= *_0x(?:[a-f0-9]){4}\('0x0', *'(.{4})'\);/;
+                const variableRegExp2: RegExp = /var *test *= *_0x(?:[a-f0-9]){4}\('0x1', *'(.{4})'\);/;
+
+                let encodedLiteralValue1: string;
+                let encodedLiteralValue2: string;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/same-literal-values.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayEncoding: StringArrayEncoding.Rc4,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+
+                    encodedLiteralValue1 = getRegExpMatch(obfuscatedCode, variableRegExp1);
+                    encodedLiteralValue2 = getRegExpMatch(obfuscatedCode, variableRegExp2);
+                });
+
+                it('Match #1: should replace literal node value with value from string array encoded using rc4', () => {
+                    assert.match(obfuscatedCode, variableRegExp1);
+                });
+
+                it('Match #2: should replace literal node value with value from string array encoded using rc4', () => {
+                    assert.match(obfuscatedCode, variableRegExp2);
+                });
+
+                it('Should encode same values as two different encoded string array items', () => {
+                    assert.notEqual(encodedLiteralValue1, encodedLiteralValue2);
+                });
             });
         });
 

+ 37 - 2
test/unit-tests/storages/MapStorage.spec.ts

@@ -91,6 +91,41 @@ describe('MapStorage', () => {
             });
         });
 
+        describe('Variant #2: value isn\'t exist', () => {
+            const expectedValue: undefined = undefined;
+
+            let value: string;
+
+            before(() => {
+                storage = getStorageInstance<string>();
+
+                value = storage.get(storageKey);
+            });
+
+            it('should return undefined', () => {
+                assert.equal(value, expectedValue);
+            });
+        });
+    });
+
+    describe('getOrThrow', () => {
+        describe('Variant #1: value exist', () => {
+            const expectedValue: string = storageValue;
+
+            let value: string;
+
+            before(() => {
+                storage = getStorageInstance<string>();
+                storage.set(storageKey, storageValue);
+
+                value = storage.getOrThrow(storageKey);
+            });
+
+            it('should return value from storage by key', () => {
+                assert.equal(value, expectedValue);
+            });
+        });
+
         describe('Variant #2: value isn\'t exist', () => {
             const expectedError: ErrorConstructor = Error;
 
@@ -99,7 +134,7 @@ describe('MapStorage', () => {
             before(() => {
                 storage = getStorageInstance<string>();
 
-                testFunc = () => storage.get(storageKey);
+                testFunc = () => storage.getOrThrow(storageKey);
             });
 
             it('should throw an error', () => {
@@ -219,7 +254,7 @@ describe('MapStorage', () => {
             storage = getStorageInstance<string>();
             storage.set(storageKey, storageValue);
 
-            value = storage.get(storageKey);
+            value = storage.getOrThrow(storageKey);
         });
 
         it('should set value to the storage', () => {

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