Browse Source

Merge pull request #812 from javascript-obfuscator/string-array-calls-wrapper-additional-parameters

WIP: added random string array scope calls wrappers parameters
Timofey Kachalov 4 năm trước cách đây
mục cha
commit
a4328da67f
34 tập tin đã thay đổi với 913 bổ sung184 xóa
  1. 2 0
      CHANGELOG.md
  2. 16 3
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  7. 2 0
      src/container/modules/custom-nodes/CustomNodesModule.ts
  8. 25 0
      src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts
  9. 76 10
      src/custom-nodes/string-array-nodes/StringArrayCallNode.ts
  10. 121 32
      src/custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode.ts
  11. 8 0
      src/custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode.ts
  12. 18 0
      src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData.ts
  13. 7 0
      src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeData.ts
  14. 11 0
      src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperParameterIndexesData.ts
  15. 1 0
      src/interfaces/options/IOptions.ts
  16. 7 0
      src/interfaces/utils/IArrayUtils.ts
  17. 8 0
      src/interfaces/utils/IRandomGenerator.ts
  18. 45 18
      src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts
  19. 46 14
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  20. 7 0
      src/options/Options.ts
  21. 1 0
      src/options/presets/Default.ts
  22. 1 2
      src/options/presets/HighObfuscation.ts
  23. 3 0
      src/options/presets/MediumObfuscation.ts
  24. 1 0
      src/options/presets/NoCustomNodes.ts
  25. 15 0
      src/utils/ArrayUtils.ts
  26. 20 0
      src/utils/RandomGenerator.ts
  27. 17 20
      test/dev/dev.ts
  28. 1 0
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  29. 262 85
      test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts
  30. 38 0
      test/helpers/checkCodeEvaluation.ts
  31. 1 0
      test/index.spec.ts
  32. 1 0
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  33. 55 0
      test/unit-tests/utils/ArrayUtils.spec.ts
  34. 92 0
      test/unit-tests/utils/RandomGenerator.spec.ts

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ v2.9.0
 * New option: `stringArrayIndexesType` accepts an array of types of string array call indexes
 * Changed default type of all string array call indexes from `hexadecimal-numeric-string` to `hexadecimal-number`
 * New option: `stringArrayIndexShift` enables additional index shift for all string array calls
+* New option: `stringArrayWrappersParametersMaxCount` allows to control the maximum number of string array wrappers parameters
+* `stringArrayWrappersType: 'function'` option value moved from `high-obfuscation` to `medium-obfuscation` options preset
 
 v2.8.1
 ---

+ 16 - 3
README.md

@@ -1126,6 +1126,15 @@ function test() {
 }
 ```
 
+### `stringArrayWrappersParametersCount`
+Type: `number` Default: `2`
+
+##### :warning: [`stringArray`](#stringarray) option must be enabled
+##### :warning: Currently this option affects only wrappers added by [`stringArrayWrappersType`](#stringarraywrapperstype) `function` option value
+
+Allows to control the maximum number of string array wrappers parameters.
+Default and minimum value is `2`. Recommended value between `2` and `5`.
+
 ### `stringArrayWrappersType`
 Type: `string` Default: `variable`
 
@@ -1135,7 +1144,7 @@ Allows to select a type of the wrappers that are appending by the `stringArrayWr
 
 Available values:
 * `'variable'`: appends variable wrappers. Fast performance.
-* `'function'`: appends function wrappers. More slow performance than with `variable` but allows to additionally shift the `stringArray` index.
+* `'function'`: appends function wrappers. Slower performance than with `variable` but provides more strict obfuscation
 
 Highly recommended to use `function` wrappers for higher obfuscation when a performance loss doesn't have a high impact on an obfuscated application.
 
@@ -1270,7 +1279,8 @@ Performance will 50-100% slower than without obfuscation
     stringArrayEncoding: ['rc4'],
     stringArrayIndexShift: true,
     stringArrayWrappersCount: 5,
-    stringArrayWrappersChainedCalls: true,
+    stringArrayWrappersChainedCalls: true,    
+    stringArrayWrappersParametersMaxCount: 5,
     stringArrayWrappersType: 'function',
     stringArrayThreshold: 1,
     transformObjectKeys: true,
@@ -1307,7 +1317,8 @@ Performance will 30-35% slower than without obfuscation
     stringArrayIndexShift: true,
     stringArrayWrappersCount: 2,
     stringArrayWrappersChainedCalls: true,
-    stringArrayWrappersType: 'variable',
+    stringArrayWrappersParametersMaxCount: 4,
+    stringArrayWrappersType: 'function',
     stringArrayThreshold: 0.75,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
@@ -1340,6 +1351,7 @@ Performance will slightly slower than without obfuscation
     stringArrayIndexShift: true,
     stringArrayWrappersCount: 1,
     stringArrayWrappersChainedCalls: true,
+    stringArrayWrappersParametersMaxCount: 2,
     stringArrayWrappersType: 'variable',
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
@@ -1370,6 +1382,7 @@ Performance will slightly slower than without obfuscation
     stringArrayIndexShift: true,
     stringArrayWrappersCount: 1,
     stringArrayWrappersChainedCalls: true,
+    stringArrayWrappersParametersMaxCount: 2,
     stringArrayWrappersType: 'variable',
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/index.browser.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/index.cli.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/index.js


+ 5 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -373,6 +373,11 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 'Enables the chained calls between string array wrappers',
                 BooleanSanitizer
             )
+            .option(
+                '--string-array-wrappers-parameters-count <number>',
+                'Allows to control the maximum number of string array wrappers parameters',
+                parseInt
+            )
             .option(
                 '--string-array-wrappers-type <string>',
                 'Allows to select a type of the wrappers that are appending by the `--string-array-wrappers-count` option. ' +

+ 2 - 0
src/container/modules/custom-nodes/CustomNodesModule.ts

@@ -141,6 +141,8 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
                 ServiceIdentifiers.Factory__IIdentifierNamesGenerator,
                 ServiceIdentifiers.Factory__IStringArrayIndexNode,
                 ServiceIdentifiers.ICustomCodeHelperFormatter,
+                ServiceIdentifiers.IStringArrayStorage,
+                ServiceIdentifiers.IArrayUtils,
                 ServiceIdentifiers.IRandomGenerator,
                 ServiceIdentifiers.IOptions
             ));

+ 25 - 0
src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts

@@ -10,6 +10,7 @@ import { TStringArrayIndexNodeFactory } from '../../types/container/custom-nodes
 import { ICustomCodeHelperFormatter } from '../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 
 import { StringArrayIndexesType } from '../../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayIndexNode } from '../../enums/custom-nodes/string-array-index-nodes/StringArrayIndexNode';
@@ -18,9 +19,17 @@ import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeUtils } from '../../node/NodeUtils';
+import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 
 @injectable()
 export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
+    /**
+     * Max count of root string array calls wrapper parameters
+     *
+     * @type {number}
+     */
+    protected static readonly stringArrayRootCallsWrapperParametersCount: number = 2;
+
     /**
      * @type {Map<TStringArrayIndexesType, StringArrayIndexNode>}
      */
@@ -29,6 +38,16 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
         [StringArrayIndexesType.HexadecimalNumericString, StringArrayIndexNode.StringArrayHexadecimalNumericStringIndexNode]
     ]);
 
+    /**
+     * @type {IArrayUtils}
+     */
+    protected readonly arrayUtils: IArrayUtils;
+
+    /**
+     * @type {IStringArrayStorage}
+     */
+    protected readonly stringArrayStorage: IStringArrayStorage;
+
     /**
      * @type {TStringArrayIndexNodeFactory}
      */
@@ -38,6 +57,8 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TStringArrayIndexNodeFactory} stringArrayIndexNodeFactory
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter
+     * @param {IStringArrayStorage} stringArrayStorage
+     * @param {IArrayUtils} arrayUtils
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
@@ -47,6 +68,8 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
         @inject(ServiceIdentifiers.Factory__IStringArrayIndexNode)
             stringArrayIndexNodeFactory: TStringArrayIndexNodeFactory,
         @inject(ServiceIdentifiers.ICustomCodeHelperFormatter) customCodeHelperFormatter: ICustomCodeHelperFormatter,
+        @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
+        @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
@@ -58,6 +81,8 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode {
         );
 
         this.stringArrayIndexNodeFactory = stringArrayIndexNodeFactory;
