Переглянути джерело

New option: `stringArrayIntermediateCalls` sets the passed amount of intermediate variables for the `string array`

sanex 4 роки тому
батько
коміт
1b656d7386
34 змінених файлів з 344 додано та 52 видалено
  1. 4 0
      CHANGELOG.md
  2. 44 0
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 1 1
      package.json
  7. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  8. 1 1
      src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts
  9. 36 7
      src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts
  10. 1 1
      src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts
  11. 4 2
      src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts
  12. 8 0
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperIntermediateTemplate.ts
  13. 2 0
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate.ts
  14. 1 0
      src/interfaces/options/IOptions.ts
  15. 11 0
      src/interfaces/storages/string-array-storage/IStringArrayCallsWrapperNames.ts
  16. 3 2
      src/interfaces/storages/string-array-storage/IStringArrayStorage.ts
  17. 10 1
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  18. 7 0
      src/options/Options.ts
  19. 1 0
      src/options/normalizer-rules/StringArrayRule.ts
  20. 1 0
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  21. 1 0
      src/options/presets/Default.ts
  22. 1 0
      src/options/presets/HighObfuscation.ts
  23. 1 0
      src/options/presets/MediumObfuscation.ts
  24. 1 0
      src/options/presets/NoCustomNodes.ts
  25. 25 12
      src/storages/string-array/StringArrayStorage.ts
  26. 9 5
      test/dev/dev.ts
  27. 31 0
      test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts
  28. 0 0
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts
  29. 5 2
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  30. 113 17
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  31. 11 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/intermediate-calls-eval.js
  32. 4 0
      test/functional-tests/options/OptionsNormalizer.spec.ts
  33. 1 1
      test/index.spec.ts
  34. 1 0
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 Change Log
 
 
+v2.2.0
+---
+* **New option:** `stringArrayIntermediateCalls` sets the passed amount of intermediate variables for the `string array`
+
 v2.1.0
 v2.1.0
 ---
 ---
 * **New API:** `getOptionsByPreset` allows to get options for the passed options preset name 
 * **New API:** `getOptionsByPreset` allows to get options for the passed options preset name 

+ 44 - 0
README.md

@@ -360,6 +360,7 @@ Following options are available for the JS Obfuscator:
     splitStringsChunkLength: 10,
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: [],
     stringArrayEncoding: [],
+    stringArrayIntermediateCalls: true,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
     target: 'browser',
     target: 'browser',
     transformObjectKeys: false,
     transformObjectKeys: false,
@@ -408,6 +409,7 @@ Following options are available for the JS Obfuscator:
     --split-strings-chunk-length <number>
     --split-strings-chunk-length <number>
     --string-array <boolean>
     --string-array <boolean>
     --string-array-encoding '<list>' (comma separated) [none, base64, rc4]
     --string-array-encoding '<list>' (comma separated) [none, base64, rc4]
+    --string-array-intermediate-calls <number>
     --string-array-threshold <number>
     --string-array-threshold <number>
     --target <string> [browser, browser-no-eval, node]
     --target <string> [browser, browser-no-eval, node]
     --transform-object-keys <boolean>
     --transform-object-keys <boolean>
@@ -947,6 +949,44 @@ stringArrayEncoding: [
     'rc4'
     'rc4'
 ]
 ]
 ```
 ```
+
+### `stringArrayIntermediateCalls`
+Type: `number` Default: `0`
+
+##### :warning: [`stringArray`](#stringarray) option must be enabled
+
+Sets the passed amount of intermediate variables for the `string array`. 
+
+Example:
+```ts
+// Input
+const foo = 'foo';
+const bar = 'bar';
+const baz = 'baz';
+
+console.log(foo, bar, baz);
+
+// Output, stringArrayIntermediateCalls: 5
+const _0x513c = [
+    'foo',
+    'bar',
+    'baz',
+    'log'
+];
+const _0x19a1 = function (_0x513ce6, _0x135380) {
+    _0x513ce6 = _0x513ce6 - 0x0;
+    let _0x19a103 = _0x513c[_0x513ce6];
+    return _0x19a103;
+};
+const _0x3745 = _0x19a1;
+const _0x261a = _0x19a1;
+const _0x2f15 = _0x19a1;
+const _0x4a63 = _0x19a1;
+const _0x2e9d = _0x19a1;
+const foo = _0x4a63('0x0');
+const bar = _0x3745('0x1');
+const baz = _0x2e9d('0x2');
+```
     
     
 ### `stringArrayThreshold`
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
@@ -1046,6 +1086,7 @@ Performance will 50-100% slower than without obfuscation
     splitStringsChunkLength: 5,
     splitStringsChunkLength: 5,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: ['rc4'],
     stringArrayEncoding: ['rc4'],
+    stringArrayIntermediateCalls: 10,
     stringArrayThreshold: 1,
     stringArrayThreshold: 1,
     transformObjectKeys: true,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
     unicodeEscapeSequence: false
