소스 검색

Merge pull request #845 from javascript-obfuscator/rotate-string-array-improvements

Rotate string array improvements
Timofey Kachalov 4 년 전
부모
커밋
86fe1df40c
40개의 변경된 파일935개의 추가작업 그리고 423개의 파일을 삭제
  1. 4 0
      CHANGELOG.md
  2. 0 0
      dist/index.browser.js
  3. 0 0
      dist/index.cli.js
  4. 0 0
      dist/index.js
  5. 4 4
      package.json
  6. 1 0
      src/JavaScriptObfuscator.ts
  7. 11 6
      src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts
  8. 16 9
      src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts
  9. 5 0
      src/container/modules/node-transformers/StringArrayTransformersModule.ts
  10. 1 0
      src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts
  11. 1 0
      src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts
  12. 21 40
      src/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.ts
  13. 0 23
      src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts
  14. 4 2
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate.ts
  15. 5 3
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate.ts
  16. 0 81
      src/custom-code-helpers/string-array/templates/string-array-rotate-function/SelfDefendingTemplate.ts
  17. 13 7
      src/custom-code-helpers/string-array/templates/string-array-rotate-function/StringArrayRotateFunctionTemplate.ts
  18. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  19. 6 2
      src/interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer.ts
  20. 11 0
      src/interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer.ts
  21. 20 87
      src/node-transformers/converting-transformers/NumberToNumericalExpressionTransformer.ts
  22. 257 0
      src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer.ts
  23. 8 0
      src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts
  24. 11 1
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  25. 0 53
      src/node/NodeFactory.ts
  26. 131 0
      src/node/NumericalExpressionDataToNodeConverter.ts
  27. 6 0
      src/types/node/TNumericalExpressionDataToNodeConverterLiteralNodeGetter.ts
  28. 10 4
      test/dev/dev.ts
  29. 31 5
      test/functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec.ts
  30. 68 6
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec.ts
  31. 1 0
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/fixtures/simple-input.js
  32. 123 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/StringArrayRotateFunctionTransformer.spec.ts
  33. 1 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/fixtures/no-string-literals.js
  34. 1 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/fixtures/simple-input.js
  35. 0 12
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  36. 22 23
      test/functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec.ts
  37. 3 1
      test/index.spec.ts
  38. 37 9
      test/unit-tests/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.spec.ts
  39. 56 0
      test/unit-tests/node/numerical-expression-data-to-node-converter/NumericalExpressionDataToNodeConverter.spec.ts
  40. 45 45
      yarn.lock

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v2.10.0
+---
+* Improved `rotateStringArray` option
+
 v2.9.6
 ---
 * Preventing move of `"use strict";` directive during obfuscation

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/index.browser.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/index.cli.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/index.js


+ 4 - 4
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.9.6",
+  "version": "2.10.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -62,8 +62,8 @@
     "@types/sinon": "9.0.10",
     "@types/string-template": "1.0.2",
     "@types/webpack-env": "1.16.0",
-    "@typescript-eslint/eslint-plugin": "4.11.0",
-    "@typescript-eslint/parser": "4.11.0",
+    "@typescript-eslint/eslint-plugin": "4.11.1",
+    "@typescript-eslint/parser": "4.11.1",
     "chai": "4.2.0",
     "chai-exclude": "2.0.2",
     "cross-env": "7.0.3",
@@ -85,7 +85,7 @@
     "ts-loader": "8.0.12",
     "ts-node": "9.1.1",
     "typescript": "4.1.3",
-    "webpack": "5.11.0",
+    "webpack": "5.11.1",
     "webpack-cli": "4.3.0",
     "webpack-node-externals": "2.5.2"
   },

+ 1 - 0
src/JavaScriptObfuscator.ts

@@ -88,6 +88,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.ScopeIdentifiersTransformer,
         NodeTransformer.SplitStringTransformer,
+        NodeTransformer.StringArrayRotateFunctionTransformer,
         NodeTransformer.StringArrayScopeCallsWrapperTransformer,
         NodeTransformer.StringArrayTransformer,
         NodeTransformer.TemplateLiteralTransformer,

+ 11 - 6
src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts

@@ -17,7 +17,7 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
     /**
      * @type {number}
      */
-    private static readonly additionalParts: number = 3;
+    public static readonly defaultAdditionalPartsCount: number = 3;
 
     /**
      * @type {Map<number, number[]>}
@@ -40,9 +40,13 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
 
     /**
      * @param {number} number
+     * @param {number} additionalPartsCount
      * @returns {TNumberNumericalExpressionData}
      */
-    public analyze (number: number): TNumberNumericalExpressionData {
+    public analyze (
+        number: number,
+        additionalPartsCount: number
+    ): TNumberNumericalExpressionData {
         if (isNaN(number)) {
             throw new Error('Given value is NaN');
         }
@@ -51,16 +55,17 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
             return [number];
         }
 
-        const additionParts: number[] = this.generateAdditionParts(number);
+        const additionParts: number[] = this.generateAdditionParts(number, additionalPartsCount);
 
         return additionParts.map((addition: number) => this.mixWithMultiplyParts(addition));
     }
 
     /**
      * @param {number} number
+     * @param {number} additionalPartsCount
      * @returns {number[]}
      */
-    private generateAdditionParts (number: number): number[] {
+    private generateAdditionParts (number: number, additionalPartsCount: number): number[] {
         const additionParts = [];
 
         const upperNumberLimit: number = Math.min(Math.abs(number * 2), Number.MAX_SAFE_INTEGER);
@@ -70,8 +75,8 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
 
         let temporarySum = 0;
 
-        for (let i = 0; i < NumberNumericalExpressionAnalyzer.additionalParts; i++) {
-            if (i < NumberNumericalExpressionAnalyzer.additionalParts - 1) {
+        for (let i = 0; i < additionalPartsCount; i++) {
+            if (i < additionalPartsCount - 1) {
                 // trailing parts
 
                 let addition: number = this.randomGenerator.getRandomInteger(from, to);

+ 16 - 9
src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts

@@ -86,19 +86,11 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
         });
     }
 
-    /**
-     * @param {Literal} literalNode
-     * @returns {IStringArrayStorageItemData | undefined}
-     */
-    public getItemDataForLiteralNode (literalNode: ESTree.Literal): IStringArrayStorageItemData | undefined {
-        return this.stringArrayStorageData.get(literalNode);
-    }
-
     /**
      * @param {Literal} literalNode
      * @param {Node} parentNode
      */
-    private analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
+    public analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
         if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return;
         }
@@ -111,12 +103,27 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
             return;
         }
 
+        this.addItemDataForLiteralNode(literalNode);
+    }
+
+    /**
+     * @param {(SimpleLiteral & {value: string}) | (RegExpLiteral & {value: string})} literalNode
+     */
+    public addItemDataForLiteralNode (literalNode: ESTree.Literal & {value: string}): void {
         this.stringArrayStorageData.set(
             literalNode,
             this.stringArrayStorage.getOrThrow(literalNode.value)
         );
     }
 
+    /**
+     * @param {Literal} literalNode
+     * @returns {IStringArrayStorageItemData | undefined}
+     */
+    public getItemDataForLiteralNode (literalNode: ESTree.Literal): IStringArrayStorageItemData | undefined {
+        return this.stringArrayStorageData.get(literalNode);
+    }
+
     /**
      * @param {(SimpleLiteral & {value: string})} literalNode
      * @returns {boolean}

+ 5 - 0
src/container/modules/node-transformers/StringArrayTransformersModule.ts

@@ -5,11 +5,16 @@ import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTra
 
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 
+import { StringArrayRotateFunctionTransformer } from '../../../node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer';
 import { StringArrayScopeCallsWrapperTransformer } from '../../../node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer';
 import { StringArrayTransformer } from '../../../node-transformers/string-array-transformers/StringArrayTransformer';
 
 export const stringArrayTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // strings transformers
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(StringArrayRotateFunctionTransformer)
+        .whenTargetNamed(NodeTransformer.StringArrayRotateFunctionTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(StringArrayScopeCallsWrapperTransformer)
         .whenTargetNamed(NodeTransformer.StringArrayScopeCallsWrapperTransformer);

+ 1 - 0
src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts

@@ -25,6 +25,7 @@ export class StringArrayCallsWrapperBase64CodeHelper extends StringArrayCallsWra
                 atobPolyfill,
                 atobFunctionName,
                 selfDefendingCode,
+                stringArrayName: this.stringArrayName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
             }
         );

+ 1 - 0
src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts

@@ -29,6 +29,7 @@ export class StringArrayCallsWrapperRc4CodeHelper extends StringArrayCallsWrappe
                 atobPolyfill,
                 rc4Polyfill,
                 selfDefendingCode,
+                stringArrayName: this.stringArrayName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
             }
         );

+ 21 - 40
src/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.ts

@@ -1,3 +1,4 @@
+import type { Expression } from 'estree';
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
@@ -6,37 +7,35 @@ import { TStatement } from '../../types/node/TStatement';
 
 import { ICustomCodeHelperFormatter } from '../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter';
 import { ICustomCodeHelperObfuscator } from '../../interfaces/custom-code-helpers/ICustomCodeHelperObfuscator';
-import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { SelfDefendingTemplate } from './templates/string-array-rotate-function/SelfDefendingTemplate';
 import { StringArrayRotateFunctionTemplate } from './templates/string-array-rotate-function/StringArrayRotateFunctionTemplate';
 
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { NodeUtils } from '../../node/NodeUtils';
-import { NumberUtils } from '../../utils/NumberUtils';
 
 @injectable()
 export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelper {
     /**
-     * @type {string}
+     * @type {number}
      */
     @initializable()