+        this.stringArrayStorage = stringArrayStorage;
+        this.arrayUtils = arrayUtils;
     }
 
     /**

+ 76 - 10
src/custom-nodes/string-array-nodes/StringArrayCallNode.ts

@@ -7,9 +7,12 @@ import { TIdentifierNamesGeneratorFactory } from '../../types/container/generato
 import { TStatement } from '../../types/node/TStatement';
 import { TStringArrayIndexNodeFactory } from '../../types/container/custom-nodes/string-array-index-nodes/TStringArrayIndexNodeFactory';
 
+import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { ICustomCodeHelperFormatter } from '../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayScopeCallsWrapperParameterIndexesData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperParameterIndexesData';
+import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 
 import { initializable } from '../../decorators/Initializable';
 
@@ -43,10 +46,18 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
     @initializable()
     private stringArrayCallsWrapperName!: string;
 
+    /**
+     * @type {IStringArrayScopeCallsWrapperParameterIndexesData | null}
+     */
+    @initializable()
+    private stringArrayCallsWrapperParameterIndexesData!: IStringArrayScopeCallsWrapperParameterIndexesData | null;
+
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TStringArrayIndexNodeFactory} stringArrayIndexNodeFactory
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter
+     * @param {IStringArrayStorage} stringArrayStorage
+     * @param {IArrayUtils} arrayUtils
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
@@ -56,6 +67,8 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
         @inject(ServiceIdentifiers.Factory__IStringArrayIndexNode)
             stringArrayIndexNodeFactory: TStringArrayIndexNodeFactory,
         @inject(ServiceIdentifiers.ICustomCodeHelperFormatter) customCodeHelperFormatter: ICustomCodeHelperFormatter,
+        @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
+        @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
@@ -63,6 +76,8 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
             identifierNamesGeneratorFactory,
             stringArrayIndexNodeFactory,
             customCodeHelperFormatter,
+            stringArrayStorage,
+            arrayUtils,
             randomGenerator,
             options
         );
@@ -70,17 +85,20 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
 
     /**
      * @param {string} stringArrayCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} stringArrayCallsWrapperParameterIndexesData
      * @param {number} index
      * @param {number} indexShiftAmount
      * @param {string | null} decodeKey
      */
     public initialize (
         stringArrayCallsWrapperName: string,
+        stringArrayCallsWrapperParameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null,
         index: number,
         indexShiftAmount: number,
         decodeKey: string | null
     ): void {
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
+        this.stringArrayCallsWrapperParameterIndexesData = stringArrayCallsWrapperParameterIndexesData;
         this.index = index;
         this.indexShiftAmount = indexShiftAmount;
         this.decodeKey = decodeKey;
@@ -90,21 +108,46 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
      * @returns {TStatement[]}
      */
     protected getNodeStructure (): TStatement[] {
-        const callExpressionArgs: ESTree.Expression[] = [
-            this.getStringArrayIndexNode(this.indexShiftAmount + this.index)
-        ];
-
-        if (this.decodeKey) {
-            callExpressionArgs.push(this.getRc4KeyLiteralNode(this.decodeKey));
-        }
+        const resultIndex: number = this.indexShiftAmount + this.index;
+
+        const indexNode: ESTree.Expression = this.getStringArrayIndexNode(resultIndex);
+        const rc4KeyLiteralNode: ESTree.Literal | null = this.decodeKey
+            ? this.getRc4KeyLiteralNode(this.decodeKey)
+            : null;
+
+        // filling call expression arguments with a fake arguments first
+        const callExpressionArgs: ESTree.Expression[] = this.arrayUtils.fillWithRange(
+            !this.stringArrayCallsWrapperParameterIndexesData
+                // root string array calls wrapper
+                ? AbstractStringArrayCallNode.stringArrayRootCallsWrapperParametersCount
+                // scope string array calls wrapper
+                : this.options.stringArrayWrappersParametersMaxCount,
+            () => this.getFakeStringArrayIndexNode(resultIndex)
+        );
 
-        const stringArrayIdentifierNode: ESTree.Identifier =
-            NodeFactory.identifierNode(this.stringArrayCallsWrapperName);
+        callExpressionArgs.splice(
+            this.stringArrayCallsWrapperParameterIndexesData?.valueIndexParameterIndex ?? 0,
+            1,
+            indexNode
+        );
 
+        if (this.stringArrayCallsWrapperParameterIndexesData) {
+            callExpressionArgs.splice(
+                this.stringArrayCallsWrapperParameterIndexesData.decodeKeyParameterIndex,
+                1,
+                // use rc4 key literal node if exists or a node with fake string array wrapper index
+                rc4KeyLiteralNode ?? this.getFakeStringArrayIndexNode(resultIndex)
+            );
+        } else if (rc4KeyLiteralNode) {
+            callExpressionArgs.splice(1, 1, rc4KeyLiteralNode);
+        } else {
+            // have to delete element
+            callExpressionArgs.splice(1, 1);
+        }
 
         const structure: TStatement = NodeFactory.expressionStatementNode(
             NodeFactory.callExpressionNode(
-                stringArrayIdentifierNode,
+                NodeFactory.identifierNode(this.stringArrayCallsWrapperName),
                 callExpressionArgs
             )
         );
@@ -113,4 +156,27 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
 
         return [structure];
     }
+
+    /**
+     * @param {number} actualIndex
+     * @returns {Expression}
+     */
+    private getFakeStringArrayIndexNode (actualIndex: number): ESTree.Expression {
+        return this.getStringArrayIndexNode(this.getFakeStringArrayIndex(actualIndex));
+    }
+
+    /**
+     * @param {number} actualIndex
+     * @returns {number}
+     */
+    private getFakeStringArrayIndex (actualIndex: number): number {
+        const stringArrayStorageLength: number = this.stringArrayStorage.getLength();
+
+        const fakeIndexOffset: number = stringArrayStorageLength / 2;
+
+        const minimumIndex: number = actualIndex - fakeIndexOffset;
+        const maximumIndex: number = actualIndex + fakeIndexOffset;
+
+        return this.randomGenerator.getRandomInteger(minimumIndex, maximumIndex);
+    }
 }

+ 121 - 32
src/custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode.ts

@@ -7,9 +7,12 @@ import { TIdentifierNamesGeneratorFactory } from '../../types/container/generato
 import { TStatement } from '../../types/node/TStatement';
 import { TStringArrayIndexNodeFactory } from '../../types/container/custom-nodes/string-array-index-nodes/TStringArrayIndexNodeFactory';
 
+import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { ICustomCodeHelperFormatter } from '../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayScopeCallsWrapperParameterIndexesData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperParameterIndexesData';
+import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 
 import { initializable } from '../../decorators/Initializable';
 
@@ -29,7 +32,13 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
      * @type {string}
      */
     @initializable()
-    private stringArrayCallsWrapperName!: string;
+    private upperStringArrayCallsWrapperName!: string;
+
+    /**
+     * @type {IStringArrayScopeCallsWrapperParameterIndexesData}
+     */
+    @initializable()
+    private upperStringArrayCallsWrapperParameterIndexesData!: IStringArrayScopeCallsWrapperParameterIndexesData | null;
 
     /**
      * @type {string}
@@ -37,11 +46,19 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
     @initializable()
     private stringArrayScopeCallsWrapperName!: string;
 
+    /**
+     * @type {IStringArrayScopeCallsWrapperParameterIndexesData}
+     */
+    @initializable()
+    private stringArrayScopeCallsWrapperParameterIndexesData!: IStringArrayScopeCallsWrapperParameterIndexesData | null;
+
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TStringArrayIndexNodeFactory} stringArrayIndexNodeFactory
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter
+     * @param {IStringArrayStorage} stringArrayStorage
+     * @param {IArrayUtils} arrayUtils
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
@@ -51,6 +68,8 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
         @inject(ServiceIdentifiers.Factory__IStringArrayIndexNode)
             stringArrayIndexNodeFactory: TStringArrayIndexNodeFactory,
         @inject(ServiceIdentifiers.ICustomCodeHelperFormatter) customCodeHelperFormatter: ICustomCodeHelperFormatter,
+        @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
+        @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
@@ -58,6 +77,8 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
             identifierNamesGeneratorFactory,
             stringArrayIndexNodeFactory,
             customCodeHelperFormatter,
+            stringArrayStorage,
+            arrayUtils,
             randomGenerator,
             options
         );
@@ -65,16 +86,22 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
 
     /**
      * @param {string} stringArrayScopeCallsWrapperName
-     * @param {string} stringArrayCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} stringArrayScopeCallsWrapperParameterIndexesData
+     * @param {string} upperStringArrayCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} upperStringArrayCallsWrapperParameterIndexesData
      * @param {number} shiftedIndex
      */
     public initialize (
         stringArrayScopeCallsWrapperName: string,
-        stringArrayCallsWrapperName: string,
-        shiftedIndex: number
+        stringArrayScopeCallsWrapperParameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null,
+        upperStringArrayCallsWrapperName: string,
+        upperStringArrayCallsWrapperParameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null,
+        shiftedIndex: number,
     ): void {
         this.stringArrayScopeCallsWrapperName = stringArrayScopeCallsWrapperName;
-        this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
+        this.stringArrayScopeCallsWrapperParameterIndexesData = stringArrayScopeCallsWrapperParameterIndexesData;
+        this.upperStringArrayCallsWrapperName = upperStringArrayCallsWrapperName;
+        this.upperStringArrayCallsWrapperParameterIndexesData = upperStringArrayCallsWrapperParameterIndexesData;
         this.shiftedIndex = shiftedIndex;
     }
 