@@ -1078,6 +1119,7 @@ Performance will 30-35% slower than without obfuscation
     splitStringsChunkLength: 10,
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: ['base64'],
     stringArrayEncoding: ['base64'],
+    stringArrayIntermediateCalls: 5,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
     transformObjectKeys: true,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
     unicodeEscapeSequence: false
@@ -1107,6 +1149,7 @@ Performance will slightly slower than without obfuscation
     splitStrings: false,
     splitStrings: false,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: [],
     stringArrayEncoding: [],
+    stringArrayIntermediateCalls: 0,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
     unicodeEscapeSequence: false
 }
 }
@@ -1133,6 +1176,7 @@ Performance will slightly slower than without obfuscation
     splitStrings: false,
     splitStrings: false,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: [],
     stringArrayEncoding: [],
+    stringArrayIntermediateCalls: 0,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
     unicodeEscapeSequence: false
 }
 }

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/index.browser.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/index.cli.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "2.1.0",
+  "version": "2.2.0",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",

+ 5 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -339,6 +339,11 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 `Default: ${StringArrayEncoding.None}`,
                 `Default: ${StringArrayEncoding.None}`,
                 ArraySanitizer
                 ArraySanitizer
             )
             )
+            .option(
+                '--string-array-intermediate-calls <number>',
+                'Sets the passed amount of intermediate variables for the string array.',
+                parseInt
+            )
             .option(
             .option(
                 '--string-array-threshold <number>',
                 '--string-array-threshold <number>',
                 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)',
                 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)',

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

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

+ 36 - 7
src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts

@@ -9,11 +9,13 @@ import { ICustomCodeHelperObfuscator } from '../../interfaces/custom-code-helper
 import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayCallsWrapperNames } from '../../interfaces/storages/string-array-storage/IStringArrayCallsWrapperNames';
 
 
 import { initializable } from '../../decorators/Initializable';
 import { initializable } from '../../decorators/Initializable';
 
 
 import { SelfDefendingTemplate } from './templates/string-array-calls-wrapper/SelfDefendingTemplate';
 import { SelfDefendingTemplate } from './templates/string-array-calls-wrapper/SelfDefendingTemplate';
 import { StringArrayCallsWrapperTemplate } from './templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate';
 import { StringArrayCallsWrapperTemplate } from './templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate';
+import { StringArrayCallsWrapperIntermediateTemplate } from './templates/string-array-calls-wrapper/StringArrayCallsWrapperIntermediateTemplate';
 
 
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { NodeUtils } from '../../node/NodeUtils';
 import { NodeUtils } from '../../node/NodeUtils';
@@ -27,10 +29,10 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     protected stringArrayName!: string;
     protected stringArrayName!: string;
 
 
     /**
     /**
-     * @type {string}
+     * @type {IStringArrayCallsWrapperNames}
      */
      */
     @initializable()
     @initializable()
-    protected stringArrayCallsWrapperName!: string;
+    protected stringArrayCallsWrapperNames!: IStringArrayCallsWrapperNames;
 
 
     /**
     /**
      * @type {IEscapeSequenceEncoder}
      * @type {IEscapeSequenceEncoder}
@@ -67,14 +69,14 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
 
 
     /**
     /**
      * @param {string} stringArrayName
      * @param {string} stringArrayName
-     * @param {string} stringArrayCallsWrapperName
+     * @param {IStringArrayCallsWrapperNames} stringArrayCallsWrapperNames
      */
      */
     public initialize (
     public initialize (
         stringArrayName: string,
         stringArrayName: string,
-        stringArrayCallsWrapperName: string
+        stringArrayCallsWrapperNames: IStringArrayCallsWrapperNames
     ): void {
     ): void {
         this.stringArrayName = stringArrayName;
         this.stringArrayName = stringArrayName;
-        this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
+        this.stringArrayCallsWrapperNames = stringArrayCallsWrapperNames;
     }
     }
 
 
     /**
     /**
@@ -90,13 +92,15 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
      */
      */
     protected getCodeHelperTemplate (): string {
     protected getCodeHelperTemplate (): string {
         const decodeCodeHelperTemplate: string = this.getDecodeStringArrayTemplate();
         const decodeCodeHelperTemplate: string = this.getDecodeStringArrayTemplate();
+        const intermediateTemplate: string = this.getIntermediateTemplate();
 
 
         const preservedNames: string[] = [`^${this.stringArrayName}$`];
         const preservedNames: string[] = [`^${this.stringArrayName}$`];
 
 
         return this.customCodeHelperObfuscator.obfuscateTemplate(
         return this.customCodeHelperObfuscator.obfuscateTemplate(
             this.customCodeHelperFormatter.formatTemplate(StringArrayCallsWrapperTemplate(), {
             this.customCodeHelperFormatter.formatTemplate(StringArrayCallsWrapperTemplate(), {
                 decodeCodeHelperTemplate,
                 decodeCodeHelperTemplate,
-                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
+                intermediateTemplate,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperNames.name,
                 stringArrayName: this.stringArrayName
                 stringArrayName: this.stringArrayName
             }),
             }),
             {
             {
@@ -126,9 +130,34 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
                 this.escapeSequenceEncoder
                 this.escapeSequenceEncoder
             ),
             ),
             {
             {
-                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperNames.name,
                 stringArrayName: this.stringArrayName
                 stringArrayName: this.stringArrayName
             }
             }
         );
         );
     }
     }