-    private stringArrayName!: string;
+    private comparisonValue!: number;
 
     /**
-     * @param {number}
+     * @type {Expression}
      */
     @initializable()
-    private stringArrayRotationAmount!: number;
+    private comparisonExpressionNode!: Expression;
 
     /**
-     * @type {IEscapeSequenceEncoder}
+     * @type {string}
      */
-    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
+    @initializable()
+    private stringArrayName!: string;
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -44,7 +43,6 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
      * @param {ICustomCodeHelperObfuscator} customCodeHelperObfuscator
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
-     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
      */
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
@@ -52,8 +50,7 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
         @inject(ServiceIdentifiers.ICustomCodeHelperFormatter) customCodeHelperFormatter: ICustomCodeHelperFormatter,
         @inject(ServiceIdentifiers.ICustomCodeHelperObfuscator) customCodeHelperObfuscator: ICustomCodeHelperObfuscator,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(
             identifierNamesGeneratorFactory,
@@ -62,20 +59,21 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
             randomGenerator,
             options
         );
-
-        this.escapeSequenceEncoder = escapeSequenceEncoder;
     }
 
     /**
      * @param {string} stringArrayName
-     * @param {number} stringArrayRotationAmount
+     * @param {number} comparisonValue
+     * @param {Expression} comparisonExpressionNode
      */
     public initialize (
         stringArrayName: string,
-        stringArrayRotationAmount: number
+        comparisonValue: number,
+        comparisonExpressionNode: Expression
     ): void {
         this.stringArrayName = stringArrayName;
-        this.stringArrayRotationAmount = stringArrayRotationAmount;
+        this.comparisonValue = comparisonValue;
+        this.comparisonExpressionNode = comparisonExpressionNode;
     }
 
     /**
@@ -90,31 +88,14 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
      * @returns {string}
      */
     protected getCodeHelperTemplate (): string {
-        const timesName: string = this.identifierNamesGenerator.generateNext();
-        const whileFunctionName: string = this.identifierNamesGenerator.generateNext();
-        const preservedNames: string[] = [`^${this.stringArrayName}$`];
-
-        let code: string = '';
-
-        if (this.options.selfDefending) {
-            code = this.customCodeHelperFormatter.formatTemplate(SelfDefendingTemplate(this.escapeSequenceEncoder), {
-                timesName,
-                whileFunctionName
-            });
-        } else {
-            code = `${whileFunctionName}(++${timesName})`;
-        }
+        const comparisonExpressionCode: string = NodeUtils.convertStructureToCode([this.comparisonExpressionNode]);
 
-        return this.customCodeHelperObfuscator.obfuscateTemplate(
-            this.customCodeHelperFormatter.formatTemplate(StringArrayRotateFunctionTemplate(), {
-                code,
-                timesName,
-                whileFunctionName,
-                stringArrayName: this.stringArrayName,
-                stringArrayRotationAmount: NumberUtils.toHex(this.stringArrayRotationAmount)
-            }),
+        return this.customCodeHelperFormatter.formatTemplate(
+            StringArrayRotateFunctionTemplate(),
             {
-                reservedNames: preservedNames
+                comparisonExpressionCode,
+                comparisonValue: this.comparisonValue,
+                stringArrayName: this.stringArrayName
             }
         );
     }

+ 0 - 23
src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts

@@ -22,7 +22,6 @@ import { AbstractCustomCodeHelperGroup } from '../../AbstractCustomCodeHelperGro
 import { NodeAppender } from '../../../node/NodeAppender';
 import { StringArrayCallsWrapperCodeHelper } from '../StringArrayCallsWrapperCodeHelper';
 import { StringArrayCodeHelper } from '../StringArrayCodeHelper';
-import { StringArrayRotateFunctionCodeHelper } from '../StringArrayRotateFunctionCodeHelper';
 
 @injectable()
 export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
@@ -102,14 +101,6 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
                 }
             );
         }
-
-        // stringArrayRotateFunction helper nodes append
-        this.appendCustomNodeIfExist(
-            CustomCodeHelper.StringArrayRotateFunction,
-            (customCodeHelper: ICustomCodeHelper<TInitialData<StringArrayRotateFunctionCodeHelper>>) => {
-                NodeAppender.insertAtIndex(nodeWithStatements, customCodeHelper.getNode(), 1);
-            }
-        );
     }
 
     public initialize (): void {
@@ -133,7 +124,6 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
             const stringArrayCallsWrapperCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>> =
                 this.customCodeHelperFactory(stringArrayCallsWrapperCodeHelperName);
             const stringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName(stringArrayEncoding);
-
             stringArrayCallsWrapperCodeHelper.initialize(
                 stringArrayName,
                 stringArrayCallsWrapperName,
@@ -142,19 +132,6 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
 
             this.customCodeHelpers.set(stringArrayCallsWrapperCodeHelperName, stringArrayCallsWrapperCodeHelper);
         }
-
-        // stringArrayRotateFunction helper initialize
-        const stringArrayRotateFunctionCodeHelper: ICustomCodeHelper<TInitialData<StringArrayRotateFunctionCodeHelper>> =
-            this.customCodeHelperFactory(CustomCodeHelper.StringArrayRotateFunction);
-
-        stringArrayRotateFunctionCodeHelper.initialize(
-            stringArrayName,
-            this.stringArrayStorage.getRotationAmount()
-        );
-
-        if (this.options.rotateStringArray) {
-            this.customCodeHelpers.set(CustomCodeHelper.StringArrayRotateFunction, stringArrayRotateFunctionCodeHelper);
-        }
     }
 
     /**

+ 4 - 2
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate.ts

@@ -33,13 +33,15 @@ export function StringArrayBase64DecodeTemplate (
             {stringArrayCallsWrapperName}.${initializedIdentifier} = true;
         }
                   
-        const cachedValue = {stringArrayCallsWrapperName}.${dataIdentifier}[index];
+        const firstValue = {stringArrayName}[0];
+        const cacheKey = index + firstValue;
+        const cachedValue = {stringArrayCallsWrapperName}.${dataIdentifier}[cacheKey];
                         
         if (cachedValue === undefined) {
             {selfDefendingCode}
             
             value = {stringArrayCallsWrapperName}.${base64DecodeFunctionIdentifier}(value);
-            {stringArrayCallsWrapperName}.${dataIdentifier}[index] = value;
+            {stringArrayCallsWrapperName}.${dataIdentifier}[cacheKey] = value;
         } else {
             value = cachedValue;
         }

+ 5 - 3
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate.ts

@@ -13,7 +13,7 @@ export function StringArrayRC4DecodeTemplate (
     const rc4Identifier: string = randomGenerator.getRandomString(identifierLength);
     const dataIdentifier: string = randomGenerator.getRandomString(identifierLength);
     const onceIdentifier: string = randomGenerator.getRandomString(identifierLength);
-  
+
     return `
         if ({stringArrayCallsWrapperName}.${initializedIdentifier} === undefined) {
             {atobPolyfill}
@@ -26,7 +26,9 @@ export function StringArrayRC4DecodeTemplate (
             {stringArrayCallsWrapperName}.${initializedIdentifier} = true;
         }
   
-        const cachedValue = {stringArrayCallsWrapperName}.${dataIdentifier}[index];
+        const firstValue = {stringArrayName}[0];
+        const cacheKey = index + firstValue;
+        const cachedValue = {stringArrayCallsWrapperName}.${dataIdentifier}[cacheKey];
 
         if (cachedValue === undefined) {
             if ({stringArrayCallsWrapperName}.${onceIdentifier} === undefined) {
@@ -36,7 +38,7 @@ export function StringArrayRC4DecodeTemplate (
             }
             
             value = {stringArrayCallsWrapperName}.${rc4Identifier}(value, key);
-            {stringArrayCallsWrapperName}.${dataIdentifier}[index] = value;
+            {stringArrayCallsWrapperName}.${dataIdentifier}[cacheKey] = value;
         } else {
             value = cachedValue;
         }

+ 0 - 81
src/custom-code-helpers/string-array/templates/string-array-rotate-function/SelfDefendingTemplate.ts

@@ -1,81 +0,0 @@
-import { IEscapeSequenceEncoder } from '../../../../interfaces/utils/IEscapeSequenceEncoder';
-
-/**
- * SelfDefendingTemplate. Enter code in infinity loop.
- *
- * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
- * @returns {string}
- */
-export function SelfDefendingTemplate (escapeSequenceEncoder: IEscapeSequenceEncoder): string {
-    return `
-        const selfDefendingFunc = function () {
-            const object = {
-                data: {
-                    key: 'cookie',
-                    value: 'timeout'
-                },
-                setCookie: function (options, name, value, document) {
-                    document = document || {};
-                    
-                    let updatedCookie = name + "=" + value;
-                    let i = 0;
-                                                            
-                    for (let i = 0, len = options.length; i < len; i++) {
-                        const propName = options[i];
-                                     
-                        updatedCookie += "; " + propName;
-                        
-                        const propValue = options[propName];
-                        
-                        options.push(propValue);
-                        len = options.length;
-                                                                        
-                        if (propValue !== true) {
-                            updatedCookie += "=" + propValue;
-                        }
-                    }
-
-                    document['cookie'] = updatedCookie;
-                },
-                removeCookie: function(){return 'dev';},
-                getCookie: function (document, name) {
-                    document = document || function (value) { return value };
-                    const matches = document(new RegExp(
-                        "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
-                    ));
-                    
-                    const func = function (param1, param2) {
-                        param1(++param2);
-                    };
-                    
-                    func({whileFunctionName}, {timesName});
-                                        
-                    return matches ? decodeURIComponent(matches[1]) : undefined;
-                }
-            };
-            
-            const test1 = function () {
-                const regExp = new RegExp('${
-                    escapeSequenceEncoder.encode('\\w+ *\\(\\) *{\\w+ *[\'|"].+[\'|"];? *}', true)
-                }');
-                
-                return regExp.test(object.removeCookie.toString());
-            };
-            
-            object['updateCookie'] = test1;
-            
-            let cookie = '';
-            const result = object['updateCookie']();
-                                    
-            if (!result) {
-                object['setCookie'](['*'], 'counter', 1);
-            } else if (result) {
-                cookie = object['getCookie'](null, 'counter');
-            } else {
-                object['removeCookie']();
-            }
-        };
-        
-        selfDefendingFunc();
-    `;
-}