@@ -84,32 +111,68 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
     protected getNodeStructure (): TStatement[] {
         // identifiers of function expression parameters
         // as a temporary names use random strings
-        const firstParameterIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
-        const secondParameterIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
+        const stringArrayCallIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
+        const decodeKeyIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
 
-        // identifiers of call to the parent string array scope wrapper
-        // as a temporary names use random strings
-        const firstCallArgumentIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
-        const secondCallArgumentIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
+        const stringArrayCallNode: ESTree.Expression = this.getUpperStringArrayCallNode(
+            stringArrayCallIdentifierNode,
+            this.getStringArrayIndexNode(this.shiftedIndex)
+        );
+
+        // stage 1: function expression node parameters
+        // filling all parameters with a fake parameters first
+        const parameters: ESTree.Identifier[] = this.arrayUtils.fillWithRange(
+            !this.stringArrayScopeCallsWrapperParameterIndexesData
+                // root string array calls wrapper
+                ? AbstractStringArrayCallNode.stringArrayRootCallsWrapperParametersCount
+                // scope string array calls wrapper
+                : this.options.stringArrayWrappersParametersMaxCount,
+            () => this.getFakeParameterNode()
+        );
+        parameters.splice(
+            this.stringArrayScopeCallsWrapperParameterIndexesData?.valueIndexParameterIndex ?? 0,
+            1,
+            stringArrayCallIdentifierNode
+        );
+        parameters.splice(
+            this.stringArrayScopeCallsWrapperParameterIndexesData?.decodeKeyParameterIndex ?? 1,
+            1,
+            decodeKeyIdentifierNode
+        );
+
+        // stage 2: upper string array call expression arguments
+        // filling all call expression arguments with a fake string array calls
+        const callExpressionArgs: ESTree.Expression[] = this.arrayUtils.fillWithRange(
+            !this.upperStringArrayCallsWrapperParameterIndexesData
+                // root string array calls wrapper
+                ? AbstractStringArrayCallNode.stringArrayRootCallsWrapperParametersCount
+                // scope string array calls wrapper
+                : this.options.stringArrayWrappersParametersMaxCount,
+            (index: number) => this.getUpperStringArrayCallNode(
+                parameters[index],
+                this.getFakeUpperStringArrayIndexNode()
+            )
+        );
+
+        callExpressionArgs.splice(
+            this.upperStringArrayCallsWrapperParameterIndexesData?.valueIndexParameterIndex ?? 0,
+            1,
+            stringArrayCallNode
+        );
+        callExpressionArgs.splice(
+            this.upperStringArrayCallsWrapperParameterIndexesData?.decodeKeyParameterIndex ?? 1,
+            1,
+            decodeKeyIdentifierNode
+        );
 
-        // function expression node
+        // stage 3: function expression node
         const functionExpressionNode: ESTree.FunctionExpression =  NodeFactory.functionExpressionNode(
-            [
-                firstParameterIdentifierNode,
-                secondParameterIdentifierNode,
-            ],
+            parameters,
             NodeFactory.blockStatementNode([
                 NodeFactory.returnStatementNode(
                     NodeFactory.callExpressionNode(
-                        NodeFactory.identifierNode(this.stringArrayCallsWrapperName),
-                        [
-                            NodeFactory.binaryExpressionNode(
-                                '-',
-                                firstCallArgumentIdentifierNode,
-                                this.getStringArrayIndexNode(this.shiftedIndex)
-                            ),
-                            secondCallArgumentIdentifierNode
-                        ]
+                        NodeFactory.identifierNode(this.upperStringArrayCallsWrapperName),
+                        callExpressionArgs
                     )
                 )
             ])
@@ -127,16 +190,42 @@ export class StringArrayScopeCallsWrapperFunctionNode extends AbstractStringArra
 
         NodeUtils.parentizeAst(structure);
 
+        // stage 4: rename
         // have to generate names for both parameter and call identifiers
-        const firstParameterName: string = this.identifierNamesGenerator.generateForLexicalScope(functionExpressionNode);
-        const secondParameterName: string = this.identifierNamesGenerator.generateForLexicalScope(functionExpressionNode);
+        for (const parameter of parameters) {
+            parameter.name = this.identifierNamesGenerator.generateForLexicalScope(functionExpressionNode);
+        }
 
-        firstParameterIdentifierNode.name = firstParameterName;
-        secondParameterIdentifierNode.name = secondParameterName;
+        return [structure];
+    }
 
-        firstCallArgumentIdentifierNode.name = firstParameterName;
-        secondCallArgumentIdentifierNode.name = secondParameterName;
+    /**
+     * @param {Identifier} indexParameterIdentifierNode
+     * @param {Expression} indexShiftNode
+     * @returns {Expression}
+     */
+    private getUpperStringArrayCallNode (
+        indexParameterIdentifierNode: ESTree.Identifier,
+        indexShiftNode: ESTree.Expression
+    ): ESTree.Expression {
+        return NodeFactory.binaryExpressionNode(
+            '-',
+            indexParameterIdentifierNode,
+            indexShiftNode
+        );
+    }
 
-        return [structure];
+    /**
+     * @returns {Identifier}
+     */
+    private getFakeParameterNode (): ESTree.Identifier {
+        return NodeFactory.identifierNode(this.randomGenerator.getRandomString(6));
+    }
+
+    /**
+     * @returns {Expression}
+     */
+    private getFakeUpperStringArrayIndexNode (): ESTree.Expression {
+        return this.getStringArrayIndexNode(this.randomGenerator.getRandomInteger(0, 500));
     }
 }

+ 8 - 0
src/custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode.ts

@@ -5,9 +5,11 @@ import { TIdentifierNamesGeneratorFactory } from '../../types/container/generato
 import { TStatement } from '../../types/node/TStatement';
 import { TStringArrayIndexNodeFactory } from '../../types/container/custom-nodes/string-array-index-nodes/TStringArrayIndexNodeFactory';
 
+import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { ICustomCodeHelperFormatter } from '../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 
 import { initializable } from '../../decorators/Initializable';
 
@@ -34,6 +36,8 @@ export class StringArrayScopeCallsWrapperVariableNode extends AbstractStringArra
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TStringArrayIndexNodeFactory} stringArrayIndexNodeFactory
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter
+     * @param {IStringArrayStorage} stringArrayStorage
+     * @param {IArrayUtils} arrayUtils
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
@@ -43,6 +47,8 @@ export class StringArrayScopeCallsWrapperVariableNode extends AbstractStringArra
         @inject(ServiceIdentifiers.Factory__IStringArrayIndexNode)
             stringArrayIndexNodeFactory: TStringArrayIndexNodeFactory,
         @inject(ServiceIdentifiers.ICustomCodeHelperFormatter) customCodeHelperFormatter: ICustomCodeHelperFormatter,
+        @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
+        @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
     ) {
@@ -50,6 +56,8 @@ export class StringArrayScopeCallsWrapperVariableNode extends AbstractStringArra
             identifierNamesGeneratorFactory,
             stringArrayIndexNodeFactory,
             customCodeHelperFormatter,
+            stringArrayStorage,
+            arrayUtils,
             randomGenerator,
             options
         );

+ 18 - 0
src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData.ts

@@ -0,0 +1,18 @@
+import { IStringArrayScopeCallsWrapperParameterIndexesData } from './IStringArrayScopeCallsWrapperParameterIndexesData';
+
+export interface IStringArrayScopeCallsWrapperData {
+    /**
+     * @type {number}
+     */
+    index: number;
+
+    /**
+     * @type {string}
+     */
+    name: string;
+
+    /**
+     * @type {IStringArrayScopeCallsWrapperParameterIndexesData | null}
+     */
+    parameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null;
+}

+ 7 - 0
src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeData.ts

@@ -1,5 +1,7 @@
 import { TNodeWithLexicalScopeStatements } from '../../../types/node/TNodeWithLexicalScopeStatements';
 
+import { IStringArrayScopeCallsWrapperParameterIndexesData } from './IStringArrayScopeCallsWrapperParameterIndexesData';
+
 export interface IStringArrayScopeCallsWrapperLexicalScopeData {
     /**
      * @type {TNodeWithLexicalScopeStatements | null}
@@ -15,4 +17,9 @@ export interface IStringArrayScopeCallsWrapperLexicalScopeData {
      * @type {number}
      */
     scopeShiftedIndex: number;
+
+    /**
+     * @type {IStringArrayScopeCallsWrapperParameterIndexesData | null}
+     */
+    callsWrappersParameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null;
 }

+ 11 - 0
src/interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperParameterIndexesData.ts

@@ -0,0 +1,11 @@
+export interface IStringArrayScopeCallsWrapperParameterIndexesData {
+    /**
+     * @type {number}
+     */
+    valueIndexParameterIndex: number;
+
+    /**
+     * @type {number}
+     */
+    decodeKeyParameterIndex: number;
+}

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