+
+    /**
+     * @returns {string}
+     */
+    private getIntermediateTemplate (): string {
+        const stringArrayCallsWrapperIntermediateNamesLength: number = this.stringArrayCallsWrapperNames
+            .intermediateNames
+            .length;
+
+        let intermediateTemplate: string = '';
+
+        for (let i = 0; i < stringArrayCallsWrapperIntermediateNamesLength; i++) {
+            const intermediateName: string = this.stringArrayCallsWrapperNames.intermediateNames[i];
+
+            intermediateTemplate += this.customCodeHelperFormatter.formatTemplate(
+                StringArrayCallsWrapperIntermediateTemplate(),
+                {
+                    intermediateName,
+                    stringArrayCallsWrapperName: this.stringArrayCallsWrapperNames.name
+                }
+            );
+        }
+
+        return intermediateTemplate;
+    }
 }
 }

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

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

+ 4 - 2
src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts

@@ -11,6 +11,7 @@ import { ICallsGraphData } from '../../../interfaces/analyzers/calls-graph-analy
 import { ICustomCodeHelper } from '../../../interfaces/custom-code-helpers/ICustomCodeHelper';
 import { ICustomCodeHelper } from '../../../interfaces/custom-code-helpers/ICustomCodeHelper';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
+import { IStringArrayCallsWrapperNames } from '../../../interfaces/storages/string-array-storage/IStringArrayCallsWrapperNames';
 import { IStringArrayStorage } from '../../../interfaces/storages/string-array-storage/IStringArrayStorage';
 import { IStringArrayStorage } from '../../../interfaces/storages/string-array-storage/IStringArrayStorage';
 
 
 import { initializable } from '../../../decorators/Initializable';
 import { initializable } from '../../../decorators/Initializable';
@@ -138,11 +139,12 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
             const stringArrayCallsWrapperCodeHelperName: CustomCodeHelper = this.getStringArrayCallsWrapperCodeHelperName(stringArrayEncoding);
             const stringArrayCallsWrapperCodeHelperName: CustomCodeHelper = this.getStringArrayCallsWrapperCodeHelperName(stringArrayEncoding);
             const stringArrayCallsWrapperCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>> =
             const stringArrayCallsWrapperCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>> =
                 this.customCodeHelperFactory(stringArrayCallsWrapperCodeHelperName);
                 this.customCodeHelperFactory(stringArrayCallsWrapperCodeHelperName);
-            const stringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName(stringArrayEncoding);
+            const stringArrayCallsWrapperNames: IStringArrayCallsWrapperNames =
+                this.stringArrayStorage.getStorageCallsWrapperNames(stringArrayEncoding);
 
 
             stringArrayCallsWrapperCodeHelper.initialize(
             stringArrayCallsWrapperCodeHelper.initialize(
                 stringArrayName,
                 stringArrayName,
-                stringArrayCallsWrapperName
+                stringArrayCallsWrapperNames
             );
             );
 
 
             this.customCodeHelpers.set(stringArrayCallsWrapperCodeHelperName, stringArrayCallsWrapperCodeHelper);
             this.customCodeHelpers.set(stringArrayCallsWrapperCodeHelperName, stringArrayCallsWrapperCodeHelper);

+ 8 - 0
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperIntermediateTemplate.ts

@@ -0,0 +1,8 @@
+/**
+ * @returns {string}
+ */
+export function StringArrayCallsWrapperIntermediateTemplate (): string {
+    return `
+        const {intermediateName} = {stringArrayCallsWrapperName};
+    `;
+}

+ 2 - 0
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate.ts

@@ -12,5 +12,7 @@ export function StringArrayCallsWrapperTemplate (): string {
         
         
             return value;
             return value;
         };
         };
+        
+        {intermediateTemplate}
     `;
     `;
 }
 }

+ 1 - 0
src/interfaces/options/IOptions.ts