+ 13 - 7
src/custom-code-helpers/string-array/templates/string-array-rotate-function/StringArrayRotateFunctionTemplate.ts

@@ -3,14 +3,20 @@
  */
 export function StringArrayRotateFunctionTemplate (): string {
     return `
-        (function (array, {timesName}) {
-            const {whileFunctionName} = function (times) {
-                while (--times) {
+        (function (array, comparisonValue) {
+            while (true) {
+                try {
+                    const expression = {comparisonExpressionCode};
+                                            
+                    if (expression === comparisonValue) {
+                        break;
+                    } else {
+                        array['push'](array['shift']());
+                    }
+                } catch (e) {
                     array['push'](array['shift']());
                 }
-            };
-            
-            {code}
-        })({stringArrayName}, {stringArrayRotationAmount});
+            }
+        })({stringArrayName}, {comparisonValue});
     `;
 }

+ 1 - 0
src/enums/node-transformers/NodeTransformer.ts

@@ -28,6 +28,7 @@ export enum NodeTransformer {
     ScopeThroughIdentifiersTransformer = 'ScopeThroughIdentifiersTransformer',
     SplitStringTransformer = 'SplitStringTransformer',
     StringArrayTransformer = 'StringArrayTransformer',
+    StringArrayRotateFunctionTransformer = 'StringArrayRotateFunctionTransformer',
     StringArrayScopeCallsWrapperTransformer = 'StringArrayScopeCallsWrapperTransformer',
     TemplateLiteralTransformer = 'TemplateLiteralTransformer',
     VariableDeclarationsMergeTransformer = 'VariableDeclarationsMergeTransformer',

+ 6 - 2
src/interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer.ts

@@ -2,10 +2,14 @@ import { TNumberNumericalExpressionData } from '../../../types/analyzers/number-
 
 import { IAnalyzer } from '../IAnalyzer';
 
-export interface INumberNumericalExpressionAnalyzer extends IAnalyzer<[number], TNumberNumericalExpressionData> {
+export interface INumberNumericalExpressionAnalyzer extends IAnalyzer<[number, number], TNumberNumericalExpressionData> {
     /**
      * @param {number} number
+     * @param {number} additionalPartsCount
      * @returns {TNumberNumericalExpressionData}
      */
-    analyze (number: number): TNumberNumericalExpressionData;
+    analyze (
+        number: number,
+        additionalPartsCount: number
+    ): TNumberNumericalExpressionData;
 }

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

@@ -9,6 +9,17 @@ export interface IStringArrayStorageAnalyzer extends IAnalyzer<[ESTree.Program],
      */
     analyze (astTree: ESTree.Program): void;
 
+    /**
+     * @param {Literal} literalNode
+     * @param {Node} parentNode
+     */
+    analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void;
+
+    /**
+     * @param {(SimpleLiteral & {value: string}) | (RegExpLiteral & {value: string})} literalNode
+     */
+    addItemDataForLiteralNode (literalNode: ESTree.Literal & {value: string}): void;
+
     /**
      * @param {Literal} literalNode
      * @returns {IStringArrayStorageItemData | undefined}

+ 20 - 87
src/node-transformers/converting-transformers/NumberToNumericalExpressionTransformer.ts

@@ -15,7 +15,8 @@ import { NodeTransformationStage } from '../../enums/node-transformers/NodeTrans
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeFactory } from '../../node/NodeFactory';
-import { NumberUtils } from '../../utils/NumberUtils';
+import { NumberNumericalExpressionAnalyzer } from '../../analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer';
+import { NumericalExpressionDataToNodeConverter } from '../../node/NumericalExpressionDataToNodeConverter';
 
 /**
  * replaces:
@@ -85,91 +86,23 @@ export class NumberToNumericalExpressionTransformer extends AbstractNodeTransfor
             return literalNode;
         }
 
-        const numberNumericalExpressionData: TNumberNumericalExpressionData = this.numberNumericalExpressionAnalyzer.analyze(literalNode.value);
-
-        return this.convertNumericalExpressionDataToNode(numberNumericalExpressionData);
-    }
-
-    /**
-     * @param {TNumberNumericalExpressionData} numberNumericalExpressionData
-     * @param {ESTree.BinaryOperator} operator
-     * @returns {ESTree.BinaryExpression | ESTree.Literal | ESTree.UnaryExpression}
-     */
-    private convertNumericalExpressionDataToNode (
-        numberNumericalExpressionData: TNumberNumericalExpressionData,
-        operator: ESTree.BinaryOperator = '+'
-    ): ESTree.BinaryExpression | ESTree.Literal | ESTree.UnaryExpression {
-        const numberNumericalExpressionDataLength: number = numberNumericalExpressionData.length;
-
-        const leftParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
-            ? numberNumericalExpressionData.slice(0, numberNumericalExpressionDataLength - 1)
-            : [numberNumericalExpressionData[0]];
-        const rightParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
-            ? numberNumericalExpressionData.slice(-1)
-            : [];
-
-        // trailing iterations
-        if (rightParts.length) {
-            return this.convertPartsToBinaryExpression(operator, leftParts, rightParts);
-        }
-
-        const firstLeftPartOrNumber: number | number[] | null = leftParts[0] ?? null;
-
-        // last iteration when only single left part is left
-        return Array.isArray(firstLeftPartOrNumber)
-            ? this.convertNumericalExpressionDataToNode(firstLeftPartOrNumber, '*')
-            : this.convertPartOrNumberToLiteralNode(firstLeftPartOrNumber);
-    }
-
-    /**
-     * @param {ESTree.BinaryOperator} operator
-     * @param {TNumberNumericalExpressionData} leftParts
-     * @param {TNumberNumericalExpressionData} rightParts
-     * @returns {ESTree.BinaryExpression}
-     */
-    private convertPartsToBinaryExpression (
-        operator: ESTree.BinaryOperator,
-        leftParts: TNumberNumericalExpressionData,
-        rightParts: TNumberNumericalExpressionData
-    ): ESTree.BinaryExpression {
-        const rightPartOrNumber: number | number[] = rightParts[0];
-
-        if (Array.isArray(rightPartOrNumber)) {
-            // right part is array with multiply numbers
-            return NodeFactory.binaryExpressionNode(
-                operator,
-                this.convertNumericalExpressionDataToNode(leftParts),
-                this.convertNumericalExpressionDataToNode(rightPartOrNumber, '*')
-            );
-        } else {
-            // right part is number
-            return NodeFactory.binaryExpressionNode(
-                operator,
-                this.convertNumericalExpressionDataToNode(leftParts),
-                this.convertPartOrNumberToLiteralNode(rightPartOrNumber)
-            );
-        }
-    }
-
-    /**
-     * @param {number | number[]} partOrNumber
-     * @returns {ESTree.Literal}
-     */
-    private convertPartOrNumberToLiteralNode (
-        partOrNumber: number | number[]
-    ): ESTree.Literal | ESTree.UnaryExpression {
-        const number: number = Array.isArray(partOrNumber)
-            ? partOrNumber[0]
-            : partOrNumber;
-        const isPositiveNumber: boolean = NumberUtils.isPositive(number);
-
-        const literalNode: ESTree.Literal = NodeFactory.literalNode(Math.abs(number));
-
-        return isPositiveNumber
-            ? literalNode
-            : NodeFactory.unaryExpressionNode(
-                '-',
-                literalNode
-            );
+        const numberNumericalExpressionData: TNumberNumericalExpressionData = this.numberNumericalExpressionAnalyzer.analyze(
+            literalNode.value,
+            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+        );
+
+        return NumericalExpressionDataToNodeConverter.convert(
+            numberNumericalExpressionData,
+            (number: number, isPositiveNumber: boolean) => {
+                const numberLiteralNode: ESTree.Literal = NodeFactory.literalNode(number);
+
+                return isPositiveNumber
+                    ? numberLiteralNode
+                    : NodeFactory.unaryExpressionNode(
+                        '-',
+                        numberLiteralNode
+                    );
+            }
+        );
     }
 }

+ 257 - 0
src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer.ts

@@ -0,0 +1,257 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TCustomCodeHelperFactory } from '../../types/container/custom-code-helpers/TCustomCodeHelperFactory';
+import { TInitialData } from '../../types/TInitialData';
+import { TNumberNumericalExpressionData } from '../../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+import { TStatement } from '../../types/node/TStatement';
+
+import { ICustomCodeHelper } from '../../interfaces/custom-code-helpers/ICustomCodeHelper';
+import { INodeTransformersRunner } from '../../interfaces/node-transformers/INodeTransformersRunner';
+import { INumberNumericalExpressionAnalyzer } from '../../interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
+import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { CustomCodeHelper } from '../../enums/custom-code-helpers/CustomCodeHelper';
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeAppender } from '../../node/NodeAppender';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeFactory } from '../../node/NodeFactory';
+import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
+import { NodeMetadata } from '../../node/NodeMetadata';
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+import { NodeUtils } from '../../node/NodeUtils';
+import { NumericalExpressionDataToNodeConverter } from '../../node/NumericalExpressionDataToNodeConverter';
+import { StringArrayRotateFunctionCodeHelper } from '../../custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper';
+
+@injectable()
+export class StringArrayRotateFunctionTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {NodeTransformer[]}
+     */
+    private static readonly stringArrayRotateFunctionTransformers: NodeTransformer[] = [
+        NodeTransformer.BooleanLiteralTransformer,
+        NodeTransformer.MemberExpressionTransformer,
+        NodeTransformer.NumberLiteralTransformer,
+        NodeTransformer.NumberToNumericalExpressionTransformer,
+        NodeTransformer.ParentificationTransformer,
+        NodeTransformer.ScopeIdentifiersTransformer
+    ];
+
+    /**
+     * @type {number}
+     */
+    private static readonly comparisonExpressionAdditionalPartsCount: number = 7;
+
+    /**
+     * @type {INumberNumericalExpressionAnalyzer}
+     */
+    private readonly numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer;
+
+    /**
+     * @type {IStringArrayStorage}
+     */
+    private readonly stringArrayStorage: IStringArrayStorage;
+
+    /**
+     * @type {IStringArrayStorageAnalyzer}
+     */
+    private readonly stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer;
+
+    /**
+     * @type {TCustomCodeHelperFactory}
+     */
+    private readonly customCodeHelperFactory: TCustomCodeHelperFactory;
+
+    /**
+     * @type {INodeTransformersRunner}
+     */
+    private readonly transformersRunner: INodeTransformersRunner;
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     * @param {INodeTransformersRunner} transformersRunner
+     * @param {IStringArrayStorage} stringArrayStorage
+     * @param {IStringArrayStorageAnalyzer} stringArrayStorageAnalyzer
+     * @param {TCustomCodeHelperFactory} customCodeHelperFactory
+     * @param {INumberNumericalExpressionAnalyzer} numberNumericalExpressionAnalyzer
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.INodeTransformersRunner) transformersRunner: INodeTransformersRunner,
+        @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
+        @inject(ServiceIdentifiers.IStringArrayStorageAnalyzer) stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer,
+        @inject(ServiceIdentifiers.Factory__ICustomCodeHelper) customCodeHelperFactory: TCustomCodeHelperFactory,
+        @inject(ServiceIdentifiers.INumberNumericalExpressionAnalyzer)
+            numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer
+    ) {
+        super(randomGenerator, options);
+
+        this.stringArrayStorage = stringArrayStorage;
+        this.stringArrayStorageAnalyzer = stringArrayStorageAnalyzer;
+        this.transformersRunner = transformersRunner;
+        this.customCodeHelperFactory = customCodeHelperFactory;
+        this.numberNumericalExpressionAnalyzer = numberNumericalExpressionAnalyzer;
+    }
+
+    /**
+     * Because this transformer runs BEFORE string array analyzer we can't check string array storage length.
+     * So we have to traverse over program node and check if it has any string literal node.
+     *
+     * @param {Program} programNode
+     * @returns {boolean}
+     */
+    private static isProgramNodeHasStringLiterals (programNode: ESTree.Program): boolean {
+        let hasStringLiterals: boolean = false;
+
+        estraverse.traverse(programNode, {
+            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
+                if (
+                    NodeGuards.isLiteralNode(node)
+                    && NodeLiteralUtils.isStringLiteralNode(node)
+                ) {
+                    hasStringLiterals = true;
+
+                    return estraverse.VisitorOption.Break;
+                }
+            }
+        });
+
+        return hasStringLiterals;
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        if (!this.options.rotateStringArray) {
+            return null;
+        }
+
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.StringArray:
+                return {
+                    enter: (node: ESTree.Node): ESTree.Node | estraverse.VisitorOption => {
+                        if (!NodeGuards.isProgramNode(node)) {
+                            return node;
+                        }
+
+                        if (!StringArrayRotateFunctionTransformer.isProgramNodeHasStringLiterals(node)) {
+                            return estraverse.VisitorOption.Break;
+                        }
+
+                        return this.transformNode(node);
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {Program} programNode
+     * @returns {Node}
+     */
+    public transformNode (programNode: ESTree.Program): ESTree.Node {
+        const stringArrayRotateFunctionNode: TStatement = this.getStringArrayRotateFunctionNode();
+        const wrappedStringArrayRotateFunctionNode: ESTree.Program = NodeFactory.programNode([
+            stringArrayRotateFunctionNode
+        ]);
+
+        NodeUtils.parentizeAst(wrappedStringArrayRotateFunctionNode);
+
+        const transformationStages: NodeTransformationStage[] = [
+            NodeTransformationStage.Preparing,
+            NodeTransformationStage.Converting,
+            NodeTransformationStage.RenameIdentifiers,
+            NodeTransformationStage.Finalizing
+        ];
+
+        // custom transformation of string array rotate function node
+        for (const transformationStage of transformationStages) {
+            this.transformersRunner.transform(
+                wrappedStringArrayRotateFunctionNode,
+                StringArrayRotateFunctionTransformer.stringArrayRotateFunctionTransformers,
+                transformationStage
+            );
+        }
+
+        // mark all child nodes (except literals inside comparison expression)
+        // as ignored to prevent additional transformation of these nodes
+        estraverse.traverse(wrappedStringArrayRotateFunctionNode, {
+            enter: (node: ESTree.Node): void => {
+                if (
+                    !NodeGuards.isLiteralNode(node)
+                    || !NodeLiteralUtils.isStringLiteralNode(node)
+                ) {
+                   return;
+                }
+
+                if (/\d/.test(node.value)) {
+                    this.stringArrayStorageAnalyzer.addItemDataForLiteralNode(node);
+                } else {
+                    NodeMetadata.set(node, {ignoredNode: true});
+                }
+            }
+        });
+
+        NodeAppender.prepend(programNode, [stringArrayRotateFunctionNode]);
+
+        return programNode;
+    }
+
+    /**
+     * @returns {TStatement}
+     */
+    private getStringArrayRotateFunctionNode (): TStatement {
+        const comparisonValue: number = this.randomGenerator.getRandomInteger(100000, 1_000_000);
+        const comparisonExpressionNumberNumericalExpressionData: TNumberNumericalExpressionData =
+            this.numberNumericalExpressionAnalyzer.analyze(
+                comparisonValue,
+                StringArrayRotateFunctionTransformer.comparisonExpressionAdditionalPartsCount
+            );
+
+        const comparisonExpressionNode: ESTree.Expression = NumericalExpressionDataToNodeConverter.convert(
+            comparisonExpressionNumberNumericalExpressionData,
+            ((number: number, isPositiveNumber) => {
+                const literalNode: ESTree.Literal = NodeFactory.literalNode(
+                    `${number}${this.randomGenerator.getRandomString(6)}`
+                );
+                const parseIntCallExpression: ESTree.CallExpression = NodeFactory.callExpressionNode(
+                    NodeFactory.identifierNode('parseInt'),
+                    [literalNode]
+                );
+
+                return isPositiveNumber
+                    ? parseIntCallExpression
+                    : NodeFactory.unaryExpressionNode(
+                        '-',
+                        parseIntCallExpression
+                    );
+            })
+        );
+
+        const stringArrayRotateFunctionCodeHelper: ICustomCodeHelper<TInitialData<StringArrayRotateFunctionCodeHelper>> =
+            this.customCodeHelperFactory(CustomCodeHelper.StringArrayRotateFunction);
+
+        stringArrayRotateFunctionCodeHelper.initialize(
+            this.stringArrayStorage.getStorageName(),
+            comparisonValue,
+            comparisonExpressionNode
+        );
+
+        return stringArrayRotateFunctionCodeHelper.getNode()[0];
+    }
+}

+ 8 - 0
src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts

@@ -22,6 +22,7 @@ import { IStringArrayStorage } from '../../interfaces/storages/string-array-tran
 import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages/string-array-transformers/IVisitedLexicalScopeNodesStackStorage';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 import { StringArrayCustomNode } from '../../enums/custom-nodes/StringArrayCustomNode';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -34,6 +35,13 @@ import { StringArrayScopeCallsWrapperVariableNode } from '../../custom-nodes/str
 
 @injectable()
 export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {NodeTransformer[]}
+     */
+    public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.StringArrayRotateFunctionTransformer
+    ];
+
     /**
      * @type {IStringArrayStorage}
      */

