瀏覽代碼

Added `stringArrayCallsTransform` and `stringArrayCallsTransformThreshold` options (#987)

Timofey Kachalov 3 年之前
父節點
當前提交
f075a0e2d1
共有 78 個文件被更改,包括 1754 次插入328 次删除
  1. 2 2
      .github/workflows/ci.yml
  2. 5 0
      CHANGELOG.md
  3. 30 5
      README.md
  4. 1 1
      package.json
  5. 3 4
      src/JavaScriptObfuscator.ts
  6. 11 1
      src/cli/JavaScriptObfuscatorCLI.ts
  7. 2 1
      src/container/ServiceIdentifiers.ts
  8. 5 5
      src/container/modules/custom-nodes/CustomNodesModule.ts
  9. 10 0
      src/container/modules/node-transformers/ControlFlowTransformersModule.ts
  10. 27 30
      src/container/modules/storages/StoragesModule.ts
  11. 7 0
      src/container/modules/utils/UtilsModule.ts
  12. 9 9
      src/custom-nodes/control-flow-flattening-nodes/LiteralNode.ts
  13. 5 5
      src/custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts
  14. 2 2
      src/enums/custom-nodes/ControlFlowCustomNode.ts
  15. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  16. 1 0
      src/enums/node-transformers/control-flow-transformers/control-flow-replacers/ControlFlowReplacer.ts
  17. 4 0
      src/enums/storages/ControlFlowStorage.ts
  18. 7 0
      src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts
  19. 39 24
      src/generators/identifier-names-generators/DictionaryIdentifierNamesGenerator.ts
  20. 9 0
      src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts
  21. 100 20
      src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts
  22. 5 10
      src/generators/identifier-names-generators/MangledShuffledIdentifierNamesGenerator.ts
  23. 6 0
      src/interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator.ts
  24. 4 4
      src/interfaces/node-transformers/control-flow-transformers/IControlFlowReplacer.ts
  25. 2 0
      src/interfaces/options/IOptions.ts
  26. 5 0
      src/interfaces/storages/control-flow-transformers/IControlFlowStorage.ts
  27. 7 0
      src/interfaces/utils/ISetUtils.ts
  28. 149 111
      src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts
  29. 148 0
      src/node-transformers/control-flow-transformers/StringArrayControlFlowTransformer.ts
  30. 7 7
      src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts
  31. 3 3
      src/node-transformers/control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts
  32. 3 3
      src/node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer.ts
  33. 3 3
      src/node-transformers/control-flow-transformers/control-flow-replacers/LogicalExpressionControlFlowReplacer.ts
  34. 132 0
      src/node-transformers/control-flow-transformers/control-flow-replacers/StringArrayCallControlFlowReplacer.ts
  35. 8 8
      src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts
  36. 1 1
      src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer.ts
  37. 14 0
      src/options/Options.ts
  38. 2 0
      src/options/OptionsNormalizer.ts
  39. 19 0
      src/options/normalizer-rules/StringArrayCallsTransform.ts
  40. 2 0
      src/options/normalizer-rules/StringArrayRule.ts
  41. 2 0
      src/options/presets/Default.ts
  42. 1 0
      src/options/presets/HighObfuscation.ts
  43. 4 2
      src/options/presets/LowObfuscation.ts
  44. 1 0
      src/options/presets/MediumObfuscation.ts
  45. 2 0
      src/options/presets/NoCustomNodes.ts
  46. 38 0
      src/storages/control-flow-transformers/FunctionControlFlowStorage.ts
  47. 33 0
      src/storages/control-flow-transformers/StringControlFlowStorage.ts
  48. 0 22
      src/storages/custom-nodes/ControlFlowStorage.ts
  49. 2 2
      src/types/container/node-transformers/TControlFlowStorageFactory.ts
  50. 5 0
      src/types/container/node-transformers/TControlFlowStorageFactoryCreator.ts
  51. 0 4
      src/types/storages/TControlFlowStorage.ts
  52. 32 0
      src/utils/SetUtils.ts
  53. 36 12
      test/dev/dev.ts
  54. 2 1
      test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec.ts
  55. 2 0
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  56. 11 3
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts
  57. 29 13
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts
  58. 17 5
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts
  59. 8 2
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts
  60. 427 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/StringArrayControlFlowTransformer.spec.ts
  61. 3 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/input-1.js
  62. 7 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/input-2.js
  63. 4 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/multiple-items.js
  64. 7 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/multiple-storages.js
  65. 3 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/prevailing-kind-of-variables-const.js
  66. 3 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/prevailing-kind-of-variables-let.js
  67. 3 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/prevailing-kind-of-variables-var.js
  68. 1 0
      test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/root-block-scope-1.js
  69. 1 1
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec.ts
  70. 2 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/StringArrayRotateFunctionTransformer.spec.ts
  71. 25 1
      test/functional-tests/options/OptionsNormalizer.spec.ts
  72. 2 0
      test/index.spec.ts
  73. 3 1
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  74. 35 0
      test/unit-tests/generators/identifier-names-generators/DictionarylIdentifierNamesGenerator.spec.ts
  75. 42 0
      test/unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec.ts
  76. 34 0
      test/unit-tests/generators/identifier-names-generators/MangledShuffledlIdentifierNamesGenerator.spec.ts
  77. 40 0
      test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts
  78. 87 0
      test/unit-tests/utils/SetUtils.spec.ts

+ 2 - 2
.github/workflows/ci.yml

@@ -22,8 +22,8 @@ jobs:
             node-version: 14.x
           - os: ubuntu-latest,
             node-version: 16.x
-          - os: windows-latest,
-            node-version: 14.x
+          - os: ubuntu-latest,
+            node-version: 17.x
           - os: windows-latest,
             node-version: 16.x
 

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 
+v3.2.0
+---
+* **New options**: `stringArrayCallsTransform` and `stringArrayCallsTransformThreshold`
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/pull/1046
+
 v3.1.0
 ---
 * Added support of `es2022` features: class static block

+ 30 - 5
README.md

@@ -379,10 +379,12 @@ Following options are available for the JS Obfuscator:
     splitStrings: false,
     splitStringsChunkLength: 10,
     stringArray: true,
+    stringArrayCallsTransform: true,
+    stringArrayCallsTransformThreshold: 0.5,
+    stringArrayEncoding: [],
     stringArrayIndexesType: [
         'hexadecimal-number'
     ],
-    stringArrayEncoding: [],
     stringArrayIndexShift: true,
     stringArrayRotate: true,
     stringArrayShuffle: true,
@@ -441,8 +443,10 @@ Following options are available for the JS Obfuscator:
     --split-strings <boolean>
     --split-strings-chunk-length <number>
     --string-array <boolean>
-    --string-array-indexes-type '<list>' (comma separated) [hexadecimal-number, hexadecimal-numeric-string]
+    --string-array-calls-transform <boolean>
+    --string-array-calls-transform-threshold <number>
     --string-array-encoding '<list>' (comma separated) [none, base64, rc4]
+    --string-array-indexes-type '<list>' (comma separated) [hexadecimal-number, hexadecimal-numeric-string]
     --string-array-index-shift <boolean>
     --string-array-rotate <boolean>
     --string-array-shuffle <boolean>
@@ -1097,6 +1101,20 @@ Type: `boolean` Default: `true`
 
 Removes string literals and place them in a special array. For instance, the string `"Hello World"` in `var m = "Hello World";` will be replaced with something like `var m = _0x12c456[0x1];`
 
+### `stringArrayCallsTransform`
+Type: `boolean` Default: `true`
+
+##### :warning: [`stringArray`](#stringarray) option must be enabled
+
+Enables the transformation of calls to the [`stringArray`](#stringarray). All arguments of these calls may be extracted to different object depending on [`stringArrayCallsTransformThreshold`](#stringarraycallstransformthreshold) value.
+
+### `stringArrayCallsTransformThreshold`
+Type: `number` Default: `0.5`
+
+##### :warning: [`stringArray`](#stringarray) and [`stringArrayCallsTransformThreshold`](#stringarraycallstransformthreshold) options must be enabled
+
+You can use this setting to adjust the probability (from 0 to 1) that calls to the string array will be transformed.
+
 ### `stringArrayEncoding`
 Type: `string[]` Default: `[]`
 
@@ -1419,7 +1437,7 @@ Unicode escape sequence increases code size greatly and strings easily can be re
 ## Preset Options
 ### High obfuscation, low performance
 
-Performance will 50-100% slower than without obfuscation
+The performance will be much slower than without obfuscation
 
 ```javascript
 {
@@ -1440,6 +1458,8 @@ Performance will 50-100% slower than without obfuscation
     splitStrings: true,
     splitStringsChunkLength: 5,
     stringArray: true,
+    stringArray: true,
+    stringArrayCallsTransform: true,
     stringArrayEncoding: ['rc4'],
     stringArrayIndexShift: true,
     stringArrayRotate: true,
@@ -1456,7 +1476,7 @@ Performance will 50-100% slower than without obfuscation
 
 ### Medium obfuscation, optimal performance
 
-Performance will 30-35% slower than without obfuscation
+The performance will be slower than without obfuscation
 
 ```javascript
 {
@@ -1477,6 +1497,8 @@ Performance will 30-35% slower than without obfuscation
     splitStrings: true,
     splitStringsChunkLength: 10,
     stringArray: true,
+    stringArrayCallsTransform: true,
+    stringArrayCallsTransformThreshold: 0.75,
     stringArrayEncoding: ['base64'],
     stringArrayIndexShift: true,
     stringArrayRotate: true,
@@ -1493,7 +1515,7 @@ Performance will 30-35% slower than without obfuscation
 
 ### Low obfuscation, High performance
 
-Performance will slightly slower than without obfuscation
+The performance will be at a relatively normal level
 
 ```javascript
 {
@@ -1511,6 +1533,7 @@ Performance will slightly slower than without obfuscation
     simplify: true,
     splitStrings: false,
     stringArray: true,
+    stringArrayCallsTransform: false,
     stringArrayEncoding: [],
     stringArrayIndexShift: true,
     stringArrayRotate: true,
@@ -1542,6 +1565,8 @@ Performance will slightly slower than without obfuscation
     simplify: true,
     splitStrings: false,
     stringArray: true,
+    stringArrayCallsTransform: true,
+    stringArrayCallsTransformThreshold: 0.5,
     stringArrayEncoding: [],
     stringArrayIndexShift: true,
     stringArrayRotate: true,

+ 1 - 1
package.json

@@ -111,7 +111,7 @@
     "test:devRuntimePerformance": "ts-node test/dev/dev-runtime-performance.ts",
     "test:full": "yarn run test:dev && yarn run test:mocha-coverage && yarn run test:mocha-memory-performance",
     "test:mocha": "mocha --require ts-node/register --require source-map-support/register test/index.spec.ts --exit",
-    "test:mocha-coverage": "nyc --reporter text-summary --no-clean yarn run test:mocha",
+    "test:mocha-coverage": "NODE_OPTIONS=--max-old-space-size=4096 nyc --reporter text-summary --no-clean yarn run test:mocha",
     "test:mocha-coverage:report": "nyc report --reporter=lcov",
     "test:mocha-memory-performance": "cross-env NODE_OPTIONS=--max-old-space-size=230 mocha --require ts-node/register test/performance-tests/JavaScriptObfuscatorMemory.spec.ts",
     "test": "yarn run test:full",

+ 3 - 4
src/JavaScriptObfuscator.ts

@@ -20,7 +20,7 @@ import { CodeTransformer } from './enums/code-transformers/CodeTransformer';
 import { CodeTransformationStage } from './enums/code-transformers/CodeTransformationStage';
 import { LoggingMessage } from './enums/logger/LoggingMessage';
 import { NodeTransformer } from './enums/node-transformers/NodeTransformer';
-import { NodeTransformationStage     } from './enums/node-transformers/NodeTransformationStage';
+import { NodeTransformationStage } from './enums/node-transformers/NodeTransformationStage';
 import { SourceMapSourcesMode } from './enums/source-map/SourceMapSourcesMode';
 
 import { ecmaVersion } from './constants/EcmaVersion';
@@ -90,6 +90,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.ScopeIdentifiersTransformer,
         NodeTransformer.ScopeThroughIdentifiersTransformer,
         NodeTransformer.SplitStringTransformer,
+        NodeTransformer.StringArrayControlFlowTransformer,
         NodeTransformer.StringArrayRotateFunctionTransformer,
         NodeTransformer.StringArrayScopeCallsWrapperTransformer,
         NodeTransformer.StringArrayTransformer,
@@ -220,9 +221,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
             astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.DeadCodeInjection);
         }
 
-        if (this.options.controlFlowFlattening) {
-            astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.ControlFlowFlattening);
-        }
+        astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.ControlFlowFlattening);
 
         if (this.options.renameProperties) {
             astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.RenameProperties);

+ 11 - 1
src/cli/JavaScriptObfuscatorCLI.ts

@@ -363,9 +363,19 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
             )
             .option(
                 '--string-array <boolean>',
-                'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
+                'Enables gathering of all literal strings into an array and replacing every literal string with an array call',
                 BooleanSanitizer
             )
+            .option(
+                '--string-array-calls-transform <boolean>',
+                'Enables the transformation of calls to the string array',
+                BooleanSanitizer
+            )
+            .option(
+                '--string-array-calls-transform-threshold <number>',
+                'The probability that that calls to the string array will be transformed',
+                parseFloat
+            )
             .option(
                 '--string-array-encoding <list> (comma separated, without whitespaces)',
                 'Encodes each string in strings array using base64 or rc4 (this option can slow down your code speed). ' +

+ 2 - 1
src/container/ServiceIdentifiers.ts

@@ -21,6 +21,7 @@ export enum ServiceIdentifiers {
     ICodeTransformer = 'ICodeTransformer',
     ICodeTransformerNamesGroupsBuilder = 'ICodeTransformerNamesGroupsBuilder',
     ICodeTransformersRunner = 'ICodeTransformersRunner',
+    IControlFlowStorage = 'IControlFlowStorage',
     ICryptUtils = 'ICryptUtils',
     ICryptUtilsStringArray = 'ICryptUtilsStringArray',
     ICustomCodeHelper = 'ICustomCodeHelper',
@@ -50,6 +51,7 @@ export enum ServiceIdentifiers {
     IRandomGenerator = 'IRandomGenerator',
     IRenamePropertiesReplacer = 'IRenamePropertiesReplacer',
     IScopeIdentifiersTraverser = 'IScopeIdentifiersTraverser',
+    ISetUtils = 'ISetUtils',
     ISourceCode = 'ISourceCode',
     IScopeAnalyzer = 'IScopeAnalyzer',
     IStringArrayIndexNode = 'IStringArrayIndexNode',
@@ -59,7 +61,6 @@ export enum ServiceIdentifiers {
     IThroughIdentifierReplacer = 'IThroughIdentifierReplacer',
     IVisitedLexicalScopeNodesStackStorage = 'IVisitedLexicalScopeNodesStackStorage',
     Newable__ICustomNode = 'Newable<ICustomNode>',
-    Newable__TControlFlowStorage = 'Newable<TControlFlowStorage>',
     TCustomNodeGroupStorage = 'TCustomNodeGroupStorage',
     TInputOptions = 'TInputOptions'
 }

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

@@ -26,7 +26,7 @@ import { StringArrayIndexNode } from '../../../enums/custom-nodes/string-array-i
 import { StringArrayScopeCallsWrapperFunctionNode } from '../../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode';
 import { StringArrayScopeCallsWrapperVariableNode } from '../../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode';
 import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
-import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
+import { LiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/LiteralNode';
 
 export const customNodesModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // control flow custom nodes
@@ -55,12 +55,12 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .whenTargetNamed(ControlFlowCustomNode.ExpressionWithOperatorControlFlowStorageCallNode);
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
-        .toConstructor(LogicalExpressionFunctionNode)
-        .whenTargetNamed(ControlFlowCustomNode.LogicalExpressionFunctionNode);
+        .toConstructor(LiteralNode)
+        .whenTargetNamed(ControlFlowCustomNode.LiteralNode);
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
-        .toConstructor(StringLiteralNode)
-        .whenTargetNamed(ControlFlowCustomNode.StringLiteralNode);
+        .toConstructor(LogicalExpressionFunctionNode)
+        .whenTargetNamed(ControlFlowCustomNode.LogicalExpressionFunctionNode);
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringLiteralControlFlowStorageCallNode)

+ 10 - 0
src/container/modules/node-transformers/ControlFlowTransformersModule.ts

@@ -13,6 +13,8 @@ import { BlockStatementControlFlowTransformer } from '../../../node-transformers
 import { CallExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer';
 import { FunctionControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/FunctionControlFlowTransformer';
 import { LogicalExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/LogicalExpressionControlFlowReplacer';
+import { StringArrayCallControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/StringArrayCallControlFlowReplacer';
+import { StringArrayControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/StringArrayControlFlowTransformer';
 import { StringLiteralControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer';
 
 export const controlFlowTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
@@ -25,6 +27,10 @@ export const controlFlowTransformersModule: interfaces.ContainerModule = new Con
         .to(FunctionControlFlowTransformer)
         .whenTargetNamed(NodeTransformer.FunctionControlFlowTransformer);
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(StringArrayControlFlowTransformer)
+        .whenTargetNamed(NodeTransformer.StringArrayControlFlowTransformer);
+
     // control flow replacers
     bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
         .to(BinaryExpressionControlFlowReplacer)
@@ -38,6 +44,10 @@ export const controlFlowTransformersModule: interfaces.ContainerModule = new Con
         .to(LogicalExpressionControlFlowReplacer)
         .whenTargetNamed(ControlFlowReplacer.LogicalExpressionControlFlowReplacer);
 
+    bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
+        .to(StringArrayCallControlFlowReplacer)
+        .whenTargetNamed(ControlFlowReplacer.StringArrayCallControlFlowReplacer);
+
     bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
         .to(StringLiteralControlFlowReplacer)
         .whenTargetNamed(ControlFlowReplacer.StringLiteralControlFlowReplacer);

+ 27 - 30
src/container/modules/storages/StoragesModule.ts

@@ -1,25 +1,31 @@
 import { ContainerModule, interfaces } from 'inversify';
 import { ServiceIdentifiers } from '../../ServiceIdentifiers';
 
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
-import { TConstructor } from '../../../types/TConstructor';
+import { TControlFlowStorageFactory } from '../../../types/container/node-transformers/TControlFlowStorageFactory';
+import {
+    TControlFlowStorageFactoryCreator
+} from '../../../types/container/node-transformers/TControlFlowStorageFactoryCreator';
 import { TCustomCodeHelperGroupStorage } from '../../../types/storages/TCustomCodeHelperGroupStorage';
 
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { IGlobalIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
 import { ILiteralNodesCacheStorage } from '../../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
-import { IOptions } from '../../../interfaces/options/IOptions';
 import { IPropertyIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
-import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
 import { IStringArrayScopeCallsWrappersDataStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrappersDataStorage';
 import { IStringArrayStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 import { IVisitedLexicalScopeNodesStackStorage } from '../../../interfaces/storages/string-array-transformers/IVisitedLexicalScopeNodesStackStorage';
 
-import { ControlFlowStorage } from '../../../storages/custom-nodes/ControlFlowStorage';
+import { ControlFlowStorage } from '../../../enums/storages/ControlFlowStorage';
+
 import { CustomCodeHelperGroupStorage } from '../../../storages/custom-code-helpers/CustomCodeHelperGroupStorage';
+import { FunctionControlFlowStorage } from '../../../storages/control-flow-transformers/FunctionControlFlowStorage';
 import { GlobalIdentifierNamesCacheStorage } from '../../../storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage';
 import { LiteralNodesCacheStorage } from '../../../storages/string-array-transformers/LiteralNodesCacheStorage';
 import { PropertyIdentifierNamesCacheStorage } from '../../../storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage';
 import { StringArrayScopeCallsWrappersDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrappersDataStorage';
+import {
+    StringControlFlowStorage
+} from '../../../storages/control-flow-transformers/StringControlFlowStorage';
 import { StringArrayStorage } from '../../../storages/string-array-transformers/StringArrayStorage';
 import { VisitedLexicalScopeNodesStackStorage } from '../../../storages/string-array-transformers/VisitedLexicalScopeNodesStackStorage';
 
@@ -29,6 +35,10 @@ export const storagesModule: interfaces.ContainerModule = new ContainerModule((b
         .to(CustomCodeHelperGroupStorage)
         .inSingletonScope();
 
+    bind<IControlFlowStorage>(ServiceIdentifiers.IControlFlowStorage)
+        .to(FunctionControlFlowStorage)
+        .whenTargetNamed(ControlFlowStorage.FunctionControlFlowStorage);
+
     bind<IGlobalIdentifierNamesCacheStorage>(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
         .to(GlobalIdentifierNamesCacheStorage)
         .inSingletonScope();
@@ -49,34 +59,21 @@ export const storagesModule: interfaces.ContainerModule = new ContainerModule((b
         .to(StringArrayScopeCallsWrappersDataStorage)
         .inSingletonScope();
 
+    bind<IControlFlowStorage>(ServiceIdentifiers.IControlFlowStorage)
+        .to(StringControlFlowStorage)
+        .whenTargetNamed(ControlFlowStorage.StringControlFlowStorage);
+
     bind<IVisitedLexicalScopeNodesStackStorage>(ServiceIdentifiers.IVisitedLexicalScopeNodesStackStorage)
         .to(VisitedLexicalScopeNodesStackStorage)
         .inSingletonScope();
 
-    bind<interfaces.Newable<TControlFlowStorage>>(ServiceIdentifiers.Newable__TControlFlowStorage)
-        .toConstructor(ControlFlowStorage);
-
     // controlFlowStorage factory
-    bind<TControlFlowStorage>(ServiceIdentifiers.Factory__TControlFlowStorage)
-        .toFactory<TControlFlowStorage>((context: interfaces.Context) => {
-            return (): TControlFlowStorage => {
-                const constructor = context.container
-                    .get<TConstructor<[IRandomGenerator, IOptions], TControlFlowStorage>>(
-                        ServiceIdentifiers.Newable__TControlFlowStorage
-                    );
-                const randomGenerator: IRandomGenerator = context.container
-                    .get<IRandomGenerator>(ServiceIdentifiers.IRandomGenerator);
-                const options: IOptions = context.container
-                    .get<IOptions>(ServiceIdentifiers.IOptions);
-
-                const storage: TControlFlowStorage = new constructor(
-                    randomGenerator,
-                    options
-                );
-
-                storage.initialize();
-
-                return storage;
-            };
-        });
+    bind<IControlFlowStorage>(ServiceIdentifiers.Factory__TControlFlowStorage)
+        .toFactory((context: interfaces.Context): TControlFlowStorageFactoryCreator =>
+            (controlFlowStorageName: ControlFlowStorage): TControlFlowStorageFactory => (): IControlFlowStorage =>
+                context.container.getNamed<IControlFlowStorage>(
+                ServiceIdentifiers.IControlFlowStorage,
+                controlFlowStorageName
+                )
+            );
 });

+ 7 - 0
src/container/modules/utils/UtilsModule.ts

@@ -7,6 +7,7 @@ import { ICryptUtilsStringArray } from '../../../interfaces/utils/ICryptUtilsStr
 import { IEscapeSequenceEncoder } from '../../../interfaces/utils/IEscapeSequenceEncoder';
 import { ILevelledTopologicalSorter } from '../../../interfaces/utils/ILevelledTopologicalSorter';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
+import { ISetUtils } from '../../../interfaces/utils/ISetUtils';
 
 import { ArrayUtils } from '../../../utils/ArrayUtils';
 import { CryptUtils } from '../../../utils/CryptUtils';
@@ -14,6 +15,7 @@ import { CryptUtilsStringArray } from '../../../utils/CryptUtilsStringArray';
 import { EscapeSequenceEncoder } from '../../../utils/EscapeSequenceEncoder';
 import { LevelledTopologicalSorter } from '../../../utils/LevelledTopologicalSorter';
 import { RandomGenerator } from '../../../utils/RandomGenerator';
+import { SetUtils } from '../../../utils/SetUtils';
 
 export const utilsModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // array utils
@@ -44,4 +46,9 @@ export const utilsModule: interfaces.ContainerModule = new ContainerModule((bind
     // levelled topological sorter
     bind<ILevelledTopologicalSorter>(ServiceIdentifiers.ILevelledTopologicalSorter)
         .to(LevelledTopologicalSorter);
+
+    // set utils
+    bind<ISetUtils>(ServiceIdentifiers.ISetUtils)
+        .to(SetUtils)
+        .inSingletonScope();
 });

+ 9 - 9
src/custom-nodes/control-flow-flattening-nodes/StringLiteralNode.ts → src/custom-nodes/control-flow-flattening-nodes/LiteralNode.ts

@@ -1,6 +1,8 @@
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import type * as ESTree from 'estree';
+
 import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TStatement } from '../../types/node/TStatement';
 
@@ -14,12 +16,12 @@ import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeFactory } from '../../node/NodeFactory';
 
 @injectable()
-export class StringLiteralNode extends AbstractCustomNode {
+export class LiteralNode extends AbstractCustomNode {
     /**
-     * @type {string}
+     * @type {ESTree.Literal}
      */
     @initializable()
-    private literalValue!: string;
+    private literalNode!: ESTree.Literal;
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -43,19 +45,17 @@ export class StringLiteralNode extends AbstractCustomNode {
     }
 
     /**
-     * @param {string} literalValue
+     * @param {ESTree.Literal} literalNode
      */
-    public initialize (literalValue: string): void {
-        this.literalValue = literalValue;
+    public initialize (literalNode: ESTree.Literal): void {
+        this.literalNode = literalNode;
     }
 
     /**
      * @returns {TStatement[]}
      */
     protected getNodeStructure (): TStatement[] {
-        const structure: TStatement = NodeFactory.expressionStatementNode(
-            NodeFactory.literalNode(this.literalValue)
-        );
+        const structure: TStatement = NodeFactory.expressionStatementNode(this.literalNode);
 
         return [structure];
     }

+ 5 - 5
src/custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts

@@ -3,10 +3,10 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import * as ESTree from 'estree';
 
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TStatement } from '../../../types/node/TStatement';
 
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomCodeHelperFormatter } from '../../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter';
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -21,10 +21,10 @@ import { NodeGuards } from '../../../node/NodeGuards';
 @injectable()
 export class ControlFlowStorageNode extends AbstractCustomNode {
     /**
-     * @type {TControlFlowStorage}
+     * @type {IControlFlowStorage}
      */
     @initializable()
-    private controlFlowStorage!: TControlFlowStorage;
+    private controlFlowStorage!: IControlFlowStorage;
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -48,9 +48,9 @@ export class ControlFlowStorageNode extends AbstractCustomNode {
     }
 
     /**
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      */
-    public initialize (controlFlowStorage: TControlFlowStorage): void {
+    public initialize (controlFlowStorage: IControlFlowStorage): void {
         this.controlFlowStorage = controlFlowStorage;
     }
 

+ 2 - 2
src/enums/custom-nodes/ControlFlowCustomNode.ts

@@ -5,7 +5,7 @@ export enum ControlFlowCustomNode {
     CallExpressionFunctionNode = 'CallExpressionFunctionNode',
     ControlFlowStorageNode = 'ControlFlowStorageNode',
     ExpressionWithOperatorControlFlowStorageCallNode = 'ExpressionWithOperatorControlFlowStorageCallNode',
+    LiteralNode = 'LiteralNode',
     LogicalExpressionFunctionNode = 'LogicalExpressionFunctionNode',
-    StringLiteralControlFlowStorageCallNode = 'StringLiteralControlFlowStorageCallNode',
-    StringLiteralNode = 'StringLiteralNode'
+    StringLiteralControlFlowStorageCallNode = 'StringLiteralControlFlowStorageCallNode'
 }

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

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

+ 1 - 0
src/enums/node-transformers/control-flow-transformers/control-flow-replacers/ControlFlowReplacer.ts

@@ -2,5 +2,6 @@ export enum ControlFlowReplacer {
     BinaryExpressionControlFlowReplacer = 'BinaryExpressionControlFlowReplacer',
     CallExpressionControlFlowReplacer = 'CallExpressionControlFlowReplacer',
     LogicalExpressionControlFlowReplacer = 'LogicalExpressionControlFlowReplacer',
+    StringArrayCallControlFlowReplacer = 'StringArrayCallControlFlowReplacer',
     StringLiteralControlFlowReplacer = 'StringLiteralControlFlowReplacer'
 }

+ 4 - 0
src/enums/storages/ControlFlowStorage.ts

@@ -0,0 +1,4 @@
+export enum ControlFlowStorage {
+    FunctionControlFlowStorage = 'function-control-flow-storage',
+    StringControlFlowStorage = 'string-control-flow-storage'
+}

+ 7 - 0
src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts

@@ -134,6 +134,13 @@ export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNam
      */
     public abstract generateForLexicalScope (lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string;
 
+    /**
+     * @param {string} label
+     * @param {number} nameLength
+     * @returns {string}
+     */
+    public abstract generateForLabel (label: string, nameLength?: number): string;
+
     /**
      * @param {number} nameLength
      * @returns {string}

+ 39 - 24
src/generators/identifier-names-generators/DictionaryIdentifierNamesGenerator.ts

@@ -84,12 +84,13 @@ export class DictionaryIdentifierNamesGenerator extends AbstractIdentifierNamesG
         const prefix: string = this.options.identifiersPrefix ?
             `${this.options.identifiersPrefix}`
             : '';
-        const identifierName: string = this.generateNewDictionaryName();
-        const identifierNameWithPrefix: string = `${prefix}${identifierName}`;
 
-        if (!this.isValidIdentifierName(identifierNameWithPrefix)) {
-            return this.generateForGlobalScope();
-        }
+        const identifierName: string = this.generateNewDictionaryName((newIdentifierName: string) => {
+            const identifierNameWithPrefix: string = `${prefix}${newIdentifierName}`;
+
+            return this.isValidIdentifierName(identifierNameWithPrefix);
+        });
+        const identifierNameWithPrefix = `${prefix}${identifierName}`;
 
         this.preserveName(identifierNameWithPrefix);
 
@@ -105,11 +106,9 @@ export class DictionaryIdentifierNamesGenerator extends AbstractIdentifierNamesG
             lexicalScopeNode,
             ...NodeLexicalScopeUtils.getLexicalScopes(lexicalScopeNode)
         ];
-        const identifierName: string = this.generateNewDictionaryName();
-
-        if (!this.isValidIdentifierNameInLexicalScopes(identifierName, lexicalScopes)) {
-            return this.generateForLexicalScope(lexicalScopeNode);
-        }
+        const identifierName: string = this.generateNewDictionaryName((newIdentifierName: string) =>
+            this.isValidIdentifierNameInLexicalScopes(newIdentifierName, lexicalScopes)
+        );
 
         this.preserveNameForLexicalScope(identifierName, lexicalScopeNode);
 
@@ -117,29 +116,45 @@ export class DictionaryIdentifierNamesGenerator extends AbstractIdentifierNamesG
     }
 
     /**
+     * @param {string} label
      * @returns {string}
      */
-    private generateNewDictionaryName (): string {
-        if (!this.identifierNamesSet.size) {
-            throw new Error('Too many identifiers in the code, add more words to identifiers dictionary');
-        }
+    public generateForLabel (label: string): string {
+        return this.generateNewDictionaryName();
+    }
 
-        const iteratorResult: IteratorResult<string> = this.identifiersIterator.next();
+    /**
+     * @param {(newIdentifierName: string) => boolean} validationFunction
+     * @returns {string}
+     */
+    private generateNewDictionaryName (validationFunction?: (newIdentifierName: string) => boolean): string {
+        const generateNewDictionaryName = (): string => {
+            if (!this.identifierNamesSet.size) {
+                throw new Error('Too many identifiers in the code, add more words to identifiers dictionary');
+            }
+
+            const iteratorResult: IteratorResult<string> = this.identifiersIterator.next();
+
+            if (!iteratorResult.done) {
+                const identifierName: string = iteratorResult.value;
 
-        if (!iteratorResult.done) {
-            const identifierName: string =iteratorResult.value;
+                const isValidIdentifierName = validationFunction?.(identifierName)
+                    ?? this.isValidIdentifierName(identifierName);
 
-            if (!this.isValidIdentifierName(identifierName)) {
-                return this.generateNewDictionaryName();
+                if (!isValidIdentifierName) {
+                    return generateNewDictionaryName();
+                }
+
+                return iteratorResult.value;
             }
 
-            return iteratorResult.value;
-        }
+            this.identifierNamesSet = new Set(this.getIncrementedIdentifierNames([...this.identifierNamesSet]));
+            this.identifiersIterator = this.identifierNamesSet.values();
 
-        this.identifierNamesSet = new Set(this.getIncrementedIdentifierNames([...this.identifierNamesSet]));
-        this.identifiersIterator = this.identifierNamesSet.values();
+            return generateNewDictionaryName();
+        };
 
-        return this.generateNewDictionaryName();
+        return generateNewDictionaryName();
     }
 
     /**

+ 9 - 0
src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts

@@ -70,4 +70,13 @@ export class HexadecimalIdentifierNamesGenerator extends AbstractIdentifierNames
     public generateForLexicalScope (lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string {
         return this.generateNext(nameLength);
     }
+
+    /**
+     * @param {string} label
+     * @param {number} nameLength
+     * @returns {string}
+     */
+    public generateForLabel (label: string, nameLength?: number): string {
+        return this.generateNext(nameLength);
+    }
 }

+ 100 - 20
src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts

@@ -5,6 +5,7 @@ import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { ISetUtils } from '../../interfaces/utils/ISetUtils';
 
 import { numbersString } from '../../constants/NumbersString';
 import { alphabetString } from '../../constants/AlphabetString';
@@ -15,6 +16,11 @@ import { NodeLexicalScopeUtils } from '../../node/NodeLexicalScopeUtils';
 
 @injectable()
 export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGenerator {
+    /**
+     * @type {number}
+     */
+    private static readonly maxRegenerationAttempts: number = 20;
+
     /**
      * @type {string}
      */
@@ -43,20 +49,34 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         'var', 'void', 'with'
     ]);
 
+    /**
+     * @type {WeakMap<string, string>}
+     */
+    private readonly lastMangledNameForLabelMap: Map <string, string> = new Map();
+
     /**
      * @type {string}
      */
     private previousMangledName: string = MangledIdentifierNamesGenerator.initMangledNameCharacter;
 
+    /**
+     * @type {ISetUtils}
+     */
+    private readonly setUtils: ISetUtils;
+
     /**
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
+     * @param {ISetUtils} setUtils
      */
     public constructor (
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.ISetUtils) setUtils: ISetUtils,
     ) {
         super(randomGenerator, options);
+
+        this.setUtils = setUtils;
     }
 
     /**
@@ -83,15 +103,18 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         const prefix: string = this.options.identifiersPrefix ?
             `${this.options.identifiersPrefix}`
             : '';
-        const identifierName: string = this.generateNewMangledName(this.previousMangledName);
-        const identifierNameWithPrefix: string = `${prefix}${identifierName}`;
 
-        this.updatePreviousMangledName(identifierName);
+        const identifierName: string = this.generateNewMangledName(
+            this.previousMangledName,
+            (newIdentifierName: string) => {
+                const identifierNameWithPrefix: string = `${prefix}${newIdentifierName}`;
 
-        if (!this.isValidIdentifierName(identifierNameWithPrefix)) {
-            return this.generateForGlobalScope(nameLength);
-        }
+                return this.isValidIdentifierName(identifierNameWithPrefix);
+            }
+        );
+        const identifierNameWithPrefix: string = `${prefix}${identifierName}`;
 
+        this.updatePreviousMangledName(identifierName);
         this.preserveName(identifierNameWithPrefix);
 
         return identifierNameWithPrefix;
@@ -109,12 +132,11 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         ];
 
         const lastMangledNameForScope: string = this.getLastMangledNameForScopes(lexicalScopes);
-
-        let identifierName: string = lastMangledNameForScope;
-
-        do {
-            identifierName = this.generateNewMangledName(identifierName);
-        } while (!this.isValidIdentifierNameInLexicalScopes(identifierName, lexicalScopes));
+        const identifierName: string = this.generateNewMangledName(
+            lastMangledNameForScope,
+            (newIdentifierName: string) =>
+                this.isValidIdentifierNameInLexicalScopes(newIdentifierName, lexicalScopes)
+        );
 
         MangledIdentifierNamesGenerator.lastMangledNameInScopeMap.set(lexicalScopeNode, identifierName);
 
@@ -124,6 +146,21 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         return identifierName;
     }
 
+    /**
+     * @param {string} label
+     * @param {number} nameLength
+     * @returns {string}
+     */
+    public generateForLabel (label: string, nameLength?: number): string {
+        const lastMangledNameForLabel: string = this.getLastMangledNameForLabel(label);
+
+        const identifierName: string = this.generateNewMangledName(lastMangledNameForLabel);
+
+        this.updatePreviousMangledNameForLabel(identifierName, label, lastMangledNameForLabel);
+
+        return identifierName;
+    }
+
     /**
      * @param {string} nextName
      * @param {string} prevName
@@ -188,12 +225,42 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         this.previousMangledName = name;
     }
 
+    /**
+     * @param {string} name
+     * @param {string} label
+     * @param {string} lastMangledNameForLabel
+     */
+    protected updatePreviousMangledNameForLabel (name: string, label: string, lastMangledNameForLabel: string): void {
+        if (!this.isIncrementedMangledName(name, lastMangledNameForLabel)) {
+            return;
+        }
+
+        this.lastMangledNameForLabelMap.set(label, name);
+    }
+
     /**
      * @param {string} previousMangledName
+     * @param {(newIdentifierName: string) => boolean} validationFunction
      * @returns {string}
      */
-    protected generateNewMangledName (previousMangledName: string): string {
-        const generateNewMangledName: (name: string) => string = (name: string): string => {
+    protected generateNewMangledName (
+        previousMangledName: string,
+        validationFunction?: (newIdentifierName: string) => boolean
+    ): string {
+        const generateNewMangledName = (name: string, regenerationAttempt: number = 0): string => {
+            /**
+             * Attempt to decrease amount of regeneration tries because of large preserved names set
+             * When we reached the limit, we're trying to generate next mangled name based on the latest
+             * preserved name
+             */
+            if (regenerationAttempt > MangledIdentifierNamesGenerator.maxRegenerationAttempts) {
+                const lastPreservedName = this.setUtils.getLastElement(this.preservedNamesSet);
+
+                if (lastPreservedName) {
+                    return this.generateNewMangledName(lastPreservedName);
+                }
+            }
+
             const nameSequence: string[] = this.getNameSequence();
             const nameSequenceLength: number = nameSequence.length;
             const nameLength: number = name.length;
@@ -226,13 +293,16 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
             return `${firstLetterCharacter}${zeroSequence(nameLength)}`;
         };
 
-        let newMangledName: string = generateNewMangledName(previousMangledName);
+        let identifierName: string = previousMangledName;
+        let isValidIdentifierName: boolean;
 
-        if (!this.isValidIdentifierName(newMangledName)) {
-            newMangledName = this.generateNewMangledName(newMangledName);
-        }
+        do {
+            identifierName = generateNewMangledName(identifierName);
+            isValidIdentifierName = validationFunction?.(identifierName)
+                ?? this.isValidIdentifierName(identifierName);
+        } while (!isValidIdentifierName);
 
-        return newMangledName;
+        return identifierName;
     }
 
     /**
@@ -253,4 +323,14 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
 
         return MangledIdentifierNamesGenerator.initMangledNameCharacter;
     }
+
+    /**
+     * @param {string} label
+     * @returns {string}
+     */
+    private getLastMangledNameForLabel (label: string): string {
+        const lastMangledName: string | null = this.lastMangledNameForLabelMap.get(label) ?? null;
+
+        return lastMangledName ?? MangledIdentifierNamesGenerator.initMangledNameCharacter;
+    }
 }

+ 5 - 10
src/generators/identifier-names-generators/MangledShuffledIdentifierNamesGenerator.ts

@@ -4,6 +4,7 @@ import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { ISetUtils } from '../../interfaces/utils/ISetUtils';
 
 import { numbersString } from '../../constants/NumbersString';
 import { alphabetString } from '../../constants/AlphabetString';
@@ -27,13 +28,15 @@ export class MangledShuffledIdentifierNamesGenerator extends MangledIdentifierNa
      * @param {IArrayUtils} arrayUtils
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
+     * @param {ISetUtils} setUtils
      */
     public constructor (
         @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.ISetUtils) setUtils: ISetUtils
     ) {
-        super(randomGenerator, options);
+        super(randomGenerator, options, setUtils);
 
         this.arrayUtils = arrayUtils;
     }
@@ -61,12 +64,4 @@ export class MangledShuffledIdentifierNamesGenerator extends MangledIdentifierNa
     protected override getNameSequence (): string[] {
         return MangledShuffledIdentifierNamesGenerator.shuffledNameSequence;
     }
-
-    /**
-     * @param {string} previousMangledName
-     * @returns {string}
-     */
-    protected override generateNewMangledName (previousMangledName: string): string {
-        return super.generateNewMangledName(previousMangledName);
-    }
 }

+ 6 - 0
src/interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator.ts

@@ -21,6 +21,12 @@ export interface IIdentifierNamesGenerator {
      */
     generateForLexicalScope (lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string;
 
+    /**
+     * @param {string} label
+     * @param {number} nameLength
+     * @returns {string}
+     */
+    generateForLabel (label: string, nameLength?: number): string;
 
     /**
      * @param {number} nameLength

+ 4 - 4
src/interfaces/node-transformers/control-flow-transformers/IControlFlowReplacer.ts

@@ -1,23 +1,23 @@
 import * as ESTree from 'estree';
 
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
+import { IControlFlowStorage } from '../../storages/control-flow-transformers/IControlFlowStorage';
 
 export interface IControlFlowReplacer {
     /**
      * @param {Node} node
      * @param {Node} parentNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {Node}
      */
     replace (
         node: ESTree.Node,
         parentNode: ESTree.Node,
-        controlFlowStorage: TControlFlowStorage
+        controlFlowStorage: IControlFlowStorage
     ): ESTree.Node;
 
     /**
      * @param {TControlFlowStorage} controlFlowStorage
      * @returns {string}
      */
-    generateStorageKey (controlFlowStorage: TControlFlowStorage): string;
+    generateStorageKey (controlFlowStorage: IControlFlowStorage): string;
 }

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

@@ -48,6 +48,8 @@ export interface IOptions {
     readonly splitStrings: boolean;
     readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;
+    readonly stringArrayCallsTransform: boolean;
+    readonly stringArrayCallsTransformThreshold: number;
     readonly stringArrayEncoding: TStringArrayEncoding[];
     readonly stringArrayIndexesType: TStringArrayIndexesType[];
     readonly stringArrayIndexShift: boolean;

+ 5 - 0
src/interfaces/storages/control-flow-transformers/IControlFlowStorage.ts

@@ -0,0 +1,5 @@
+import { IMapStorage } from '../IMapStorage';
+import { ICustomNode } from '../../custom-nodes/ICustomNode';
+
+// eslint-disable-next-line
+export interface IControlFlowStorage extends IMapStorage <string, ICustomNode> {}

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

@@ -0,0 +1,7 @@
+export interface ISetUtils {
+    /**
+     * @param {Set<T>} set
+     * @returns {T | undefined}
+     */
+    getLastElement <T> (set: Set<T>): T | undefined;
+}

+ 149 - 111
src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -6,23 +6,31 @@ import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
 import { TControlFlowReplacerFactory } from '../../types/container/node-transformers/TControlFlowReplacerFactory';
-import { TControlFlowStorage } from '../../types/storages/TControlFlowStorage';
 import { TControlFlowStorageFactory } from '../../types/container/node-transformers/TControlFlowStorageFactory';
+import {
+    TControlFlowStorageFactoryCreator
+} from '../../types/container/node-transformers/TControlFlowStorageFactoryCreator';
 import { TInitialData } from '../../types/TInitialData';
 import { TNodeWithStatements } from '../../types/node/TNodeWithStatements';
 
+import { IControlFlowStorage } from '../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { ControlFlowCustomNode } from '../../enums/custom-nodes/ControlFlowCustomNode';
-import { ControlFlowReplacer } from '../../enums/node-transformers/control-flow-transformers/control-flow-replacers/ControlFlowReplacer';
+import {
+    ControlFlowReplacer
+} from '../../enums/node-transformers/control-flow-transformers/control-flow-replacers/ControlFlowReplacer';
+import { ControlFlowStorage } from '../../enums/storages/ControlFlowStorage';
 import { NodeType } from '../../enums/node/NodeType';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { ControlFlowStorageNode } from '../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
+import {
+    ControlFlowStorageNode
+} from '../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeMetadata } from '../../node/NodeMetadata';
@@ -31,16 +39,6 @@ import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
 export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
-    /**
-     * @type {Map <string, ControlFlowReplacer>}
-     */
-    private static readonly controlFlowReplacersMap: Map <string, ControlFlowReplacer> = new Map([
-        [NodeType.BinaryExpression, ControlFlowReplacer.BinaryExpressionControlFlowReplacer],
-        [NodeType.CallExpression, ControlFlowReplacer.CallExpressionControlFlowReplacer],
-        [NodeType.LogicalExpression, ControlFlowReplacer.LogicalExpressionControlFlowReplacer],
-        [NodeType.Literal, ControlFlowReplacer.StringLiteralControlFlowReplacer]
-    ]);
-
     /**
      * @type {number}
      */
@@ -52,37 +50,47 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     private static readonly hostNodeSearchMaxDepth: number = 2;
 
     /**
-     * @type {Map<ESTree.Node, TControlFlowStorage>}
+     * @type {Map <string, ControlFlowReplacer>}
      */
-    private readonly controlFlowData: Map <ESTree.Node, TControlFlowStorage> = new Map();
+    protected readonly controlFlowReplacersMap: Map <string, ControlFlowReplacer> = new Map([
+        [NodeType.BinaryExpression, ControlFlowReplacer.BinaryExpressionControlFlowReplacer],
+        [NodeType.CallExpression, ControlFlowReplacer.CallExpressionControlFlowReplacer],
+        [NodeType.LogicalExpression, ControlFlowReplacer.LogicalExpressionControlFlowReplacer],
+        [NodeType.Literal, ControlFlowReplacer.StringLiteralControlFlowReplacer]
+    ]);
 
     /**
-     * @type {Set<ESTree.Function>}
+     * @type {WeakMap<TNodeWithStatements, IControlFlowStorage>}
      */
-    private readonly visitedFunctionNodes: Set<ESTree.Function> = new Set();
+    protected readonly controlFlowData: WeakMap <TNodeWithStatements, IControlFlowStorage> = new WeakMap();
 
     /**
      * @type {WeakMap<TNodeWithStatements, VariableDeclaration>}
      */
-    private readonly hostNodesWithControlFlowNode: WeakMap<TNodeWithStatements, ESTree.VariableDeclaration> = new WeakMap();
+    protected readonly hostNodesWithControlFlowNode: WeakMap<TNodeWithStatements, ESTree.VariableDeclaration> = new WeakMap();
 
     /**
      * @type {TControlFlowReplacerFactory}
      */
-    private readonly controlFlowReplacerFactory: TControlFlowReplacerFactory;
+    protected readonly controlFlowReplacerFactory: TControlFlowReplacerFactory;
 
     /**
      * @type {TControlFlowStorageFactory}
      */
-    private readonly controlFlowStorageFactory: TControlFlowStorageFactory;
+    protected controlFlowStorageFactory: TControlFlowStorageFactory;
 
     /**
      * @type {TControlFlowCustomNodeFactory}
      */
-    private readonly controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory;
+    protected readonly controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory;
+
+    /**
+     * @type {WeakSet<ESTree.Function>}
+     */
+    protected readonly visitedFunctionNodes: WeakSet<ESTree.Function> = new WeakSet();
 
     /**
-     * @param {TControlFlowStorageFactory} controlFlowStorageFactory
+     * @param {TControlFlowStorageFactoryCreator} controlFlowStorageFactoryCreator
      * @param {TControlFlowReplacerFactory} controlFlowReplacerFactory
      * @param {TControlFlowCustomNodeFactory} controlFlowCustomNodeFactory
      * @param {IRandomGenerator} randomGenerator
@@ -90,7 +98,7 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
      */
     public constructor (
         @inject(ServiceIdentifiers.Factory__TControlFlowStorage)
-            controlFlowStorageFactory: TControlFlowStorageFactory,
+            controlFlowStorageFactoryCreator: TControlFlowStorageFactoryCreator,
         @inject(ServiceIdentifiers.Factory__IControlFlowReplacer)
             controlFlowReplacerFactory: TControlFlowReplacerFactory,
         @inject(ServiceIdentifiers.Factory__IControlFlowCustomNode)
@@ -100,7 +108,7 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     ) {
         super(randomGenerator, options);
 
-        this.controlFlowStorageFactory = controlFlowStorageFactory;
+        this.controlFlowStorageFactory = controlFlowStorageFactoryCreator(ControlFlowStorage.FunctionControlFlowStorage);
         this.controlFlowReplacerFactory = controlFlowReplacerFactory;
         this.controlFlowCustomNodeFactory = controlFlowCustomNodeFactory;
     }
@@ -110,17 +118,18 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
      * @returns {IVisitor | null}
      */
     public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        if (!this.options.controlFlowFlattening) {
+            return null;
+        }
+
         switch (nodeTransformationStage) {
             case NodeTransformationStage.ControlFlowFlattening:
                 return {
-                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
-                        if (
-                            parentNode && (
-                                NodeGuards.isFunctionDeclarationNode(node) ||
-                                NodeGuards.isFunctionExpressionNode(node) ||
-                                NodeGuards.isArrowFunctionExpressionNode(node)
-                            )
-                        ) {
+                    leave: (
+                        node: ESTree.Node,
+                        parentNode: ESTree.Node | null
+                    ): ESTree.Node | estraverse.VisitorOption | void => {
+                        if (parentNode && NodeGuards.isFunctionNode(node)) {
                             return this.transformNode(node, parentNode);
                         }
                     }
@@ -133,7 +142,7 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {Function} functionNode
-     * @param {NodeGuards} parentNode
+     * @param {Node} parentNode
      * @returns {Function}
      */
     public transformNode (functionNode: ESTree.Function, parentNode: ESTree.Node): ESTree.Function {
@@ -144,77 +153,81 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         }
 
         const hostNode: TNodeWithStatements = this.getHostNode(functionNode.body);
-        const controlFlowStorage: TControlFlowStorage = this.getControlFlowStorage(hostNode);
+        const controlFlowStorage: IControlFlowStorage = this.getControlFlowStorage(hostNode);
 
-        this.controlFlowData.set(hostNode, controlFlowStorage);
-        this.transformFunctionBody(functionNode.body, controlFlowStorage);
+        this.transformFunctionBody(functionNode, controlFlowStorage);
 
         if (!controlFlowStorage.getLength()) {
             return functionNode;
         }
 
-        const controlFlowStorageCustomNode: ICustomNode<TInitialData<ControlFlowStorageNode>> =
-            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.ControlFlowStorageNode);
-
-        controlFlowStorageCustomNode.initialize(controlFlowStorage);
-
         const controlFlowStorageNode: ESTree.VariableDeclaration = this.getControlFlowStorageNode(controlFlowStorage);
 
-        NodeAppender.prepend(hostNode, [controlFlowStorageNode]);
-        this.hostNodesWithControlFlowNode.set(hostNode, controlFlowStorageNode);
-
-        NodeUtils.parentizeAst(functionNode);
+        this.appendControlFlowStorageNode(hostNode, controlFlowStorageNode);
 
         return functionNode;
     }
 
     /**
-     * @param {TNodeWithStatements} hostNode
-     * @returns {TControlFlowStorage}
+     * @param {BlockStatement} functionNode
+     * @param {IControlFlowStorage} controlFlowStorage
      */
-    private getControlFlowStorage (hostNode: TNodeWithStatements): TControlFlowStorage {
-        const controlFlowStorage: TControlFlowStorage = this.controlFlowStorageFactory();
-
-        if (this.controlFlowData.has(hostNode)) {
-            const existingControlFlowStorageNode: ESTree.VariableDeclaration | null =
-                this.hostNodesWithControlFlowNode.get(hostNode) ?? null;
-
-            if (existingControlFlowStorageNode) {
-                NodeAppender.remove(hostNode, existingControlFlowStorageNode);
-            }
-
-            const hostControlFlowStorage: TControlFlowStorage = <TControlFlowStorage>this.controlFlowData.get(hostNode);
-
-            controlFlowStorage.mergeWith(hostControlFlowStorage, true);
-        }
-
-        return controlFlowStorage;
+    protected transformFunctionBody (functionNode: ESTree.Function, controlFlowStorage: IControlFlowStorage): void {
+        estraverse.replace(functionNode.body, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): estraverse.VisitorOption | ESTree.Node =>
+                this.transformFunctionBodyNode(node, parentNode, functionNode, controlFlowStorage)
+        });
     }
 
     /**
-     * @param {TControlFlowStorage} controlFlowStorage
-     * @returns {VariableDeclaration}
+     * @param {Node} node
+     * @param {Node | null} parentNode
+     * @param {Function} functionNode
+     * @param {IControlFlowStorage} controlFlowStorage
+     * @returns {ESTraverse.VisitorOption | Node}
      */
-    private getControlFlowStorageNode (controlFlowStorage: TControlFlowStorage): ESTree.VariableDeclaration {
-        const controlFlowStorageCustomNode: ICustomNode<TInitialData<ControlFlowStorageNode>> =
-            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.ControlFlowStorageNode);
+    protected transformFunctionBodyNode (
+        node: ESTree.Node,
+        parentNode: ESTree.Node | null,
+        functionNode: ESTree.Function,
+        controlFlowStorage: IControlFlowStorage
+    ): estraverse.VisitorOption | ESTree.Node {
+        const shouldSkipTraverse = !parentNode
+            || NodeMetadata.isIgnoredNode(node)
+            || this.isVisitedFunctionNode(node);
+
+        if (shouldSkipTraverse) {
+            return estraverse.VisitorOption.Skip;
+        }
 
-        controlFlowStorageCustomNode.initialize(controlFlowStorage);
+        const controlFlowReplacerName: ControlFlowReplacer | null = this.controlFlowReplacersMap.get(node.type)
+            ?? null;
 
-        const controlFlowStorageNode: ESTree.Node = controlFlowStorageCustomNode.getNode()[0];
+        if (!controlFlowReplacerName) {
+            return node;
+        }
 
-        if (!NodeGuards.isVariableDeclarationNode(controlFlowStorageNode)) {
-            throw new Error('`controlFlowStorageNode` should contain `VariableDeclaration` node with control flow storage object');
+        if (!this.isAllowedTransformationByThreshold()) {
+            return node;
         }
 
-        return controlFlowStorageNode;
+        const replacedNode: ESTree.Node = this.controlFlowReplacerFactory(controlFlowReplacerName)
+            .replace(
+                node,
+                parentNode,
+                controlFlowStorage
+            );
+
+        NodeUtils.parentizeNode(replacedNode, parentNode);
+
+        return replacedNode;
     }
 
     /**
      * @param {BlockStatement} functionNodeBody
      * @returns {TNodeWithStatements}
      */
-    private getHostNode (functionNodeBody: ESTree.BlockStatement): TNodeWithStatements {
+    protected getHostNode (functionNodeBody: ESTree.BlockStatement): TNodeWithStatements {
         const blockScopesOfNode: TNodeWithStatements[] = NodeStatementUtils.getParentNodesWithStatements(functionNodeBody);
 
         if (blockScopesOfNode.length === 1) {
@@ -235,52 +248,77 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {NodeGuards} node
-     * @returns {boolean}
+     * @param {TNodeWithStatements} hostNode
+     * @returns {TControlFlowStorage}
      */
-    private isVisitedFunctionNode (node: ESTree.Node): boolean {
-        return (
-            NodeGuards.isFunctionDeclarationNode(node) ||
-            NodeGuards.isFunctionExpressionNode(node) ||
-            NodeGuards.isArrowFunctionExpressionNode(node)
-        ) && this.visitedFunctionNodes.has(node);
+    protected getControlFlowStorage (hostNode: TNodeWithStatements): IControlFlowStorage {
+        let controlFlowStorage: IControlFlowStorage;
+
+        const hostControlFlowStorage: IControlFlowStorage | null = this.controlFlowData.get(hostNode) ?? null;
+
+        if (!hostControlFlowStorage) {
+            controlFlowStorage = this.controlFlowStorageFactory();
+        } else {
+            const existingControlFlowStorageNode: ESTree.VariableDeclaration | null =
+                this.hostNodesWithControlFlowNode.get(hostNode) ?? null;
+
+            if (existingControlFlowStorageNode) {
+                NodeAppender.remove(hostNode, existingControlFlowStorageNode);
+            }
+
+            controlFlowStorage = hostControlFlowStorage;
+        }
+
+        this.controlFlowData.set(hostNode, controlFlowStorage);
+
+        return controlFlowStorage;
     }
 
     /**
-     * @param {BlockStatement} functionNodeBody
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
+     * @returns {VariableDeclaration}
      */
-    private transformFunctionBody (functionNodeBody: ESTree.BlockStatement, controlFlowStorage: TControlFlowStorage): void {
-        estraverse.replace(functionNodeBody, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): estraverse.VisitorOption | ESTree.Node => {
-                if (NodeMetadata.isIgnoredNode(node)) {
-                    return estraverse.VisitorOption.Skip;
-                }
+    protected getControlFlowStorageNode (controlFlowStorage: IControlFlowStorage): ESTree.VariableDeclaration {
+        const controlFlowStorageCustomNode: ICustomNode<TInitialData<ControlFlowStorageNode>> =
+            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.ControlFlowStorageNode);
 
-                if (this.isVisitedFunctionNode(node) || !parentNode) {
-                    return estraverse.VisitorOption.Skip;
-                }
+        controlFlowStorageCustomNode.initialize(controlFlowStorage);
 
-                if (!FunctionControlFlowTransformer.controlFlowReplacersMap.has(node.type)) {
-                    return node;
-                }
+        const controlFlowStorageNode: ESTree.Node = controlFlowStorageCustomNode.getNode()[0];
 
-                if (this.randomGenerator.getMathRandom() > this.options.controlFlowFlatteningThreshold) {
-                    return node;
-                }
+        if (!NodeGuards.isVariableDeclarationNode(controlFlowStorageNode)) {
+            throw new Error('`controlFlowStorageNode` should contain `VariableDeclaration` node with control flow storage object');
+        }
 
-                const controlFlowReplacerName: ControlFlowReplacer = <ControlFlowReplacer>FunctionControlFlowTransformer
-                    .controlFlowReplacersMap.get(node.type);
+        return controlFlowStorageNode;
+    }
 
-                if (controlFlowReplacerName === undefined) {
-                    return node;
-                }
+    /**
+     * @param {TNodeWithStatements} hostNode
+     * @param {VariableDeclaration} controlFlowStorageNode
+     */
+    protected appendControlFlowStorageNode (
+        hostNode: TNodeWithStatements,
+        controlFlowStorageNode: ESTree.VariableDeclaration
+    ): void {
+        NodeUtils.parentizeAst(controlFlowStorageNode);
+        NodeAppender.prepend(hostNode, [controlFlowStorageNode]);
 
-                return {
-                    ...this.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage),
-                    parentNode
-                };
-            }
-        });
+        this.hostNodesWithControlFlowNode.set(hostNode, controlFlowStorageNode);
+    }
+
+    /**
+     * @param {NodeGuards} node
+     * @returns {boolean}
+     */
+    protected isVisitedFunctionNode (node: ESTree.Node): boolean {
+        return NodeGuards.isFunctionNode(node) && this.visitedFunctionNodes.has(node);
+    }
+
+    /**
+     * @returns {boolean}
+     */
+    protected isAllowedTransformationByThreshold (): boolean {
+        return this.randomGenerator.getMathRandom() <= this.options.controlFlowFlatteningThreshold;
     }
 }

+ 148 - 0
src/node-transformers/control-flow-transformers/StringArrayControlFlowTransformer.ts

@@ -0,0 +1,148 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from '@javascript-obfuscator/estraverse';
+import * as ESTree from 'estree';
+
+import { TControlFlowCustomNodeFactory } from '../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
+import { TControlFlowReplacerFactory } from '../../types/container/node-transformers/TControlFlowReplacerFactory';
+import {
+    TControlFlowStorageFactoryCreator
+} from '../../types/container/node-transformers/TControlFlowStorageFactoryCreator';
+import { TNodeWithStatements } from '../../types/node/TNodeWithStatements';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { ControlFlowReplacer } from '../../enums/node-transformers/control-flow-transformers/control-flow-replacers/ControlFlowReplacer';
+import { ControlFlowStorage } from '../../enums/storages/ControlFlowStorage';
+import { NodeType } from '../../enums/node/NodeType';
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+
+import { FunctionControlFlowTransformer } from './FunctionControlFlowTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { IControlFlowStorage } from '../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
+
+@injectable()
+export class StringArrayControlFlowTransformer extends FunctionControlFlowTransformer {
+    /**
+     * @type {NodeTransformer[]}
+     */
+    public override readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.StringArrayTransformer,
+        NodeTransformer.StringArrayRotateFunctionTransformer,
+        NodeTransformer.StringArrayScopeCallsWrapperTransformer
+    ];
+
+    /**
+     * @type {Map <string, ControlFlowReplacer>}
+     */
+    protected override readonly controlFlowReplacersMap: Map <string, ControlFlowReplacer> = new Map([
+        [NodeType.Literal, ControlFlowReplacer.StringArrayCallControlFlowReplacer]
+    ]);
+
+    /**
+     * @type {WeakSet<VariableDeclaration>}
+     */
+    protected readonly controlFlowStorageNodes: WeakSet<ESTree.VariableDeclaration> = new WeakSet();
+
+    /**
+     * @param {TControlFlowStorageFactoryCreator} controlFlowStorageFactoryCreator
+     * @param {TControlFlowReplacerFactory} controlFlowReplacerFactory
+     * @param {TControlFlowCustomNodeFactory} controlFlowCustomNodeFactory
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.Factory__TControlFlowStorage)
+            controlFlowStorageFactoryCreator: TControlFlowStorageFactoryCreator,
+        @inject(ServiceIdentifiers.Factory__IControlFlowReplacer)
+            controlFlowReplacerFactory: TControlFlowReplacerFactory,
+        @inject(ServiceIdentifiers.Factory__IControlFlowCustomNode)
+            controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(
+            controlFlowStorageFactoryCreator,
+            controlFlowReplacerFactory,
+            controlFlowCustomNodeFactory,
+            randomGenerator,
+            options
+        );
+
+        this.controlFlowStorageFactory = controlFlowStorageFactoryCreator(ControlFlowStorage.StringControlFlowStorage);
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public override getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        if (!this.options.stringArrayCallsTransform) {
+            return null;
+        }
+
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.StringArray:
+                return {
+                    leave: (
+                        node: ESTree.Node,
+                        parentNode: ESTree.Node | null
+                    ): ESTree.Node | estraverse.VisitorOption | void => {
+                        if (parentNode && NodeGuards.isFunctionNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {Node} node
+     * @param {Node | null} parentNode
+     * @param {Function} functionNode
+     * @param {IControlFlowStorage} controlFlowStorage
+     * @returns {ESTraverse.VisitorOption | Node}
+     */
+    protected override transformFunctionBodyNode (
+        node: ESTree.Node,
+        parentNode: ESTree.Node | null,
+        functionNode: ESTree.Function,
+        controlFlowStorage: IControlFlowStorage
+    ): estraverse.VisitorOption | ESTree.Node {
+        const isControlFlowStorageNode = NodeGuards.isVariableDeclarationNode(node)
+            && this.controlFlowStorageNodes.has(node);
+
+        if (isControlFlowStorageNode) {
+            return estraverse.VisitorOption.Break;
+        }
+
+        return super.transformFunctionBodyNode(node, parentNode, functionNode, controlFlowStorage);
+    }
+
+    /**
+     * @param {TNodeWithStatements} hostNode
+     * @param {VariableDeclaration} controlFlowStorageNode
+     */
+    protected override appendControlFlowStorageNode (
+        hostNode: TNodeWithStatements,
+        controlFlowStorageNode: ESTree.VariableDeclaration
+    ): void {
+        super.appendControlFlowStorageNode(hostNode, controlFlowStorageNode);
+
+        this.controlFlowStorageNodes.add(controlFlowStorageNode);
+    }
+
+    /**
+     * @returns {boolean}
+     */
+    protected override isAllowedTransformationByThreshold (): boolean {
+        return this.randomGenerator.getMathRandom() <= this.options.stringArrayCallsTransformThreshold;
+    }
+}

+ 7 - 7
src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts

@@ -4,10 +4,10 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 
 import { IControlFlowReplacer } from '../../../interfaces/node-transformers/control-flow-transformers/IControlFlowReplacer';
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IIdentifierNamesGenerator } from '../../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -64,10 +64,10 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace
      * Generates storage key with length of 5 characters to prevent collisions and to guarantee that
      * these keys will be added to the string array storage
      *
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {string}
      */
-    public generateStorageKey (controlFlowStorage: TControlFlowStorage): string {
+    public generateStorageKey (controlFlowStorage: IControlFlowStorage): string {
         const key: string = this.randomGenerator.getRandomString(5);
 
         if (controlFlowStorage.has(key)) {
@@ -79,14 +79,14 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace
 
     /**
      * @param {ICustomNode} customNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @param {string} replacerId
      * @param {number} usingExistingIdentifierChance
      * @returns {string}
      */
     protected insertCustomNodeToControlFlowStorage (
         customNode: ICustomNode,
-        controlFlowStorage: TControlFlowStorage,
+        controlFlowStorage: IControlFlowStorage,
         replacerId: string,
         usingExistingIdentifierChance: number
     ): string {
@@ -115,12 +115,12 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace
      * @param {Node} node
      * @param {Node} parentNode
      * @param {TNodeWithLexicalScope} controlFlowStorageLexicalScopeNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {Node}
      */
     public abstract replace (
         node: ESTree.Node,
         parentNode: ESTree.Node,
-        controlFlowStorage: TControlFlowStorage
+        controlFlowStorage: IControlFlowStorage
     ): ESTree.Node;
 }

+ 3 - 3
src/node-transformers/control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts

@@ -4,10 +4,10 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TInitialData } from '../../../types/TInitialData';
 
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
@@ -50,13 +50,13 @@ export class BinaryExpressionControlFlowReplacer extends ExpressionWithOperatorC
      * @param {BinaryExpression} binaryExpressionNode
      * @param {Node} parentNode
      * @param {TNodeWithLexicalScope} controlFlowStorageLexicalScopeNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {Node}
      */
     public replace (
         binaryExpressionNode: ESTree.BinaryExpression,
         parentNode: ESTree.Node,
-        controlFlowStorage: TControlFlowStorage
+        controlFlowStorage: IControlFlowStorage
     ): ESTree.Node {
         const operator: ESTree.BinaryOperator = binaryExpressionNode.operator;
         const binaryExpressionFunctionCustomNode: ICustomNode<TInitialData<BinaryExpressionFunctionNode>> =

+ 3 - 3
src/node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer.ts

@@ -4,11 +4,11 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TInitialData } from '../../../types/TInitialData';
 import { TStatement } from '../../../types/node/TStatement';
 
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
@@ -52,13 +52,13 @@ export class CallExpressionControlFlowReplacer extends AbstractControlFlowReplac
     /**
      * @param {CallExpression} callExpressionNode
      * @param {Node} parentNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {Node}
      */
     public replace (
         callExpressionNode: ESTree.CallExpression,
         parentNode: ESTree.Node,
-        controlFlowStorage: TControlFlowStorage
+        controlFlowStorage: IControlFlowStorage
     ): ESTree.Node {
         const callee: ESTree.Expression = <ESTree.Expression>callExpressionNode.callee;
 

+ 3 - 3
src/node-transformers/control-flow-transformers/control-flow-replacers/LogicalExpressionControlFlowReplacer.ts

@@ -4,10 +4,10 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TInitialData } from '../../../types/TInitialData';
 
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
@@ -51,13 +51,13 @@ export class LogicalExpressionControlFlowReplacer extends ExpressionWithOperator
     /**
      * @param {LogicalExpression} logicalExpressionNode
      * @param {Node} parentNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {Node}
      */
     public replace (
         logicalExpressionNode: ESTree.LogicalExpression,
         parentNode: ESTree.Node,
-        controlFlowStorage: TControlFlowStorage
+        controlFlowStorage: IControlFlowStorage
     ): ESTree.Node {
         if (this.checkForProhibitedExpressions(logicalExpressionNode.left, logicalExpressionNode.right)) {
             return logicalExpressionNode;

+ 132 - 0
src/node-transformers/control-flow-transformers/control-flow-replacers/StringArrayCallControlFlowReplacer.ts

@@ -0,0 +1,132 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { TControlFlowCustomNodeFactory } from '../../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
+import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
+import { TInitialData } from '../../../types/TInitialData';
+import { TStatement } from '../../../types/node/TStatement';
+
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
+
+import { ControlFlowCustomNode } from '../../../enums/custom-nodes/ControlFlowCustomNode';
+
+import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
+import { NodeGuards } from '../../../node/NodeGuards';
+import { NodeLiteralUtils } from '../../../node/NodeLiteralUtils';
+import { NodeMetadata } from '../../../node/NodeMetadata';
+import {
+    StringLiteralControlFlowStorageCallNode
+} from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
+import { LiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/LiteralNode';
+
+@injectable()
+export class StringArrayCallControlFlowReplacer extends AbstractControlFlowReplacer {
+    /**
+     * @type {number}
+     */
+    private static readonly usingExistingIdentifierChance: number = 0.5;
+
+    /**
+     * @param {TControlFlowCustomNodeFactory} controlFlowCustomNodeFactory
+     * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.Factory__IControlFlowCustomNode)
+            controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory,
+        @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
+            identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(
+            controlFlowCustomNodeFactory,
+            identifierNamesGeneratorFactory,
+            randomGenerator,
+            options
+        );
+    }
+
+    /**
+     * @param {Literal} literalNode
+     * @param {Node} parentNode
+     * @param {IControlFlowStorage} controlFlowStorage
+     * @returns {Node}
+     */
+    public override replace (
+        literalNode: ESTree.Literal,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IControlFlowStorage
+    ): ESTree.Node {
+        const isStringArrayCallLiteralNode = NodeMetadata.isStringArrayCallLiteralNode(literalNode)
+            && (
+                NodeLiteralUtils.isNumberLiteralNode(literalNode)
+                || NodeLiteralUtils.isStringLiteralNode(literalNode)
+            );
+
+        if (!isStringArrayCallLiteralNode) {
+            return literalNode;
+        }
+
+        const replacerId: string = String(literalNode.value);
+        const literalCustomNode: ICustomNode<TInitialData<LiteralNode>> =
+            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.LiteralNode);
+
+        literalCustomNode.initialize(literalNode);
+
+        const storageKey: string = this.insertCustomNodeToControlFlowStorage(
+            literalCustomNode,
+            controlFlowStorage,
+            replacerId,
+            StringArrayCallControlFlowReplacer.usingExistingIdentifierChance
+        );
+
+        return this.getControlFlowStorageCallNode(controlFlowStorage.getStorageId(), storageKey);
+    }
+
+    /**
+     * Generates storage key based on a current control flow storage identifier
+     *
+     * @param {IControlFlowStorage} controlFlowStorage
+     * @returns {string}
+     */
+    public override generateStorageKey (controlFlowStorage: IControlFlowStorage): string {
+        const key: string = this.identifierNamesGenerator
+            .generateForLabel(controlFlowStorage.getStorageId());
+
+        if (controlFlowStorage.has(key)) {
+            return this.generateStorageKey(controlFlowStorage);
+        }
+
+        return key;
+    }
+
+    /**
+     * @param {string} controlFlowStorageId
+     * @param {string} storageKey
+     * @returns {NodeGuards}
+     */
+    protected getControlFlowStorageCallNode (
+        controlFlowStorageId: string,
+        storageKey: string
+    ): ESTree.Node {
+        const controlFlowStorageCallCustomNode: ICustomNode<TInitialData<StringLiteralControlFlowStorageCallNode>> =
+            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.StringLiteralControlFlowStorageCallNode);
+
+        controlFlowStorageCallCustomNode.initialize(controlFlowStorageId, storageKey);
+
+        const statementNode: TStatement = controlFlowStorageCallCustomNode.getNode()[0];
+
+        if (!statementNode || !NodeGuards.isExpressionStatementNode(statementNode)) {
+            throw new Error('`controlFlowStorageCallCustomNode.getNode()[0]` should returns array with `ExpressionStatement` node');
+        }
+
+        return statementNode.expression;
+    }
+}

+ 8 - 8
src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts

@@ -4,11 +4,11 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
-import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TInitialData } from '../../../types/TInitialData';
 import { TStatement } from '../../../types/node/TStatement';
 
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
@@ -19,7 +19,7 @@ import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
 import { NodeGuards } from '../../../node/NodeGuards';
 import { NodeLiteralUtils } from '../../../node/NodeLiteralUtils';
 import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
-import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
+import { LiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/LiteralNode';
 
 @injectable()
 export class StringLiteralControlFlowReplacer extends AbstractControlFlowReplacer {
@@ -53,13 +53,13 @@ export class StringLiteralControlFlowReplacer extends AbstractControlFlowReplace
     /**
      * @param {Literal} literalNode
      * @param {Node} parentNode
-     * @param {TControlFlowStorage} controlFlowStorage
+     * @param {IControlFlowStorage} controlFlowStorage
      * @returns {Node}
      */
     public replace (
         literalNode: ESTree.Literal,
         parentNode: ESTree.Node,
-        controlFlowStorage: TControlFlowStorage
+        controlFlowStorage: IControlFlowStorage
     ): ESTree.Node {
         if (NodeGuards.isPropertyNode(parentNode) && parentNode.key === literalNode) {
             return literalNode;
@@ -70,13 +70,13 @@ export class StringLiteralControlFlowReplacer extends AbstractControlFlowReplace
         }
 
         const replacerId: string = String(literalNode.value);
-        const literalFunctionCustomNode: ICustomNode<TInitialData<StringLiteralNode>> =
-            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.StringLiteralNode);
+        const literalCustomNode: ICustomNode<TInitialData<LiteralNode>> =
+            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.LiteralNode);
 
-        literalFunctionCustomNode.initialize(literalNode.value);
+        literalCustomNode.initialize(literalNode);
 
         const storageKey: string = this.insertCustomNodeToControlFlowStorage(
-            literalFunctionCustomNode,
+            literalCustomNode,
             controlFlowStorage,
             replacerId,
             StringLiteralControlFlowReplacer.usingExistingIdentifierChance

+ 1 - 1
src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer.ts

@@ -197,7 +197,7 @@ export class StringArrayRotateFunctionTransformer extends AbstractNodeTransforme
                     !NodeGuards.isLiteralNode(node)
                     || !NodeLiteralUtils.isStringLiteralNode(node)
                 ) {
-                   return;
+                    return;
                 }
 
                 // force add item data for string literal nodes of comparison expressions

+ 14 - 0
src/options/Options.ts

@@ -328,6 +328,20 @@ export class Options implements IOptions {
     @IsBoolean()
     public readonly stringArray!: boolean;
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly stringArrayCallsTransform!: boolean;
+
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    @Min(0)
+    @Max(1)
+    public readonly stringArrayCallsTransformThreshold!: number;
+
     /**
      * @type {TStringArrayEncoding[]}
      */

+ 2 - 0
src/options/OptionsNormalizer.ts

@@ -18,6 +18,7 @@ import { SourceMapBaseUrlRule } from './normalizer-rules/SourceMapBaseUrlRule';
 import { SourceMapFileNameRule } from './normalizer-rules/SourceMapFileNameRule';
 import { SplitStringsChunkLengthRule } from './normalizer-rules/SplitStringsChunkLengthRule';
 import { StringArrayRule } from './normalizer-rules/StringArrayRule';
+import { StringArrayCallsTransformRule } from './normalizer-rules/StringArrayCallsTransform';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
 import { StringArrayWrappersChainedCallsRule } from './normalizer-rules/StringArrayWappersChainedCalls';
 
@@ -40,6 +41,7 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         SourceMapFileNameRule,
         SplitStringsChunkLengthRule,
         StringArrayRule,
+        StringArrayCallsTransformRule,
         StringArrayEncodingRule,
         StringArrayWrappersChainedCallsRule,
     ];

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

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

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

@@ -13,6 +13,8 @@ export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOpt
         options = {
             ...options,
             stringArray: false,
+            stringArrayCallsTransform: false,
+            stringArrayCallsTransformThreshold: 0,
             stringArrayEncoding: [
                 StringArrayEncoding.None
             ],

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

@@ -51,6 +51,8 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     splitStrings: false,
     splitStringsChunkLength: 10,
     stringArray: true,
+    stringArrayCallsTransform: true,
+    stringArrayCallsTransformThreshold: 0.5,
     stringArrayEncoding: [
         StringArrayEncoding.None
     ],

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

@@ -13,6 +13,7 @@ export const HIGH_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     debugProtectionInterval: true,
     optionsPreset: OptionsPreset.HighObfuscation,
     splitStringsChunkLength: 5,
+    stringArrayCallsTransformThreshold: 1,
     stringArrayEncoding: [
         StringArrayEncoding.Rc4
     ],

+ 4 - 2
src/options/presets/LowObfuscation.ts

@@ -10,6 +10,8 @@ export const LOW_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     optionsPreset: OptionsPreset.LowObfuscation,
     stringArrayRotate: true,
     selfDefending: true,
-    stringArrayShuffle: true,
-    simplify: true
+    simplify: true,
+    stringArrayCallsTransform: false,
+    stringArrayCallsTransformThreshold: 0,
+    stringArrayShuffle: true
 });

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

@@ -14,6 +14,7 @@ export const MEDIUM_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     optionsPreset: OptionsPreset.MediumObfuscation,
     splitStrings: true,
     splitStringsChunkLength: 10,
+    stringArrayCallsTransformThreshold: 0.75,
     stringArrayEncoding: [
         StringArrayEncoding.Base64
     ],

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

@@ -47,6 +47,8 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     splitStrings: false,
     splitStringsChunkLength: 0,
     stringArray: false,
+    stringArrayCallsTransform: false,
+    stringArrayCallsTransformThreshold: 0,
     stringArrayEncoding: [
         StringArrayEncoding.None
     ],

+ 38 - 0
src/storages/control-flow-transformers/FunctionControlFlowStorage.ts

@@ -0,0 +1,38 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
+
+import { IControlFlowStorage } from '../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import {
+    IIdentifierNamesGenerator
+} from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { MapStorage } from '../MapStorage';
+
+@injectable()
+export class FunctionControlFlowStorage extends MapStorage <string, ICustomNode> implements IControlFlowStorage {
+    /**
+     * @type {IIdentifierNamesGenerator}
+     */
+    protected readonly identifierNamesGenerator: IIdentifierNamesGenerator;
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
+            identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+    ) {
+        super(randomGenerator, options);
+
+        this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
+    }
+}

+ 33 - 0
src/storages/control-flow-transformers/StringControlFlowStorage.ts

@@ -0,0 +1,33 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { FunctionControlFlowStorage } from './FunctionControlFlowStorage';
+
+@injectable()
+export class StringControlFlowStorage extends FunctionControlFlowStorage {
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     */
+    public constructor (
+
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
+            identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+    ) {
+        super(randomGenerator, options, identifierNamesGeneratorFactory);
+    }
+
+    public override initialize (): void {
+        super.initialize();
+
+        this.storageId = this.identifierNamesGenerator.generateForGlobalScope();
+    }
+}

+ 0 - 22
src/storages/custom-nodes/ControlFlowStorage.ts

@@ -1,22 +0,0 @@
-import { inject, injectable, } from 'inversify';
-import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
-
-import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../../interfaces/options/IOptions';
-import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
-
-import { MapStorage } from '../MapStorage';
-
-@injectable()
-export class ControlFlowStorage extends MapStorage <string, ICustomNode> {
-    /**
-     * @param {IRandomGenerator} randomGenerator
-     * @param {IOptions} options
-     */
-    public constructor (
-        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions
-    ) {
-        super(randomGenerator, options);
-    }
-}

+ 2 - 2
src/types/container/node-transformers/TControlFlowStorageFactory.ts

@@ -1,3 +1,3 @@
-import { TControlFlowStorage } from '../../storages/TControlFlowStorage';
+import { IControlFlowStorage } from '../../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
 
-export type TControlFlowStorageFactory = () => TControlFlowStorage;
+export type TControlFlowStorageFactory = () => IControlFlowStorage;

+ 5 - 0
src/types/container/node-transformers/TControlFlowStorageFactoryCreator.ts

@@ -0,0 +1,5 @@
+import { TControlFlowStorageFactory } from './TControlFlowStorageFactory';
+
+import { ControlFlowStorage } from '../../../enums/storages/ControlFlowStorage';
+
+export type TControlFlowStorageFactoryCreator = (controlFlowStorageName: ControlFlowStorage) => TControlFlowStorageFactory;

+ 0 - 4
src/types/storages/TControlFlowStorage.ts

@@ -1,4 +0,0 @@
-import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
-import { IMapStorage } from '../../interfaces/storages/IMapStorage';
-
-export type TControlFlowStorage = IMapStorage <string, ICustomNode>;

+ 32 - 0
src/utils/SetUtils.ts

@@ -0,0 +1,32 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
+
+import { IArrayUtils } from '../interfaces/utils/IArrayUtils';
+import { ISetUtils } from '../interfaces/utils/ISetUtils';
+
+@injectable()
+export class SetUtils implements ISetUtils {
+    /**
+     * @type {IArrayUtils}
+     */
+    private readonly arrayUtils: IArrayUtils;
+
+    /**
+     * @param {IArrayUtils} arrayUtils
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils
+    ) {
+        this.arrayUtils = arrayUtils;
+    }
+
+    /**
+     * @param {Set<T>} set
+     * @returns {T | undefined}
+     */
+    public getLastElement <T> (set: Set<T>): T | undefined {
+        const array = [...set];
+
+        return this.arrayUtils.getLastElement(array);
+    }
+}

+ 36 - 12
test/dev/dev.ts

@@ -1,22 +1,46 @@
 'use strict';
 
+import { StringArrayWrappersType } from '../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
+
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
-    const code: string = `
-        class Foo {}
-    `;
 
-    let obfuscationResult = JavaScriptObfuscator.obfuscate(
-        code,
+    let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+        `
+            function foo () {
+                var bar = 'bar';
+                
+                function baz () {
+                    var baz = 'baz';
+                    
+                    return baz;
+                }
+                
+                return bar + baz();
+            }
+            
+            console.log(foo());
+        `,
         {
-            compact: false
+            identifierNamesGenerator: 'mangled',
+            compact: false,
+            controlFlowFlattening: true,
+            controlFlowFlatteningThreshold: 1,
+            simplify: false,
+            stringArray: true,
+            stringArrayIndexesType: [
+                'hexadecimal-number',
+                'hexadecimal-numeric-string'
+            ],
+            stringArrayThreshold: 1,
+            stringArrayCallsTransform: true,
+            stringArrayCallsTransformThreshold: 1,
+            rotateStringArray: true,
+            stringArrayWrappersType: StringArrayWrappersType.Function,
+            transformObjectKeys: true
         }
-    );
-
-    let obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-    let identifierNamesCache = obfuscationResult.getIdentifierNamesCache();
+    ).getObfuscatedCode();
 
     console.log(obfuscatedCode);
     console.log(eval(obfuscatedCode));
-    console.log(identifierNamesCache);
-})();
+})();

+ 2 - 1
test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec.ts

@@ -41,7 +41,8 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                             identifiersPrefix: 'a',
                             transformObjectKeys: true,
                             stringArray: true,
-                            stringArrayThreshold: 1
+                            stringArrayThreshold: 1,
+                            seed: 1
                         }
                     ).getObfuscatedCode();
 

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

@@ -1310,6 +1310,8 @@ describe('JavaScriptObfuscator', () => {
                         renameProperties: true,
                         stringArrayRotate: true,
                         stringArray: true,
+                        stringArrayCallsTransform: true,
+                        stringArrayCallsTransformThreshold: 1,
                         stringArrayEncoding: [
                             StringArrayEncoding.Base64,
                             StringArrayEncoding.Rc4

+ 11 - 3
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts

@@ -10,8 +10,12 @@ describe('BinaryExpressionControlFlowReplacer', function () {
     this.timeout(100000);
 
     describe('replace', () => {
+        const variableMatch: string = '_0x([a-f0-9]){4,6}';
+
         describe('Variant #1 - single binary expression', () => {
-            const controlFlowStorageCallRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{5}'\]\(0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `var ${variableMatch} *= *${variableMatch}\\['\\w{5}'\\]\\(0x1, *0x2\\);`
+            );
 
             let obfuscatedCode: string;
 
@@ -40,8 +44,12 @@ describe('BinaryExpressionControlFlowReplacer', function () {
             const samplesCount: number = 1000;
             const delta: number = 0.1;
 
-            const controlFlowStorageCallRegExp1: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{5}'\])\(0x1, *0x2\);/;
-            const controlFlowStorageCallRegExp2: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{5}'\])\(0x2, *0x3\);/;
+            const controlFlowStorageCallRegExp1: RegExp = new RegExp(
+                `var _0x(?:[a-f0-9]){4,6} *= *(${variableMatch}\\['\\w{5}'\\])\\(0x1, *0x2\\);`
+            );
+            const controlFlowStorageCallRegExp2: RegExp = new RegExp(
+                `var _0x(?:[a-f0-9]){4,6} *= *(${variableMatch}\\['\\w{5}'\\])\\(0x2, *0x3\\);`
+            );
 
             let matchErrorsCount: number = 0,
                 usingExistingIdentifierChance: number;

+ 29 - 13
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts

@@ -10,8 +10,12 @@ describe('CallExpressionControlFlowReplacer', function () {
     this.timeout(100000);
 
     describe('replace', () => {
+        const variableMatch: string = '_0x([a-f0-9]){4,6}';
+
         describe('Variant #1 - single call expression', () => {
-            const controlFlowStorageCallRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{5}'\]\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `var ${variableMatch} *= *${variableMatch}\\['\\w{5}'\\]\\(${variableMatch}, *0x1, *0x2\\);`
+            );
 
             let obfuscatedCode: string;
 
@@ -40,8 +44,12 @@ describe('CallExpressionControlFlowReplacer', function () {
             const samplesCount: number = 1000;
             const delta: number = 0.1;
 
-            const controlFlowStorageCallRegExp1: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{5}'\])\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
-            const controlFlowStorageCallRegExp2: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{5}'\])\(_0x([a-f0-9]){4,6}, *0x2, *0x3\);/;
+            const controlFlowStorageCallRegExp1: RegExp = new RegExp(
+                `var _0x(?:[a-f0-9]){4,6} *= *(${variableMatch}\\['\\w{5}'\\])\\(${variableMatch}, *0x1, *0x2\\);`
+            );
+            const controlFlowStorageCallRegExp2: RegExp = new RegExp(
+                `var _0x(?:[a-f0-9]){4,6} *= *(${variableMatch}\\['\\w{5}'\\])\\(${variableMatch}, *0x2, *0x3\\);`
+            );
 
             let matchErrorsCount: number = 0,
                 usingExistingIdentifierChance: number;
@@ -96,7 +104,9 @@ describe('CallExpressionControlFlowReplacer', function () {
         });
 
         describe('Variant #3 - call expression callee is member expression node', () => {
-            const regExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['sum'\]\(0x1, *0x2\);/;
+            const regExp: RegExp = new RegExp(
+                `var ${variableMatch} *= *${variableMatch}\\['sum'\\]\\(0x1, *0x2\\);`
+            );
 
             let obfuscatedCode: string;
 
@@ -119,10 +129,12 @@ describe('CallExpressionControlFlowReplacer', function () {
         });
 
         describe('Variant #4 - rest as start call argument', () => {
-            const controlFlowStorageCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\['\w{5}']\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `${variableMatch}\\['\\w{5}']\\(${variableMatch}, *\\.\\.\\.${variableMatch}, *${variableMatch}\\);`
+            );
             const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` +
-                `'\\w{5}' *: *function *\\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\) *\\{` +
-                    `return *_0x([a-f0-9]){4,6}\\(\.\.\._0x([a-f0-9]){4,6}\\);` +
+                `'\\w{5}' *: *function *\\(${variableMatch}, *\.\.\.${variableMatch}\\) *\\{` +
+                    `return *${variableMatch}\\(\.\.\.${variableMatch}\\);` +
                 `\\}` +
             ``);
 
@@ -151,10 +163,12 @@ describe('CallExpressionControlFlowReplacer', function () {
         });
 
         describe('Variant #5 - rest as middle call argument', () => {
-            const controlFlowStorageCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\['\w{5}']\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `${variableMatch}\\['\\w{5}']\\(${variableMatch}, *${variableMatch}, *\\.\\.\\.${variableMatch}, *${variableMatch}\\);`
+            );
             const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` +
-                `'\\w{5}' *: *function *\\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\) *\\{` +
-                    `return *_0x([a-f0-9]){4,6}\\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\);` +
+                `'\\w{5}' *: *function *\\(${variableMatch}, *${variableMatch}, *\.\.\.${variableMatch}\\) *\\{` +
+                    `return *${variableMatch}\\(${variableMatch}, *\.\.\.${variableMatch}\\);` +
                 `\\}` +
             ``);
 
@@ -183,10 +197,12 @@ describe('CallExpressionControlFlowReplacer', function () {
         });
 
         describe('Variant #6 - rest as last call argument', () => {
-            const controlFlowStorageCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\['\w{5}']\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `${variableMatch}\\['\\w{5}']\\(${variableMatch}, *${variableMatch}, *\\.\\.\\.${variableMatch}\\);`
+            );
             const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` +
-                `'\\w{5}' *: *function *\\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\) *\\{` +
-                   `return *_0x([a-f0-9]){4,6}\\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\);` +
+                `'\\w{5}' *: *function *\\(${variableMatch}, *${variableMatch}, *\.\.\.${variableMatch}\\) *\\{` +
+                   `return *${variableMatch}\\(${variableMatch}, *\.\.\.${variableMatch}\\);` +
                 `\\}` +
             ``);
 

+ 17 - 5
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts

@@ -10,8 +10,12 @@ describe('LogicalExpressionControlFlowReplacer', function () {
     this.timeout(100000);
 
     describe('replace', () => {
+        const variableMatch: string = '_0x([a-f0-9]){4,6}';
+
         describe('Variant #1 - single logical expression', () => {
-            const controlFlowStorageCallRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{5}'\]\(!!\[\], *!\[\]\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `var ${variableMatch} *= *${variableMatch}\\['\\w{5}'\\]\\(!!\\[\\], *!\\[\\]\\);`
+            );
 
             let obfuscatedCode: string;
 
@@ -40,8 +44,12 @@ describe('LogicalExpressionControlFlowReplacer', function () {
             const samplesCount: number = 1000;
             const delta: number = 0.1;
 
-            const controlFlowStorageCallRegExp1: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{5}'\])\(!!\[\], *!\[\]\);/;
-            const controlFlowStorageCallRegExp2: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{5}'\])\(!\[\], *!!\[\]\);/;
+            const controlFlowStorageCallRegExp1: RegExp = new RegExp(
+                `var _0x(?:[a-f0-9]){4,6} *= *(${variableMatch}\\['\\w{5}'\\])\\(!!\\[\\], *!\\[\\]\\);`
+            );
+            const controlFlowStorageCallRegExp2: RegExp = new RegExp(
+                `var _0x(?:[a-f0-9]){4,6} *= *(${variableMatch}\\['\\w{5}'\\])\\(!\\[\\], *!!\\[\\]\\);`
+            );
 
             let matchErrorsCount: number = 0,
                 usingExistingIdentifierChance: number;
@@ -96,7 +104,9 @@ describe('LogicalExpressionControlFlowReplacer', function () {
         });
 
         describe('Variant #3 - single logical expression with unary expression', () => {
-            const controlFlowStorageCallRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{5}'\]\(!_0x([a-f0-9]){4,6}, *!_0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                `var ${variableMatch} *= *${variableMatch}\\['\\w{5}'\\]\\(!${variableMatch}, *!${variableMatch}\\);`
+            );
 
             let obfuscatedCode: string;
 
@@ -119,7 +129,9 @@ describe('LogicalExpressionControlFlowReplacer', function () {
         });
 
         describe('prohibited nodes Variant #1', () => {
-            const regExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\] *&& *!\[\];/;
+            const regExp: RegExp = new RegExp(
+                `var ${variableMatch} *= *${variableMatch}\\[${variableMatch}\\] *&& *!\\[\\];`
+            );
 
             let obfuscatedCode: string;
 

+ 8 - 2
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts

@@ -8,8 +8,14 @@ import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator
 
 describe('StringLiteralControlFlowReplacer', () => {
     describe('replace', () => {
-        const controlFlowStorageStringLiteralRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *\{'\w{5}' *: *'test'\};/;
-        const controlFlowStorageCallRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{5}'\];/;
+        const variableMatch: string = '_0x([a-f0-9]){4,6}';
+
+        const controlFlowStorageStringLiteralRegExp: RegExp = new RegExp(
+            `var ${variableMatch} *= *\\{'\\w{5}' *: *'test'\\};`
+        );
+        const controlFlowStorageCallRegExp: RegExp = new RegExp(
+            `var ${variableMatch} *= *${variableMatch}\\['\\w{5}'\\];`
+        );
 
         let obfuscatedCode: string;
 

+ 427 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/StringArrayControlFlowTransformer.spec.ts

@@ -0,0 +1,427 @@
+import { assert } from 'chai';
+
+import {
+    IdentifierNamesGenerator
+} from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
+import {
+    StringArrayIndexesType
+} from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('StringArrayControlFlowTransformer', function () {
+    this.timeout(100000);
+
+    const hexadecimalVariableMatch: string = '_0x([a-f0-9]){4,6}';
+
+    describe('transformNode', () => {
+        describe('Variant #1 - hexadecimal number generator', () => {
+            const stringArrayVariableMatch: string = '_0x([a-f0-9]){4}';
+
+            const controlFlowStorageMatch: string = `var ${hexadecimalVariableMatch} *= *\\{` +
+                `${hexadecimalVariableMatch} *: *0x0, *` +
+                `${hexadecimalVariableMatch} *: *0x1 *` +
+            `\\};`;
+            const controlFlowStorageCallMatch: string = `${stringArrayVariableMatch}\\(${hexadecimalVariableMatch}.${hexadecimalVariableMatch}\\)`;
+
+            describe('Variant #1 - positive cases', () => {
+                describe('Variant #1 - hexadecimal number items', () => {
+                    const controlFlowStorageRegExp: RegExp = new RegExp(controlFlowStorageMatch);
+
+                    const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                        `var ${hexadecimalVariableMatch} *= *${controlFlowStorageCallMatch} *\\+ *${controlFlowStorageCallMatch};`
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayIndexesType: [StringArrayIndexesType.HexadecimalNumber],
+                                stringArrayCallsTransform: true,
+                                stringArrayCallsTransformThreshold: 1
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should add `control flow storage` node to the obfuscated code', () => {
+                        assert.match(obfuscatedCode, controlFlowStorageRegExp);
+                    });
+
+                    it('should add calls to `control flow storage` node to the obfuscated code', () => {
+                        assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+                    });
+                });
+
+                describe('Variant #2 - hexadecimal numeric string items', () => {
+                    const controlFlowStorageRegExp: RegExp = new RegExp(
+                        `var ${hexadecimalVariableMatch} *= *\\{` +
+                            `${hexadecimalVariableMatch} *: *'0x0', *` +
+                            `${hexadecimalVariableMatch} *: *'0x1' *` +
+                        `\\};`
+                    );
+
+                    const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                        `var ${hexadecimalVariableMatch} *= *${controlFlowStorageCallMatch} *\\+ *${controlFlowStorageCallMatch};`
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayIndexesType: [StringArrayIndexesType.HexadecimalNumericString],
+                                stringArrayCallsTransform: true,
+                                stringArrayCallsTransformThreshold: 1
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should add `control flow storage` node to the obfuscated code', () => {
+                        assert.match(obfuscatedCode, controlFlowStorageRegExp);
+                    });
+
+                    it('should add calls to `control flow storage` node to the obfuscated code', () => {
+                        assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+                    });
+                });
+
+                describe('Variant #3 - two scopes for `control flow storage` node', () => {
+                    const expectedAppendToScopeThreshold: number = 0.5;
+
+                    const samplesCount: number = 1000;
+                    const delta: number = 0.1;
+
+                    const regExp1: RegExp = new RegExp(
+                        `\\(function\\(\\) *\\{ *${controlFlowStorageMatch}`,
+                    );
+                    const regExp2: RegExp = new RegExp(
+                        `function *${hexadecimalVariableMatch} *\\(${hexadecimalVariableMatch}\\) *\\{ *${controlFlowStorageMatch}`,
+                    );
+
+                    let appendToScopeThreshold1: number = 0;
+                    let appendToScopeThreshold2: number = 0;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/input-2.js');
+
+                        let obfuscatedCode: string,
+                            totalValue1: number = 0,
+                            totalValue2: number = 0;
+
+                        for (let i = 0; i < samplesCount; i++) {
+                            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                                code,
+                                {
+                                    ...NO_ADDITIONAL_NODES_PRESET,
+                                    stringArray: true,
+                                    stringArrayThreshold: 1,
+                                    stringArrayCallsTransform: true,
+                                    stringArrayCallsTransformThreshold: 1
+                                }
+                            ).getObfuscatedCode();
+
+                            if (!regExp1.test(obfuscatedCode) && !regExp2.test(obfuscatedCode)) {
+                                console.log(obfuscatedCode);
+                            }
+
+                            if (regExp1.test(obfuscatedCode)) {
+                                totalValue1++;
+                            }
+
+                            if (regExp2.test(obfuscatedCode)) {
+                                totalValue2++;
+                            }
+                        }
+
+                        appendToScopeThreshold1 = totalValue1 / samplesCount;
+                        appendToScopeThreshold2 = totalValue2 / samplesCount;
+                    });
+
+                    it('should add `control flow storage` node to the obfuscated code in one of the scopes', () => {
+                        assert.closeTo(appendToScopeThreshold1, expectedAppendToScopeThreshold, delta);
+                        assert.closeTo(appendToScopeThreshold2, expectedAppendToScopeThreshold, delta);
+                    });
+                });
+
+                describe('Variant #4 - single `control flow storage` node with four items', () => {
+                    const regexp: RegExp = new RegExp(
+                        `var ${hexadecimalVariableMatch} *= *\\{` +
+                            `${hexadecimalVariableMatch} *: *0x0, *` +
+                            `${hexadecimalVariableMatch} *: *0x1, *` +
+                            `${hexadecimalVariableMatch} *: *0x2, *` +
+                            `${hexadecimalVariableMatch} *: *0x3 *` +
+                        `\\};`
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/multiple-items.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayCallsTransform: true,
+                                stringArrayCallsTransformThreshold: 1
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should add `control flow storage` node with multiple items to the obfuscated code', () => {
+                        assert.match(obfuscatedCode, regexp);
+                    });
+                });
+            });
+
+            describe('Variant #2 - negative cases', function () {
+                describe('Variant #1 - string array call in the root block scope', () => {
+                    const stringArrayCallsRegExp: RegExp = new RegExp(
+                        `var test *= *${stringArrayVariableMatch}\\(0x0\\) *\\+ *${stringArrayVariableMatch}\\(0x1\\);`
+                    );
+                    const controlFlowStorageRegExp: RegExp = new RegExp(controlFlowStorageMatch);
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/root-block-scope-1.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayCallsTransform: true,
+                                stringArrayCallsTransformThreshold: 1
+                            }
+                        ).getObfuscatedCode();
+                        console.log(obfuscatedCode);
+                    });
+
+                    it('shouldn\'t add control flow storage node', () => {
+                        assert.notMatch(obfuscatedCode, controlFlowStorageRegExp);
+                        assert.match(obfuscatedCode, stringArrayCallsRegExp);
+                    });
+                });
+
+                describe('Variant #2 - threshold is `0`', () => {
+                    const stringArrayCallsRegExp: RegExp = new RegExp(
+                        `var ${hexadecimalVariableMatch} *= *${stringArrayVariableMatch}\\(0x0\\) *\\+ *${stringArrayVariableMatch}\\(0x1\\);`
+                    );
+                    const controlFlowStorageRegExp: RegExp = new RegExp(controlFlowStorageMatch);
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayCallsTransform: true,
+                                stringArrayCallsTransformThreshold: 0
+                            }
+                        ).getObfuscatedCode();
+
+                        console.log(obfuscatedCode);
+                    });
+
+                    it('shouldn\'t add control flow storage node', () => {
+                        assert.notMatch(obfuscatedCode, controlFlowStorageRegExp);
+                        assert.match(obfuscatedCode, stringArrayCallsRegExp);
+                    });
+                });
+            });
+        });
+
+        describe('Variant #2 - mangled number generator', () => {
+            describe('Variant #1 - single control flow storage', () => {
+                const controlFlowStorageRegExp: RegExp = new RegExp(
+                    `var d *= *\\{` +
+                        `c *: *0x0, *` +
+                        `e *: *0x1 *` +
+                    `\\};`
+                );
+                const controlFlowStorageCallRegExp: RegExp = new RegExp(
+                    `var c *= *b\\(d.c\\) *\\+ *b\\(d.e\\);`
+                );
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayCallsTransform: true,
+                            stringArrayCallsTransformThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should add `control flow storage` node to the obfuscated code', () => {
+                    assert.match(obfuscatedCode, controlFlowStorageRegExp);
+                });
+
+                it('should add calls to `control flow storage` node to the obfuscated code', () => {
+                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+                });
+            });
+
+            describe('Variant #2 - multiple control flow storages', () => {
+                const controlFlowStorageRegExp1: RegExp = new RegExp(
+                    `var d *= *\\{` +
+                        `c *: *0x0, *` +
+                        `e *: *0x1 *` +
+                    `\\};`
+                );
+                const controlFlowStorageCallRegExp1: RegExp = new RegExp(
+                    `var c *= *b\\(d.c\\) *\\+ *b\\(d.e\\);`
+                );
+
+                const controlFlowStorageRegExp2: RegExp = new RegExp(
+                    `var e *= *\\{` +
+                    `c *: *0x0, *` +
+                    `f *: *0x1 *` +
+                    `\\};`
+                );
+                const controlFlowStorageCallRegExp2: RegExp = new RegExp(
+                    `var c *= *b\\(e.c\\) *\\+ *b\\(e.f\\);`
+                );
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/multiple-storages.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayCallsTransform: true,
+                            stringArrayCallsTransformThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('Match #1: should add `control flow storage` №1 and its calls to the obfuscated code', () => {
+                    assert.match(obfuscatedCode, controlFlowStorageRegExp1);
+                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
+                });
+
+                it('Match #2: should add `control flow storage` №2 and its calls to the obfuscated code', () => {
+                    assert.match(obfuscatedCode, controlFlowStorageRegExp2);
+                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
+                });
+            });
+        });
+
+        describe('Variant #3 - prevailing kind of variables', () => {
+            describe('Variant #1 - `var` kind', () => {
+                const regexp: RegExp = new RegExp(`var ${hexadecimalVariableMatch} *= *\\{`);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayCallsTransform: true,
+                            stringArrayCallsTransformThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should use correct kind of variables for `control flow storage`', () => {
+                    assert.match(obfuscatedCode, regexp);
+                });
+            });
+
+            describe('Variant #2 - `const` kind', () => {
+                const regexp: RegExp = new RegExp(`const ${hexadecimalVariableMatch} *= *\\{`);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayCallsTransform: true,
+                            stringArrayCallsTransformThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should use correct kind of variables for `control flow storage`', () => {
+                    assert.match(obfuscatedCode, regexp);
+                });
+            });
+
+            describe('Variant #3 - `let` kind', () => {
+                const regexp: RegExp = new RegExp(`const ${hexadecimalVariableMatch} *= *\\{`);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayCallsTransform: true,
+                            stringArrayCallsTransformThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should use correct kind of variables for `control flow storage`', () => {
+                    assert.match(obfuscatedCode, regexp);
+                });
+            });
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/input-1.js

