Bläddra i källkod

New option `stringArrayWrappersChainedCalls`. Renamed option `stringArrayIntermediateVariablesCount` on `stringArrayWrappersCount`

sanex 4 år sedan
förälder
incheckning
f43b138e8a
35 ändrade filer med 390 tillägg och 172 borttagningar
  1. 2 1
      CHANGELOG.md
  2. 70 10
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 7 2
      src/cli/JavaScriptObfuscatorCLI.ts
  7. 3 3
      src/container/modules/custom-nodes/CustomNodesModule.ts
  8. 6 6
      src/custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperNode.ts
  9. 1 1
      src/enums/custom-nodes/StringArrayTransformerCustomNode.ts
  10. 1 1
      src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData.ts
  11. 2 1
      src/interfaces/options/IOptions.ts
  12. 74 45
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  13. 7 1
      src/options/Options.ts
  14. 2 0
      src/options/OptionsNormalizer.ts
  15. 2 1
      src/options/normalizer-rules/StringArrayRule.ts
  16. 2 1
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  17. 19 0
      src/options/normalizer-rules/StringArrayWappersChainedCalls.ts
  18. 2 1
      src/options/presets/Default.ts
  19. 1 1
      src/options/presets/HighObfuscation.ts
  20. 2 1
      src/options/presets/MediumObfuscation.ts
  21. 2 1
      src/options/presets/NoCustomNodes.ts
  22. 1 1
      src/storages/string-array-transformers/LiteralNodesCacheStorage.ts
  23. 0 7
      src/types/node-transformers/string-array-transformers/TStringArrayIntermediateCallsWrapperDataByEncoding.ts
  24. 7 0
      src/types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperDataByEncoding.ts
  25. 7 4
      test/dev/dev.ts
  26. 2 1
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  27. 98 58
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  28. 0 11
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/intermediate-variables-count-eval.js
  29. 17 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-chained-calls-eval.js
  30. 0 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-count-const.js
  31. 11 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-count-eval.js
  32. 0 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-count-var.js
  33. 28 4
      test/functional-tests/options/OptionsNormalizer.spec.ts
  34. 8 3
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  35. 6 6
      test/unit-tests/storages/string-array-transformers/literal-nodes-cache/LiteralNodesCacheStorage.spec.ts

+ 2 - 1
CHANGELOG.md

@@ -2,7 +2,8 @@ Change Log
 
 v2.2.0
 ---
-* **New option:** `stringArrayIntermediateVariablesCount` sets the maximum count of intermediate variables for the `string array` inside each lexical scope
+* **New option:** `stringArrayWrappersCount` sets the count of wrappers for the `string array` inside each root or function scope
+* **New option:** `stringArrayWrappersChainedCalls` enables the chained calls between `string array` wrappers
 
 v2.1.0
 ---

+ 70 - 10
README.md

@@ -360,7 +360,7 @@ Following options are available for the JS Obfuscator:
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArrayEncoding: [],
-    stringArrayIntermediateVariablesCount: true,
+    stringArrayWrappersCount: true,
     stringArrayThreshold: 0.75,
     target: 'browser',
     transformObjectKeys: false,
@@ -409,7 +409,8 @@ Following options are available for the JS Obfuscator:
     --split-strings-chunk-length <number>
     --string-array <boolean>
     --string-array-encoding '<list>' (comma separated) [none, base64, rc4]
-    --string-array-intermediate-variables-count <number>
+    --string-array-wrappers-count <number>
+    --string-array-wrappers-chained-calls <boolean>
     --string-array-threshold <number>
     --target <string> [browser, browser-no-eval, node]
     --transform-object-keys <boolean>
@@ -950,13 +951,13 @@ stringArrayEncoding: [
 ]
 ```
 
-### `stringArrayIntermediateVariablesCount`
+### `stringArrayWrappersCount`
 Type: `number` Default: `0`
 
 ##### :warning: [`stringArray`](#stringarray) option must be enabled
 
-Sets the maximum count of intermediate variables for the `string array` inside each lexical scope.
-The actual count of intermediate variables inside each scope cannot be higher than a number of `literal` nodes within this scope.
+Sets the count of wrappers for the `string array` inside each root or function scope.
+The actual count of wrappers inside each scope is limited by a count of `literal` nodes within this scope.
 
 Example:
 ```ts