+ 11 - 1
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -26,6 +26,7 @@ import { IStringArrayStorageItemData } from '../../interfaces/storages/string-ar
 import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages/string-array-transformers/IVisitedLexicalScopeNodesStackStorage';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 import { StringArrayCustomNode } from '../../enums/custom-nodes/StringArrayCustomNode';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -49,6 +50,13 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      */
     private static readonly maxShiftedIndexValue: number = 1000;
 
+    /**
+     * @type {NodeTransformer[]}
+     */
+    public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.StringArrayRotateFunctionTransformer
+    ];
+
 
     /**
      * @type {IIdentifierNamesGenerator}
@@ -158,7 +166,9 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @param {Program} programNode
      */
     public prepareNode (programNode: ESTree.Program): void {
-        this.stringArrayStorageAnalyzer.analyze(programNode);
+        if (this.options.stringArray) {
+            this.stringArrayStorageAnalyzer.analyze(programNode);
+        }
 
         if (this.options.shuffleStringArray) {
             this.stringArrayStorage.shuffleStorage();

+ 0 - 53
src/node/NodeFactory.ts

@@ -34,25 +34,6 @@ export class NodeFactory {
         };
     }
 
-    /**
-     * @param {Pattern[]} params
-     * @param {boolean} expression
-     * @param {BlockStatement | Expression} body
-     * @returns {ArrowFunctionExpression}
-     */
-    public static arrowFunctionExpressionNode (
-        params: ESTree.Pattern[],
-        expression: boolean,
-        body: ESTree.BlockStatement | ESTree.Expression
-    ): ESTree.ArrowFunctionExpression {
-        return {
-            type: NodeType.ArrowFunctionExpression,
-            params,
-            expression,
-            body
-        };
-    }
-
     /**
      * @param {AssignmentOperator} operator
      * @param {Pattern | MemberExpression} left
@@ -505,40 +486,6 @@ export class NodeFactory {
         };
     }
 
-    /**
-     * @param {ESTree.TemplateElement["value"]} value
-     * @param {boolean} tail
-     * @returns {ESTree.TemplateElement}
-     */
-    public static templateElement (
-        value: ESTree.TemplateElement['value'],
-        tail: boolean
-    ): ESTree.TemplateElement {
-        return {
-            type: NodeType.TemplateElement,
-            value,
-            tail,
-            metadata: { ignoredNode: false }
-        };
-    }
-
-    /**
-     * @param {ESTree.Expression[]} expressions
-     * @param {ESTree.TemplateElement[]} quasis
-     * @returns {ESTree.TemplateLiteral}
-     */
-    public static templateLiteral (
-        expressions: ESTree.Expression[],
-        quasis: ESTree.TemplateElement[]
-    ): ESTree.TemplateLiteral {
-        return {
-            type: NodeType.TemplateLiteral,
-            expressions,
-            quasis,
-            metadata: { ignoredNode: false }
-        };
-    }
-
     /**
      * @param {VariableDeclarator[]} declarations
      * @param {string} kind

+ 131 - 0
src/node/NumericalExpressionDataToNodeConverter.ts

@@ -0,0 +1,131 @@
+import * as ESTree from 'estree';
+
+import { TNumericalExpressionDataToNodeConverterLiteralNodeGetter } from '../types/node/TNumericalExpressionDataToNodeConverterLiteralNodeGetter';
+import { TNumberNumericalExpressionData } from '../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+import { NodeFactory } from './NodeFactory';
+import { NumberUtils } from '../utils/NumberUtils';
+
+/**
+ * Converts NumberNumericalExpressionData to node
+ */
+export class NumericalExpressionDataToNodeConverter {
+    /**
+     * @param {TNumberNumericalExpressionData} numberNumericalExpressionData
+     * @param {TNumericalExpressionDataToNodeConverterLiteralNodeGetter} literalNodeGetter
+     * @returns {Expression}
+     */
+    public static convert (
+        numberNumericalExpressionData: TNumberNumericalExpressionData,
+        literalNodeGetter: TNumericalExpressionDataToNodeConverterLiteralNodeGetter
+    ): ESTree.Expression {
+       return NumericalExpressionDataToNodeConverter.convertNumericalExpressionDataToNode(
+           numberNumericalExpressionData,
+           literalNodeGetter
+       );
+    }
+
+    /**
+     * @param {TNumberNumericalExpressionData} numberNumericalExpressionData
+     * @param {TNumericalExpressionDataToNodeConverterLiteralNodeGetter} literalNodeGetter
+     * @param {BinaryOperator} operator
+     * @returns {Expression}
+     */
+    private static convertNumericalExpressionDataToNode (
+        numberNumericalExpressionData: TNumberNumericalExpressionData,
+        literalNodeGetter: TNumericalExpressionDataToNodeConverterLiteralNodeGetter,
+        operator: ESTree.BinaryOperator = '+'
+    ): ESTree.Expression {
+        const numberNumericalExpressionDataLength: number = numberNumericalExpressionData.length;
+
+        const leftParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
+            ? numberNumericalExpressionData.slice(0, numberNumericalExpressionDataLength - 1)
+            : [numberNumericalExpressionData[0]];
+        const rightParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
+            ? numberNumericalExpressionData.slice(-1)
+            : [];
+
+        // trailing iterations
+        if (rightParts.length) {
+            return NumericalExpressionDataToNodeConverter
+                .convertPartsToBinaryExpression(operator, leftParts, rightParts, literalNodeGetter);
+        }
+
+        const firstLeftPartOrNumber: number | number[] | null = leftParts[0] ?? null;
+
+        // last iteration when only single left part is left
+        return Array.isArray(firstLeftPartOrNumber)
+            ? NumericalExpressionDataToNodeConverter.convertNumericalExpressionDataToNode(
+                firstLeftPartOrNumber,
+                literalNodeGetter,
+                '*'
+            )
+            : NumericalExpressionDataToNodeConverter.convertPartOrNumberToLiteralNode(
+                firstLeftPartOrNumber,
+                literalNodeGetter
+            );
+    }
+
+    /**
+     * @param {BinaryOperator} operator
+     * @param {TNumberNumericalExpressionData} leftParts
+     * @param {TNumberNumericalExpressionData} rightParts
+     * @param {TNumericalExpressionDataToNodeConverterLiteralNodeGetter} literalNodeGetter
+     * @returns {BinaryExpression}
+     */
+    private static convertPartsToBinaryExpression (
+        operator: ESTree.BinaryOperator,
+        leftParts: TNumberNumericalExpressionData,
+        rightParts: TNumberNumericalExpressionData,
+        literalNodeGetter: TNumericalExpressionDataToNodeConverterLiteralNodeGetter
+    ): ESTree.BinaryExpression {
+        const rightPartOrNumber: number | number[] = rightParts[0];
+
+        if (Array.isArray(rightPartOrNumber)) {
+            // right part is array with multiply numbers
+            return NodeFactory.binaryExpressionNode(
+                operator,
+                NumericalExpressionDataToNodeConverter.convertNumericalExpressionDataToNode(
+                    leftParts,
+                    literalNodeGetter
+                ),
+                NumericalExpressionDataToNodeConverter.convertNumericalExpressionDataToNode(
+                    rightPartOrNumber,
+                    literalNodeGetter,
+                    '*'
+                )
+            );
+        } else {
+            // right part is number
+            return NodeFactory.binaryExpressionNode(
+                operator,
+                NumericalExpressionDataToNodeConverter.convertNumericalExpressionDataToNode(
+                    leftParts,
+                    literalNodeGetter
+                ),
+                this.convertPartOrNumberToLiteralNode(
+                    rightPartOrNumber,
+                    literalNodeGetter
+                )
+            );
+        }
+    }
+
+    /**
+     * @param {number | number[]} partOrNumber
+     * @param {TNumericalExpressionDataToNodeConverterLiteralNodeGetter} literalNodeGetter
+     * @returns {Expression}
+     */
+    private static convertPartOrNumberToLiteralNode (
+        partOrNumber: number | number[],
+        literalNodeGetter: TNumericalExpressionDataToNodeConverterLiteralNodeGetter
+    ): ESTree.Expression {
+        const number: number = Array.isArray(partOrNumber)
+            ? partOrNumber[0]
+            : partOrNumber;
+        const isPositiveNumber: boolean = NumberUtils.isPositive(number);
+        const absoluteNumber: number = Math.abs(number);
+
+        return literalNodeGetter(absoluteNumber, isPositiveNumber);
+    }
+}

+ 6 - 0
src/types/node/TNumericalExpressionDataToNodeConverterLiteralNodeGetter.ts

@@ -0,0 +1,6 @@
+import * as ESTree from 'estree';
+
+export type TNumericalExpressionDataToNodeConverterLiteralNodeGetter = (
+    number: number,
+    isPositiveNumber: boolean
+) => ESTree.Expression;

+ 10 - 4
test/dev/dev.ts

@@ -7,16 +7,22 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            function hi() {
-              console.log("Hello World!");
-            }
-            hi();
+            console.log('222');
+            console.log('333');
+            console.log('444');
+            console.log('555');
+            console.log('999');
+            console.log('888');
+            console.log('777');
+            console.log('666');
+            console.log('111');
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
             stringArray: true,
             stringArrayThreshold: 1,
+            shuffleStringArray: true,
             rotateStringArray: true
         }
     ).getObfuscatedCode();