@@ -0,0 +1,3 @@
+(function () {
+    var variable = 'foo' + 'bar';
+})();

+ 7 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/input-2.js

@@ -0,0 +1,7 @@
+(function () {
+    function foo (arg) {
+        function bar () {
+            var variable = 'foo' + 'bar';
+        }
+    }
+})();

+ 4 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/multiple-items.js

@@ -0,0 +1,4 @@
+(function () {
+    var test1 = 'foo' + 'bar';
+    var test2 = 'baz' + 'bark';
+})();

+ 7 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/multiple-storages.js

@@ -0,0 +1,7 @@
+(function () {
+    var variable1 = 'foo' + 'bar';
+})();
+
+(function () {
+    var variable2 = 'foo' + 'bar';
+})();

+ 3 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/prevailing-kind-of-variables-const.js

@@ -0,0 +1,3 @@
+(function () {
+    const variable = 'foo' + 'bar';
+})();

+ 3 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/prevailing-kind-of-variables-let.js

@@ -0,0 +1,3 @@
+(function () {
+    let variable = 'foo' + 'bar';
+})();

+ 3 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/prevailing-kind-of-variables-var.js

@@ -0,0 +1,3 @@
+(function () {
+    var variable = 'foo' + 'bar';
+})();

+ 1 - 0
test/functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/fixtures/root-block-scope-1.js

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