@@ -41,6 +41,7 @@ export interface IOptions {
     readonly splitStringsChunkLength: number;
     readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;
     readonly stringArray: boolean;
     readonly stringArrayEncoding: TStringArrayEncoding[];
     readonly stringArrayEncoding: TStringArrayEncoding[];
+    readonly stringArrayIntermediateCalls: number;
     readonly stringArrayThreshold: number;
     readonly stringArrayThreshold: number;
     readonly target: TypeFromEnum<typeof ObfuscationTarget>;
     readonly target: TypeFromEnum<typeof ObfuscationTarget>;
     readonly transformObjectKeys: boolean;
     readonly transformObjectKeys: boolean;

+ 11 - 0
src/interfaces/storages/string-array-storage/IStringArrayCallsWrapperNames.ts

@@ -0,0 +1,11 @@
+export interface IStringArrayCallsWrapperNames {
+    /**
+     * @type {string}
+     */
+    name: string;
+
+    /**
+     * @type {string[]}
+     */
+    intermediateNames: string[];
+}

+ 3 - 2
src/interfaces/storages/string-array-storage/IStringArrayStorage.ts

@@ -1,6 +1,7 @@
 import { TStringArrayEncoding } from '../../../types/options/TStringArrayEncoding';
 import { TStringArrayEncoding } from '../../../types/options/TStringArrayEncoding';
 
 
 import { IMapStorage } from '../IMapStorage';
 import { IMapStorage } from '../IMapStorage';
+import { IStringArrayCallsWrapperNames } from './IStringArrayCallsWrapperNames';
 import { IStringArrayStorageItemData } from './IStringArrayStorageItem';
 import { IStringArrayStorageItemData } from './IStringArrayStorageItem';
 
 
 export interface IStringArrayStorage extends IMapStorage <string, IStringArrayStorageItemData> {
 export interface IStringArrayStorage extends IMapStorage <string, IStringArrayStorageItemData> {
@@ -16,9 +17,9 @@ export interface IStringArrayStorage extends IMapStorage <string, IStringArraySt
 
 
     /**
     /**
      * @param {TStringArrayEncoding | null} stringArrayEncoding
      * @param {TStringArrayEncoding | null} stringArrayEncoding
-     * @returns {string}
+     * @returns {IStringArrayCallsWrapperNames}
      */
      */
-    getStorageCallsWrapperName (stringArrayEncoding: TStringArrayEncoding | null): string;
+    getStorageCallsWrapperNames (stringArrayEncoding: TStringArrayEncoding | null): IStringArrayCallsWrapperNames;
 
 
     rotateStorage (): void;
     rotateStorage (): void;
 
 

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

@@ -6,6 +6,7 @@ import * as ESTree from 'estree';
 import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayCallsWrapperNames } from '../../interfaces/storages/string-array-storage/IStringArrayCallsWrapperNames';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-storage/IStringArrayStorage';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-storage/IStringArrayStorage';
 import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
@@ -194,8 +195,16 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
             callExpressionArgs.push(StringArrayTransformer.getRc4KeyLiteralNode(decodeKey));
             callExpressionArgs.push(StringArrayTransformer.getRc4KeyLiteralNode(decodeKey));
         }
         }
 
 
+        const stringArrayCallsWrapperNames: IStringArrayCallsWrapperNames =
+            this.stringArrayStorage.getStorageCallsWrapperNames(encoding);
+        const stringArrayCallsWrapperName: string = stringArrayCallsWrapperNames.intermediateNames.length
+            ? this.randomGenerator
+                .getRandomGenerator()
+                .pickone(stringArrayCallsWrapperNames.intermediateNames)
+            : stringArrayCallsWrapperNames.name;
+
         const stringArrayIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(
         const stringArrayIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(
-            this.stringArrayStorage.getStorageCallsWrapperName(encoding)
+            stringArrayCallsWrapperName
         );
         );
 
 
         return NodeFactory.callExpressionNode(
         return NodeFactory.callExpressionNode(

+ 7 - 0
src/options/Options.ts

@@ -297,6 +297,13 @@ export class Options implements IOptions {
     @IsIn([StringArrayEncoding.None, StringArrayEncoding.Base64, StringArrayEncoding.Rc4], { each: true })
     @IsIn([StringArrayEncoding.None, StringArrayEncoding.Base64, StringArrayEncoding.Rc4], { each: true })
     public readonly stringArrayEncoding!: TStringArrayEncoding[];
     public readonly stringArrayEncoding!: TStringArrayEncoding[];
 
 
+    /**
+     * @type {boolean}
+     */
+    @IsNumber()
+    @Min(0)
+    public readonly stringArrayIntermediateCalls!: number;
+
     /**
     /**
      * @type {number}
      * @type {number}
      */
      */

+ 1 - 0
src/options/normalizer-rules/StringArrayRule.ts

@@ -18,6 +18,7 @@ export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOpt
             stringArrayEncoding: [
             stringArrayEncoding: [
                 StringArrayEncoding.None
                 StringArrayEncoding.None
             ],
             ],
+            stringArrayIntermediateCalls: 0,
             stringArrayThreshold: 0
             stringArrayThreshold: 0
         };
         };
     }
     }

+ 1 - 0
src/options/normalizer-rules/StringArrayThresholdRule.ts

@@ -17,6 +17,7 @@ export const StringArrayThresholdRule: TOptionsNormalizerRule = (options: IOptio
             stringArrayEncoding: [
             stringArrayEncoding: [
                 StringArrayEncoding.None
                 StringArrayEncoding.None
             ],
             ],
+            stringArrayIntermediateCalls: 0,
             stringArrayThreshold: 0
             stringArrayThreshold: 0
         };
         };
     }
     }