@@ -972,7 +973,7 @@ function test () {
 
 const eagle = 'eagle';
 
-// Output, stringArrayIntermediateVariablesCount: 5
+// Output, stringArrayWrappersCount: 5
 const _0x3018 = [
     'foo',
     'bar',
@@ -1001,6 +1002,61 @@ function test() {
 }
 const eagle = _0x26ca42('0x5');
 ```
+
+### `stringArrayWrappersChainedCalls`
+Type: `boolean` Default: `false`
+
+##### :warning: [`stringArray`](#stringarray) and [`stringArrayWrappersCount`](#stringArrayWrappersCount) options must be enabled
+
+Enables the chained calls between `string array` wrappers.
+
+Example:
+```ts
+// Input
+const foo = 'foo';
+const bar = 'bar';
+        
+function test () {
+    const baz = 'baz';
+    const bark = 'bark';
+
+    function test1() {
+        const hawk = 'hawk';
+        const eagle = 'eagle';
+    } 
+}
+
+// Output, stringArrayWrappersCount: 5, stringArrayWrappersChainedCalls: true
+const _0x4714 = [
+    'foo',
+    'bar',
+    'baz',
+    'bark',
+    'hawk',
+    'eagle'
+];
+const _0x2bdb = function (_0x471439, _0x2bdb71) {
+    _0x471439 = _0x471439 - 0x0;
+    let _0x6e47e6 = _0x4714[_0x471439];
+    return _0x6e47e6;
+};
+const _0x1c3d52 = _0x2bdb;
+const _0xd81c2a = _0x2bdb;
+const foo = _0xd81c2a('0x0');
+const bar = _0x1c3d52('0x1');
+function test() {
+    const _0x21a0b4 = _0x1c3d52;
+    const _0x12842d = _0xd81c2a;
+    const _0x6e47e6 = _0x12842d('0x2');
+    const _0x4f3aef = _0x12842d('0x3');
+    function _0x40f1dc() {
+        const _0x468540 = _0x12842d;
+        const _0x1f4b05 = _0x21a0b4;
+        const _0x40a980 = _0x1f4b05('0x4');
+        const _0x4d1285 = _0x468540('0x5');
+    }
+}
+```
     
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
@@ -1100,7 +1156,8 @@ Performance will 50-100% slower than without obfuscation
     splitStringsChunkLength: 5,
     stringArray: true,
     stringArrayEncoding: ['rc4'],
-    stringArrayIntermediateVariablesCount: 10,
+    stringArrayWrappersCount: 5,
+    stringArrayWrappersChainedCalls: true,
     stringArrayThreshold: 1,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
@@ -1133,7 +1190,8 @@ Performance will 30-35% slower than without obfuscation
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArrayEncoding: ['base64'],
-    stringArrayIntermediateVariablesCount: 5,
+    stringArrayWrappersCount: 2,
+    stringArrayWrappersChainedCalls: true,
     stringArrayThreshold: 0.75,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
@@ -1163,7 +1221,8 @@ Performance will slightly slower than without obfuscation
     splitStrings: false,
     stringArray: true,
     stringArrayEncoding: [],
-    stringArrayIntermediateVariablesCount: 0,
+    stringArrayWrappersCount: 0,
+    stringArrayWrappersChainedCalls: false,
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
 }
@@ -1190,7 +1249,8 @@ Performance will slightly slower than without obfuscation
     splitStrings: false,
     stringArray: true,
     stringArrayEncoding: [],
-    stringArrayIntermediateVariablesCount: 0,
+    stringArrayWrappersCount: 0,
+    stringArrayWrappersChainedCalls: false,
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
 }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/index.browser.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/index.cli.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/index.js


+ 7 - 2
src/cli/JavaScriptObfuscatorCLI.ts

@@ -340,10 +340,15 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 ArraySanitizer
             )
             .option(
-                '--string-array-intermediate-variables-count <number>',
-                'Sets the maximum count of intermediate variables for the string array inside each lexical scope',
+                '--string-array-wrappers-count <number>',
+                'Sets the count of wrappers for the string array inside each root or function scope',
                 parseInt
             )
+            .option(
+                '--string-array-wrappers-chained-calls <boolean>',
+                'Enables the chained calls between string array wrappers',
+                BooleanSanitizer
+            )
             .option(
                 '--string-array-threshold <number>',
                 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)',

+ 3 - 3
src/container/modules/custom-nodes/CustomNodesModule.ts

@@ -19,7 +19,7 @@ import { ControlFlowStorageNode } from '../../../custom-nodes/control-flow-flatt
 import { ExpressionWithOperatorControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ExpressionWithOperatorControlFlowStorageCallNode';
 import { LogicalExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/LogicalExpressionFunctionNode';
 import { StringArrayCallNode } from '../../../custom-nodes/string-array-nodes/StringArrayCallNode';
-import { StringArrayIntermediateCallsWrapperNode } from '../../../custom-nodes/string-array-nodes/StringArrayIntermediateCallsWrapperNode';
+import { StringArrayScopeCallsWrapperNode } from '../../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperNode';
 import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
 import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
 
@@ -77,8 +77,8 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayCallNode);
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
-        .toConstructor(StringArrayIntermediateCallsWrapperNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayIntermediateCallsWrapperNode);
+        .toConstructor(StringArrayScopeCallsWrapperNode)
+        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperNode);
 
     // control flow customNode constructor factory
     bind<ICustomNode>(ServiceIdentifiers.Factory__IControlFlowCustomNode)

+ 6 - 6
src/custom-nodes/string-array-nodes/StringArrayIntermediateCallsWrapperNode.ts → src/custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperNode.ts

@@ -15,7 +15,7 @@ import { NodeFactory } from '../../node/NodeFactory';
 import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
-export class StringArrayIntermediateCallsWrapperNode extends AbstractCustomNode {
+export class StringArrayScopeCallsWrapperNode extends AbstractCustomNode {
     /**
      * @type {string}
      */
@@ -26,7 +26,7 @@ export class StringArrayIntermediateCallsWrapperNode extends AbstractCustomNode
      * @type {string}
      */
     @initializable()
-    private stringArrayIntermediateCallsWrapperName!: string;
+    private stringArrayScopeCallsWrapperName!: string;
 
 
     /**
@@ -51,14 +51,14 @@ export class StringArrayIntermediateCallsWrapperNode extends AbstractCustomNode
     }
 
     /**
-     * @param {string} stringArrayIntermediateCallsWrapperName
+     * @param {string} stringArrayScopeCallsWrapperName
      * @param {string} stringArrayCallsWrapperName
      */
     public initialize (
-        stringArrayIntermediateCallsWrapperName: string,
+        stringArrayScopeCallsWrapperName: string,
         stringArrayCallsWrapperName: string
     ): void {
-        this.stringArrayIntermediateCallsWrapperName = stringArrayIntermediateCallsWrapperName;
+        this.stringArrayScopeCallsWrapperName = stringArrayScopeCallsWrapperName;
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
     }
 
@@ -69,7 +69,7 @@ export class StringArrayIntermediateCallsWrapperNode extends AbstractCustomNode
         const structure: TStatement = NodeFactory.variableDeclarationNode(
             [
                 NodeFactory.variableDeclaratorNode(
-                    NodeFactory.identifierNode(this.stringArrayIntermediateCallsWrapperName),
+                    NodeFactory.identifierNode(this.stringArrayScopeCallsWrapperName),
                     NodeFactory.identifierNode(this.stringArrayCallsWrapperName)
                 )
             ],

+ 1 - 1
src/enums/custom-nodes/StringArrayTransformerCustomNode.ts

@@ -1,4 +1,4 @@
 export enum StringArrayTransformerCustomNode {
     StringArrayCallNode = 'StringArrayCallNode',
-    StringArrayIntermediateCallsWrapperNode = 'StringArrayIntermediateCallsWrapperNode'
+    StringArrayScopeCallsWrapperNode = 'StringArrayScopeCallsWrapperNode'
 }

+ 1 - 1
src/interfaces/node-transformers/string-array-transformers/IStringArrayIntermediateCallsWrapperData.ts → src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData.ts

@@ -1,6 +1,6 @@
 import { TStringArrayEncoding } from '../../../types/options/TStringArrayEncoding';
 
-export interface IStringArrayIntermediateCallsWrapperData {
+export interface IStringArrayScopeCallsWrapperData {
     /**
      * @type {TStringArrayEncoding}
      */

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

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

+ 74 - 45
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -7,7 +7,7 @@ import { TInitialData } from '../../types/TInitialData';
 import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 import { TStatement } from '../../types/node/TStatement';
 import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
-import { TStringArrayIntermediateCallsWrapperDataByEncoding } from '../../types/node-transformers/string-array-transformers/TStringArrayIntermediateCallsWrapperDataByEncoding';
+import { TStringArrayScopeCallsWrapperDataByEncoding } from '../../types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperDataByEncoding';
 import { TStringArrayTransformerCustomNodeFactory } from '../../types/container/custom-nodes/TStringArrayTransformerCustomNodeFactory';
 
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
@@ -18,7 +18,7 @@ import { TIdentifierNamesGeneratorFactory } from '../../types/container/generato
 import { ILiteralNodesCacheStorage } from '../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
-import { IStringArrayIntermediateCallsWrapperData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayIntermediateCallsWrapperData';
+import { IStringArrayScopeCallsWrapperData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem';
@@ -35,7 +35,7 @@ import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
 import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeUtils } from '../../node/NodeUtils';
 import { StringArrayCallNode } from '../../custom-nodes/string-array-nodes/StringArrayCallNode';
-import { StringArrayIntermediateCallsWrapperNode } from '../../custom-nodes/string-array-nodes/StringArrayIntermediateCallsWrapperNode';
+import { StringArrayScopeCallsWrapperNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperNode';
 
 @injectable()
 export class StringArrayTransformer extends AbstractNodeTransformer {
@@ -60,11 +60,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
     private readonly literalNodesCacheStorage: ILiteralNodesCacheStorage;
 
     /**
-     * @type {Map<TNodeWithLexicalScope, TStringArrayIntermediateCallsWrapperDataByEncoding>}
+     * @type {Map<TNodeWithLexicalScope, TStringArrayScopeCallsWrapperDataByEncoding>}
      */
-    private readonly stringArrayIntermediateCallsWrapperDataByEncodingMap: Map<
+    private readonly stringArrayScopeCallsWrapperDataByEncodingMap: Map<
         TNodeWithLexicalScope,
-        TStringArrayIntermediateCallsWrapperDataByEncoding
+        TStringArrayScopeCallsWrapperDataByEncoding
     > = new Map();
 
     /**
@@ -252,7 +252,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
         const stringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName(encoding);
 
-        if (!this.options.stringArrayIntermediateVariablesCount) {
+        if (!this.options.stringArrayWrappersCount) {
             return stringArrayCallsWrapperName;
         }
 
@@ -262,35 +262,27 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
             throw new Error('Cannot find current lexical scope node');
         }
 
-        const stringArrayIntermediateCallsWrapperDataByEncoding: TStringArrayIntermediateCallsWrapperDataByEncoding =
-            this.stringArrayIntermediateCallsWrapperDataByEncodingMap.get(currentLexicalScopeNode) ?? {};
-        const stringArrayIntermediateCallsWrapperNames: string[] = stringArrayIntermediateCallsWrapperDataByEncoding[encoding]?.names ?? [];
-        const isFilledIntermediateCallsWrapperNamesList: boolean = stringArrayIntermediateCallsWrapperNames.length === this.options.stringArrayIntermediateVariablesCount;
+        const stringArrayScopeCallsWrapperDataByEncoding: TStringArrayScopeCallsWrapperDataByEncoding =
+            this.stringArrayScopeCallsWrapperDataByEncodingMap.get(currentLexicalScopeNode) ?? {};
+        const stringArrayScopeCallsWrapperNames: string[] = stringArrayScopeCallsWrapperDataByEncoding[encoding]?.names ?? [];
+        const isFilledScopeCallsWrapperNamesList: boolean = stringArrayScopeCallsWrapperNames.length === this.options.stringArrayWrappersCount;
 
-        if (!isFilledIntermediateCallsWrapperNamesList) {
-            const nextIntermediateCallsWrapperName: string = this.identifierNamesGenerator.generateForLexicalScope(currentLexicalScopeNode);
+        if (!isFilledScopeCallsWrapperNamesList) {
+            const nextScopeCallsWrapperName: string = this.identifierNamesGenerator.generateForLexicalScope(currentLexicalScopeNode);
 
-            stringArrayIntermediateCallsWrapperNames.push(nextIntermediateCallsWrapperName);
-            stringArrayIntermediateCallsWrapperDataByEncoding[encoding] = {
+            stringArrayScopeCallsWrapperNames.push(nextScopeCallsWrapperName);
+            stringArrayScopeCallsWrapperDataByEncoding[encoding] = {
                 encoding,
-                names: stringArrayIntermediateCallsWrapperNames
+                names: stringArrayScopeCallsWrapperNames
             };
 
-            this.stringArrayIntermediateCallsWrapperDataByEncodingMap.set(
+            this.stringArrayScopeCallsWrapperDataByEncodingMap.set(
                 currentLexicalScopeNode,
-                stringArrayIntermediateCallsWrapperDataByEncoding
+                stringArrayScopeCallsWrapperDataByEncoding
             );
         }
 
-        return this.randomGenerator.getRandomGenerator().pickone(stringArrayIntermediateCallsWrapperNames);
-    }
-
-    /**
-     * @param {TStringArrayEncoding} encoding
-     * @returns {string}
-     */
-    private getStringArrayRootCallsWrapperName (encoding: TStringArrayEncoding): string {
-        return this.stringArrayStorage.getStorageCallsWrapperName(encoding);
+        return this.randomGenerator.getRandomGenerator().pickone(stringArrayScopeCallsWrapperNames);
     }
 
     /**
@@ -309,7 +301,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @returns {TNodeWithLexicalScope}
      */
     private transformLexicalScopeNode (lexicalScopeNode: TNodeWithLexicalScope): TNodeWithLexicalScope {
-        if (!this.options.stringArrayIntermediateVariablesCount) {
+        if (!this.options.stringArrayWrappersCount) {
             return lexicalScopeNode;
         }
 
@@ -326,40 +318,41 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
             return lexicalScopeNode;
         }
 
-        const stringArrayIntermediateCallsWrapperDataByEncoding: TStringArrayIntermediateCallsWrapperDataByEncoding | null =
-            this.stringArrayIntermediateCallsWrapperDataByEncodingMap.get(lexicalScopeNode) ?? null;
+        const stringArrayScopeCallsWrapperDataByEncoding: TStringArrayScopeCallsWrapperDataByEncoding | null =
+            this.stringArrayScopeCallsWrapperDataByEncodingMap.get(lexicalScopeNode) ?? null;
 
-        if (!stringArrayIntermediateCallsWrapperDataByEncoding) {
+        if (!stringArrayScopeCallsWrapperDataByEncoding) {
             return lexicalScopeNode;
         }
 
-        const stringArrayIntermediateCallsWrapperDataList: (IStringArrayIntermediateCallsWrapperData | undefined)[] =
-            Object.values(stringArrayIntermediateCallsWrapperDataByEncoding);
+        const stringArrayScopeCallsWrapperDataList: (IStringArrayScopeCallsWrapperData | undefined)[] =
+            Object.values(stringArrayScopeCallsWrapperDataByEncoding);
 
         // iterates over data for each encoding type
-        for (const stringArrayIntermediateCallsWrapperData of stringArrayIntermediateCallsWrapperDataList) {
-            if (!stringArrayIntermediateCallsWrapperData) {
+        for (const stringArrayScopeCallsWrapperData of stringArrayScopeCallsWrapperDataList) {
+            if (!stringArrayScopeCallsWrapperData) {
                 continue;
             }
 
-            const {encoding, names} = stringArrayIntermediateCallsWrapperData;
+            const {encoding, names} = stringArrayScopeCallsWrapperData;
 
-            // iterates over each name of intermediate calls wrapper name
-            for (const stringArrayIntermediateCallsWrapperName of names) {
-                const stringArrayRootCallsWrapperName: string = this.getStringArrayRootCallsWrapperName(encoding);
-                const stringArrayIntermediateCallsWrapperNode: ICustomNode<TInitialData<StringArrayIntermediateCallsWrapperNode>> =
+            // iterates over each name of scope wrapper name
+            for (const stringArrayScopeCallsWrapperName of names) {
+                const upperStringArrayCallsWrapperName: string = this.getUpperStringArrayCallsWrapperName(encoding);
+
+                const stringArrayScopeCallsWrapperNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperNode>> =
                     this.stringArrayTransformerCustomNodeFactory(
-                        StringArrayTransformerCustomNode.StringArrayIntermediateCallsWrapperNode
+                        StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperNode
                     );
 
-                stringArrayIntermediateCallsWrapperNode.initialize(
-                    stringArrayIntermediateCallsWrapperName,
-                    stringArrayRootCallsWrapperName
+                stringArrayScopeCallsWrapperNode.initialize(
+                    stringArrayScopeCallsWrapperName,
+                    upperStringArrayCallsWrapperName
                 );
 
                 NodeAppender.prepend(
                     lexicalScopeBodyNode,
-                    stringArrayIntermediateCallsWrapperNode.getNode()
+                    stringArrayScopeCallsWrapperNode.getNode()
                 );
             }
         }
@@ -367,6 +360,42 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         return lexicalScopeNode;
     }
 
+    /**
+     * @param {TStringArrayEncoding} encoding
+     * @returns {string}
+     */
+    private getRootStringArrayCallsWrapperName (encoding: TStringArrayEncoding): string {
+        return this.stringArrayStorage.getStorageCallsWrapperName(encoding);
+    }
+
+    /**
+     * @param {TStringArrayEncoding} encoding
+     * @returns {string}
+     */
+    private getUpperStringArrayCallsWrapperName (encoding: TStringArrayEncoding): string {
+        const rootStringArrayCallsWrapperName: string = this.getRootStringArrayCallsWrapperName(encoding);
+
+        if (!this.options.stringArrayWrappersChainedCalls) {
+            return rootStringArrayCallsWrapperName;
+        }
+
+        const parentLexicalScope: TNodeWithLexicalScope | null = this.arrayUtils.getLastElement(this.visitedLexicalScopeNodesStack);
+
+        if (!parentLexicalScope) {
+            return rootStringArrayCallsWrapperName;
+        }
+
+        const parentLexicalScopeDataByEncoding = this.stringArrayScopeCallsWrapperDataByEncodingMap
+            .get(parentLexicalScope) ?? null;
+        const parentLexicalScopeNames: string[] | null = parentLexicalScopeDataByEncoding?.[encoding]?.names ?? null;
+
+        return parentLexicalScopeNames?.length
+            ? this.randomGenerator
+                .getRandomGenerator()
+                .pickone(parentLexicalScopeNames)
+            : rootStringArrayCallsWrapperName;
+    }
+
     /**
      * @param {Literal} literalNode
      * @param {Node} parentNode

+ 7 - 1
src/options/Options.ts

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

+ 2 - 0
src/options/OptionsNormalizer.ts

@@ -18,6 +18,7 @@ import { SplitStringsChunkLengthRule } from './normalizer-rules/SplitStringsChun
 import { StringArrayRule } from './normalizer-rules/StringArrayRule';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
 import { StringArrayThresholdRule } from './normalizer-rules/StringArrayThresholdRule';
+import { StringArrayWrappersChainedCallsRule } from './normalizer-rules/StringArrayWappersChainedCalls';
 
 @injectable()
 export class OptionsNormalizer implements IOptionsNormalizer {
@@ -38,6 +39,7 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         StringArrayRule,
         StringArrayEncodingRule,
         StringArrayThresholdRule,
+        StringArrayWrappersChainedCallsRule,
     ];
 
     /**

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

@@ -18,7 +18,8 @@ export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOpt
             stringArrayEncoding: [
                 StringArrayEncoding.None
             ],
-            stringArrayIntermediateVariablesCount: 0,
+            stringArrayWrappersChainedCalls: false,
+            stringArrayWrappersCount: 0,
             stringArrayThreshold: 0
         };
     }

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

@@ -17,7 +17,8 @@ export const StringArrayThresholdRule: TOptionsNormalizerRule = (options: IOptio
             stringArrayEncoding: [
                 StringArrayEncoding.None
             ],
-            stringArrayIntermediateVariablesCount: 0,
+            stringArrayWrappersChainedCalls: false,
+            stringArrayWrappersCount: 0,
             stringArrayThreshold: 0
         };
     }

+ 19 - 0
src/options/normalizer-rules/StringArrayWappersChainedCalls.ts

@@ -0,0 +1,19 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+/**
+ * @param {IOptions} options
+ * @returns {IOptions}
+ */
+export const StringArrayWrappersChainedCallsRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.stringArrayWrappersCount === 0) {
+        options = {
+            ...options,
+            stringArrayWrappersChainedCalls: false,
+            stringArrayWrappersCount: 0
+        };
+    }
+
+    return options;
+};

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

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

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

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

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

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

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

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

+ 1 - 1
src/storages/string-array-transformers/LiteralNodesCacheStorage.ts

@@ -47,7 +47,7 @@ export class LiteralNodesCacheStorage extends MapStorage <string, ESTree.Node> i
         stringArrayStorageItemData: IStringArrayStorageItemData | undefined
     ): boolean {
         // for each function scope different nodes will be created, so cache have no sense
-        return !this.options.stringArrayIntermediateVariablesCount
+        return !this.options.stringArrayWrappersCount
             // different nodes will be created with different rc4 keys, so cache have no sense
             && stringArrayStorageItemData?.encoding !== StringArrayEncoding.Rc4
             && this.storage.has(key);

+ 0 - 7
src/types/node-transformers/string-array-transformers/TStringArrayIntermediateCallsWrapperDataByEncoding.ts

@@ -1,7 +0,0 @@
-import { TStringArrayEncoding } from '../../options/TStringArrayEncoding';
-
-import { IStringArrayIntermediateCallsWrapperData } from '../../../interfaces/node-transformers/string-array-transformers/IStringArrayIntermediateCallsWrapperData';
-
-export type TStringArrayIntermediateCallsWrapperDataByEncoding = Partial<{
-    [key in TStringArrayEncoding]: IStringArrayIntermediateCallsWrapperData;
-}>;

+ 7 - 0
src/types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperDataByEncoding.ts

@@ -0,0 +1,7 @@
+import { TStringArrayEncoding } from '../../options/TStringArrayEncoding';
+
+import { IStringArrayScopeCallsWrapperData } from '../../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData';
+
+export type TStringArrayScopeCallsWrapperDataByEncoding = Partial<{
+    [key in TStringArrayEncoding]: IStringArrayScopeCallsWrapperData;
+}>;

+ 7 - 4
test/dev/dev.ts

@@ -14,17 +14,20 @@ import { StringArrayEncoding } from '../../src/enums/StringArrayEncoding';
             function test () {
                 const baz = 'baz';
                 const bark = 'bark';
-                const hawk = 'hawk';
-            }
             
-            const eagle = 'eagle';
+                function test1() {
+                    const hawk = 'hawk';
+                    const eagle = 'eagle';
+                } 
+            }
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
             stringArray: true,
             stringArrayThreshold: 1,
-            stringArrayIntermediateVariablesCount: 5,
+            stringArrayWrappersChainedCalls: true,
+            stringArrayWrappersCount: 5,
             stringArrayEncoding: [
                 StringArrayEncoding.None
             ]

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

@@ -848,7 +848,8 @@ describe('JavaScriptObfuscator', () => {
                             StringArrayEncoding.Base64,
                             StringArrayEncoding.Rc4
                         ],
-                        stringArrayIntermediateVariablesCount: 10,
+                        stringArrayWrappersChainedCalls: true,
+                        stringArrayWrappersCount: 10,
                         stringArrayThreshold: 1,
                         transformObjectKeys: true,
                         unicodeEscapeSequence: false

+ 98 - 58
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -63,7 +63,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #3: `stringArrayIntermediateVariablesCount` option is enabled', () => {
+    describe('Variant #3: `stringArrayWrappersCount` option is enabled', () => {
         describe('Variant #1: root scope', () => {
             describe('Variant #1: option value value is lower then count `literal` nodes in the scope', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
@@ -79,7 +79,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -87,12 +87,12 @@ describe('StringArrayTransformer', function () {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             stringArray: true,
                             stringArrayThreshold: 1,
-                            stringArrayIntermediateVariablesCount: 2
+                            stringArrayWrappersCount: 2
                         }
                     ).getObfuscatedCode();
                 });
 
-                it('should add intermediate calls to the string array calls wrapper', () => {
+                it('should add scope calls wrappers', () => {
                     assert.match(obfuscatedCode, stringArrayCallRegExp);
                 });
             });
@@ -112,7 +112,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -120,12 +120,12 @@ describe('StringArrayTransformer', function () {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             stringArray: true,
                             stringArrayThreshold: 1,
-                            stringArrayIntermediateVariablesCount: 5
+                            stringArrayWrappersCount: 5
                         }
                     ).getObfuscatedCode();
                 });
 
-                it('should add intermediate calls to the string array calls wrapper', () => {
+                it('should add scope calls wrappers', () => {
                     assert.match(obfuscatedCode, stringArrayCallRegExp);
                 });
             });
@@ -146,7 +146,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -154,12 +154,12 @@ describe('StringArrayTransformer', function () {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             stringArray: true,
                             stringArrayThreshold: 1,
-                            stringArrayIntermediateVariablesCount: 2
+                            stringArrayWrappersCount: 2
                         }
                     ).getObfuscatedCode();
                 });
 
-                it('should add intermediate calls to the string array calls wrapper', () => {
+                it('should add scope calls wrappers', () => {
                     assert.match(obfuscatedCode, stringArrayCallRegExp);
                 });
             });
@@ -179,7 +179,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -187,12 +187,12 @@ describe('StringArrayTransformer', function () {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             stringArray: true,
                             stringArrayThreshold: 1,
-                            stringArrayIntermediateVariablesCount: 5
+                            stringArrayWrappersCount: 5
                         }
                     ).getObfuscatedCode();
                 });
 
-                it('should add intermediate calls to the string array calls wrapper', () => {
+                it('should add scope calls wrappers', () => {
                     assert.match(obfuscatedCode, stringArrayCallRegExp);
                 });
             });
@@ -212,7 +212,7 @@ describe('StringArrayTransformer', function () {
             let obfuscatedCode: string;
 
             before(() => {
-                const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-var.js');
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-var.js');
 
                 obfuscatedCode = JavaScriptObfuscator.obfuscate(
                     code,
@@ -220,22 +220,22 @@ describe('StringArrayTransformer', function () {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1,
-                        stringArrayIntermediateVariablesCount: 2
+                        stringArrayWrappersCount: 2
                     }
                 ).getObfuscatedCode();
             });
 
-            it('should add intermediate calls to the string array calls wrapper with a correct variables kind', () => {
+            it('should add scope calls wrappers with a correct variables kind', () => {
                 assert.match(obfuscatedCode, stringArrayCallRegExp);
             });
         });
 
-        describe('Variant #4: correct evaluation of the intermediate calls', () => {
-            const expectedEvaluationResult: string = '12345';
+        describe('Variant #4: correct evaluation of the scope calls wrappers', () => {
+            const expectedEvaluationResult: string = 'aaabbbcccdddeee';
             let evaluationResult: string;
 
             before(() => {
-                const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-eval.js');
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-eval.js');
 
                 const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
                     code,
@@ -243,17 +243,57 @@ describe('StringArrayTransformer', function () {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1,
-                        stringArrayIntermediateVariablesCount: 5
+                        stringArrayWrappersCount: 5
                     }
                 ).getObfuscatedCode();
 
                 evaluationResult = eval(obfuscatedCode);
             });
 
-            it('should correctly evaluate intermediate calls', () => {
+            it('should correctly evaluate scope calls wrappers', () => {
                 assert.equal(evaluationResult, expectedEvaluationResult);
             });
         });
+
+        describe('Variant #5: `stringArrayWrappersChainedCalls` option is enabled', () => {
+            describe('Variant #1: correct evaluation of the string array wrappers chained calls', () => {
+                const samplesCount: number = 50;
+                const expectedEvaluationResult: string = 'aaabbbcccdddeee';
+                let isEvaluationSuccessful: boolean = true;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-chained-calls-eval.js');
+
+                    for (let i = 0; i < samplesCount; i++) {
+                        const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayEncoding: [
+                                    StringArrayEncoding.None,
+                                    StringArrayEncoding.Rc4
+                                ],
+                                stringArrayWrappersChainedCalls: true,
+                                stringArrayWrappersCount: 5
+                            }
+                        ).getObfuscatedCode();
+
+                        const evaluationResult: string = eval(obfuscatedCode);
+
+                        if (evaluationResult !== expectedEvaluationResult) {
+                            isEvaluationSuccessful = false;
+                            break;
+                        }
+                    }
+                });
+
+                it('should correctly evaluate string array wrappers chained calls', () => {
+                    assert.equal(isEvaluationSuccessful, true);
+                });
+            });
+        });
     });
 
     describe('Variant #4: string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
@@ -530,10 +570,10 @@ describe('StringArrayTransformer', function () {
             });
         });
 
-        describe('Variant #2: `stringArrayIntermediateVariablesCount` option is enabled', () => {
+        describe('Variant #2: `stringArrayWrappersCount` option is enabled', () => {
             describe('Variant #1: root scope', () => {
-                describe('Variant #1: `1` intermediate variable for each encoding type', () => {
-                    const stringArrayIntermediateCallRegExp: RegExp = new RegExp(
+                describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
+                    const stringArrayWrappersRegExp: RegExp = new RegExp(
                             'return _0x([a-f0-9]){4,6};' +
                         '};' +
                         'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
@@ -548,7 +588,7 @@ describe('StringArrayTransformer', function () {
                     let obfuscatedCode: string;
 
                     before(() => {
-                        const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                        const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                         obfuscatedCode = JavaScriptObfuscator.obfuscate(
                             code,
@@ -559,19 +599,19 @@ describe('StringArrayTransformer', function () {
                                     StringArrayEncoding.None,
                                     StringArrayEncoding.Base64
                                 ],
-                                stringArrayIntermediateVariablesCount: 1,
+                                stringArrayWrappersCount: 1,
                                 stringArrayThreshold: 1
                             }
                         ).getObfuscatedCode();
                     });
 
-                    it('should add intermediate variables for both `none` and `base64` string array wrappers', () => {
-                        assert.match(obfuscatedCode, stringArrayIntermediateCallRegExp);
+                    it('should add scope calls wrappers for both `none` and `base64` string array wrappers', () => {
+                        assert.match(obfuscatedCode, stringArrayWrappersRegExp);
                     });
                 });
 
-                describe('Variant #2: `2` intermediate variables for each encoding type', () => {
-                    const stringArrayIntermediateCallRegExp: RegExp = new RegExp(
+                describe('Variant #2: `2` scope calls wrappers for each encoding type', () => {
+                    const stringArrayWrappersRegExp: RegExp = new RegExp(
                             'return _0x([a-f0-9]){4,6};' +
                         '};' +
                         'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
@@ -587,7 +627,7 @@ describe('StringArrayTransformer', function () {
                     let obfuscatedCode: string;
 
                     before(() => {
-                        const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                        const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                         obfuscatedCode = JavaScriptObfuscator.obfuscate(
                             code,
@@ -598,21 +638,21 @@ describe('StringArrayTransformer', function () {
                                     StringArrayEncoding.None,
                                     StringArrayEncoding.Base64
                                 ],
-                                stringArrayIntermediateVariablesCount: 2,
+                                stringArrayWrappersCount: 2,
                                 stringArrayThreshold: 1
                             }
                         ).getObfuscatedCode();
                     });
 
-                    it('should add intermediate variables for both `none` and `base64` string array wrappers', () => {
-                        assert.match(obfuscatedCode, stringArrayIntermediateCallRegExp);
+                    it('should add scope calls wrappers for both `none` and `base64` string array wrappers', () => {
+                        assert.match(obfuscatedCode, stringArrayWrappersRegExp);
                     });
                 });
             });
 
             describe('Variant #2: function scope', () => {
-                describe('Variant #1: `1` intermediate variable for each encoding type', () => {
-                    const stringArrayIntermediateCallRegExp: RegExp = new RegExp(
+                describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
+                    const stringArrayWrappersRegExp: RegExp = new RegExp(
                         'function test *\\( *\\) *{' +
                             'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                             // this one may be added or not depends on:
@@ -627,7 +667,7 @@ describe('StringArrayTransformer', function () {
                     let obfuscatedCode: string;
 
                     before(() => {
-                        const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                        const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                         obfuscatedCode = JavaScriptObfuscator.obfuscate(
                             code,
@@ -638,19 +678,19 @@ describe('StringArrayTransformer', function () {
                                     StringArrayEncoding.None,
                                     StringArrayEncoding.Base64
                                 ],
-                                stringArrayIntermediateVariablesCount: 1,
+                                stringArrayWrappersCount: 1,
                                 stringArrayThreshold: 1
                             }
                         ).getObfuscatedCode();
                     });
 
-                    it('should add intermediate variables for both `none` and `base64` string array wrappers', () => {
-                        assert.match(obfuscatedCode, stringArrayIntermediateCallRegExp);
+                    it('should add scope calls wrappers for both `none` and `base64` string array wrappers', () => {
+                        assert.match(obfuscatedCode, stringArrayWrappersRegExp);
                     });
                 });
 
-                describe('Variant #2: `2` intermediate variables for each encoding type', () => {
-                    const stringArrayIntermediateCallRegExp: RegExp = new RegExp(
+                describe('Variant #2: `2` scope calls wrappers for each encoding type', () => {
+                    const stringArrayWrappersRegExp: RegExp = new RegExp(
                         'function test *\\( *\\) *{' +
                             'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                             'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
@@ -666,7 +706,7 @@ describe('StringArrayTransformer', function () {
                     let obfuscatedCode: string;
 
                     before(() => {
-                        const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-const.js');
+                        const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-const.js');
 
                         obfuscatedCode = JavaScriptObfuscator.obfuscate(
                             code,
@@ -677,14 +717,14 @@ describe('StringArrayTransformer', function () {
                                     StringArrayEncoding.None,
                                     StringArrayEncoding.Base64
                                 ],
-                                stringArrayIntermediateVariablesCount: 2,
+                                stringArrayWrappersCount: 2,
                                 stringArrayThreshold: 1
                             }
                         ).getObfuscatedCode();
                     });
 
-                    it('should add intermediate variables for both `none` and `base64` string array wrappers', () => {
-                        assert.match(obfuscatedCode, stringArrayIntermediateCallRegExp);
+                    it('should add scope calls wrappers for both `none` and `base64` string array wrappers', () => {
+                        assert.match(obfuscatedCode, stringArrayWrappersRegExp);
                     });
                 });
             });
@@ -746,13 +786,13 @@ describe('StringArrayTransformer', function () {
             });
         });
 
-        describe('Variant #2: `stringArrayIntermediateVariablesCount` option is enabled', () => {
-            describe('Variant #1: correct evaluation of the intermediate calls', () => {
-                const expectedEvaluationResult: string = '12345';
+        describe('Variant #2: `stringArrayWrappersCount` option is enabled', () => {
+            describe('Variant #1: correct evaluation of the scope calls wrappers', () => {
+                const expectedEvaluationResult: string = 'aaabbbcccdddeee';
                 let evaluationResult: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-eval.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-eval.js');
 
                     const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
                         code,
@@ -764,14 +804,14 @@ describe('StringArrayTransformer', function () {
                                 StringArrayEncoding.None,
                                 StringArrayEncoding.Rc4
                             ],
-                            stringArrayIntermediateVariablesCount: 5
+                            stringArrayWrappersCount: 5
                         }
                     ).getObfuscatedCode();
 
                     evaluationResult = eval(obfuscatedCode);
                 });
 
-                it('should correctly evaluate intermediate calls', () => {
+                it('should correctly evaluate scope calls wrappers', () => {
                     assert.equal(evaluationResult, expectedEvaluationResult);
                 });
             });
@@ -833,13 +873,13 @@ describe('StringArrayTransformer', function () {
             });
         });
 
-        describe('Variant #2: `stringArrayIntermediateVariablesCount` option is enabled', () => {
-            describe('Variant #1: correct evaluation of the intermediate calls', () => {
-                const expectedEvaluationResult: string = '12345';
+        describe('Variant #2: `stringArrayWrappersCount` option is enabled', () => {
+            describe('Variant #1: correct evaluation of the scope calls wrappers', () => {
+                const expectedEvaluationResult: string = 'aaabbbcccdddeee';
                 let evaluationResult: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/intermediate-variables-count-eval.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/string-array-wrappers-count-eval.js');
 
                     const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
                         code,
@@ -851,14 +891,14 @@ describe('StringArrayTransformer', function () {
                                 StringArrayEncoding.Base64,
                                 StringArrayEncoding.Rc4
                             ],
-                            stringArrayIntermediateVariablesCount: 5
+                            stringArrayWrappersCount: 5
                         }
                     ).getObfuscatedCode();
 
                     evaluationResult = eval(obfuscatedCode);
                 });
 
-                it('should correctly evaluate intermediate calls', () => {
+                it('should correctly evaluate scope calls wrappers', () => {
                     assert.equal(evaluationResult, expectedEvaluationResult);
                 });
             });

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

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

+ 17 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-chained-calls-eval.js

@@ -0,0 +1,17 @@
+const foo = 'aaa';
+
+function test () {
+    const bar = 'bbb';
+    const baz = 'ccc';
+
+    function test1 () {
+        const bark = 'ddd';
+        const hawk = 'eee';
+
+        return bark + hawk;
+    }
+
+    return bar + baz + test1();
+}
+
+foo + test();

+ 0 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/intermediate-variables-count-const.js → test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-count-const.js


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

@@ -0,0 +1,11 @@
+function func () {
+    var foo = 'aaa';
+    var bar = 'bbb';
+    var baz = 'ccc';
+    var bark = 'ddd';
+    var hawk = 'eee';
+
+    return foo + bar + baz + bark + hawk;
+}
+
+func();

+ 0 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/intermediate-variables-count-var.js → test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-wrappers-count-var.js


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

@@ -544,7 +544,8 @@ describe('OptionsNormalizer', () => {
                     shuffleStringArray: true,
                     stringArray: false,
                     stringArrayEncoding: [StringArrayEncoding.Rc4],
-                    stringArrayIntermediateVariablesCount: 5,
+                    stringArrayWrappersChainedCalls: true,
+                    stringArrayWrappersCount: 5,
                     stringArrayThreshold: 0.5,
                     rotateStringArray: true
                 });
@@ -554,7 +555,8 @@ describe('OptionsNormalizer', () => {
                     shuffleStringArray: false,
                     stringArray: false,
                     stringArrayEncoding: [StringArrayEncoding.None],
-                    stringArrayIntermediateVariablesCount: 0,
+                    stringArrayWrappersChainedCalls: false,
+                    stringArrayWrappersCount: 0,
                     stringArrayThreshold: 0,
                     rotateStringArray: false
                 };
@@ -592,7 +594,8 @@ describe('OptionsNormalizer', () => {
                     rotateStringArray: true,
                     shuffleStringArray: true,
                     stringArray: true,
-                    stringArrayIntermediateVariablesCount: 5,
+                    stringArrayWrappersChainedCalls: true,
+                    stringArrayWrappersCount: 5,
                     stringArrayThreshold: 0
                 });
 
@@ -601,7 +604,8 @@ describe('OptionsNormalizer', () => {
                     rotateStringArray: false,
                     shuffleStringArray: false,
                     stringArray: false,
-                    stringArrayIntermediateVariablesCount: 0,
+                    stringArrayWrappersChainedCalls: false,
+                    stringArrayWrappersCount: 0,
                     stringArrayThreshold: 0
                 };
             });
@@ -610,5 +614,25 @@ describe('OptionsNormalizer', () => {
                 assert.deepEqual(optionsPreset, expectedOptionsPreset);
             });
         });
+
+        describe('stringArrayWrappersChainedCallsRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...getDefaultOptions(),
+                    stringArrayWrappersChainedCalls: true,
+                    stringArrayWrappersCount: 0
+                });
+
+                expectedOptionsPreset = {
+                    ...getDefaultOptions(),
+                    stringArrayWrappersChainedCalls: false,
+                    stringArrayWrappersCount: 0
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
+        });
     });
 });

+ 8 - 3
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -39,7 +39,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
             StringArrayEncoding.Base64,
             StringArrayEncoding.Rc4
         ],
-        stringArrayIntermediateVariablesCount: 20,
+        stringArrayWrappersCount: 5,
         stringArrayThreshold: 1,
         transformObjectKeys: true,
         unicodeEscapeSequence: true
@@ -47,7 +47,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
 
     this.timeout(200000);
 
-    [
+    const options: Partial<TInputOptions>[] = [
         {
             identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
             renameGlobals: false
@@ -71,8 +71,13 @@ describe('JavaScriptObfuscator runtime eval', function () {
         {
             identifierNamesGenerator: IdentifierNamesGenerator.MangledShuffledIdentifierNamesGenerator,
             renameGlobals: true
+        },
+        {
+            stringArrayWrappersChainedCalls: true
         }
-    ].forEach((options: Partial<TInputOptions>) => {
+    ];
+
+    options.forEach((options: Partial<TInputOptions>) => {
         const detailedDescription: string = `Identifier names generator: ${options.identifierNamesGenerator}, rename globals: ${options.renameGlobals?.toString()}`;
 
         describe(`Astring. ${detailedDescription}`, () => {

+ 6 - 6
test/unit-tests/storages/string-array-transformers/literal-nodes-cache/LiteralNodesCacheStorage.spec.ts

@@ -65,14 +65,14 @@ describe('LiteralNodesCacheStorage', () => {
         const literalNode: ESTree.Literal =  NodeFactory.literalNode('foo');
         const key: string = 'key';
 
-        describe('Encoding is not `rc4` and `stringArrayIntermediateVariablesCount` option is disabled', () => {
+        describe('Encoding is not `rc4` and `stringArrayWrappersCount` option is disabled', () => {
             const expectedResult: boolean = true;
 
             let result: boolean;
 
             before(() => {
                 const literalNodesCacheStorage: ILiteralNodesCacheStorage = getStorageInstance({
-                    stringArrayIntermediateVariablesCount: 0
+                    stringArrayWrappersCount: 0
                 });
 
                 literalNodesCacheStorage.set(key, literalNode);
@@ -94,14 +94,14 @@ describe('LiteralNodesCacheStorage', () => {
             });
         });
 
-        describe('Encoding is `rc4` and `stringArrayIntermediateVariablesCount` option is disabled', () => {
+        describe('Encoding is `rc4` and `stringArrayWrappersCount` option is disabled', () => {
             const expectedResult: boolean = false
 
             let result: boolean;
 
             before(() => {
                 const literalNodesCacheStorage: ILiteralNodesCacheStorage = getStorageInstance({
-                    stringArrayIntermediateVariablesCount: 0
+                    stringArrayWrappersCount: 0
                 });
 
                 literalNodesCacheStorage.set(key, literalNode);
@@ -123,7 +123,7 @@ describe('LiteralNodesCacheStorage', () => {
             });
         });
 
-        describe('Encoding is not `rc4` and `stringArrayIntermediateVariablesCount` option is enabled', () => {
+        describe('Encoding is not `rc4` and `stringArrayWrappersCount` option is enabled', () => {
             const expectedResult: boolean = false;
 
             let result: boolean;
@@ -132,7 +132,7 @@ describe('LiteralNodesCacheStorage', () => {
                 const literalNodesCacheStorage: ILiteralNodesCacheStorage = getStorageInstance({
                     stringArray: true,
                     stringArrayThreshold: 1,
-                    stringArrayIntermediateVariablesCount: 5
+                    stringArrayWrappersCount: 5
                 });
 
                 literalNodesCacheStorage.set(key, literalNode);

Vissa filer visades inte eftersom för många filer har ändrats