+ 1 - 1
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec.ts

@@ -209,7 +209,7 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         });
 
         describe('Variant #6: `disable` and `enable` conditional comments with control flow flattening', () => {
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *_0x([a-f0-9]){5,6}\['[a-zA-Z0-9]{1,5}'];/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *_0x([a-f0-9]){5,6}\['\w{5}'];/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *'bar';/;
 
             let obfuscatedCode: string;

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

@@ -160,6 +160,8 @@ describe('StringArrayRotateFunctionTransformer', function () {
                             splitStrings: true,
                             splitStringsChunkLength: 3,
                             stringArray: true,
+                            stringArrayCallsTransform: true,
+                            stringArrayCallsTransformThreshold: 1,
                             stringArrayEncoding: [
                                 StringArrayEncoding.None,
                                 StringArrayEncoding.Base64,

+ 25 - 1
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -740,6 +740,8 @@ describe('OptionsNormalizer', () => {
                 optionsPreset = getNormalizedOptions({
                     ...getDefaultOptions(),
                     stringArray: false,
+                    stringArrayCallsTransform: true,
+                    stringArrayCallsTransformThreshold: 1,
                     stringArrayEncoding: [StringArrayEncoding.Rc4],
                     stringArrayIndexShift: true,
                     stringArrayRotate: true,
@@ -752,6 +754,8 @@ describe('OptionsNormalizer', () => {
                 expectedOptionsPreset = {
                     ...getDefaultOptions(),
                     stringArray: false,
+                    stringArrayCallsTransform: false,
+                    stringArrayCallsTransformThreshold: 0,
                     stringArrayEncoding: [StringArrayEncoding.None],
                     stringArrayIndexShift: false,
                     stringArrayRotate: false,
@@ -767,6 +771,26 @@ describe('OptionsNormalizer', () => {
             });
         });
 
+        describe('stringArrayCallsTransformRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...getDefaultOptions(),
+                    stringArrayCallsTransform: false,
+                    stringArrayCallsTransformThreshold: 1
+                });
+
+                expectedOptionsPreset = {
+                    ...getDefaultOptions(),
+                    stringArrayCallsTransform: false,
+                    stringArrayCallsTransformThreshold: 0
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
+        });
+
         describe('stringArrayEncodingRule', () => {
             before(() => {
                 optionsPreset = getNormalizedOptions({
@@ -807,4 +831,4 @@ describe('OptionsNormalizer', () => {
             });
         });
     });
-});
+});

+ 2 - 0
test/index.spec.ts

@@ -50,6 +50,7 @@ import './unit-tests/utils/LevelledTopologicalSorter.spec';
 import './unit-tests/utils/NumberUtils.spec';
 import './unit-tests/utils/ObfuscatedCodeFileUtils.spec'
 import './unit-tests/utils/RandomGenerator.spec';
+import './unit-tests/utils/SetUtils.spec';
 import './unit-tests/utils/StringUtils.spec';
 import './unit-tests/utils/Utils.spec';
 
@@ -90,6 +91,7 @@ import './functional-tests/node-transformers/control-flow-transformers/control-f
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec';
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec';
 import './functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec';
+import './functional-tests/node-transformers/control-flow-transformers/string-array-control-flow-transformer/StringArrayControlFlowTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/boolean-literal-transformer/BooleanLiteralTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/class-field-transformer/ClassFieldTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/export-specifier-transformer/ExportSpecifierTransformer.spec';

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

@@ -38,6 +38,8 @@ describe('JavaScriptObfuscator runtime eval', function () {
         splitStrings: true,
         splitStringsChunkLength: 3,
         stringArray: true,
+        stringArrayCallsTransform: true,
+        stringArrayCallsTransformThreshold: 1,
         stringArrayEncoding: [
             StringArrayEncoding.None,
             StringArrayEncoding.Base64,
@@ -57,7 +59,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
         unicodeEscapeSequence: true
     };
 
-    this.timeout(200000);
+    this.timeout(600000);
 
     const options: Partial<TInputOptions>[] = [
         {

+ 35 - 0
test/unit-tests/generators/identifier-names-generators/DictionarylIdentifierNamesGenerator.spec.ts

@@ -224,4 +224,39 @@ describe('DictionaryIdentifierNamesGenerator', () => {
             });
         });
     });
+
+    describe('generateForLabel', () => {
+        const label1: string = 'label1';
+        const label2: string = 'label2';
+
+        const dictionaryNames1: string[] = [];
+        const dictionaryNames2: string[] = [];
+
+        let identifierNamesGenerator: IIdentifierNamesGenerator;
+
+        before(() => {
+            const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+            inversifyContainerFacade.load('', '', {
+                identifiersPrefix: 'foo',
+                identifiersDictionary: ['a', 'b', 'c']
+            });
+            identifierNamesGenerator = inversifyContainerFacade.getNamed<IIdentifierNamesGenerator>(
+                ServiceIdentifiers.IIdentifierNamesGenerator,
+                IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator
+            );
+
+            dictionaryNames1.push(identifierNamesGenerator.generateForLabel(label1));
+            dictionaryNames1.push(identifierNamesGenerator.generateForLabel(label1));
+            dictionaryNames1.push(identifierNamesGenerator.generateForLabel(label1));
+
+            dictionaryNames2.push(identifierNamesGenerator.generateForLabel(label2));
+            dictionaryNames2.push(identifierNamesGenerator.generateForLabel(label2));
+            dictionaryNames2.push(identifierNamesGenerator.generateForLabel(label2));
+        });
+
+        it('should return different dictionary names for different labels', () => {
+            assert.notDeepEqual(dictionaryNames1, dictionaryNames2);
+        });
+    });
 });

+ 42 - 0
test/unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec.ts

@@ -84,4 +84,46 @@ describe('HexadecimalIdentifierNamesGenerator', () => {
             assert.match(hexadecimalIdentifierName, regExp);
         })
     });
+
+    describe('generateForLabel', () => {
+        const label1: string = 'label1';
+        const label2: string = 'label2';
+        const regExp: RegExp = /^_0x(\w){4,6}$/;
+
+        let identifierNamesGenerator: IIdentifierNamesGenerator,
+            hexadecimalIdentifierName1: string,
+            hexadecimalIdentifierName2: string;
+
+        before(() => {
+            const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+            inversifyContainerFacade.load('', '', {
+                identifiersPrefix: 'foo'
+            });
+            identifierNamesGenerator = inversifyContainerFacade.getNamed<IIdentifierNamesGenerator>(
+                ServiceIdentifiers.IIdentifierNamesGenerator,
+                IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator
+            );
+
+            identifierNamesGenerator.generateForLabel(label1)
+            identifierNamesGenerator.generateForLabel(label1)
+            hexadecimalIdentifierName1 = identifierNamesGenerator.generateForLabel(label1);
+
+            identifierNamesGenerator.generateForLabel(label2)
+            identifierNamesGenerator.generateForLabel(label2)
+            hexadecimalIdentifierName2 = identifierNamesGenerator.generateForLabel(label2);
+        });
+
+        it('should return valid hexadecimal name 1', () => {
+            assert.match(hexadecimalIdentifierName1, regExp);
+        })
+
+        it('should return valid hexadecimal name 2', () => {
+            assert.match(hexadecimalIdentifierName2, regExp);
+        })
+
+        it('should generate different hexadecimal names for different labels', () => {
+            assert.notEqual(hexadecimalIdentifierName1, hexadecimalIdentifierName2);
+        })
+    });
 });