+ 1 - 0
src/options/presets/Default.ts

@@ -44,6 +44,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: [
     stringArrayEncoding: [
         StringArrayEncoding.None
         StringArrayEncoding.None
     ],
     ],
+    stringArrayIntermediateCalls: 0,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
     target: ObfuscationTarget.Browser,
     target: ObfuscationTarget.Browser,
     transformObjectKeys: false,
     transformObjectKeys: false,

+ 1 - 0
src/options/presets/HighObfuscation.ts

@@ -16,5 +16,6 @@ export const HIGH_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: [
     stringArrayEncoding: [
         StringArrayEncoding.Rc4
         StringArrayEncoding.Rc4
     ],
     ],
+    stringArrayIntermediateCalls: 10,
     stringArrayThreshold: 1
     stringArrayThreshold: 1
 });
 });

+ 1 - 0
src/options/presets/MediumObfuscation.ts

@@ -16,5 +16,6 @@ export const MEDIUM_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: [
     stringArrayEncoding: [
         StringArrayEncoding.Base64
         StringArrayEncoding.Base64
     ],
     ],
+    stringArrayIntermediateCalls: 5,
     transformObjectKeys: true
     transformObjectKeys: true
 });
 });

+ 1 - 0
src/options/presets/NoCustomNodes.ts

@@ -41,6 +41,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: [
     stringArrayEncoding: [
         StringArrayEncoding.None
         StringArrayEncoding.None
     ],
     ],
+    stringArrayIntermediateCalls: 0,
     stringArrayThreshold: 0,
     stringArrayThreshold: 0,
     target: ObfuscationTarget.Browser,
     target: ObfuscationTarget.Browser,
     transformObjectKeys: false,
     transformObjectKeys: false,

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

@@ -1,17 +1,17 @@
 import { inject, injectable, postConstruct } from 'inversify';
 import { inject, injectable, postConstruct } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
-
 import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
+import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
 
 
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { ICryptUtilsSwappedAlphabet } from '../../interfaces/utils/ICryptUtilsSwappedAlphabet';
 import { ICryptUtilsSwappedAlphabet } from '../../interfaces/utils/ICryptUtilsSwappedAlphabet';
 import { IEncodedValue } from '../../interfaces/IEncodedValue';
 import { IEncodedValue } from '../../interfaces/IEncodedValue';
 import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
+import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayCallsWrapperNames } from '../../interfaces/storages/string-array-storage/IStringArrayCallsWrapperNames';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-storage/IStringArrayStorage';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-storage/IStringArrayStorage';
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
 
 
@@ -87,9 +87,9 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
     private stringArrayStorageName!: string;
     private stringArrayStorageName!: string;
 
 
     /**
     /**
-     * @type {Map<TStringArrayEncoding | null, string>}
+     * @type {Map<TStringArrayEncoding | null, IStringArrayCallsWrapperNames>}
      */
      */
-    private readonly stringArrayStorageCallsWrapperNamesMap: Map<TStringArrayEncoding | null, string> = new Map();
+    private readonly stringArrayStorageCallsWrapperNamesMap: Map<TStringArrayEncoding | null, IStringArrayCallsWrapperNames> = new Map();
 
 
     /**
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -171,24 +171,24 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
 
 
     /**
     /**
      * @param {TStringArrayEncoding | null} stringArrayEncoding
      * @param {TStringArrayEncoding | null} stringArrayEncoding
-     * @returns {string}
+     * @returns {IStringArrayCallsWrapperNames}
      */
      */
-    public getStorageCallsWrapperName (stringArrayEncoding: TStringArrayEncoding | null): string {
-        const storageCallsWrapperName: string | null = this.stringArrayStorageCallsWrapperNamesMap.get(stringArrayEncoding) ?? null;
+    public getStorageCallsWrapperNames (stringArrayEncoding: TStringArrayEncoding | null): IStringArrayCallsWrapperNames {
+        const storageCallsWrapperName: IStringArrayCallsWrapperNames | null = this.stringArrayStorageCallsWrapperNamesMap
+            .get(stringArrayEncoding) ?? null;
 
 
         if (storageCallsWrapperName) {
         if (storageCallsWrapperName) {
             return storageCallsWrapperName;
             return storageCallsWrapperName;
         }
         }
 
 
-        const newStorageCallsWrapperName: string = this.identifierNamesGenerator
-            .generateForGlobalScope(StringArrayStorage.stringArrayNameLength);
+        const newStorageCallsWrapperNames: IStringArrayCallsWrapperNames = this.getStringArrayCallsWrapperNames();
 
 
         this.stringArrayStorageCallsWrapperNamesMap.set(
         this.stringArrayStorageCallsWrapperNamesMap.set(
             stringArrayEncoding,
             stringArrayEncoding,
-            newStorageCallsWrapperName
+            newStorageCallsWrapperNames
         );
         );
 
 
-        return newStorageCallsWrapperName;
+        return newStorageCallsWrapperNames;
     }
     }
 
 
     public rotateStorage (): void {
     public rotateStorage (): void {
@@ -329,4 +329,17 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
             }
             }
         }
         }
     }
     }