@@ -48,6 +48,7 @@ export interface IOptions {
     readonly stringArrayIndexShift: boolean;
     readonly stringArrayWrappersChainedCalls: boolean;
     readonly stringArrayWrappersCount: number;
+    readonly stringArrayWrappersParametersMaxCount: number;
     readonly stringArrayWrappersType: TStringArrayWrappersType;
     readonly stringArrayThreshold: number;
     readonly target: TTypeFromEnum<typeof ObfuscationTarget>;

+ 7 - 0
src/interfaces/utils/IArrayUtils.ts

@@ -5,6 +5,13 @@ export interface IArrayUtils {
      */
     createWithRange (length: number): number[];
 
+    /**
+     * @param {number} length
+     * @param {(index: number) => TValue} valueFunction
+     * @returns {TValue[]}
+     */
+    fillWithRange <TValue> (length: number, valueFunction: (index: number) => TValue): TValue[];
+
     /**
      * @param {T[]} array
      * @returns {T | null}

+ 8 - 0
src/interfaces/utils/IRandomGenerator.ts

@@ -20,6 +20,14 @@ export interface IRandomGenerator {
      */
     getRandomInteger (min: number, max: number): number;
 
+    /**
+     * @param {number} min
+     * @param {number} max
+     * @param {number[]} valuesToExclude
+     * @returns {number}
+     */
+    getRandomIntegerExcluding (min: number, max: number, valuesToExclude: number[]): number;
+
     /**
      * @param length
      * @param pool

+ 45 - 18
src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts

@@ -12,10 +12,12 @@ import { TStringArrayCustomNodeFactory } from '../../types/container/custom-node
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayScopeCallsWrapperData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData';
 import { IStringArrayScopeCallsWrapperLexicalScopeData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeData';
 import { IStringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { IStringArrayScopeCallsWrapperNamesData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperNamesData';
 import { IStringArrayScopeCallsWrapperNamesDataStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperNamesDataStorage';
+import { IStringArrayScopeCallsWrapperParameterIndexesData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperParameterIndexesData';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages/string-array-transformers/IVisitedLexicalScopeNodesStackStorage';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
@@ -132,6 +134,9 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
             return lexicalScopeBodyNode;
         }
 
+        const {
+            callsWrappersParameterIndexesData: stringArrayScopeCallsWrapperParameterIndexes
+        } = stringArrayScopeCallsWrapperLexicalScopeData;
         const stringArrayScopeCallsWrapperNamesDataList: (IStringArrayScopeCallsWrapperNamesData | undefined)[] =
             Object.values(stringArrayScopeCallsWrapperNamesDataByEncoding);
 
@@ -150,17 +155,20 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
              */
             for (let i = namesLength - 1; i >= 0; i--) {
                 const stringArrayScopeCallsWrapperName: string = names[i];
-                const [
-                    upperStringArrayCallsWrapperName,
-                    upperStringArrayCallsWrapperShiftedIndex,
-                ] = this.getUpperStringArrayCallsWrapperData(
+                const {
+                    name: upperStringArrayCallsWrapperName,
+                    index: upperStringArrayCallsWrapperShiftedIndex,
+                    parameterIndexesData: upperStringArrayCallsWrapperParameterIndexes
+                } = this.getUpperStringArrayCallsWrapperData(
                     stringArrayScopeCallsWrapperNamesData,
                     stringArrayScopeCallsWrapperLexicalScopeData,
                 );
 
                 const stringArrayScopeCallsWrapperNode: TStatement[] = this.getStringArrayScopeCallsWrapperNode(
                     stringArrayScopeCallsWrapperName,
+                    stringArrayScopeCallsWrapperParameterIndexes,
                     upperStringArrayCallsWrapperName,
+                    upperStringArrayCallsWrapperParameterIndexes,
                     upperStringArrayCallsWrapperShiftedIndex
                 );
 
@@ -177,30 +185,31 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     /**
      * @param {IStringArrayScopeCallsWrapperNamesData} stringArrayScopeCallsWrapperNamesData
      * @param {IStringArrayScopeCallsWrapperLexicalScopeData} stringArrayScopeCallsWrapperLexicalScopeData
-     * @returns {[name: string, index: number]}
+     * @returns {IStringArrayScopeCallsWrapperData}
      */
     private getRootStringArrayCallsWrapperData (
         stringArrayScopeCallsWrapperNamesData: IStringArrayScopeCallsWrapperNamesData,
         stringArrayScopeCallsWrapperLexicalScopeData: IStringArrayScopeCallsWrapperLexicalScopeData
-    ): [name: string, index: number] {
+    ): IStringArrayScopeCallsWrapperData {
         const {encoding} = stringArrayScopeCallsWrapperNamesData;
         const {resultShiftedIndex} = stringArrayScopeCallsWrapperLexicalScopeData;
 
-        return [
-            this.stringArrayStorage.getStorageCallsWrapperName(encoding),
-            resultShiftedIndex
-        ];
+        return {
+            name: this.stringArrayStorage.getStorageCallsWrapperName(encoding),
+            index: resultShiftedIndex,
+            parameterIndexesData: null
+        };
     }
 
     /**
      * @param {IStringArrayScopeCallsWrapperNamesData} stringArrayScopeCallsWrapperNamesData
      * @param {IStringArrayScopeCallsWrapperLexicalScopeData} stringArrayScopeCallsWrapperLexicalScopeData
-     * @returns {[name: string, index: number]}
+     * @returns {IStringArrayScopeCallsWrapperData}
      */
     private getUpperStringArrayCallsWrapperData (
         stringArrayScopeCallsWrapperNamesData: IStringArrayScopeCallsWrapperNamesData,
         stringArrayScopeCallsWrapperLexicalScopeData: IStringArrayScopeCallsWrapperLexicalScopeData
-    ): [name: string, index: number] {
+    ): IStringArrayScopeCallsWrapperData {
         const {encoding} = stringArrayScopeCallsWrapperNamesData;
         const {scopeShiftedIndex} = stringArrayScopeCallsWrapperLexicalScopeData;
 
@@ -223,6 +232,9 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
 
         const parentLexicalScopeNamesDataByEncoding: TStringArrayScopeCallsWrapperNamesDataByEncoding | null = this.stringArrayScopeCallsWrapperNamesDataStorage
             .get(parentLexicalScopeBodyNode) ?? null;
+        const parentScopeCallsWrapperLexicalScopeData: IStringArrayScopeCallsWrapperLexicalScopeData | null = this.stringArrayScopeCallsWrapperLexicalScopeDataStorage
+            .get(parentLexicalScopeBodyNode) ?? null;
+
         const parentLexicalScopeNames: string[] | null = parentLexicalScopeNamesDataByEncoding?.[encoding]?.names ?? null;
 
         if (!parentLexicalScopeNames?.length) {
@@ -232,29 +244,38 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
         const upperStringArrayCallsWrapperName: string = this.randomGenerator
             .getRandomGenerator()
             .pickone(parentLexicalScopeNames);
-
-        return [
-            upperStringArrayCallsWrapperName,
-            scopeShiftedIndex
-        ];
+        const parameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null =
+            parentScopeCallsWrapperLexicalScopeData?.callsWrappersParameterIndexesData ?? null;
+
+        return {
+            name: upperStringArrayCallsWrapperName,
+            index: scopeShiftedIndex,
+            parameterIndexesData
+        };
     }
 
     /**
      * @param {string} stringArrayScopeCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} stringArrayScopeCallsWrapperParameterIndexes
      * @param {string} upperStringArrayCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} upperStringArrayCallsWrapperParameterIndexes
      * @param {number} stringArrayScopeCallsWrapperShiftedIndex
      * @returns {TStatement[]}
      */
     private getStringArrayScopeCallsWrapperNode (
         stringArrayScopeCallsWrapperName: string,
+        stringArrayScopeCallsWrapperParameterIndexes: IStringArrayScopeCallsWrapperParameterIndexesData | null,
         upperStringArrayCallsWrapperName: string,
+        upperStringArrayCallsWrapperParameterIndexes: IStringArrayScopeCallsWrapperParameterIndexesData | null,
         stringArrayScopeCallsWrapperShiftedIndex: number
     ): TStatement[] {
         switch (this.options.stringArrayWrappersType) {
             case StringArrayWrappersType.Function:
                 return this.getStringArrayScopeCallsWrapperFunctionNode(
                     stringArrayScopeCallsWrapperName,
+                    stringArrayScopeCallsWrapperParameterIndexes,
                     upperStringArrayCallsWrapperName,
+                    upperStringArrayCallsWrapperParameterIndexes,
                     stringArrayScopeCallsWrapperShiftedIndex
                 );
 
@@ -291,13 +312,17 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
 
     /**
      * @param {string} stringArrayScopeCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} stringArrayScopeCallsWrapperParameterIndexes
      * @param {string} upperStringArrayCallsWrapperName
+     * @param {IStringArrayScopeCallsWrapperParameterIndexesData | null} upperStringArrayCallsWrapperParameterIndexes
      * @param {number} stringArrayScopeCallsWrapperShiftedIndex
      * @returns {TStatement[]}
      */
     private getStringArrayScopeCallsWrapperFunctionNode (
         stringArrayScopeCallsWrapperName: string,
+        stringArrayScopeCallsWrapperParameterIndexes: IStringArrayScopeCallsWrapperParameterIndexesData | null,
         upperStringArrayCallsWrapperName: string,
+        upperStringArrayCallsWrapperParameterIndexes: IStringArrayScopeCallsWrapperParameterIndexesData | null,
         stringArrayScopeCallsWrapperShiftedIndex: number
     ): TStatement[] {
         const stringArrayScopeCallsWrapperFunctionNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperFunctionNode>> =
@@ -307,8 +332,10 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
 
         stringArrayScopeCallsWrapperFunctionNode.initialize(
             stringArrayScopeCallsWrapperName,
+            stringArrayScopeCallsWrapperParameterIndexes,
             upperStringArrayCallsWrapperName,
-            stringArrayScopeCallsWrapperShiftedIndex
+            upperStringArrayCallsWrapperParameterIndexes,
+            stringArrayScopeCallsWrapperShiftedIndex,
         );
 
         return stringArrayScopeCallsWrapperFunctionNode.getNode();

+ 46 - 14
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -15,9 +15,11 @@ import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifie
 import { ILiteralNodesCacheStorage } from '../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStringArrayScopeCallsWrapperData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperData';
 import { IStringArrayScopeCallsWrapperLexicalScopeData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeData';
 import { IStringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { IStringArrayScopeCallsWrapperNamesDataStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperNamesDataStorage';
+import { IStringArrayScopeCallsWrapperParameterIndexesData } from '../../interfaces/node-transformers/string-array-transformers/IStringArrayScopeCallsWrapperParameterIndexesData';
 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';
@@ -207,7 +209,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @returns {Expression}
      */
     private getStringArrayCallNode (stringArrayStorageItemData: IStringArrayStorageItemData): ESTree.Expression {
-        const [stringArrayCallsWrapperName, index] = this.getStringArrayCallsWrapperData(stringArrayStorageItemData);
+        const {
+            name: stringArrayCallsWrapperName,
+            index,
+            parameterIndexesData
+        } = this.getStringArrayCallsWrapperData(stringArrayStorageItemData);
         const {decodeKey } = stringArrayStorageItemData;
 
         const stringArrayCallCustomNode: ICustomNode<TInitialData<StringArrayCallNode>> =
@@ -215,6 +221,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
         stringArrayCallCustomNode.initialize(
             stringArrayCallsWrapperName,
+            parameterIndexesData,
             index,
             this.stringArrayStorage.getIndexShiftAmount(),
             decodeKey
@@ -231,11 +238,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
-     * @returns {[name: string, index: number]}
+     * @returns {IStringArrayScopeCallsWrapperData}
      */
     private getStringArrayCallsWrapperData (
         stringArrayStorageItemData: IStringArrayStorageItemData
-    ): [name: string, index: number] {
+    ): IStringArrayScopeCallsWrapperData {
         return !this.options.stringArrayWrappersCount
             ? this.getRootStringArrayCallsWrapperData(stringArrayStorageItemData)
             : this.getUpperStringArrayCallsWrapperData(stringArrayStorageItemData);
@@ -243,28 +250,29 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
-     * @returns {[name: string, index: number]}
+     * @returns {IStringArrayScopeCallsWrapperData}
      */
     private getRootStringArrayCallsWrapperData (
         stringArrayStorageItemData: IStringArrayStorageItemData
-    ): [name: string, index: number] {
+    ): IStringArrayScopeCallsWrapperData {
         const {encoding, index} = stringArrayStorageItemData;
 
         const rootStringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName(encoding);
 
-        return [
-            rootStringArrayCallsWrapperName,
+        return {
+            name: rootStringArrayCallsWrapperName,
+            parameterIndexesData: null,
             index
-        ];
+        };
     }
 
     /**
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
-     * @returns {[name: string, index: number]}
+     * @returns {IStringArrayScopeCallsWrapperData}
      */
     private getUpperStringArrayCallsWrapperData (
         stringArrayStorageItemData: IStringArrayStorageItemData
-    ): [name: string, index: number] {
+    ): IStringArrayScopeCallsWrapperData {
         const {encoding, index} = stringArrayStorageItemData;
         const currentLexicalScopeBodyNode: TNodeWithLexicalScopeStatements | null =
             this.visitedLexicalScopeNodesStackStorage.getLastElement() ?? null;
@@ -295,10 +303,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
             ? stringArrayScopeCallsWrapperLexicalScopeData.resultShiftedIndex + index
             : index;
 
-        return [
-            randomUpperStringArrayCallsWrapperName,
-            resultIndex
-        ];
+        return {
+            name: randomUpperStringArrayCallsWrapperName,
+            index: resultIndex,
+            parameterIndexesData: stringArrayScopeCallsWrapperLexicalScopeData.callsWrappersParameterIndexesData
+        };
     }
 
     /**
@@ -362,6 +371,10 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
             ? this.stringArrayScopeCallsWrapperLexicalScopeDataStorage.get(parentLexicalScopeBodyNode) ?? null
             : null;
 
+        const callsWrappersParameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null =
+            this.options.stringArrayWrappersType === StringArrayWrappersType.Function
+            ? this.getStringArrayCallsWrapperParameterIndexesData()
+            : null;
         const scopeShiftedIndex: number = this.options.stringArrayWrappersType === StringArrayWrappersType.Function
             ? this.randomGenerator.getRandomInteger(
                 StringArrayTransformer.minShiftedIndexValue,
@@ -373,6 +386,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
             : scopeShiftedIndex;
 
         const lexicalScopeData: IStringArrayScopeCallsWrapperLexicalScopeData = {
+            callsWrappersParameterIndexesData,
             parentLexicalScopeBodyNode,
             resultShiftedIndex,
             scopeShiftedIndex
@@ -385,4 +399,22 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
         return lexicalScopeData;
     }
+
+    /**
+     * @returns {IStringArrayScopeCallsWrapperParameterIndexesData}
+     */
+    private getStringArrayCallsWrapperParameterIndexesData (): IStringArrayScopeCallsWrapperParameterIndexesData {
+        const minIndexValue: number = 0;
+        const maxIndexValue: number = this.options.stringArrayWrappersParametersMaxCount - 1;
+
+        const valueIndexParameterIndex: number = this.randomGenerator
+            .getRandomInteger(minIndexValue, maxIndexValue);
+        const decodeKeyParameterIndex: number = this.randomGenerator
+            .getRandomIntegerExcluding(minIndexValue, maxIndexValue, [valueIndexParameterIndex]);
+
+        return {
+            valueIndexParameterIndex,
+            decodeKeyParameterIndex
+        };
+    }
 }

+ 7 - 0
src/options/Options.ts

@@ -344,6 +344,13 @@ export class Options implements IOptions {
     @Min(0)
     public readonly stringArrayWrappersCount!: number;
 
+    /**
+     * @type {boolean}
+     */
+    @IsNumber()
+    @Min(2)
+    public readonly stringArrayWrappersParametersMaxCount!: number;
+
     /**
      * @type {TStringArrayWrappersType}
      */

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

@@ -54,6 +54,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     stringArrayIndexShift: true,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersCount: 1,
+    stringArrayWrappersParametersMaxCount: 2,
     stringArrayWrappersType: StringArrayWrappersType.Variable,
     stringArrayThreshold: 0.75,
     target: ObfuscationTarget.Browser,

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

@@ -2,7 +2,6 @@ import { TInputOptions } from '../../types/options/TInputOptions';
 
 import { OptionsPreset } from '../../enums/options/presets/OptionsPreset';
 import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
-import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 import { MEDIUM_OBFUSCATION_PRESET } from './MediumObfuscation';
 
@@ -18,6 +17,6 @@ export const HIGH_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
         StringArrayEncoding.Rc4
     ],
     stringArrayWrappersCount: 5,
-    stringArrayWrappersType: StringArrayWrappersType.Function,
+    stringArrayWrappersParametersMaxCount: 5,
     stringArrayThreshold: 1
 });

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

@@ -2,6 +2,7 @@ import { TInputOptions } from '../../types/options/TInputOptions';
 
 import { OptionsPreset } from '../../enums/options/presets/OptionsPreset';
 import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
+import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 import { LOW_OBFUSCATION_PRESET } from './LowObfuscation';
 
@@ -17,5 +18,7 @@ export const MEDIUM_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
         StringArrayEncoding.Base64
     ],
     stringArrayWrappersCount: 2,
+    stringArrayWrappersParametersMaxCount: 4,
+    stringArrayWrappersType: StringArrayWrappersType.Function,
     transformObjectKeys: true
 });

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

@@ -51,6 +51,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     stringArrayIndexShift: false,
     stringArrayWrappersChainedCalls: false,
     stringArrayWrappersCount: 0,
+    stringArrayWrappersParametersMaxCount: 2,
     stringArrayWrappersType: StringArrayWrappersType.Variable,
     stringArrayThreshold: 0,
     target: ObfuscationTarget.Browser,

+ 15 - 0
src/utils/ArrayUtils.ts

@@ -34,6 +34,21 @@ export class ArrayUtils implements IArrayUtils {
         return range;
     }
 
+    /**
+     * @param {number} length
+     * @param {(index: number) => TValue} valueFunction
+     * @returns {TValue[]}
+     */
+    public fillWithRange <TValue> (length: number, valueFunction: (index: number) => TValue): TValue[] {
+        const range: TValue[] = [];
+
+        for (let i: number = 0; i < length; i++) {
+            range.push(valueFunction(i));
+        }
+
+        return range;
+    }
+
     /**
      * @param {T[]} array
      * @returns {T | null}

+ 20 - 0
src/utils/RandomGenerator.ts

@@ -80,6 +80,26 @@ export class RandomGenerator implements IRandomGenerator, IInitializable {
         });
     }
 
+    /**
+     * @param {number} min
+     * @param {number} max
+     * @param {number[]} valuesToExclude
+     * @returns {number}
+     */
+    public getRandomIntegerExcluding (min: number, max: number, valuesToExclude: number[]): number {
+        const valuesToPickArray: number[] = [];
+
+        for (let value: number = min; value <= max; value++) {
+            if (valuesToExclude.includes(value)) {
+                continue;
+            }
+
+            valuesToPickArray.push(value);
+        }
+
+        return this.randomGenerator.pickone(valuesToPickArray);
+    }
+
     /**
      * @param {number} length
      * @param {string} pool

+ 17 - 20
test/dev/dev.ts

@@ -8,26 +8,18 @@ import { StringArrayIndexesType } from '../../src/enums/node-transformers/string
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            const foo = 'foo';
-            const bar = 'bar';
-            
-            function test1 () {
-                const baz = 'baz';
-                
-                function test2() {
-                    const bark = 'bark';
-                    
-                    console.log(bark);
-                }
-                
-                console.log(baz);
-                
-                test2();
+            // Paste your JavaScript code here
+            function hi() {
+              function inner () {
+                  console.log('inner');
+                  console.log('inner1');
+              }
+              
+              console.log("Hello World!");
+              
+              inner();
             }
-            
-            console.log(foo, bar);
-            
-            test1();
+            hi();
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
@@ -35,14 +27,19 @@ import { StringArrayIndexesType } from '../../src/enums/node-transformers/string
             rotateStringArray: true,
             shuffleStringArray: true,
             stringArray: true,
+            /*stringArrayEncoding: [
+                StringArrayEncoding.None,
+                StringArrayEncoding.Rc4
+            ],*/
             stringArrayIndexesType: [
                 StringArrayIndexesType.HexadecimalNumericString,
                 StringArrayIndexesType.HexadecimalNumber
             ],
             stringArrayIndexShift: true,
             stringArrayThreshold: 1,
-            stringArrayWrappersCount: 2,
+            stringArrayWrappersCount: 1,
             stringArrayWrappersChainedCalls: true,
+            stringArrayWrappersParametersMaxCount: 2,
             stringArrayWrappersType: 'function'
         }
     ).getObfuscatedCode();

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

@@ -926,6 +926,7 @@ describe('JavaScriptObfuscator', () => {
                         stringArrayIndexShift: true,
                         stringArrayWrappersChainedCalls: true,
                         stringArrayWrappersCount: 10,
+                        stringArrayWrappersParametersMaxCount: 5,
                         stringArrayWrappersType: StringArrayWrappersType.Function,
                         stringArrayThreshold: 1,
                         transformObjectKeys: true,

+ 262 - 85
test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts

@@ -2,14 +2,15 @@ import { assert } from 'chai';
 
 import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { StringArrayEncoding } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
+import { StringArrayIndexesType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayWrappersType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
+import { checkCodeEvaluation } from '../../../../helpers/checkCodeEvaluation';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
-import { StringArrayIndexesType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 
 describe('StringArrayScopeCallsWrapperTransformer', function () {
     this.timeout(120000);
@@ -576,33 +577,34 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
         });
 
         describe('Variant #7: `stringArrayWrappersType` option has `Function` value', () => {
+            const evaluationSamplesCount: number = 500;
             const hexadecimalIndexMatch: string = '0x[a-z0-9]{1,3}';
 
             describe('Variant #1: base', () => {
                 describe('Variant #1: `hexadecimal-number` indexes type', () => {
                     const stringArrayCallRegExp: RegExp = new RegExp(
                         'const f *= *function *\\(c, *d\\) *{' +
-                            `return b\\(c *-(?: -)?${hexadecimalIndexMatch}, *d\\);` +
+                            `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
                         '};' +
-                        `const foo *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                        `const bar *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                        `const baz *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
+                        `const foo *= *f\\(-? *${hexadecimalIndexMatch}\\, *-? *${hexadecimalIndexMatch}\\);` +
+                        `const bar *= *f\\(-? *${hexadecimalIndexMatch}\\, *-? *${hexadecimalIndexMatch}\\);` +
+                        `const baz *= *f\\(-? *${hexadecimalIndexMatch}\\, *-? *${hexadecimalIndexMatch}\\);` +
                         'function test *\\( *\\) *{' +
                             'const g *= *function *\\(c, *d\\) *{' +
-                                `return b\\(c *-(?: -)?${hexadecimalIndexMatch}, *d\\);` +
+                                `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
                             '};' +
-                            `const c *= *g\\(-? *${hexadecimalIndexMatch}\\);` +
-                            `const d *= *g\\(-? *${hexadecimalIndexMatch}\\);` +
-                            `const e *= *g\\(-? *${hexadecimalIndexMatch}\\);` +
+                            `const c *= *g\\(-? *${hexadecimalIndexMatch}\\, *-? *${hexadecimalIndexMatch}\\);` +
+                            `const d *= *g\\(-? *${hexadecimalIndexMatch}\\, *-? *${hexadecimalIndexMatch}\\);` +
+                            `const e *= *g\\(-? *${hexadecimalIndexMatch}\\, *-? *${hexadecimalIndexMatch}\\);` +
                         '}'
                     );
 
                     let obfuscatedCode: string;
+                    let areSuccessEvaluations: boolean;
 
                     before(() => {
                         const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const.js');
-
-                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        const getObfuscatedCode: () => string = () => JavaScriptObfuscator.obfuscate(
                             code,
                             {
                                 ...NO_ADDITIONAL_NODES_PRESET,
@@ -617,37 +619,47 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                                 stringArrayWrappersType: StringArrayWrappersType.Function
                             }
                         ).getObfuscatedCode();
+
+                        obfuscatedCode = getObfuscatedCode();
+                        areSuccessEvaluations = checkCodeEvaluation(
+                            getObfuscatedCode,
+                            evaluationSamplesCount
+                        ).areSuccessEvaluations;
                     });
 
                     it('should add correct scope calls wrappers', () => {
                         assert.match(obfuscatedCode, stringArrayCallRegExp);
                     });
+
+                    it('should evaluate code without errors', () => {
+                        assert.isTrue(areSuccessEvaluations);
+                    });
                 });
 
                 describe('Variant #2: `hexadecimal-numeric-string` indexes type', () => {
                     const stringArrayCallRegExp: RegExp = new RegExp(
                         'const f *= *function *\\(c, *d\\) *{' +
-                            `return b\\(c *-(?: -)?'${hexadecimalIndexMatch}', *d\\);` +
+                            `return b\\([cd] *-(?: -)?'${hexadecimalIndexMatch}', *[cd]\\);` +
                         '};' +
-                        `const foo *= *f\\(-? *'${hexadecimalIndexMatch}'\\);` +
-                        `const bar *= *f\\(-? *'${hexadecimalIndexMatch}'\\);` +
-                        `const baz *= *f\\(-? *'${hexadecimalIndexMatch}'\\);` +
+                        `const foo *= *f\\(-? *'${hexadecimalIndexMatch}', *-? *'${hexadecimalIndexMatch}'\\);` +
+                        `const bar *= *f\\(-? *'${hexadecimalIndexMatch}', *-? *'${hexadecimalIndexMatch}'\\);` +
+                        `const baz *= *f\\(-? *'${hexadecimalIndexMatch}', *-? *'${hexadecimalIndexMatch}'\\);` +
                         'function test *\\( *\\) *{' +
                             'const g *= *function *\\(c, *d\\) *{' +
-                                `return b\\(c *-(?: -)?'${hexadecimalIndexMatch}', *d\\);` +
+                                `return b\\([cd] *-(?: -)?'${hexadecimalIndexMatch}', *[cd]\\);` +
                             '};' +
-                            `const c *= *g\\(-? *'${hexadecimalIndexMatch}'\\);` +
-                            `const d *= *g\\(-? *'${hexadecimalIndexMatch}'\\);` +
-                            `const e *= *g\\(-? *'${hexadecimalIndexMatch}'\\);` +
+                            `const c *= *g\\(-? *'${hexadecimalIndexMatch}', *-? *'${hexadecimalIndexMatch}'\\);` +
+                            `const d *= *g\\(-? *'${hexadecimalIndexMatch}', *-? *'${hexadecimalIndexMatch}'\\);` +
+                            `const e *= *g\\(-? *'${hexadecimalIndexMatch}', *-? *'${hexadecimalIndexMatch}'\\);` +
                         '}'
                     );
 
                     let obfuscatedCode: string;
+                    let areSuccessEvaluations: boolean;
 
                     before(() => {
                         const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const.js');
-
-                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        const getObfuscatedCode = () => JavaScriptObfuscator.obfuscate(
                             code,
                             {
                                 ...NO_ADDITIONAL_NODES_PRESET,
@@ -662,94 +674,128 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                                 stringArrayWrappersType: StringArrayWrappersType.Function
                             }
                         ).getObfuscatedCode();
+
+                        obfuscatedCode = getObfuscatedCode();
+                        areSuccessEvaluations = checkCodeEvaluation(
+                            getObfuscatedCode,
+                            evaluationSamplesCount
+                        ).areSuccessEvaluations;
                     });
 
                     it('should add correct scope calls wrappers', () => {
                         assert.match(obfuscatedCode, stringArrayCallRegExp);
                     });
+
+                    it('should evaluate code without errors', () => {
+                        assert.isTrue(areSuccessEvaluations);
+                    });
                 });
             });
 
             describe('Variant #2: correct chained calls', () => {
-                    const stringArrayCallRegExp: RegExp = new RegExp(
-                        'const f *= *function *\\(c, *d\\) *{' +
-                            `return b\\(c *-(?: -)?${hexadecimalIndexMatch}, *d\\);` +
+                const stringArrayCallRegExp: RegExp = new RegExp(
+                    'const f *= *function *\\(c, *d\\) *{' +
+                        `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
+                    '};' +
+                    `const foo *= *f\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                    `const bar *= *f\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                    `const baz *= *f\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                    'function test *\\( *\\) *{' +
+                        'const g *= *function *\\(c, *d\\) *{' +
+                            `return f\\(` +
+                                // order of arguments depends on the parent wrapper parameters order
+                                `[cd](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                `[cd](?: *-(?: -)?${hexadecimalIndexMatch})?` +
+                            `\\);` +
                         '};' +
-                        `const foo *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                        `const bar *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                        `const baz *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                        'function test *\\( *\\) *{' +
-                            'const g *= *function *\\(c, *d\\) *{' +
-                                `return f\\(c *-(?: -)?${hexadecimalIndexMatch}, *d\\);` +
-                            '};' +
-                            `const c *= *g\\(-? *${hexadecimalIndexMatch}\\);` +
-                            `const d *= *g\\(-? *${hexadecimalIndexMatch}\\);` +
-                            `const e *= *g\\(-? *${hexadecimalIndexMatch}\\);` +
-                        '}'
-                    );
+                        `const c *= *g\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                        `const d *= *g\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                        `const e *= *g\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                    '}'
+                );
 
-                    let obfuscatedCode: string;
+                let obfuscatedCode: string;
+                let areSuccessEvaluations: boolean;
 
-                    before(() => {
-                        const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const.js');
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const.js');
+                    const getObfuscatedCode = () => JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayWrappersChainedCalls: true,
+                            stringArrayWrappersCount: 1,
+                            stringArrayWrappersType: StringArrayWrappersType.Function
+                        }
+                    ).getObfuscatedCode();
 
-                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                            code,
-                            {
-                                ...NO_ADDITIONAL_NODES_PRESET,
-                                identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
-                                stringArray: true,
-                                stringArrayThreshold: 1,
-                                stringArrayWrappersChainedCalls: true,
-                                stringArrayWrappersCount: 1,
-                                stringArrayWrappersType: StringArrayWrappersType.Function
-                            }
-                        ).getObfuscatedCode();
-                    });
+                    obfuscatedCode = getObfuscatedCode();
+                    areSuccessEvaluations = checkCodeEvaluation(
+                        getObfuscatedCode,
+                        evaluationSamplesCount
+                    ).areSuccessEvaluations;
+                });
 
-                    it('should add correct scope calls wrappers', () => {
-                        assert.match(obfuscatedCode, stringArrayCallRegExp);
-                    });
+                it('should add correct scope calls wrappers', () => {
+                    assert.match(obfuscatedCode, stringArrayCallRegExp);
                 });
 
+                it('should evaluate code without errors', () => {
+                    assert.isTrue(areSuccessEvaluations);
+                });
+            });
+
             describe('Variant #3: no wrappers on a root scope', () => {
-                    const stringArrayCallRegExp: RegExp = new RegExp(
-                            'return e;' +
+                const stringArrayCallRegExp: RegExp = new RegExp(
+                        'return e;' +
+                    '};' +
+                    'function test *\\( *\\) *{' +
+                        'const f *= *function *\\(c, *d\\) *{' +
+                            `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
                         '};' +
-                        'function test *\\( *\\) *{' +
-                            'const f *= *function *\\(c, *d\\) *{' +
-                                `return b\\(c *-(?: -)?${hexadecimalIndexMatch}, *d\\);` +
-                            '};' +
-                            `const c *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                            `const d *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                            `const e *= *f\\(-? *${hexadecimalIndexMatch}\\);` +
-                        '}'
-                    );
+                        `const c *= *f\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                        `const d *= *f\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                        `const e *= *f\\(-? *${hexadecimalIndexMatch}, *-? *${hexadecimalIndexMatch}\\);` +
+                    '}'
+                );
 
-                    let obfuscatedCode: string;
+                let obfuscatedCode: string;
+                let areSuccessEvaluations: boolean;
 
-                    before(() => {
-                        const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const-no-root-wrappers.js');
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const-no-root-wrappers.js');
+                    const getObfuscatedCode = () => JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayWrappersChainedCalls: true,
+                            stringArrayWrappersCount: 1,
+                            stringArrayWrappersType: StringArrayWrappersType.Function
+                        }
+                    ).getObfuscatedCode();
 
-                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                            code,
-                            {
-                                ...NO_ADDITIONAL_NODES_PRESET,
-                                identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
-                                stringArray: true,
-                                stringArrayThreshold: 1,
-                                stringArrayWrappersChainedCalls: true,
-                                stringArrayWrappersCount: 1,
-                                stringArrayWrappersType: StringArrayWrappersType.Function
-                            }
-                        ).getObfuscatedCode();
-                    });
+                    obfuscatedCode = getObfuscatedCode();
+                    areSuccessEvaluations = checkCodeEvaluation(
+                        getObfuscatedCode,
+                        evaluationSamplesCount
+                    ).areSuccessEvaluations;
+                });
 
-                    it('should add correct scope calls wrappers', () => {
-                        assert.match(obfuscatedCode, stringArrayCallRegExp);
-                    });
+                it('should add correct scope calls wrappers', () => {
+                    assert.match(obfuscatedCode, stringArrayCallRegExp);
                 });
 
+                it('should evaluate code without errors', () => {
+                    assert.isTrue(areSuccessEvaluations);
+                });
+            });
+
             describe('Variant #4: correct evaluation of the string array wrappers chained calls', () => {
                 describe('Variant #1: base', () => {
                     describe('Variant #1: `Hexadecimal` identifier names generator', () => {
@@ -913,6 +959,137 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                     });
                 });
             });
+
+            describe('Variant #5: variable amount of the arguments', () => {
+                describe('Variant #1: base', () => {
+                    const stringArrayWrapperArgumentsRegExpString: string = Array(5)
+                        .fill(`-? *${hexadecimalIndexMatch}`)
+                        .join(', *');
+                    const stringArrayCallRegExp: RegExp = new RegExp(
+                        'const f *= *function *\\(c, *d, *e, *h, *i\\) *{' +
+                            `return b\\([cdehi] *-(?: -)?${hexadecimalIndexMatch}, *[cdehi]\\);` +
+                        '};' +
+                        `const foo *= *f\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        `const bar *= *f\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        `const baz *= *f\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        'function test *\\( *\\) *{' +
+                            'const g *= *function *\\(c, *d, *e, *h, *i\\) *{' +
+                                `return f\\(` +
+                                    // order of arguments depends on the parent wrapper parameters order
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?` +
+                                `\\);` +
+                            '};' +
+                            `const c *= *g\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                            `const d *= *g\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                            `const e *= *g\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        '}'
+                    );
+
+                    let obfuscatedCode: string;
+                    let areSuccessEvaluations: boolean;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const.js');
+                        const getObfuscatedCode = () => JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayWrappersChainedCalls: true,
+                                stringArrayWrappersCount: 1,
+                                stringArrayWrappersParametersMaxCount: 5,
+                                stringArrayWrappersType: StringArrayWrappersType.Function
+                            }
+                        ).getObfuscatedCode();
+
+                        obfuscatedCode = getObfuscatedCode();
+                        areSuccessEvaluations = checkCodeEvaluation(
+                            getObfuscatedCode,
+                            evaluationSamplesCount
+                        ).areSuccessEvaluations;
+                    });
+
+                    it('should add correct scope calls wrappers', () => {
+                        assert.match(obfuscatedCode, stringArrayCallRegExp);
+                    });
+
+                    it('should evaluate code without errors', () => {
+                        assert.isTrue(areSuccessEvaluations);
+                    });
+                });
+
+                describe('Variant #2: `stringArrayEncoding` option is `rc4`', () => {
+                    const stringArrayWrapperArgumentsRegExpString: string = Array(5)
+                        .fill(`(?:-? *${hexadecimalIndexMatch}|(?:'.{4}'))`)
+                        .join(', *');
+                    const stringArrayCallRegExp: RegExp = new RegExp(
+                        'const f *= *function *\\(c, *d, *e, *h, *i\\) *{' +
+                            `return b\\([cdehi] *-(?: -)?${hexadecimalIndexMatch}, *[cdehi]\\);` +
+                        '};' +
+                        `const foo *= *f\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        `const bar *= *f\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        `const baz *= *f\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        'function test *\\( *\\) *{' +
+                            'const g *= *function *\\(c, *d, *e, *h, *i\\) *{' +
+                                `return f\\(` +
+                                    // order of arguments depends on the parent wrapper parameters order
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?, *` +
+                                    `[cdehi](?: *-(?: -)?${hexadecimalIndexMatch})?` +
+                                `\\);` +
+                            '};' +
+                            `const c *= *g\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                            `const d *= *g\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                            `const e *= *g\\(${stringArrayWrapperArgumentsRegExpString}\\);` +
+                        '}'
+                    );
+
+                    let obfuscatedCode: string;
+                    let areSuccessEvaluations: boolean;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count-const.js');
+                        const getObfuscatedCode = () => JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                                stringArray: true,
+                                stringArrayEncoding: [
+                                    StringArrayEncoding.Rc4
+                                ],
+                                stringArrayThreshold: 1,
+                                stringArrayWrappersChainedCalls: true,
+                                stringArrayWrappersCount: 1,
+                                stringArrayWrappersParametersMaxCount: 5,
+                                stringArrayWrappersType: StringArrayWrappersType.Function
+                            }
+                        ).getObfuscatedCode();
+
+                        obfuscatedCode = getObfuscatedCode();
+                        areSuccessEvaluations = checkCodeEvaluation(
+                            getObfuscatedCode,
+                            evaluationSamplesCount
+                        ).areSuccessEvaluations;
+                    });
+
+                    it('should add correct scope calls wrappers', () => {
+                        assert.match(obfuscatedCode, stringArrayCallRegExp);
+                    });
+
+                    it('should evaluate code without errors', () => {
+                        assert.isTrue(areSuccessEvaluations);
+                    });
+                });
+            });
         });
     });
 