+ 34 - 0
test/unit-tests/generators/identifier-names-generators/MangledShuffledlIdentifierNamesGenerator.spec.ts

@@ -114,6 +114,40 @@ describe('MangledShuffledIdentifierNamesGenerator', () => {
         });
     });
 
+    describe('generateForLabel', () => {
+        const label1: string = 'label1';
+        const label2: string = 'label2';
+
+        const mangledNames1: string[] = [];
+        const mangledNames2: string[] = [];
+
+        let identifierNamesGenerator: IIdentifierNamesGenerator;
+
+        before(() => {
+            const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+            inversifyContainerFacade.load('', '', {
+                identifiersPrefix: 'foo'
+            });
+            identifierNamesGenerator = inversifyContainerFacade.getNamed<IIdentifierNamesGenerator>(
+                ServiceIdentifiers.IIdentifierNamesGenerator,
+                IdentifierNamesGenerator.MangledShuffledIdentifierNamesGenerator
+            );
+
+            mangledNames1.push(identifierNamesGenerator.generateForLabel(label1));
+            mangledNames1.push(identifierNamesGenerator.generateForLabel(label1));
+            mangledNames1.push(identifierNamesGenerator.generateForLabel(label1));
+
+            mangledNames2.push(identifierNamesGenerator.generateForLabel(label2));
+            mangledNames2.push(identifierNamesGenerator.generateForLabel(label2));
+            mangledNames2.push(identifierNamesGenerator.generateForLabel(label2));
+        });
+
+        it('should return the same mangled names set for different labels', () => {
+            assert.deepEqual(mangledNames1, mangledNames2);
+        })
+    });
+
     describe('isIncrementedMangledName', function () {
         this.timeout(60000);
 

+ 40 - 0
test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts

@@ -171,6 +171,46 @@ describe('MangledIdentifierNamesGenerator', () => {
         });
     });
 