+ 31 - 5
test/functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec.ts

@@ -10,7 +10,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade
 
 describe('StringArrayRotateFunctionCodeHelper', () => {
     describe('Base behaviour', () => {
-        const regExp: RegExp = /while *\(-- *_0x([a-f0-9]){4,6}\) *\{/;
+        const regExp: RegExp = /while *\(!!\[]\) *\{/;
 
         describe('`stringArray` option is set', () => {
             let obfuscatedCode: string;
@@ -57,9 +57,35 @@ describe('StringArrayRotateFunctionCodeHelper', () => {
         });
     });
 
+    describe('Comparison expression', () => {
+        describe('Should add comparison expression to the code helper', () => {
+            const comparisonExpressionRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *-?parseInt\(_0x([a-f0-9]){4,6}\(0x.\)\)/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: true,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should add comparison expression to the code', () => {
+                assert.match(obfuscatedCode, comparisonExpressionRegExp);
+            });
+        });
+    });
+
     describe('Preserve string array name', () => {
-        const rotateLogicRegExp: RegExp = /b\['push']\(b\['shift']\(\)\);/;
-        const incrementRegExp: RegExp = /f\(\+\+e\);/;
+        const arrayRotateRegExp: RegExp = /c\['push']\(c\['shift']\(\)\);/;
+        const comparisonRegExp: RegExp = /if *\(e *=== *d\) *{/;
 
         let obfuscatedCode: string;
 
@@ -79,11 +105,11 @@ describe('StringArrayRotateFunctionCodeHelper', () => {
         });
 
         it('should preserve string array name', () => {
-            assert.match(obfuscatedCode, rotateLogicRegExp);
+            assert.match(obfuscatedCode, arrayRotateRegExp);
         });
 
         it('generate valid identifier names', () => {
-            assert.match(obfuscatedCode, incrementRegExp);
+            assert.match(obfuscatedCode, comparisonRegExp);
         });
     });
 });

+ 68 - 6
test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec.ts

@@ -10,10 +10,72 @@ import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 describe('StringArrayRotateFunctionTemplate', () => {
+    describe('Computed member expressions as array method calls', () => {
+        describe('Array push', () => {
+            const arrayPushBaseRegExp: RegExp = /_0x([a-f0-9]){4,6}\.push/;
+            const arrayPushComputedRegExp: RegExp = /_0x([a-f0-9]){4,6}\['push']/;
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        rotateStringArray: true
+                    }
+                );
+
+                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+            });
+
+            it('Should use computed member expression in `array.push` method', () => {
+                assert.match(obfuscatedCode, arrayPushComputedRegExp);
+            });
+
+            it('Should not use base member expression in `array.push` method', () => {
+                assert.notMatch(obfuscatedCode, arrayPushBaseRegExp);
+            });
+        });
+
+        describe('Array shift', () => {
+            const arrayShiftBaseRegExp: RegExp = /_0x([a-f0-9]){4,6}\.shift/;
+            const arrayShiftComputedRegExp: RegExp = /_0x([a-f0-9]){4,6}\['shift']/;
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        rotateStringArray: true
+                    }
+                );
+
+                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+            });
+
+            it('Should use computed member expression in `array.shift` method', () => {
+                assert.match(obfuscatedCode, arrayShiftComputedRegExp);
+            });
+
+            it('Should not use base member expression in `array.shift` method', () => {
+                assert.notMatch(obfuscatedCode, arrayShiftBaseRegExp);
+            });
+        });
+    });
+
     describe('Prevailing kind of variables', () => {
         describe('`var` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRotateFunctionRegExp: RegExp = /function\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\){var _0x([a-f0-9]){4,6} *= *function/;
+                stringArrayRotateFunctionTryCatchRegExp: RegExp = /try *{var *_0x([a-f0-9]){4,6}/;
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
@@ -31,7 +93,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
             });
 
             it('Should return correct kind of variables for string array rotate function', () => {
-                assert.match(obfuscatedCode, stringArrayRotateFunctionRegExp);
+                assert.match(obfuscatedCode, stringArrayRotateFunctionTryCatchRegExp);
             });
 
             it('Should does not break on obfuscating', () => {
@@ -41,7 +103,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
         describe('`const` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRotateFunctionRegExp: RegExp = /function\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\){const _0x([a-f0-9]){4,6} *= *function/;
+                stringArrayRotateFunctionTryCatchRegExp: RegExp = /try *{const *_0x([a-f0-9]){4,6}/;
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
@@ -59,7 +121,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
             });
 
             it('Should return correct kind of variables for string array rotate function', () => {
-                assert.match(obfuscatedCode, stringArrayRotateFunctionRegExp);
+                assert.match(obfuscatedCode, stringArrayRotateFunctionTryCatchRegExp);
             });
 
             it('Should does not break on obfuscating', () => {
@@ -69,7 +131,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
         describe('`let` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRotateFunctionRegExp: RegExp = /function\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\){const _0x([a-f0-9]){4,6} *= *function/;
+                stringArrayRotateFunctionTryCatchRegExp: RegExp = /try *{const *_0x([a-f0-9]){4,6}/;
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
@@ -87,7 +149,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
             });
 
             it('Should return correct kind of variables for string array rotate function', () => {
-                assert.match(obfuscatedCode, stringArrayRotateFunctionRegExp);
+                assert.match(obfuscatedCode, stringArrayRotateFunctionTryCatchRegExp);
             });
 
             it('Should does not break on obfuscating', () => {

+ 1 - 0
test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/fixtures/simple-input.js

@@ -0,0 +1 @@
+const foo = 'foo';

+ 123 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/StringArrayRotateFunctionTransformer.spec.ts

@@ -0,0 +1,123 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('StringArrayRotateFunctionTransformer', () => {
+    describe('Code helper append', () => {
+        const regExp: RegExp = /while *\(!!\[]\) *\{/;
+
+        describe('`stringArray` option is set', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: true,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should correctly append code helper into the obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('`stringArray` option isn\'t set', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: false,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('shouldn\'t append code helper into the obfuscated code', () => {
+                assert.notMatch(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('`stringArrayThreshold` option is `0.00001`', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: true,
+                        stringArray: true,
+                        stringArrayThreshold: 0.00001
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should correctly append code helper into the obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('`stringArrayThreshold` option is `0`', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: true,
+                        stringArray: true,
+                        stringArrayThreshold: 0
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should correctly append code helper into the obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('Input code has no string literals', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/no-string-literals.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: true,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('shouldn\'t append code helper into the obfuscated code', () => {
+                assert.notMatch(obfuscatedCode, regExp);
+            });
+        });
+    });
+});

+ 1 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/fixtures/no-string-literals.js

@@ -0,0 +1 @@
+var test = 123;

+ 1 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/fixtures/simple-input.js

@@ -0,0 +1 @@
+var test = 'test';

+ 0 - 12
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -259,18 +259,6 @@ describe('StringArrayTransformer', function () {
                 assert.match(obfuscatedCode, stringArrayIndexShiftRegExp);
             });
 
-            it('Match #1: should create string array call with shifted index', () => {
-                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue1);
-            });
-
-            it('Match #2: should create string array call with shifted index', () => {
-                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue2 - 1);
-            });
-
-            it('Match #3: should create string array call with shifted index', () => {
-                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue3 - 2);
-            });
-
             it('should correctly evaluate code', () => {
                 assert.equal(evaluationResult, expectedEvaluationResult);
             });

+ 22 - 23
test/functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec.ts

@@ -15,17 +15,17 @@ describe('StringArrayStorage', () => {
             const delta: number = 0.1;
             const expectedVariantProbability: number = 1;
 
-            const stringArrayVariant1RegExp1: RegExp = /var _0x([a-f0-9]){4} *= *\['test'];/g;
-            const literalNodeVariant1RegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/g;
+            const stringArrayVariantRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\[(?:'.*?', *)?'test'(?:, *'.*?')?];/g;
+            const literalNodeVariantRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x.\);/g;
 
-            let stringArrayVariant1Probability: number,
-                literalNodeVariant1Probability: number;
+            let stringArrayVariantProbability: number,
+                literalNodeVariantProbability: number;
 
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/one-string.js');
 
-                let stringArrayVariant1MatchesLength: number = 0;
-                let literalNodeVariant1MatchesLength: number = 0;
+                let stringArrayVariantMatchesLength: number = 0;
+                let literalNodeVariantMatchesLength: number = 0;
 
                for (let i = 0; i < samples; i++) {
                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
@@ -38,51 +38,50 @@ describe('StringArrayStorage', () => {
                        }
                    ).getObfuscatedCode();
 
-                   if (obfuscatedCode.match(stringArrayVariant1RegExp1)) {
-                       stringArrayVariant1MatchesLength++;
+                   if (obfuscatedCode.match(stringArrayVariantRegExp)) {
+                       stringArrayVariantMatchesLength++;
                    }
 
-                   if (obfuscatedCode.match(literalNodeVariant1RegExp)) {
-                       literalNodeVariant1MatchesLength++;
+                   if (obfuscatedCode.match(literalNodeVariantRegExp)) {
+                       literalNodeVariantMatchesLength++;
                    }
                }
 
-                stringArrayVariant1Probability = stringArrayVariant1MatchesLength / samples;
-                literalNodeVariant1Probability = literalNodeVariant1MatchesLength / samples;
+                stringArrayVariantProbability = stringArrayVariantMatchesLength / samples;
+                literalNodeVariantProbability = literalNodeVariantMatchesLength / samples;
             });
 
             describe('String array probability', () => {
                 it('Variant #1: should create single string array variant', () => {
-                    assert.closeTo(stringArrayVariant1Probability, expectedVariantProbability, delta);
+                    assert.closeTo(stringArrayVariantProbability, expectedVariantProbability, delta);
                 });
             });
 
             describe('Literal node probability', () => {
                 it('Variant #1: should replace literal node with call to string array variant', () => {
-                    assert.closeTo(literalNodeVariant1Probability, expectedVariantProbability, delta);
+                    assert.closeTo(literalNodeVariantProbability, expectedVariantProbability, delta);
                 });
             });
         });
 
         describe('Variant #2: Three string array values', () => {
             const samples: number = 1000;
-            const delta: number = 0.1;
-            const expectedStringArrayVariantProbability: number = 0.33;
+            const delta: number = 0.05;
+            const expectedStringArrayVariantProbability: number = 0.07;
             const expectedLiteralNodeVariantProbability: number = 1;
 
-            const stringArrayVariantsCount: number = 3;
+            const stringArrayVariantsCount: number = 2;
             const literalNodeVariantsCount: number = 1;
 
             const stringArrayVariantRegExps: RegExp[] = [
-                /var _0x([a-f0-9]){4} *= *\['foo', *'bar', *'baz'];/g,
-                /var _0x([a-f0-9]){4} *= *\['bar', *'baz', *'foo'];/g,
-                /var _0x([a-f0-9]){4} *= *\['baz', *'foo', *'bar'];/g
+                /var _0x([a-f0-9]){4} *= *\['foo', *'bar', *'baz'(?:, *'.*?')+];/g,
+                /var _0x([a-f0-9]){4} *= *\[(?:'.*?', *)+'foo', *'bar', *'baz'];/g
             ];
             const literalNodeVariantRegExps: RegExp[] = [
                 new RegExp(
-                    `var foo *= *_0x([a-f0-9]){4}\\(0x0\\); *` +
-                    `var bar *= *_0x([a-f0-9]){4}\\(0x1\\); *` +
-                    `var baz *= *_0x([a-f0-9]){4}\\(0x2\\);`
+                    `var foo *= *_0x([a-f0-9]){4}\\(0x.\\); *` +
+                    `var bar *= *_0x([a-f0-9]){4}\\(0x.\\); *` +
+                    `var baz *= *_0x([a-f0-9]){4}\\(0x.\\);`
                 )
             ];
 

+ 3 - 1
test/index.spec.ts

@@ -29,6 +29,7 @@ import './unit-tests/node/node-literal-utils/NodeLiteralUtils.spec';
 import './unit-tests/node/node-metadata/NodeMetadata.spec';
 import './unit-tests/node/node-statement-utils/NodeStatementUtils.spec';
 import './unit-tests/node/node-utils/NodeUtils.spec';
+import './unit-tests/node/numerical-expression-data-to-node-converter/NumericalExpressionDataToNodeConverter.spec';
 import './unit-tests/options/Options.spec';
 import './unit-tests/options/ValidationErrorsFormatter.spec';
 import './unit-tests/source-code/ObfuscatedCode.spec';
@@ -122,8 +123,9 @@ import './functional-tests/node-transformers/simplifying-transformers/block-stat
 import './functional-tests/node-transformers/simplifying-transformers/expression-statements-merge-transformer/ExpressionStatementsMergeTransformer.spec';
 import './functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/IfStatementSimplifyTransformer.spec';
 import './functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/VariableDeclarationsMergeTransformer.spec';
-import './functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec';
+import './functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/StringArrayRotateFunctionTransformer.spec';
 import './functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec';
+import './functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec';
 import './functional-tests/options/OptionsNormalizer.spec';
 import './functional-tests/options/domain-lock/Validation.spec';
 import './functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec';

+ 37 - 9
test/unit-tests/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.spec.ts

@@ -10,6 +10,7 @@ import { IInversifyContainerFacade } from '../../../../src/interfaces/container/
 import { INumberNumericalExpressionAnalyzer } from '../../../../src/interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
 
 import { InversifyContainerFacade } from '../../../../src/container/InversifyContainerFacade';
+import { NumberNumericalExpressionAnalyzer } from '../../../../src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer';
 
 /**
  * @param {TNumberNumericalExpressionData} data
@@ -43,7 +44,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -58,7 +62,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -73,7 +80,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -88,7 +98,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -105,7 +118,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -120,7 +136,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -135,7 +154,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -150,7 +172,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
 
                 before(() => {
                     const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                        numberNumericalExpressionAnalyzer.analyze(number);
+                        numberNumericalExpressionAnalyzer.analyze(
+                            number,
+                            NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                        );
 
                     evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
                 });
@@ -167,7 +192,10 @@ describe('NumberNumericalExpressionAnalyzer', function() {
             let testFunc: () => void;
 
             before(() => {
-                testFunc = () => numberNumericalExpressionAnalyzer.analyze(number);
+                testFunc = () => numberNumericalExpressionAnalyzer.analyze(
+                    number,
+                    NumberNumericalExpressionAnalyzer.defaultAdditionalPartsCount
+                );
             });
 
             it('should throw error', () => {

+ 56 - 0
test/unit-tests/node/numerical-expression-data-to-node-converter/NumericalExpressionDataToNodeConverter.spec.ts

@@ -0,0 +1,56 @@
+import 'reflect-metadata';
+
+import * as ESTree from 'estree';
+
+import { assert } from 'chai';
+
+import { TNumberNumericalExpressionData } from '../../../../src/types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+import { NodeFactory } from '../../../../src/node/NodeFactory';
+import { NumericalExpressionDataToNodeConverter } from '../../../../src/node/NumericalExpressionDataToNodeConverter';
+
+describe('NumericalExpressionDataToNodeConverter', () => {
+    describe('Variant #1: base', () => {
+        const expectedExpressionNode: ESTree.Expression = NodeFactory.binaryExpressionNode(
+            '+',
+            NodeFactory.binaryExpressionNode(
+                '+',
+                NodeFactory.literalNode(1),
+                NodeFactory.binaryExpressionNode(
+                    '*',
+                    NodeFactory.unaryExpressionNode(
+                        '-',
+                        NodeFactory.literalNode(2),
+                    ),
+                    NodeFactory.literalNode(3)
+                ),
+            ),
+            NodeFactory.literalNode(4)
+        );
+        const numberNumericalExpressionData: TNumberNumericalExpressionData = [
+            1, [-2, 3], 4
+        ];
+
+        let expressionNode: ESTree.Expression;
+
+        before(() => {
+            expressionNode = NumericalExpressionDataToNodeConverter.convert(
+                numberNumericalExpressionData,
+                (number: number, isPositiveNumber: boolean): ESTree.Expression => {
+                    const numberLiteralNode: ESTree.Literal = NodeFactory.literalNode(number);
+
+                    return isPositiveNumber
+                        ? numberLiteralNode
+                        : NodeFactory.unaryExpressionNode(
+                            '-',
+                            numberLiteralNode
+                        );
+                }
+            );
+        });
+
+        it('should convert number numerical expression data to expression node', () => {
+            assert.deepEqual(expressionNode, expectedExpressionNode);
+        });
+    });
+});

+ 45 - 45
yarn.lock

@@ -585,61 +585,61 @@
   resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.0.tgz#8c0a9435dfa7b3b1be76562f3070efb3f92637b4"
   integrity sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw==
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.0.tgz#bc6c1e4175c0cf42083da4314f7931ad12f731cc"
-  integrity sha512-x4arJMXBxyD6aBXLm3W7mSDZRiABzy+2PCLJbL7OPqlp53VXhaA1HKK7R2rTee5OlRhnUgnp8lZyVIqjnyPT6g==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.1.tgz#7579c6d17ad862154c10bc14b40e5427b729e209"
+  integrity sha512-fABclAX2QIEDmTMk6Yd7Muv1CzFLwWM4505nETzRHpP3br6jfahD9UUJkhnJ/g2m7lwfz8IlswcwGGPGiq9exw==
   dependencies:
-    "@typescript-eslint/experimental-utils" "4.11.0"
-    "@typescript-eslint/scope-manager" "4.11.0"
+    "@typescript-eslint/experimental-utils" "4.11.1"
+    "@typescript-eslint/scope-manager" "4.11.1"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.0.tgz#d1a47cc6cfe1c080ce4ead79267574b9881a1565"
-  integrity sha512-1VC6mSbYwl1FguKt8OgPs8xxaJgtqFpjY/UzUYDBKq4pfQ5lBvN2WVeqYkzf7evW42axUHYl2jm9tNyFsb8oLg==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.1.tgz#2dad3535b878c25c7424e40bfa79d899f3f485bc"
+  integrity sha512-mAlWowT4A6h0TC9F+J5pdbEhjNiEMO+kqPKQ4sc3fVieKL71dEqfkKgtcFVSX3cjSBwYwhImaQ/mXQF0oaI38g==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/scope-manager" "4.11.0"
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/typescript-estree" "4.11.0"
+    "@typescript-eslint/scope-manager" "4.11.1"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/typescript-estree" "4.11.1"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.11.0.tgz#1dd3d7e42708c10ce9f3aa64c63c0ab99868b4e2"
-  integrity sha512-NBTtKCC7ZtuxEV5CrHUO4Pg2s784pvavc3cnz6V+oJvVbK4tH9135f/RBP6eUA2KHiFKAollSrgSctQGmHbqJQ==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.11.1.tgz#981e18de2e019d6ca312596615f92e8f6f6598ed"
+  integrity sha512-BJ3jwPQu1jeynJ5BrjLuGfK/UJu6uwHxJ/di7sanqmUmxzmyIcd3vz58PMR7wpi8k3iWq2Q11KMYgZbUpRoIPw==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.11.0"
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/typescript-estree" "4.11.0"
+    "@typescript-eslint/scope-manager" "4.11.1"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/typescript-estree" "4.11.1"
     debug "^4.1.1"
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.11.0.tgz#2d906537db8a3a946721699e4fc0833810490254"
-  integrity sha512-6VSTm/4vC2dHM3ySDW9Kl48en+yLNfVV6LECU8jodBHQOhO8adAVizaZ1fV0QGZnLQjQ/y0aBj5/KXPp2hBTjA==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.11.1.tgz#72dc2b60b0029ab0888479b12bf83034920b4b69"
+  integrity sha512-Al2P394dx+kXCl61fhrrZ1FTI7qsRDIUiVSuN6rTwss6lUn8uVO2+nnF4AvO0ug8vMsy3ShkbxLu/uWZdTtJMQ==
   dependencies:
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/visitor-keys" "4.11.0"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/visitor-keys" "4.11.1"
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.11.0.tgz#86cf95e7eac4ccfd183f9fcf1480cece7caf4ca4"
-  integrity sha512-XXOdt/NPX++txOQHM1kUMgJUS43KSlXGdR/aDyEwuAEETwuPt02Nc7v+s57PzuSqMbNLclblQdv3YcWOdXhQ7g==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.11.1.tgz#3ba30c965963ef9f8ced5a29938dd0c465bd3e05"
+  integrity sha512-5kvd38wZpqGY4yP/6W3qhYX6Hz0NwUbijVsX2rxczpY6OXaMxh0+5E5uLJKVFwaBM7PJe1wnMym85NfKYIh6CA==
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.0.tgz#1144d145841e5987d61c4c845442a24b24165a4b"
-  integrity sha512-eA6sT5dE5RHAFhtcC+b5WDlUIGwnO9b0yrfGa1mIOIAjqwSQCpXbLiFmKTdRbQN/xH2EZkGqqLDrKUuYOZ0+Hg==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.1.tgz#a4416b4a65872a48773b9e47afabdf7519eb10bc"
+  integrity sha512-tC7MKZIMRTYxQhrVAFoJq/DlRwv1bnqA4/S2r3+HuHibqvbrPcyf858lNzU7bFmy4mLeIHFYr34ar/1KumwyRw==
   dependencies:
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/visitor-keys" "4.11.0"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/visitor-keys" "4.11.1"
     debug "^4.1.1"
     globby "^11.0.1"
     is-glob "^4.0.1"
@@ -647,12 +647,12 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/[email protected].0":
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.0.tgz#906669a50f06aa744378bb84c7d5c4fdbc5b7d51"
-  integrity sha512-tRYKyY0i7cMk6v4UIOCjl1LhuepC/pc6adQqJk4Is3YcC6k46HvsV9Wl7vQoLbm9qADgeujiT7KdLrylvFIQ+A==
+"@typescript-eslint/[email protected].1":
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.1.tgz#4c050a4c1f7239786e2dd4e69691436143024e05"
+  integrity sha512-IrlBhD9bm4bdYcS8xpWarazkKXlE7iYb1HzRuyBP114mIaj5DJPo11Us1HgH60dTt41TCZXMaTCAW+OILIYPOg==
   dependencies:
-    "@typescript-eslint/types" "4.11.0"
+    "@typescript-eslint/types" "4.11.1"
     eslint-visitor-keys "^2.0.0"
 
 "@ungap/[email protected]":
@@ -4257,10 +4257,10 @@ webpack-sources@^2.1.1:
     source-list-map "^2.0.1"
     source-map "^0.6.1"
 
[email protected].0:
-  version "5.11.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.11.0.tgz#1647abc060441d86d01d8835b8f0fc1dae2fe76f"
-  integrity sha512-ubWv7iP54RqAC/VjixgpnLLogCFbAfSOREcSWnnOlZEU8GICC5eKmJSu6YEnph2N2amKqY9rvxSwgyHxVqpaRw==
[email protected].1:
+  version "5.11.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.11.1.tgz#39b2b9daeb5c6c620e03b7556ec674eaed4016b4"
+  integrity sha512-tNUIdAmYJv+nupRs/U/gqmADm6fgrf5xE+rSlSsf2PgsGO7j2WG7ccU6AWNlOJlHFl+HnmXlBmHIkiLf+XA9mQ==
   dependencies:
     "@types/eslint-scope" "^3.7.0"
     "@types/estree" "^0.0.45"

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.