+ 38 - 0
test/helpers/checkCodeEvaluation.ts

@@ -0,0 +1,38 @@
+/**
+ * Evaluates code passed amount time
+ *
+ * @param {() => string} codeGetterFunction
+ * @param {number} runsAmount
+ * @param expectedResult
+ * @returns {{areSuccessEvaluations: boolean, errorMessage?: string}}
+ */
+export function checkCodeEvaluation (
+    codeGetterFunction: () => string,
+    runsAmount: number,
+    expectedResult?: any
+): {
+    areSuccessEvaluations: boolean;
+    errorMessage?: string;
+} {
+    for (let i = 0; i < runsAmount; i++) {
+        try {
+            const result = eval(codeGetterFunction());
+
+            if (expectedResult !== undefined && result !== expectedResult) {
+                return {
+                    areSuccessEvaluations: false,
+                    errorMessage: `Invalid evaluation result: ${result}. Expected: ${expectedResult}`
+                };
+            }
+        } catch (error) {
+            return {
+                areSuccessEvaluations: false,
+                errorMessage: error.message
+            };
+        }
+    }
+
+    return {
+        areSuccessEvaluations: true
+    };
+}

+ 1 - 0
test/index.spec.ts

@@ -44,6 +44,7 @@ import './unit-tests/utils/CryptUtilsSwappedAlphabet.spec';
 import './unit-tests/utils/EscapeSequenceEncoder.spec';
 import './unit-tests/utils/LevelledTopologicalSorter.spec';
 import './unit-tests/utils/NumberUtils.spec';