+
+    /**
+     * @returns {IStringArrayCallsWrapperNames}
+     */
+    private getStringArrayCallsWrapperNames (): IStringArrayCallsWrapperNames {
+        return {
+            name: this.identifierNamesGenerator.generateForGlobalScope(StringArrayStorage.stringArrayNameLength),
+            intermediateNames: Array.from(
+                {length: this.options.stringArrayIntermediateCalls},
+                () => this.identifierNamesGenerator.generateForGlobalScope(StringArrayStorage.stringArrayNameLength)
+            )
+        };
+    }
 }
 }

+ 9 - 5
test/dev/dev.ts

@@ -1,24 +1,28 @@
 'use strict';
 'use strict';
 
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
+import { StringArrayEncoding } from '../../src/enums/StringArrayEncoding';
 
 
 (function () {
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
     const JavaScriptObfuscator: any = require('../../index');
 
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         `
-            var string1 = '👋🏼';
+            const foo = 'foo';
+            const bar = 'bar';
+            const baz = 'baz';
             
             
-            console.log(string1);
+            console.log(foo, bar, baz);
         `,
         `,
         {
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
             compact: false,
             stringArray: true,
             stringArray: true,
             stringArrayThreshold: 1,
             stringArrayThreshold: 1,
-            splitStrings: true,
-            splitStringsChunkLength: 1,
-            unicodeEscapeSequence: true
+            stringArrayIntermediateCalls: 2,
+            stringArrayEncoding: [
+                StringArrayEncoding.Rc4
+            ]
         }
         }
     ).getObfuscatedCode();
     ).getObfuscatedCode();
 
 

+ 31 - 0
test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts

@@ -53,6 +53,37 @@ describe('StringArrayCallsWrapperCodeHelper', () => {
         });
         });
     });
     });
 
 
+    describe('`stringArrayIntermediateCalls` option is set', () => {
+        const stringArrayCallRegExp: RegExp = new RegExp(
+                'return _0x([a-f0-9]){4,6};' +
+            '};' +
+            'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+            'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+            'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+            'var test *= *_0x([a-f0-9]){4}\\(\'0x0\'\\);'
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1,
+                    stringArrayIntermediateCalls: 3
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should correctly append `StringArrayCallsWrapperIntermediateTemplate` template into the obfuscated code', () => {
+            assert.match(obfuscatedCode, stringArrayCallRegExp);
+        });
+    });
+
     describe('Preserve string array name', () => {
     describe('Preserve string array name', () => {
         const callsWrapperRegExp: RegExp = new RegExp(`` +
         const callsWrapperRegExp: RegExp = new RegExp(`` +
             `var b *= *function *\\(c, *d\\) *{ *` +
             `var b *= *function *\\(c, *d\\) *{ *` +

+ 0 - 0
test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperNodeTemplate.spec.ts → test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts


+ 5 - 2
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -719,7 +719,10 @@ describe('JavaScriptObfuscator', () => {
                 obfuscatedCode = JavaScriptObfuscator.obfuscate(
                 obfuscatedCode = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
-                        identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                        stringArray: true,
+                        stringArrayThreshold: 1
                     }
                     }
                 ).getObfuscatedCode();
                 ).getObfuscatedCode();
             });
             });
@@ -845,6 +848,7 @@ describe('JavaScriptObfuscator', () => {
                             StringArrayEncoding.Base64,
                             StringArrayEncoding.Base64,
                             StringArrayEncoding.Rc4
                             StringArrayEncoding.Rc4
                         ],
                         ],
+                        stringArrayIntermediateCalls: 10,
                         stringArrayThreshold: 1,
                         stringArrayThreshold: 1,
                         transformObjectKeys: true,
                         transformObjectKeys: true,
                         unicodeEscapeSequence: false
                         unicodeEscapeSequence: false
@@ -938,7 +942,6 @@ describe('JavaScriptObfuscator', () => {
                             stringArrayEncoding: [StringArrayEncoding.Rc4]
                             stringArrayEncoding: [StringArrayEncoding.Rc4]
                         }
                         }
                     ).getObfuscatedCode();
                     ).getObfuscatedCode();
-
                 });
                 });
 
 
                 it('does not break on run', () => {
                 it('does not break on run', () => {

+ 113 - 17
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -63,7 +63,65 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #3: string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
+    describe('Variant #3: `stringArrayIntermediateCalls` option is enabled', () => {
+        describe('Variant #1: correct amount of intermediate calls', () => {
+            const stringArrayCallRegExp: RegExp = new RegExp(
+                    'return _0x([a-f0-9]){4,6};' +
+                '};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var test *= *_0x([a-f0-9]){4}\\(\'0x0\'\\);'
+            );
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayIntermediateCalls: 3
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should add intermediate calls to the string array calls wrapper', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
+        });
+
+        describe('Variant #2: correct evaluation of the intermediate calls', () => {
+            const expectedEvaluationResult: number = 15;
+            let evaluationResult: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/intermediate-calls-eval.js');
+
+                const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayIntermediateCalls: 5
+                    }
+                ).getObfuscatedCode();
+
+                evaluationResult = eval(obfuscatedCode);
+            });
+
+            it('should correctly evaluate intermediate calls', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+    });
+
+    describe('Variant #4: string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
         let testFunc: () => void;
         let testFunc: () => void;
 
 
         before(() => {
         before(() => {
@@ -84,7 +142,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #4: same literal node values', () => {
+    describe('Variant #5: same literal node values', () => {
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
 
@@ -112,7 +170,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #5: `unicodeEscapeSequence` option is enabled', () => {
+    describe('Variant #6: `unicodeEscapeSequence` option is enabled', () => {
         const regExp: RegExp = /^var test *= *'\\x74\\x65\\x73\\x74';$/;
         const regExp: RegExp = /^var test *= *'\\x74\\x65\\x73\\x74';$/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -135,7 +193,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #6: `unicodeEscapeSequence` and `stringArray` options are enabled', () => {
+    describe('Variant #7: `unicodeEscapeSequence` and `stringArray` options are enabled', () => {
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/;
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('\\x30\\x78\\x30'\);/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('\\x30\\x78\\x30'\);/;
 
 
@@ -164,7 +222,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #7: short literal node value', () => {
+    describe('Variant #8: short literal node value', () => {
         const regExp: RegExp = /var test *= *'te';/;
         const regExp: RegExp = /var test *= *'te';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -187,7 +245,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #8: base64 encoding', () => {
+    describe('Variant #9: base64 encoding', () => {
         const stringArrayRegExp: RegExp = new RegExp(`^var _0x([a-f0-9]){4} *= *\\['${swapLettersCase('dGVzdA==')}'];`);
         const stringArrayRegExp: RegExp = new RegExp(`^var _0x([a-f0-9]){4} *= *\\['${swapLettersCase('dGVzdA==')}'];`);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
 
@@ -216,7 +274,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #9: rc4 encoding', () => {
+    describe('Variant #10: rc4 encoding', () => {
         describe('Variant #1: single string literal', () => {
         describe('Variant #1: single string literal', () => {
             const regExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
             const regExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
 
 
@@ -282,7 +340,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #10: none and base64 encoding', () => {
+    describe('Variant #11: none and base64 encoding', () => {
         describe('Variant #1: string array values', () => {
         describe('Variant #1: string array values', () => {
             const samplesCount: number = 100;
             const samplesCount: number = 100;
             const expectedMatchesChance: number = 0.5;
             const expectedMatchesChance: number = 0.5;
@@ -336,9 +394,47 @@ describe('StringArrayTransformer', function () {
                 assert.closeTo(base64EncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
                 assert.closeTo(base64EncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
             });
             });
         });
         });
+
+        describe('Variant #2: `stringArrayIntermediateCalls` option is enabled', () => {
+            const stringArrayIntermediateCallRegExp: RegExp = new RegExp(
+                    'return _0x([a-f0-9]){4,6};' +
+                '};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var _0x([a-f0-9]){4} *= *_0x([a-f0-9]){4};' +
+                'var test *= *_0x([a-f0-9]){4}\\(\'0x0\'\\);'
+            );
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayEncoding: [
+                            StringArrayEncoding.None,
+                            StringArrayEncoding.Base64
+                        ],
+                        stringArrayIntermediateCalls: 3,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should add intermediate variables for both `none` and `base64` string array wrappers', () => {
+                assert.match(obfuscatedCode, stringArrayIntermediateCallRegExp);
+            });
+        });
     });
     });
 
 
-    describe('Variant #11: none and rc4 encoding', () => {
+    describe('Variant #12: none and rc4 encoding', () => {
         describe('Variant #1: string array calls wrapper call', () => {
         describe('Variant #1: string array calls wrapper call', () => {
             const samplesCount: number = 100;
             const samplesCount: number = 100;
             const expectedMatchesChance: number = 0.5;
             const expectedMatchesChance: number = 0.5;
@@ -394,7 +490,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #12: base64 and rc4 encoding', () => {
+    describe('Variant #13: base64 and rc4 encoding', () => {
         describe('Variant #1: single string literal', () => {
         describe('Variant #1: single string literal', () => {
             const samplesCount: number = 100;
             const samplesCount: number = 100;
             const expectedMatchesChance: number = 0.5;
             const expectedMatchesChance: number = 0.5;
@@ -450,7 +546,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #13: `stringArrayThreshold` option value', () => {
+    describe('Variant #14: `stringArrayThreshold` option value', () => {
         const samples: number = 1000;
         const samples: number = 1000;
         const stringArrayThreshold: number = 0.5;
         const stringArrayThreshold: number = 0.5;
         const delta: number = 0.1;
         const delta: number = 0.1;
@@ -493,7 +589,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #14: string array calls wrapper name', () => {
+    describe('Variant #15: string array calls wrapper name', () => {
         const regExp: RegExp = /console\[b\('0x0'\)]\('a'\);/;
         const regExp: RegExp = /console\[b\('0x0'\)]\('a'\);/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -517,7 +613,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #15: `reservedStrings` option is enabled', () => {
+    describe('Variant #16: `reservedStrings` option is enabled', () => {
         describe('Variant #1: base `reservedStrings` values', () => {
         describe('Variant #1: base `reservedStrings` values', () => {
             describe('Variant #1: single reserved string value', () => {
             describe('Variant #1: single reserved string value', () => {
                 const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
                 const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
@@ -689,7 +785,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #16: object expression key literal', () => {
+    describe('Variant #17: object expression key literal', () => {
         describe('Variant #1: base key literal', () => {
         describe('Variant #1: base key literal', () => {
             const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
             const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\('0x0'\)};/;
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\('0x0'\)};/;
@@ -747,7 +843,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #17: import declaration source literal', () => {
+    describe('Variant #18: import declaration source literal', () => {
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -770,7 +866,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #18: export all declaration source literal', () => {
+    describe('Variant #19: export all declaration source literal', () => {
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -793,7 +889,7 @@ describe('StringArrayTransformer', function () {
         });
         });
     });
     });
 
 
-    describe('Variant #19: export named declaration source literal', () => {
+    describe('Variant #20: export named declaration source literal', () => {
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;

+ 11 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/intermediate-calls-eval.js

@@ -0,0 +1,11 @@
+function func () {
+    var foo = 1;
+    var bar = 2;
+    var baz = 3;
+    var bark = 4;
+    var hawk = 5;
+
+    return foo + bar + baz + bark + hawk;
+}
+
+func();

+ 4 - 0
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -544,6 +544,7 @@ describe('OptionsNormalizer', () => {
                     shuffleStringArray: true,
                     shuffleStringArray: true,
                     stringArray: false,
                     stringArray: false,
                     stringArrayEncoding: [StringArrayEncoding.Rc4],
                     stringArrayEncoding: [StringArrayEncoding.Rc4],
+                    stringArrayIntermediateCalls: 5,
                     stringArrayThreshold: 0.5,
                     stringArrayThreshold: 0.5,
                     rotateStringArray: true
                     rotateStringArray: true
                 });
                 });
@@ -553,6 +554,7 @@ describe('OptionsNormalizer', () => {
                     shuffleStringArray: false,
                     shuffleStringArray: false,
                     stringArray: false,
                     stringArray: false,
                     stringArrayEncoding: [StringArrayEncoding.None],
                     stringArrayEncoding: [StringArrayEncoding.None],
+                    stringArrayIntermediateCalls: 0,
                     stringArrayThreshold: 0,
                     stringArrayThreshold: 0,
                     rotateStringArray: false
                     rotateStringArray: false
                 };
                 };
@@ -590,6 +592,7 @@ describe('OptionsNormalizer', () => {
                     rotateStringArray: true,
                     rotateStringArray: true,
                     shuffleStringArray: true,
                     shuffleStringArray: true,
                     stringArray: true,
                     stringArray: true,
+                    stringArrayIntermediateCalls: 5,
                     stringArrayThreshold: 0
                     stringArrayThreshold: 0
                 });
                 });
 
 
@@ -598,6 +601,7 @@ describe('OptionsNormalizer', () => {
                     rotateStringArray: false,
                     rotateStringArray: false,
                     shuffleStringArray: false,
                     shuffleStringArray: false,
                     stringArray: false,
                     stringArray: false,
+                    stringArrayIntermediateCalls: 0,
                     stringArrayThreshold: 0
                     stringArrayThreshold: 0
                 };
                 };
             });
             });

+ 1 - 1
test/index.spec.ts

@@ -64,7 +64,7 @@ import './functional-tests/custom-code-helpers/string-array/StringArrayCallsWrap
 import './functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec';
 import './functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec';
 import './functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec';
 import './functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec';
 import './functional-tests/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.spec';
 import './functional-tests/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.spec';
-import './functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperNodeTemplate.spec';
+import './functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec';
 import './functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec';
 import './functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec';
 import './functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec';
 import './functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec';
 import './functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec';
 import './functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec';

+ 1 - 0
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -39,6 +39,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
             StringArrayEncoding.Base64,
             StringArrayEncoding.Base64,
             StringArrayEncoding.Rc4
             StringArrayEncoding.Rc4
         ],
         ],
+        stringArrayIntermediateCalls: 20,
         stringArrayThreshold: 1,
         stringArrayThreshold: 1,
         transformObjectKeys: true,
         transformObjectKeys: true,
         unicodeEscapeSequence: true
         unicodeEscapeSequence: true

Деякі файли не було показано, через те що забагато файлів було змінено