+    describe('generateForLabel', () => {
+        const label1: string = 'label1';
+        const label2: string = 'label2';
+
+        const mangledNames1: string[] = [];
+        const mangledNames2: string[] = [];
+
+        const expectedMangledNames1: string[] = ['a', 'b', 'c']
+        const expectedMangledNames2: string[] = ['a', 'b']
+
+        let identifierNamesGenerator: IIdentifierNamesGenerator;
+
+        before(() => {
+            const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+            inversifyContainerFacade.load('', '', {
+                identifiersPrefix: 'foo'
+            });
+            identifierNamesGenerator = inversifyContainerFacade.getNamed<IIdentifierNamesGenerator>(
+                ServiceIdentifiers.IIdentifierNamesGenerator,
+                IdentifierNamesGenerator.MangledIdentifierNamesGenerator
+            );
+
+            mangledNames1.push(identifierNamesGenerator.generateForLabel(label1));
+            mangledNames1.push(identifierNamesGenerator.generateForLabel(label1));
+            mangledNames1.push(identifierNamesGenerator.generateForLabel(label1));
+
+            mangledNames2.push(identifierNamesGenerator.generateForLabel(label2));
+            mangledNames2.push(identifierNamesGenerator.generateForLabel(label2));
+        });
+
+        it('should return valid mangled names for label 1', () => {
+            assert.deepEqual(mangledNames1, expectedMangledNames1);
+        })
+
+        it('should return valid mangled names for label 2', () => {
+            assert.deepEqual(mangledNames2, expectedMangledNames2);
+        })
+    });
+
     describe('isIncrementedMangledName', function () {
         this.timeout(60000);
 

+ 87 - 0
test/unit-tests/utils/SetUtils.spec.ts

@@ -0,0 +1,87 @@
+import 'reflect-metadata';
+
+import { assert } from 'chai';
+
+import { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
+
+import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
+import { ISetUtils } from '../../../src/interfaces/utils/ISetUtils';
+
+import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
+
+describe('SetUtils', () => {
+    let setUtils: ISetUtils;
+
+    before(() => {
+        const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+        inversifyContainerFacade.load('', '', {});
+        setUtils = inversifyContainerFacade.get<ISetUtils>(ServiceIdentifiers.ISetUtils);
+    });
+
+    describe('getLastElement', () => {
+        describe('empty set', () => {
+            const set: Set<string> = new Set();
+            const expectedLastElement: undefined = undefined;
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = setUtils.getLastElement(set);
+            });
+
+            it('should return undefined if set is empty', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+
+        describe('set length: `1`', () => {
+            const set: Set<string> = new Set(['foo']);
+            const expectedLastElement: string = 'foo';
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = setUtils.getLastElement(set);
+            });
+
+            it('should return first element for set with length: `1`', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+
+        describe('set length: `3`', () => {
+            const set: Set<string> = new Set(['foo', 'bar', 'baz']);
+            const expectedLastElement: string = 'baz';
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = setUtils.getLastElement(set);
+            });
+
+            it('should return last element for set with length: `3`', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+
+        describe('changed set with length: `2`', () => {
+            const set: Set<string> = new Set(['foo', 'bar', 'baz']);
+            set.add('bark');
+            set.delete('bark');
+            set.delete('bar');
+
+            const expectedLastElement: string = 'baz';
+
+            let lastElement: string | undefined;
+
+            before(() => {
+                lastElement = setUtils.getLastElement(set);
+            });
+
+            it('should return last element for changed set with length: `2`', () => {
+                assert.equal(lastElement, expectedLastElement);
+            });
+        });
+    });
+});