+import './unit-tests/utils/RandomGenerator.spec';
 import './unit-tests/utils/StringUtils.spec';
 import './unit-tests/utils/Utils.spec';
 

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

@@ -48,6 +48,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
         stringArrayIndexShift: true,
         stringArrayWrappersChainedCalls: true,
         stringArrayWrappersCount: 5,
+        stringArrayWrappersParametersMaxCount: 5,
         stringArrayWrappersType: StringArrayWrappersType.Function,
         stringArrayThreshold: 1,
         transformObjectKeys: true,

+ 55 - 0
test/unit-tests/utils/ArrayUtils.spec.ts

@@ -66,6 +66,61 @@ describe('ArrayUtils', () => {
         });
     });
 
+    describe('fillWithRange', () => {
+        const valueFunction: (index: number) => string = (index: number) => `foo${index}`;
+
+        describe('range length more than 0', () => {
+            const rangeLength: number = 5;
+            const expectedArray: string[] = [
+                'foo0',
+                'foo1',
+                'foo2',
+                'foo3',
+                'foo4',
+            ];
+
+            let array: string[];
+
+            before(() => {
+                array = arrayUtils.fillWithRange(rangeLength, valueFunction);
+            });
+
+            it('should return array with range of strings', () => {
+                assert.deepEqual(array, expectedArray);
+            });
+        });
+
+        describe('range length is 0', () => {
+            const rangeLength: number = 0;
+            const expectedArray: string[] = [];
+
+            let array: string[];
+
+            before(() => {
+                array = arrayUtils.fillWithRange(rangeLength, valueFunction);
+            });
+
+            it('should return empty array', () => {
+                assert.deepEqual(array, expectedArray);
+            });
+        });
+
+        describe('range length less than 0', () => {
+            const rangeLength: number = -5;
+            const expectedArray: string[] = [];
+
+            let array: string[];
+
+            before(() => {
+                array = arrayUtils.fillWithRange(rangeLength, valueFunction);
+            });
+
+            it('should return empty array', () => {
+                assert.deepEqual(array, expectedArray);
+            });
+        });
+    });
+
     describe('findMostOccurringElement', () => {
         describe('empty array', () => {
             const array: string[] = [];

+ 92 - 0
test/unit-tests/utils/RandomGenerator.spec.ts

@@ -0,0 +1,92 @@
+import 'reflect-metadata';
+
+import { assert } from 'chai';
+
+import { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
+
+import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
+import { IRandomGenerator } from '../../../src/interfaces/utils/IRandomGenerator';
+
+import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
+
+describe('RandomGenerator', () => {
+    let randomGenerator: IRandomGenerator;
+
+    before(() => {
+        const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+        inversifyContainerFacade.load('', '', {});
+        randomGenerator = inversifyContainerFacade.get<IRandomGenerator>(ServiceIdentifiers.IRandomGenerator);
+    });
+
+    describe('getRandomIntegerExcluding', () => {
+        describe('Variant #1: avoid excluded values', () => {
+            const samplesCount: number = 500;
+
+            const minValue: number = 5;
+            const maxValue: number = 10;
+            const valuesToExclude: number[] = [6, 9];
+
+            const expectedRandomIntegerValues: number[] = [5, 7, 8, 10];
+
+            let isRandomIntegerInAllowedValuesRange: boolean = true;
+
+            before(() => {
+                for (let i = 0; i < samplesCount; i++) {
+                    const randomInteger = randomGenerator.getRandomIntegerExcluding(minValue, maxValue, valuesToExclude);
+
+                    if (!expectedRandomIntegerValues.includes(randomInteger)) {
+                        isRandomIntegerInAllowedValuesRange = false;
+                    }
+                }
+            });
+
+            it('should return a random integer in allowed values range', () => {
+                assert.isTrue(isRandomIntegerInAllowedValuesRange);
+            });
+        });
+
+        describe('Variant #2: values boundaries', () => {
+            const samplesCount: number = 500;
+
+            const minValue: number = 5;
+            const maxValue: number = 10;
+            const valuesToExclude: number[] = [6, 9];
+
+            const delta: number = 0.15;
+
+            const expectedValueChance: number = 0.2
+
+            let minValuesCount: number = 0;
+            let maxValuesCount: number = 0;
+
+            let minValueChance: number;
+            let maxValueChance: number;
+
+            before(() => {
+                for (let i = 0; i < samplesCount; i++) {
+                    const randomInteger: number = randomGenerator.getRandomIntegerExcluding(minValue, maxValue, valuesToExclude);
+
+                    if (randomInteger === minValue) {
+                        minValuesCount += 1;
+                    }
+
+                    if (randomInteger === maxValue) {
+                        maxValuesCount += 1;
+                    }
+
+                    minValueChance = minValuesCount / samplesCount;
+                    maxValueChance = maxValuesCount / samplesCount;
+                }
+            });
+
+            it('should generate min values', () => {
+                assert.closeTo(minValueChance, expectedValueChance, delta);
+            });
+
+            it('should generate max values', () => {
+                assert.closeTo(maxValueChance, expectedValueChance, delta);
+            });
+        });
+    });
+});

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác