Przeglądaj źródła

Switched from `escodegen` to `escodegen-wallaby`

sanex3339 8 lat temu
rodzic
commit
735bbb4b3c
82 zmienionych plików z 2371 dodań i 1134 usunięć
  1. 8 3
      CHANGELOG.md
  2. 171 42
      README.md
  3. 318 233
      dist/index.js
  4. 26 25
      package.json
  5. 1 1
      scripts/tslint
  6. 10 4
      src/JavaScriptObfuscatorInternal.ts
  7. 21 5
      src/Obfuscator.ts
  8. 1 1
      src/SourceMapCorrector.ts
  9. 30 61
      src/cli/JavaScriptObfuscatorCLI.ts
  10. 5 0
      src/cli/sanitizers/BooleanSanitizer.ts
  11. 17 0
      src/cli/sanitizers/SourceMapModeSanitizer.ts
  12. 19 0
      src/cli/sanitizers/StringArrayEncodingSanitizer.ts
  13. 1 0
      src/container/ServiceIdentifiers.ts
  14. 10 0
      src/container/modules/custom-nodes/CustomNodesModule.ts
  15. 5 0
      src/container/modules/node-transformers/ControlFlowTransformersModule.ts
  16. 5 0
      src/container/modules/node-transformers/NodeTransformersModule.ts
  17. 45 0
      src/custom-nodes/control-flow-flattening-nodes/StringLiteralNode.ts
  18. 61 0
      src/custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode.ts
  19. 2 1
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  20. 3 1
      src/declarations/escodegen.d.ts
  21. 5 0
      src/declarations/esmangle.d.ts
  22. 2 2
      src/decorators/Initializable.ts
  23. 2 1
      src/enums/container/ControlFlowReplacers.ts
  24. 3 1
      src/enums/container/CustomNodes.ts
  25. 1 0
      src/enums/container/NodeTransformers.ts
  26. 2 0
      src/interfaces/IInitializable.d.ts
  27. 13 0
      src/interfaces/node-transformers/IDeadCodeInjectionReplacer.d.ts
  28. 3 0
      src/interfaces/options/IOptions.d.ts
  29. 6 1
      src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts
  30. 1 1
      src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts
  31. 93 0
      src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts
  32. 207 0
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  33. 3 29
      src/node-transformers/obfuscating-transformers/ObjectExpressionTransformer.ts
  34. 35 0
      src/node/NodeUtils.ts
  35. 7 1
      src/node/Nodes.ts
  36. 18 0
      src/options/Options.ts
  37. 21 188
      src/options/OptionsNormalizer.ts
  38. 20 0
      src/options/normalizer-rules/ControlFlowFlatteningThresholdRule.ts
  39. 34 0
      src/options/normalizer-rules/DeadCodeInjectionRule.ts
  40. 20 0
      src/options/normalizer-rules/DeadCodeInjectionThresholdRule.ts
  41. 22 0
      src/options/normalizer-rules/DomainLockRule.ts
  42. 20 0
      src/options/normalizer-rules/SelfDefendingRule.ts
  43. 25 0
      src/options/normalizer-rules/SourceMapBaseUrlRule.ts
  44. 20 0
      src/options/normalizer-rules/SourceMapFileNameRule.ts
  45. 19 0
      src/options/normalizer-rules/StringArrayEncodingRule.ts
  46. 22 0
      src/options/normalizer-rules/StringArrayRule.ts
  47. 22 0
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  48. 5 2
      src/options/presets/Default.ts
  49. 4 1
      src/options/presets/NoCustomNodes.ts
  50. 1 0
      src/types/cli/TCLISanitizer.d.ts
  51. 3 4
      src/utils/CryptUtils.ts
  52. 29 79
      test/dev/dev.ts
  53. 9 3
      test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts
  54. 25 9
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  55. 3 0
      test/functional-tests/javascript-obfuscator/fixtures/mangle.js
  56. 17 15
      test/functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec.ts
  57. 3 3
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts
  58. 4 4
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts
  59. 4 4
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts
  60. 33 0
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts
  61. 3 0
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/fixtures/input-1.js
  62. 6 6
      test/functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec.ts
  63. 3 3
      test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts
  64. 2 2
      test/functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec.ts
  65. 214 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts
  66. 21 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statements-min-count.js
  67. 29 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/break-continue-statement.js
  68. 25 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/if-statement-variants-distribution.js
  69. 25 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/input-1.js
  70. 1 1
      test/functional-tests/node-transformers/obfuscating-transformers/catch-clause-transformer/CatchClauseTransformer.spec.ts
  71. 1 1
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts
  72. 38 38
      test/functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec.ts
  73. 3 14
      test/functional-tests/node-transformers/obfuscating-transformers/object-expression-transformer/ObjectExpressionTransformer.spec.ts
  74. 16 16
      test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts
  75. 2 0
      test/index.spec.ts
  76. 4 3
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  77. 58 1
      test/unit-tests/node/node-utils/NodeUtils.spec.ts
  78. 56 0
      test/unit-tests/options/options-normalizer/OptionsNormalizer.spec.ts
  79. 9 9
      tsconfig.json
  80. 5 0
      tslint.json
  81. 2 2
      webpack.config.js
  82. 303 313
      yarn.lock

+ 8 - 3
CHANGELOG.md

@@ -1,7 +1,12 @@
 Change Log
 ===
-v0.9.4
+v0.10.0
 ---
+* **New option:** `deadCodeInjection`. With this option random blocks of dead code will add to the obfuscated code.
+* **New option:** `deadCodeInjectionThreshold` allows to set percentage of nodes that will affected by `deadCodeInjection`.
+* **New option:** `mangle` enables mangling of variable names.
+* `escapeUnicodeSequence` option now disabled by default.
+* `controlFlowFlattening` now affects string literal nodes.
 * increased runtime performance with `rc4` `stringArrayEncoding`.
 
 v0.9.3
@@ -10,11 +15,11 @@ v0.9.3
 
 v0.9.2
 ---
-* fixed https://github.com/javascript-obfuscator/javascript-obfuscator/pull/42
+* Removed coverage dir from npm package
 
 v0.9.1
 ---
-* fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/37
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/37
 
 v0.9.0
 ---

+ 171 - 42
README.md

@@ -1,12 +1,12 @@
 <!--
   Title: JavaScript Obfuscator
-  Description: JavaScript obfuscator for Node.js.
+  Description: A powerful obfuscator for JavaScript and Node.js.
   Author: sanex3339
   -->
 
 # JavaScript obfuscator for Node.js
 
-JavaScript obfuscator for Node.js is a free obfuscator with a wide number of features which provides protection for your source code.
+JavaScript obfuscator is a powerful free obfuscator for JavaScript and Node.js with a wide number of features which provides protection for your source code.
 
 * has no limits or restrictions
 * runs on your local machine - does not send data to a server;
@@ -29,7 +29,7 @@ Example of obfuscated code: [gist.github.com](https://gist.github.com/sanex3339/
 *NOTE! the README on the master branch might not match that of the latest stable release!*
 
 ## :warning: Important
-#####Obfuscate only the code that belongs to you. 
+##### Obfuscate only the code that belongs to you. 
 
 It is not recommended to obfuscate vendor scripts and polyfills, since the obfuscated code is 15-80% slower (depends on options) and the files are significantly larger.
 
@@ -176,9 +176,12 @@ Following options are available for the JS Obfuscator:
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0.75,
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0.4,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
+    mangle: false,
     reservedNames: [],
     rotateStringArray: true,
     seed: 0,
@@ -189,8 +192,8 @@ Following options are available for the JS Obfuscator:
     sourceMapMode: 'separate',
     stringArray: true,
     stringArrayEncoding: false,
-    stringArrayThreshold: 0.8,
-    unicodeEscapeSequence: true
+    stringArrayThreshold: 0.75,
+    unicodeEscapeSequence: false
 }
 ```
 
@@ -204,9 +207,12 @@ Following options are available for the JS Obfuscator:
     --compact <boolean>
     --controlFlowFlattening <boolean>
     --controlFlowFlatteningThreshold <number>
+    --deadCodeInjection <boolean>
+    --deadCodeInjectionThreshold <number>
     --debugProtection <boolean>
     --debugProtectionInterval <boolean>
     --disableConsoleOutput <boolean>
+    --mangle <boolean>
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
     --seed <number>
@@ -300,6 +306,116 @@ This setting is especially useful for large code size because large amounts of c
 
 `controlFlowFlatteningThreshold: 0` equals to `controlFlowFlattening: false`.
 
+### `deadCodeInjection`
+Type: `boolean` Default: `false`
+
+##### :warning: Dramatically increases size of obfuscated code (up to 200%), use only if size of obfuscated code doesn't matter. Use [`deadCodeInjectionThreshold`](#deadcodeinjectionthreshold) to set percentage of nodes that will affected by dead code injection.
+##### :warning: This option forcibly enables `stringArray` option.
+
+With this option random blocks of dead code will add to the obfuscated code. 
+
+Example:
+```ts
+// input
+(function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+            console.log('cde');
+            console.log('efg');
+            console.log('hij');
+        };
+        
+        var bar = function () {
+            console.log('klm');
+            console.log('nop');
+            console.log('qrs');
+        };
+    
+        var baz = function () {
+            console.log('tuv');
+            console.log('wxy');
+            console.log('z');
+        };
+    
+        foo();
+        bar();
+        baz();
+    }
+})();
+
+// output
+var _0x5024 = [
+    'zaU',
+    'log',
+    'tuv',
+    'wxy',
+    'abc',
+    'cde',
+    'efg',
+    'hij',
+    'QhG',
+    'TeI',
+    'klm',
+    'nop',
+    'qrs',
+    'bZd',
+    'HMx'
+];
+var _0x4502 = function (_0x1254b1, _0x583689) {
+    _0x1254b1 = _0x1254b1 - 0x0;
+    var _0x529b49 = _0x5024[_0x1254b1];
+    return _0x529b49;
+};
+(function () {
+    if (!![]) {
+        var _0x16c18d = function () {
+            if (_0x4502('0x0') !== _0x4502('0x0')) {
+                console[_0x4502('0x1')](_0x4502('0x2'));
+                console[_0x4502('0x1')](_0x4502('0x3'));
+                console[_0x4502('0x1')]('z');
+            } else {
+                console[_0x4502('0x1')](_0x4502('0x4'));
+                console[_0x4502('0x1')](_0x4502('0x5'));
+                console[_0x4502('0x1')](_0x4502('0x6'));
+                console[_0x4502('0x1')](_0x4502('0x7'));
+            }
+        };
+        var _0x1f7292 = function () {
+            if (_0x4502('0x8') === _0x4502('0x9')) {
+                console[_0x4502('0x1')](_0x4502('0xa'));
+                console[_0x4502('0x1')](_0x4502('0xb'));
+                console[_0x4502('0x1')](_0x4502('0xc'));
+            } else {
+                console[_0x4502('0x1')](_0x4502('0xa'));
+                console[_0x4502('0x1')](_0x4502('0xb'));
+                console[_0x4502('0x1')](_0x4502('0xc'));
+            }
+        };
+        var _0x33b212 = function () {
+            if (_0x4502('0xd') !== _0x4502('0xe')) {
+                console[_0x4502('0x1')](_0x4502('0x2'));
+                console[_0x4502('0x1')](_0x4502('0x3'));
+                console[_0x4502('0x1')]('z');
+            } else {
+                console[_0x4502('0x1')](_0x4502('0x4'));
+                console[_0x4502('0x1')](_0x4502('0x5'));
+                console[_0x4502('0x1')](_0x4502('0x6'));
+                console[_0x4502('0x1')](_0x4502('0x7'));
+            }
+        };
+        _0x16c18d();
+        _0x1f7292();
+        _0x33b212();
+    }
+}());
+```
+
+### `deadCodeInjectionThreshold`
+Type: `number` Default: `0.4` Min: `0` Max: `1`
+
+Allows to set percentage of nodes that will affected by `deadCodeInjection`.
+
 ### `debugProtection`
 Type: `boolean` Default: `false`
 
@@ -330,6 +446,11 @@ Locks the obfuscated source code so it only runs on specific domains and/or sub-
 ##### Multiple domains and sub-domains
 It's possible to lock your code to more than one domain or sub-domain. For instance, to lock it so the code only runs on **www.example.com** add `www.example.com`, to make it work on any sub-domain from example.com, use `.example.com`.
 
+### `mangle`
+Type: `boolean` Default: `false`
+
+Enables mangling of variable names.
+
 ### `reservedNames`
 Type: `string[]` Default: `[]`
 
@@ -445,11 +566,11 @@ This setting is especially useful for large code size because it repeatedly call
 `stringArrayThreshold: 0` equals to `stringArray: false`.
 
 ### `unicodeEscapeSequence`
-Type: `boolean` Default: `true`
+Type: `boolean` Default: `false`
 
 Allows to enable/disable string conversion to unicode escape sequence.
 
-Unicode escape sequence increases code size greatly. It is recommended to disable this option when using [`stringArrayEncoding`](#stringarrayencoding) (especially with `rc4` encoding).
+Unicode escape sequence increases code size greatly and strings easily can be reverted to their original view. Recommended to enable this option only for small source code. 
 
 ## Preset Options
 ### High obfuscation, low performance
@@ -458,18 +579,21 @@ Performance will 50-100% slower than without obfuscation
 
 ```javascript
 {
-    compact: true,
-    controlFlowFlattening: true,
-    controlFlowFlatteningThreshold: 1,
-    debugProtection: true,
-    debugProtectionInterval: true,
-    disableConsoleOutput: true,
-    rotateStringArray: true,
-    selfDefending: true,
-    stringArray: true,
-    stringArrayEncoding: 'rc4',
-    stringArrayThreshold: 1,
-    unicodeEscapeSequence: false
+	compact: true,
+	controlFlowFlattening: true,
+	controlFlowFlatteningThreshold: 1,
+	deadCodeInjection: true,
+	deadCodeInjectionThreshold: 1,
+	debugProtection: true,
+	debugProtectionInterval: true,
+	disableConsoleOutput: true,
+	mangle: false,
+	rotateStringArray: true,
+	selfDefending: true,
+	stringArray: true,
+	stringArrayEncoding: 'rc4',
+	stringArrayThreshold: 1,
+	unicodeEscapeSequence: false
 }
 ```
 
@@ -479,18 +603,21 @@ Performance will 30-35% slower than without obfuscation
 
 ```javascript
 {
-    compact: true,
-    controlFlowFlattening: true,
-    controlFlowFlatteningThreshold: 0.75,
-    debugProtection: false,
-    debugProtectionInterval: false,
-    disableConsoleOutput: true,
-    rotateStringArray: true,
-    selfDefending: true,
-    stringArray: true,
-    stringArrayEncoding: 'base64',
-    stringArrayThreshold: 0.75,
-    unicodeEscapeSequence: false
+	compact: true,
+	controlFlowFlattening: true,
+	controlFlowFlatteningThreshold: 0.75,
+	deadCodeInjection: true,
+	deadCodeInjectionThreshold: 0.4,
+	debugProtection: false,
+	debugProtectionInterval: false,
+	disableConsoleOutput: true,
+	mangle: false,
+	rotateStringArray: true,
+	selfDefending: true,
+	stringArray: true,
+	stringArrayEncoding: 'base64',
+	stringArrayThreshold: 0.75,
+	unicodeEscapeSequence: false
 }
 ```
 
@@ -500,17 +627,19 @@ Performance will slightly slower than without obfuscation
 
 ```javascript
 {
-    compact: true,
-    controlFlowFlattening: false,
-    debugProtection: false,
-    debugProtectionInterval: false,
-    disableConsoleOutput: true,
-    rotateStringArray: true,
-    selfDefending: true,
-    stringArray: true,
-    stringArrayEncoding: false,
-    stringArrayThreshold: 0.75,
-    unicodeEscapeSequence: true
+	compact: true,
+	controlFlowFlattening: false,
+	deadCodeInjection: false,
+	debugProtection: false,
+	debugProtectionInterval: false,
+	disableConsoleOutput: true,
+	mangle: true,
+	rotateStringArray: true,
+	selfDefending: true,
+	stringArray: true,
+	stringArrayEncoding: false,
+	stringArrayThreshold: 0.75,
+	unicodeEscapeSequence: false
 }
 ```
 

Plik diff jest za duży
+ 318 - 233
dist/index.js


+ 26 - 25
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.9.4",
+  "version": "0.10.0-beta.3",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -19,47 +19,48 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
   },
   "dependencies": {
-    "chance": "1.0.6",
-    "class-validator": "0.6.8",
+    "chance": "1.0.8",
+    "class-validator": "0.7.0",
     "commander": "2.9.0",
     "escodegen-wallaby": "1.6.11",
+    "esmangle": "^1.0.1",
     "esprima": "3.1.3",
     "estraverse": "4.2.0",
-    "inversify": "3.1.0",
+    "inversify": "4.1.0",
     "mkdirp": "0.5.1",
     "reflect-metadata": "0.1.10",
-    "source-map-support": "0.4.11",
+    "source-map-support": "0.4.15",
     "string-template": "1.0.0",
-    "tslib": "1.6.0"
+    "tslib": "1.7.0"
   },
   "devDependencies": {
-    "@types/chai": "3.4.35",
-    "@types/chance": "0.7.31",
+    "@types/chai": "3.5.2",
+    "@types/chance": "0.7.33",
     "@types/commander": "2.3.31",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "2.1.33",
     "@types/estraverse": "0.0.6",
-    "@types/estree": "0.0.34",
+    "@types/estree": "0.0.35",
     "@types/mkdirp": "0.3.29",
-    "@types/mocha": "2.2.39",
-    "@types/node": "7.0.5",
-    "@types/sinon": "1.16.35",
+    "@types/mocha": "2.2.41",
+    "@types/node": "7.0.18",
+    "@types/sinon": "2.2.1",
     "@types/string-template": "1.0.2",
-    "awesome-typescript-loader": "3.0.8",
-    "babel-cli": "6.23.0",
-    "babel-loader": "6.3.2",
+    "awesome-typescript-loader": "3.1.3",
+    "babel-cli": "6.24.1",
+    "babel-loader": "7.0.0",
     "babel-plugin-array-includes": "2.0.3",
-    "babel-preset-es2015": "6.22.0",
-    "chai": "4.0.0-canary.1",
-    "coveralls": "2.11.16",
+    "babel-preset-es2015": "6.24.1",
+    "chai": "4.0.0-canary.2",
+    "coveralls": "2.13.1",
     "istanbul": "1.1.0-alpha.1",
-    "mocha": "3.2.0",
-    "sinon": "2.0.0-pre.6",
-    "ts-node": "2.1.0",
-    "tslint": "4.5.1",
-    "tslint-loader": "3.4.3",
-    "typescript": "2.2.0",
-    "webpack": "2.2.1",
+    "mocha": "3.3.0",
+    "sinon": "2.2.0",
+    "ts-node": "3.0.4",
+    "tslint": "5.2.0",
+    "tslint-loader": "3.5.3",
+    "typescript": "2.3.2",
+    "webpack": "2.5.0",
     "webpack-node-externals": "1.5.4"
   },
   "repository": {

+ 1 - 1
scripts/tslint

@@ -1,3 +1,3 @@
 #!/bin/bash
 
-$(yarn bin)/tslint src/**/*.ts
+$(yarn bin)/tslint src/**/*.ts --type-check --project tsconfig.json

+ 10 - 4
src/JavaScriptObfuscatorInternal.ts

@@ -3,6 +3,7 @@ import { ServiceIdentifiers } from './container/ServiceIdentifiers';
 
 import * as esprima from 'esprima';
 import * as escodegen from 'escodegen-wallaby';
+import * as esmangle from 'esmangle';
 import * as ESTree from 'estree';
 
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
@@ -83,11 +84,16 @@ export class JavaScriptObfuscatorInternal implements IJavaScriptObfuscator {
             escodegenParams.sourceContent = sourceCode;
         }
 
-        escodegenParams.format = {
-            compact: this.options.compact
-        };
+        if (this.options.mangle) {
+            astTree = esmangle.mangle(astTree);
+        }
 
-        const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
+        const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, {
+            ...escodegenParams,
+            format: {
+                compact: this.options.compact
+            }
+        });
 
         generatorOutput.map = generatorOutput.map ? generatorOutput.map.toString() : '';
 

+ 21 - 5
src/Obfuscator.ts

@@ -43,6 +43,13 @@ export class Obfuscator implements IObfuscator {
         NodeTransformers.TemplateLiteralTransformer
     ];
 
+    /**
+     * @type {NodeTransformers[]}
+     */
+    private static readonly deadCodeInjectionTransformersList: NodeTransformers[] = [
+        NodeTransformers.DeadCodeInjectionTransformer
+    ];
+
     /**
      * @type {NodeTransformers[]}
      */
@@ -129,12 +136,17 @@ export class Obfuscator implements IObfuscator {
 
         this.obfuscationEventEmitter.emit(ObfuscationEvents.BeforeObfuscation, astTree, stackTraceData);
 
-        // first pass: control flow flattening
-        if (this.options.controlFlowFlattening) {
-            astTree = this.transformAstTree(astTree, Obfuscator.controlFlowTransformersList);
-        }
+        // first pass transformers: dead code injection transformer
+        astTree = this.transformAstTree(astTree, [
+            ...this.options.deadCodeInjection ? Obfuscator.deadCodeInjectionTransformersList : []
+        ]);
+
+        // second pass transformers: control flow flattening transformers
+        astTree = this.transformAstTree(astTree, [
+            ...this.options.controlFlowFlattening ? Obfuscator.controlFlowTransformersList : []
+        ]);
 
-        // second pass: nodes obfuscation
+        // third pass: converting and obfuscating transformers
         astTree = this.transformAstTree(astTree, [
             ...Obfuscator.convertingTransformersList,
             ...Obfuscator.obfuscatingTransformersList
@@ -153,6 +165,10 @@ export class Obfuscator implements IObfuscator {
         astTree: ESTree.Program,
         nodeTransformers: NodeTransformers[]
     ): ESTree.Program {
+        if (!nodeTransformers.length) {
+            return astTree;
+        }
+
         const enterVisitors: IVisitor[] = [];
         const leaveVisitors: IVisitor[] = [];
         const nodeTransformersLength: number = nodeTransformers.length;

+ 1 - 1
src/SourceMapCorrector.ts

@@ -79,5 +79,5 @@ export class SourceMapCorrector implements ISourceMapCorrector {
         }
 
         return `${obfuscatedCode}\n${sourceMappingUrl}`;
-    };
+    }
 }

+ 30 - 61
src/cli/JavaScriptObfuscatorCLI.ts

@@ -2,15 +2,15 @@ import * as commander from 'commander';
 import * as path from 'path';
 
 import { TInputOptions } from '../types/options/TInputOptions';
-import { TStringArrayEncoding } from '../types/options/TStringArrayEncoding';
 
 import { IObfuscationResult } from '../interfaces/IObfuscationResult';
 
-import { SourceMapMode } from '../enums/SourceMapMode';
-import { StringArrayEncoding } from '../enums/StringArrayEncoding';
-
 import { DEFAULT_PRESET } from '../options/presets/Default';
 
+import { BooleanSanitizer } from './sanitizers/BooleanSanitizer';
+import { SourceMapModeSanitizer } from './sanitizers/SourceMapModeSanitizer';
+import { StringArrayEncodingSanitizer } from './sanitizers/StringArrayEncodingSanitizer';
+
 import { CLIUtils } from './CLIUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 
@@ -55,51 +55,6 @@ export class JavaScriptObfuscatorCLI {
         return CLIUtils.getPackageConfig().version;
     }
 
-    /**
-     * @param value
-     * @returns {boolean}
-     */
-    private static parseBoolean (value: string): boolean {
-        return value === 'true' || value === '1';
-    }
-
-    /**
-     * @param value
-     * @returns {string}
-     */
-    private static parseSourceMapMode (value: string): string {
-        const availableMode: boolean = Object
-            .keys(SourceMapMode)
-            .some((key: string): boolean => {
-                return SourceMapMode[key] === value;
-            });
-
-        if (!availableMode) {
-            throw new ReferenceError('Invalid value of `--sourceMapMode` option');
-        }
-
-        return value;
-    }
-
-    /**
-     * @param value
-     * @returns {TStringArrayEncoding}
-     */
-    private static parseStringArrayEncoding (value: string): TStringArrayEncoding {
-        switch (value) {
-            case 'true':
-            case '1':
-            case StringArrayEncoding.base64:
-                return true;
-
-            case StringArrayEncoding.rc4:
-                return StringArrayEncoding.rc4;
-
-            default:
-                return false;
-        }
-    }
-
     public run (): void {
         this.configureCommands();
 
@@ -152,38 +107,52 @@ export class JavaScriptObfuscatorCLI {
             .option(
                 '--compact <boolean>',
                 'Disable one line output code compacting',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--controlFlowFlattening <boolean>',
                 'Enables control flow flattening',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--controlFlowFlatteningThreshold <number>',
                 'The probability that the control flow flattening transformation will be applied to the node',
                 parseFloat
             )
+            .option(
+                '--deadCodeInjection <boolean>',
+                'Enables dead code injection',
+                BooleanSanitizer
+            )
+            .option(
+                '--deadCodeInjectionThreshold <number>',
+                'The probability that the dead code injection transformation will be applied to the node',
+                parseFloat
+            )
             .option(
                 '--debugProtection <boolean>',
                 'Disable browser Debug panel (can cause DevTools enabled browser freeze)',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--debugProtectionInterval <boolean>',
                 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--disableConsoleOutput <boolean>',
                 'Allow console.log, console.info, console.error and console.warn messages output into browser console',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--domainLock <list>',
                 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)',
                 (value: string) => value.split(',')
             )
+            .option(
+                '--mangle <boolean>', 'Enables mangling of variable names',
+                BooleanSanitizer
+            )
             .option(
                 '--reservedNames <list>',
                 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)',
@@ -191,7 +160,7 @@ export class JavaScriptObfuscatorCLI {
             )
             .option(
                 '--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--seed <number>',
@@ -201,12 +170,12 @@ export class JavaScriptObfuscatorCLI {
             .option(
                 '--selfDefending <boolean>',
                 'Disables self-defending for obfuscated code',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--sourceMap <boolean>',
                 'Enables source map generation',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--sourceMapBaseUrl <string>',
@@ -219,17 +188,17 @@ export class JavaScriptObfuscatorCLI {
             .option(
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',
-                JavaScriptObfuscatorCLI.parseSourceMapMode
+                SourceMapModeSanitizer
             )
             .option(
                 '--stringArray <boolean>',
                 'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .option(
                 '--stringArrayEncoding <boolean|string> [true, false, base64, rc4]',
                 'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed',
-                JavaScriptObfuscatorCLI.parseStringArrayEncoding
+                StringArrayEncodingSanitizer
             )
             .option(
                 '--stringArrayThreshold <number>',
@@ -239,7 +208,7 @@ export class JavaScriptObfuscatorCLI {
             .option(
                 '--unicodeEscapeSequence <boolean>',
                 'Allows to enable/disable string conversion to unicode escape sequence',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             .parse(this.rawArguments);
 

+ 5 - 0
src/cli/sanitizers/BooleanSanitizer.ts

@@ -0,0 +1,5 @@
+import { TCLISanitizer } from '../../types/cli/TCLISanitizer';
+
+export const BooleanSanitizer: TCLISanitizer = (value: string): boolean => {
+    return value === 'true' || value === '1';
+};

+ 17 - 0
src/cli/sanitizers/SourceMapModeSanitizer.ts

@@ -0,0 +1,17 @@
+import { TCLISanitizer } from '../../types/cli/TCLISanitizer';
+
+import { SourceMapMode } from '../../enums/SourceMapMode';
+
+export const SourceMapModeSanitizer: TCLISanitizer = (value: string): string => {
+    const availableMode: boolean = Object
+        .keys(SourceMapMode)
+        .some((key: string): boolean => {
+            return SourceMapMode[key] === value;
+        });
+
+    if (!availableMode) {
+        throw new ReferenceError('Invalid value of `--sourceMapMode` option');
+    }
+
+    return value;
+};

+ 19 - 0
src/cli/sanitizers/StringArrayEncodingSanitizer.ts

@@ -0,0 +1,19 @@
+import { TCLISanitizer } from '../../types/cli/TCLISanitizer';
+import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
+
+import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
+
+export const StringArrayEncodingSanitizer: TCLISanitizer = (value: string): TStringArrayEncoding => {
+    switch (value) {
+        case 'true':
+        case '1':
+        case StringArrayEncoding.base64:
+            return true;
+
+        case StringArrayEncoding.rc4:
+            return StringArrayEncoding.rc4;
+
+        default:
+            return false;
+    }
+};

+ 1 - 0
src/container/ServiceIdentifiers.ts

@@ -10,6 +10,7 @@ export const ServiceIdentifiers: any = {
     ICalleeDataExtractor: Symbol('ICalleeDataExtractor'),
     ICustomNodeGroup: Symbol('ICustomNodeGroup'),
     IControlFlowReplacer: Symbol('IControlFlowReplacer'),
+    IDeadCodeInjectionReplacer: Symbol('IDeadCodeInjectionReplacer'),
     IJavaScriptObfuscator: Symbol('IJavaScriptObfuscator'),
     INodeTransformer: Symbol('INodeTransformer'),
     IObfuscationEventEmitter: Symbol('IObfuscationEventEmitter'),

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

@@ -31,6 +31,8 @@ import { SelfDefendingUnicodeNode } from '../../../custom-nodes/self-defending-n
 import { StringArrayCallsWrapper } from '../../../custom-nodes/string-array-nodes/StringArrayCallsWrapper';
 import { StringArrayNode } from '../../../custom-nodes/string-array-nodes/StringArrayNode';
 import { StringArrayRotateFunctionNode } from '../../../custom-nodes/string-array-nodes/StringArrayRotateFunctionNode';
+import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
+import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
 
 export const customNodesModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // custom nodes
@@ -102,6 +104,14 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .toConstructor(StringArrayRotateFunctionNode)
         .whenTargetNamed(CustomNodes.StringArrayRotateFunctionNode);
 
+    bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
+        .toConstructor(StringLiteralControlFlowStorageCallNode)
+        .whenTargetNamed(CustomNodes.StringLiteralControlFlowStorageCallNode);
+
+    bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
+        .toConstructor(StringLiteralNode)
+        .whenTargetNamed(CustomNodes.StringLiteralNode);
+
     // node groups
     bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
         .to(ConsoleOutputCustomNodeGroup)

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

@@ -8,6 +8,7 @@ import { ControlFlowReplacers } from '../../../enums/container/ControlFlowReplac
 import { BinaryExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer';
 import { CallExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer';
 import { LogicalExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/LogicalExpressionControlFlowReplacer';
+import { StringLiteralControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer';
 
 export const controlFlowTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
@@ -22,6 +23,10 @@ export const controlFlowTransformersModule: interfaces.ContainerModule = new Con
         .to(LogicalExpressionControlFlowReplacer)
         .whenTargetNamed(ControlFlowReplacers.LogicalExpressionControlFlowReplacer);
 
+    bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
+        .to(StringLiteralControlFlowReplacer)
+        .whenTargetNamed(ControlFlowReplacers.StringLiteralControlFlowReplacer);
+
     bind<IControlFlowReplacer>(ServiceIdentifiers.Factory__IControlFlowReplacer)
         .toFactory<IControlFlowReplacer>((context: interfaces.Context) => {
             const cache: Map <ControlFlowReplacers, IControlFlowReplacer> = new Map();

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

@@ -8,6 +8,7 @@ import { NodeTransformers } from '../../../enums/container/NodeTransformers';
 import { FunctionControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/FunctionControlFlowTransformer';
 
 import { BlockStatementControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer';
+import { DeadCodeInjectionTransformer } from '../../../node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer';
 import { CatchClauseTransformer } from '../../../node-transformers/obfuscating-transformers/CatchClauseTransformer';
 import { FunctionDeclarationTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionDeclarationTransformer';
 import { FunctionTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionTransformer';
@@ -25,6 +26,10 @@ export const nodeTransformersModule: interfaces.ContainerModule = new ContainerM
         .to(BlockStatementControlFlowTransformer)
         .whenTargetNamed(NodeTransformers.BlockStatementControlFlowTransformer);
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(DeadCodeInjectionTransformer)
+        .whenTargetNamed(NodeTransformers.DeadCodeInjectionTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(FunctionControlFlowTransformer)
         .whenTargetNamed(NodeTransformers.FunctionControlFlowTransformer);

+ 45 - 0
src/custom-nodes/control-flow-flattening-nodes/StringLiteralNode.ts

@@ -0,0 +1,45 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { TStatement } from '../../types/node/TStatement';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { initializable } from '../../decorators/Initializable';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { Nodes } from '../../node/Nodes';
+
+@injectable()
+export class StringLiteralNode extends AbstractCustomNode {
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private literalValue: string;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+    }
+
+    /**
+     * @param literalValue
+     */
+    public initialize (literalValue: string): void {
+        this.literalValue = literalValue;
+    }
+
+    /**
+     * @returns {TStatement[]}
+     */
+    protected getNodeStructure (): TStatement[] {
+        const structure: TStatement = <any>Nodes.getLiteralNode(this.literalValue);
+
+        return [structure];
+    }
+}

+ 61 - 0
src/custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode.ts

@@ -0,0 +1,61 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { TStatement } from '../../../types/node/TStatement';
+
+import { IOptions } from '../../../interfaces/options/IOptions';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { AbstractCustomNode } from '../../AbstractCustomNode';
+import { Nodes } from '../../../node/Nodes';
+import { NodeUtils } from '../../../node/NodeUtils';
+
+@injectable()
+export class StringLiteralControlFlowStorageCallNode extends AbstractCustomNode {
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private controlFlowStorageKey: string;
+
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private controlFlowStorageName: string;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+    }
+
+    /**
+     * @param controlFlowStorageName
+     * @param controlFlowStorageKey
+     */
+    public initialize (
+        controlFlowStorageName: string,
+        controlFlowStorageKey: string
+    ): void {
+        this.controlFlowStorageName = controlFlowStorageName;
+        this.controlFlowStorageKey = controlFlowStorageKey;
+    }
+
+    protected getNodeStructure (): TStatement[] {
+        const structure: TStatement = Nodes.getExpressionStatementNode(
+            Nodes.getMemberExpressionNode(
+                Nodes.getIdentifierNode(this.controlFlowStorageName),
+                Nodes.getIdentifierNode(this.controlFlowStorageKey)
+            )
+        );
+
+        NodeUtils.parentize(structure);
+
+        return [structure];
+    }
+}

+ 2 - 1
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -60,7 +60,8 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
             }),
             {
                 ...NO_CUSTOM_NODES_PRESET,
-                seed: this.options.seed
+                seed: this.options.seed,
+                unicodeEscapeSequence: true
             }
         ).getObfuscatedCode();
     }

+ 3 - 1
src/declarations/escodegen.d.ts

@@ -1,3 +1,5 @@
+import * as escodegen from 'escodegen';
+
 import { IGeneratorOutput } from '../interfaces/IGeneratorOutput';
 
 declare module 'escodegen' {
@@ -6,5 +8,5 @@ declare module 'escodegen' {
      * @param options
      * @returns IGeneratorOutput
      */
-    export function generate (ast: any, options?: GenerateOptions): IGeneratorOutput;
+    export function generate (ast: any, options?: escodegen.GenerateOptions): IGeneratorOutput;
 }

+ 5 - 0
src/declarations/esmangle.d.ts

@@ -0,0 +1,5 @@
+declare module "esmangle" {
+    import * as ESTree from 'estree';
+
+    function mangle (ast: ESTree.Program): ESTree.Program;
+}

+ 2 - 2
src/decorators/Initializable.ts

@@ -11,12 +11,12 @@ export function initializable (
 ): (target: IInitializable, propertyKey: string | symbol) => any {
     const decoratorName: string = Object.keys(this)[0];
 
-    return (target: IInitializable, propertyKey: string | symbol): any => {
+    return (target: IInitializable, propertyKey: string | symbol): PropertyDescriptor => {
         const descriptor: PropertyDescriptor = {
             configurable: true,
             enumerable: true
         };
-        const initializeMethod: any = (<any>target)[initializeMethodKey];
+        const initializeMethod: Function = target[initializeMethodKey];
 
         if (!initializeMethod || typeof initializeMethod !== 'function') {
            throw new Error(`\`${initializeMethodKey}\` method with initialization logic not found. \`@${decoratorName}\` decorator requires \`${initializeMethodKey}\` method`);

+ 2 - 1
src/enums/container/ControlFlowReplacers.ts

@@ -1,5 +1,6 @@
 export enum ControlFlowReplacers {
     BinaryExpressionControlFlowReplacer,
     CallExpressionControlFlowReplacer,
-    LogicalExpressionControlFlowReplacer
+    LogicalExpressionControlFlowReplacer,
+    StringLiteralControlFlowReplacer,
 }

+ 3 - 1
src/enums/container/CustomNodes.ts

@@ -15,5 +15,7 @@ export enum CustomNodes {
     SelfDefendingUnicodeNode,
     StringArrayCallsWrapper,
     StringArrayNode,
-    StringArrayRotateFunctionNode
+    StringArrayRotateFunctionNode,
+    StringLiteralControlFlowStorageCallNode,
+    StringLiteralNode,
 }

+ 1 - 0
src/enums/container/NodeTransformers.ts

@@ -1,5 +1,6 @@
 export enum NodeTransformers {
     BlockStatementControlFlowTransformer,
+    DeadCodeInjectionTransformer,
     FunctionControlFlowTransformer,
     CatchClauseTransformer,
     FunctionDeclarationTransformer,

+ 2 - 0
src/interfaces/IInitializable.d.ts

@@ -1,4 +1,6 @@
 export interface IInitializable {
+    [key: string]: any;
+
     /**
      * @param args
      */

+ 13 - 0
src/interfaces/node-transformers/IDeadCodeInjectionReplacer.d.ts

@@ -0,0 +1,13 @@
+import * as ESTree from 'estree';
+
+export interface IDeadCodeInjectionReplacer {
+    /**
+     * @param node
+     * @param parentNode
+     * @returns ESTree.Node
+     */
+    replace (
+        node: ESTree.Node,
+        parentNode: ESTree.Node
+    ): ESTree.Node;
+}

+ 3 - 0
src/interfaces/options/IOptions.d.ts

@@ -5,10 +5,13 @@ export interface IOptions {
     readonly compact: boolean;
     readonly controlFlowFlattening: boolean;
     readonly controlFlowFlatteningThreshold: number;
+    readonly deadCodeInjection: boolean;
+    readonly deadCodeInjectionThreshold: number;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
+    readonly mangle: boolean;
     readonly reservedNames: string[];
     readonly rotateStringArray: boolean;
     readonly seed: number;

+ 6 - 1
src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -32,7 +32,8 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     private static readonly controlFlowReplacersMap: Map <string, ControlFlowReplacers> = new Map([
         [NodeType.BinaryExpression, ControlFlowReplacers.BinaryExpressionControlFlowReplacer],
         [NodeType.CallExpression, ControlFlowReplacers.CallExpressionControlFlowReplacer],
-        [NodeType.LogicalExpression, ControlFlowReplacers.LogicalExpressionControlFlowReplacer]
+        [NodeType.LogicalExpression, ControlFlowReplacers.LogicalExpressionControlFlowReplacer],
+        [NodeType.Literal, ControlFlowReplacers.StringLiteralControlFlowReplacer]
     ]);
 
     /**
@@ -220,6 +221,10 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
                 const controlFlowReplacerName: ControlFlowReplacers = <ControlFlowReplacers>FunctionControlFlowTransformer
                     .controlFlowReplacersMap.get(node.type);
 
+                if (controlFlowReplacerName === undefined) {
+                    return node;
+                }
+
                 return {
                     ...this.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage),
                     parentNode

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

@@ -88,7 +88,7 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace
         const storageKeysForCurrentId: string[] | undefined = storageKeysById.get(replacerId);
 
         if (
-            RandomGeneratorUtils.getMathRandom() > usingExistingIdentifierChance &&
+            RandomGeneratorUtils.getMathRandom() < usingExistingIdentifierChance &&
             storageKeysForCurrentId &&
             storageKeysForCurrentId.length
         ) {

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

@@ -0,0 +1,93 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TStatement } from '../../../types/node/TStatement';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStorage } from '../../../interfaces/storages/IStorage';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+
+import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
+import { Node } from '../../../node/Node';
+
+@injectable()
+export class StringLiteralControlFlowReplacer extends AbstractControlFlowReplacer {
+    /**
+     * @type {number}
+     */
+    private static readonly usingExistingIdentifierChance: number = 1;
+
+    /**
+     * @param customNodeFactory
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.Factory__ICustomNode) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(customNodeFactory, options);
+    }
+
+    /**
+     * @param literalNode
+     * @param parentNode
+     * @param controlFlowStorage
+     * @returns {ESTree.Node}
+     */
+    public replace (
+        literalNode: ESTree.Literal,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>
+    ): ESTree.Node {
+        if (Node.isPropertyNode(parentNode) && parentNode.key === literalNode) {
+            return literalNode;
+        }
+
+        if (typeof literalNode.value !== 'string' || literalNode.value.length < 3) {
+            return literalNode;
+        }
+
+        const replacerId: string = String(literalNode.value);
+        const literalFunctionCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.StringLiteralNode);
+
+        literalFunctionCustomNode.initialize(literalNode.value);
+
+        const storageKey: string = this.insertCustomNodeToControlFlowStorage(
+            literalFunctionCustomNode,
+            controlFlowStorage,
+            replacerId,
+            StringLiteralControlFlowReplacer.usingExistingIdentifierChance
+        );
+
+        return this.getControlFlowStorageCallNode(controlFlowStorage.getStorageId(), storageKey);
+    }
+
+    /**
+     * @param controlFlowStorageId
+     * @param storageKey
+     * @returns {ESTree.Node}
+     */
+    protected getControlFlowStorageCallNode (
+        controlFlowStorageId: string,
+        storageKey: string
+    ): ESTree.Node {
+        const controlFlowStorageCallCustomNode: ICustomNode = this.customNodeFactory(
+            CustomNodes.StringLiteralControlFlowStorageCallNode
+        );
+
+        controlFlowStorageCallCustomNode.initialize(controlFlowStorageId, storageKey);
+
+        const statementNode: TStatement = controlFlowStorageCallCustomNode.getNode()[0];
+
+        if (!statementNode || !Node.isExpressionStatementNode(statementNode)) {
+            throw new Error(`\`controlFlowStorageCallCustomNode.getNode()[0]\` should returns array with \`ExpressionStatement\` node`);
+        }
+
+        return statementNode.expression;
+    }
+}

+ 207 - 0
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts

@@ -0,0 +1,207 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IVisitor } from '../../interfaces/IVisitor';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { Node } from '../../node/Node';
+import { Nodes } from '../../node/Nodes';
+import { NodeUtils } from '../../node/NodeUtils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {number}
+     */
+    private static readonly maxNestedBlockStatementsCount: number = 4;
+
+    /**
+     * @type {number}
+     */
+    private static readonly minCollectedBlockStatementsCount: number = 5;
+
+    /**
+     * @type {ESTree.BlockStatement[]}
+     */
+    private readonly collectedBlockStatements: ESTree.BlockStatement[] = [];
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+    }
+
+    /**
+     * @param blockStatementNode
+     * @param collectedBlockStatements
+     */
+    private static collectBlockStatementNodes (
+        blockStatementNode: ESTree.BlockStatement,
+        collectedBlockStatements: ESTree.BlockStatement[]
+    ): void {
+        const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode);
+
+        let nestedBlockStatementsCount: number = 0,
+            isValidBlockStatementNode: boolean = true;
+
+        estraverse.replace(clonedBlockStatementNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                /**
+                 * First step: count nested block statements in current block statement
+                 */
+                if (Node.isBlockStatementNode(node)) {
+                    nestedBlockStatementsCount++;
+                }
+
+                /**
+                 * If nested block statements count bigger then specified amount or current block statement
+                 * contains prohibited nodes - we will stop traversing and leave method
+                 */
+                if (
+                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
+                    Node.isBreakStatementNode(node) ||
+                    Node.isContinueStatementNode(node)
+                ) {
+                    isValidBlockStatementNode = false;
+
+                    return estraverse.VisitorOption.Break;
+                }
+
+                /**
+                 * Second step: rename all identifiers (except identifiers in member expressions)
+                 * in current block statement
+                 */
+                if (Node.isIdentifierNode(node) && !Node.isMemberExpressionNode(parentNode)) {
+                    node.name = RandomGeneratorUtils.getRandomVariableName(6);
+                }
+
+                return node;
+            }
+        });
+
+        if (!isValidBlockStatementNode) {
+            return;
+        }
+
+        collectedBlockStatements.push(clonedBlockStatementNode);
+    }
+
+    /**
+     * @param blockStatementNode
+     * @param randomBlockStatementNode
+     * @return {ESTree.BlockStatement}
+     */
+    private static replaceBlockStatementNode (
+        blockStatementNode: ESTree.BlockStatement,
+        randomBlockStatementNode: ESTree.BlockStatement
+    ): ESTree.BlockStatement {
+        const random1: boolean = RandomGeneratorUtils.getMathRandom() > 0.5;
+        const random2: boolean = RandomGeneratorUtils.getMathRandom() > 0.5;
+
+        const operator: ESTree.BinaryOperator = random1 ? '===' : '!==';
+        const leftString: string = RandomGeneratorUtils.getRandomString(3);
+        const rightString: string = random2 ? leftString : RandomGeneratorUtils.getRandomString(3);
+
+        let consequent: ESTree.BlockStatement,
+            alternate: ESTree.BlockStatement;
+
+        if ((random1 && random2) || (!random1 && !random2)) {
+            consequent = blockStatementNode;
+            alternate = randomBlockStatementNode;
+        } else {
+            consequent = randomBlockStatementNode;
+            alternate = blockStatementNode;
+        }
+
+        let newBlockStatementNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
+            Nodes.getIfStatementNode(
+                Nodes.getBinaryExpressionNode(
+                    operator,
+                    Nodes.getLiteralNode(leftString),
+                    Nodes.getLiteralNode(rightString)
+                ),
+                consequent,
+                alternate
+            )
+        ]);
+
+        newBlockStatementNode = NodeUtils.parentize(newBlockStatementNode);
+
+        return newBlockStatementNode;
+    }
+
+    /**
+     * @return {IVisitor}
+     */
+    public getVisitor (): IVisitor {
+        return {
+            leave: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isProgramNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
+    /**
+     * @param programNode
+     * @param parentNode
+     * @returns {ESTree.Node}
+     */
+    public transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node {
+        this.transformProgramNode(programNode);
+
+        return programNode;
+    }
+
+    /**
+     * @param programNode
+     */
+    private transformProgramNode (programNode: ESTree.Program): void {
+        estraverse.traverse(programNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (!Node.isBlockStatementNode(node)) {
+                    return;
+                }
+
+                DeadCodeInjectionTransformer.collectBlockStatementNodes(node, this.collectedBlockStatements);
+            }
+        });
+
+        if (this.collectedBlockStatements.length < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount) {
+            return;
+        }
+
+        estraverse.replace(programNode, {
+            leave: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (!this.collectedBlockStatements.length) {
+                    return estraverse.VisitorOption.Break;
+                }
+
+                if (
+                    !Node.isBlockStatementNode(node) ||
+                    RandomGeneratorUtils.getMathRandom() > this.options.deadCodeInjectionThreshold
+                ) {
+                    return node;
+                }
+
+                const randomIndex: number = RandomGeneratorUtils.getRandomInteger(0, this.collectedBlockStatements.length - 1);
+                const randomBlockStatementNode: ESTree.BlockStatement = this.collectedBlockStatements.splice(randomIndex, 1)[0];
+
+                if (randomBlockStatementNode === node) {
+                    return node;
+                }
+
+                return DeadCodeInjectionTransformer.replaceBlockStatementNode(node, randomBlockStatementNode);
+            }
+        });
+    }
+}

+ 3 - 29
src/node-transformers/obfuscating-transformers/ObjectExpressionTransformer.ts

@@ -1,7 +1,6 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
-import * as escodegen from 'escodegen-wallaby';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -11,17 +10,13 @@ import { NodeType } from '../../enums/NodeType';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { Node } from '../../node/Node';
-import { Utils } from '../../utils/Utils';
 
 /**
  * replaces:
- *     var object = { 'PSEUDO': 1 };
- *
- * or:
  *     var object = { PSEUDO: 1 };
  *
  * on:
- *     var object = { '\u0050\u0053\u0045\u0055\u0044\u004f': 1 };
+ *     var object = { 'PSEUDO': 1 };
  */
 @injectable()
 export class ObjectExpressionTransformer extends AbstractNodeTransformer {
@@ -34,21 +29,6 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
         super(options);
     }
 
-    /**
-     * @param node
-     * @returns {ESTree.Literal}
-     */
-    private static transformLiteralPropertyKey (node: ESTree.Literal): ESTree.Literal {
-        if (typeof node.value === 'string' && !node['x-verbatim-property']) {
-            node['x-verbatim-property'] = {
-                content : `'${Utils.stringToUnicodeEscapeSequence(node.value)}'`,
-                precedence: escodegen.Precedence.Primary
-            };
-        }
-
-        return node;
-    }
-
     /**
      * @param node
      * @returns {ESTree.Literal}
@@ -57,11 +37,7 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
         return {
             type: NodeType.Literal,
             value: node.name,
-            raw: `'${node.name}'`,
-            'x-verbatim-property': {
-                content : `'${Utils.stringToUnicodeEscapeSequence(node.name)}'`,
-                precedence: escodegen.Precedence.Primary
-            }
+            raw: `'${node.name}'`
         };
     }
 
@@ -90,9 +66,7 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
                     property.shorthand = false;
                 }
 
-                if (Node.isLiteralNode(property.key)) {
-                    property.key = ObjectExpressionTransformer.transformLiteralPropertyKey(property.key);
-                } else if (Node.isIdentifierNode(property.key)) {
+                if (Node.isIdentifierNode(property.key)) {
                     property.key = ObjectExpressionTransformer.transformIdentifierPropertyKey(property.key);
                 }
             });

+ 35 - 0
src/node/NodeUtils.ts

@@ -40,6 +40,41 @@ export class NodeUtils {
         return astTree;
     }
 
+    /**
+     * @param astTree
+     * @return {ESTree.Node}
+     */
+    public static clone <T extends ESTree.Node> (astTree: T): T {
+        const cloneRecursive: (node: T) => T = (node: T) => {
+            const copy: {[key: string]: any} = {};
+
+            Object
+                .keys(node)
+                .filter((property: string) => property !== 'parentNode')
+                .forEach((property: string): void => {
+                    const value: any = (<{[key: string]: any}>node)[property];
+
+                    let clonedValue: any | null;
+
+                    if (value === null || value instanceof RegExp) {
+                        clonedValue = value;
+                    } else if (Array.isArray(value)) {
+                        clonedValue = value.map(cloneRecursive);
+                    } else if (typeof value === 'object') {
+                        clonedValue = cloneRecursive(value);
+                    } else {
+                        clonedValue = value;
+                    }
+
+                    copy[property] = clonedValue;
+                });
+
+            return <T>copy;
+        };
+
+        return NodeUtils.parentize(cloneRecursive(astTree));
+    }
+
     /**
      * @param code
      * @returns {TStatement[]}

+ 7 - 1
src/node/Nodes.ts

@@ -189,13 +189,19 @@ export class Nodes {
     /**
      * @param test
      * @param consequent
+     * @param alternate
      * @returns {ESTree.IfStatement}
      */
-    public static getIfStatementNode (test: ESTree.Expression, consequent: ESTree.BlockStatement): ESTree.IfStatement {
+    public static getIfStatementNode (
+        test: ESTree.Expression,
+        consequent: ESTree.BlockStatement,
+        alternate?: ESTree.BlockStatement
+    ): ESTree.IfStatement {
         return {
             type: NodeType.IfStatement,
             test,
             consequent,
+            ...alternate && {alternate},
             obfuscatedNode: false
         };
     }

+ 18 - 0
src/options/Options.ts

@@ -59,6 +59,18 @@ export class Options implements IOptions {
     @Max(1)
     public readonly controlFlowFlatteningThreshold: number;
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly deadCodeInjection: boolean;
+
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly deadCodeInjectionThreshold: number;
+
     /**
      * @type {boolean}
      */
@@ -87,6 +99,12 @@ export class Options implements IOptions {
     })
     public readonly domainLock: string[];
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly mangle: boolean;
+
     /**
      * @type {string[]}
      */

+ 21 - 188
src/options/OptionsNormalizer.ts

@@ -1,57 +1,33 @@
-import { TInputOptions } from '../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../types/options/TOptionsNormalizerRule';
 
 import { IOptions } from '../interfaces/options/IOptions';
 
-import { TOptionsNormalizerRule } from '../types/options/TOptionsNormalizerRule';
-
-import { Utils } from '../utils/Utils';
+import { ControlFlowFlatteningThresholdRule } from './normalizer-rules/ControlFlowFlatteningThresholdRule';
+import { DeadCodeInjectionRule } from './normalizer-rules/DeadCodeInjectionRule';
+import { DeadCodeInjectionThresholdRule } from './normalizer-rules/DeadCodeInjectionThresholdRule';
+import { DomainLockRule } from './normalizer-rules/DomainLockRule';
+import { SelfDefendingRule } from './normalizer-rules/SelfDefendingRule';
+import { SourceMapBaseUrlRule } from './normalizer-rules/SourceMapBaseUrlRule';
+import { SourceMapFileNameRule } from './normalizer-rules/SourceMapFileNameRule';
+import { StringArrayRule } from './normalizer-rules/StringArrayRule';
+import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
+import { StringArrayThresholdRule } from './normalizer-rules/StringArrayThresholdRule';
 
 export class OptionsNormalizer {
-    /**
-     * @type {TInputOptions}
-     */
-    private static readonly DISABLED_CONTROL_FLOW_FLATTENING_OPTIONS: TInputOptions = {
-        controlFlowFlattening: false,
-        controlFlowFlatteningThreshold: 0
-    };
-
-    /**
-     * @type {TInputOptions}
-     */
-    private static readonly DISABLED_STRING_ARRAY_OPTIONS: TInputOptions = {
-        rotateStringArray: false,
-        stringArray: false,
-        stringArrayEncoding: false,
-        stringArrayThreshold: 0
-    };
-
-    /**
-     * @type {TInputOptions}
-     */
-    private static readonly SELF_DEFENDING_OPTIONS: TInputOptions = {
-        compact: true,
-        selfDefending: true
-    };
-
-    /**
-     * @type {TInputOptions}
-     */
-    private static readonly STRING_ARRAY_ENCODING_OPTIONS: TInputOptions = {
-        stringArrayEncoding: 'base64'
-    };
-
     /**
      * @type {TOptionsNormalizerRule[]}
      */
     private static readonly normalizerRules: TOptionsNormalizerRule[] = [
-        OptionsNormalizer.controlFlowFlatteningThresholdRule,
-        OptionsNormalizer.domainLockRule,
-        OptionsNormalizer.selfDefendingRule,
-        OptionsNormalizer.sourceMapBaseUrlRule,
-        OptionsNormalizer.sourceMapFileNameRule,
-        OptionsNormalizer.stringArrayRule,
-        OptionsNormalizer.stringArrayEncodingRule,
-        OptionsNormalizer.stringArrayThresholdRule,
+        ControlFlowFlatteningThresholdRule,
+        DeadCodeInjectionRule,
+        DeadCodeInjectionThresholdRule,
+        DomainLockRule,
+        SelfDefendingRule,
+        SourceMapBaseUrlRule,
+        SourceMapFileNameRule,
+        StringArrayRule,
+        StringArrayEncodingRule,
+        StringArrayThresholdRule,
     ];
 
     /**
@@ -69,147 +45,4 @@ export class OptionsNormalizer {
 
         return normalizedOptions;
     }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static controlFlowFlatteningThresholdRule (options: IOptions): IOptions {
-        if (options.controlFlowFlatteningThreshold === 0) {
-            options = {
-                ...options,
-                ...OptionsNormalizer.DISABLED_CONTROL_FLOW_FLATTENING_OPTIONS
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static domainLockRule (options: IOptions): IOptions {
-        if (options.domainLock.length) {
-            const normalizedDomains: string[] = [];
-
-            for (const domain of options.domainLock) {
-                normalizedDomains.push(Utils.extractDomainFromUrl(domain));
-            }
-
-            options = {
-                ...options,
-                domainLock: normalizedDomains
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static selfDefendingRule (options: IOptions): IOptions {
-        if (options.selfDefending) {
-            options = {
-                ...options,
-                ...OptionsNormalizer.SELF_DEFENDING_OPTIONS
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static sourceMapBaseUrlRule (options: IOptions): IOptions {
-        const { sourceMapBaseUrl }: { sourceMapBaseUrl: string } = options;
-
-        if (!options.sourceMapFileName) {
-            options = {
-                ...options,
-                sourceMapBaseUrl: ''
-            };
-
-            return options;
-        }
-
-        if (sourceMapBaseUrl && !sourceMapBaseUrl.endsWith('/')) {
-            options = {
-                ...options,
-                sourceMapBaseUrl: `${sourceMapBaseUrl}/`
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static sourceMapFileNameRule (options: IOptions): IOptions {
-        let { sourceMapFileName }: { sourceMapFileName: string } = options;
-
-        if (sourceMapFileName) {
-            sourceMapFileName = sourceMapFileName
-                .replace(/^\/+/, '')
-                .split('.')[0];
-
-            options = {
-                ...options,
-                sourceMapFileName: `${sourceMapFileName}.js.map`
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static stringArrayRule (options: IOptions): IOptions {
-        if (!options.stringArray) {
-            options = {
-                ...options,
-                ...OptionsNormalizer.DISABLED_STRING_ARRAY_OPTIONS
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static stringArrayEncodingRule (options: IOptions): IOptions {
-        if (options.stringArrayEncoding === true) {
-            options = {
-                ...options,
-                ...OptionsNormalizer.STRING_ARRAY_ENCODING_OPTIONS
-            };
-        }
-
-        return options;
-    }
-
-    /**
-     * @param options
-     * @returns {IOptions}
-     */
-    private static stringArrayThresholdRule (options: IOptions): IOptions {
-        if (options.stringArrayThreshold === 0) {
-            options = {
-                ...options,
-                ...OptionsNormalizer.DISABLED_STRING_ARRAY_OPTIONS
-            };
-        }
-
-        return options;
-    }
 }

+ 20 - 0
src/options/normalizer-rules/ControlFlowFlatteningThresholdRule.ts

@@ -0,0 +1,20 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+const DISABLED_CONTROL_FLOW_FLATTENING_OPTIONS: TInputOptions = {
+    controlFlowFlattening: false,
+    controlFlowFlatteningThreshold: 0
+};
+
+export const ControlFlowFlatteningThresholdRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.controlFlowFlatteningThreshold === 0) {
+        options = {
+            ...options,
+            ...DISABLED_CONTROL_FLOW_FLATTENING_OPTIONS
+        };
+    }
+
+    return options;
+};

+ 34 - 0
src/options/normalizer-rules/DeadCodeInjectionRule.ts

@@ -0,0 +1,34 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { DEFAULT_PRESET } from '../presets/Default';
+
+const ENABLED_DEAD_CODE_INJECTION_OPTIONS: TInputOptions = {
+    deadCodeInjection: true,
+    stringArray: true
+};
+
+const ENABLED_STRING_ARRAY_THRESHOLD_OPTIONS: TInputOptions = {
+    stringArray: true,
+    stringArrayThreshold: DEFAULT_PRESET.stringArrayThreshold
+};
+
+export const DeadCodeInjectionRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.deadCodeInjection) {
+        options = {
+            ...options,
+            ...ENABLED_DEAD_CODE_INJECTION_OPTIONS,
+        };
+
+        if (!options.stringArrayThreshold) {
+            options = {
+                ...options,
+                ...ENABLED_STRING_ARRAY_THRESHOLD_OPTIONS
+            };
+        }
+    }
+
+    return options;
+};

+ 20 - 0
src/options/normalizer-rules/DeadCodeInjectionThresholdRule.ts

@@ -0,0 +1,20 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+const DISABLED_DEAD_CODE_INJECTION_OPTIONS: TInputOptions = {
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0
+};
+
+export const DeadCodeInjectionThresholdRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.deadCodeInjectionThreshold === 0) {
+        options = {
+            ...options,
+            ...DISABLED_DEAD_CODE_INJECTION_OPTIONS
+        };
+    }
+
+    return options;
+};

+ 22 - 0
src/options/normalizer-rules/DomainLockRule.ts

@@ -0,0 +1,22 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { Utils } from '../../utils/Utils';
+
+export const DomainLockRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.domainLock.length) {
+        const normalizedDomains: string[] = [];
+
+        for (const domain of options.domainLock) {
+            normalizedDomains.push(Utils.extractDomainFromUrl(domain));
+        }
+
+        options = {
+            ...options,
+            domainLock: normalizedDomains
+        };
+    }
+
+    return options;
+};

+ 20 - 0
src/options/normalizer-rules/SelfDefendingRule.ts

@@ -0,0 +1,20 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+const SELF_DEFENDING_OPTIONS: TInputOptions = {
+    compact: true,
+    selfDefending: true
+};
+
+export const SelfDefendingRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.selfDefending) {
+        options = {
+            ...options,
+            ...SELF_DEFENDING_OPTIONS
+        };
+    }
+
+    return options;
+};

+ 25 - 0
src/options/normalizer-rules/SourceMapBaseUrlRule.ts

@@ -0,0 +1,25 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+export const SourceMapBaseUrlRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    const { sourceMapBaseUrl }: { sourceMapBaseUrl: string } = options;
+
+    if (!options.sourceMapFileName) {
+        options = {
+            ...options,
+            sourceMapBaseUrl: ''
+        };
+
+        return options;
+    }
+
+    if (sourceMapBaseUrl && !sourceMapBaseUrl.endsWith('/')) {
+        options = {
+            ...options,
+            sourceMapBaseUrl: `${sourceMapBaseUrl}/`
+        };
+    }
+
+    return options;
+};

+ 20 - 0
src/options/normalizer-rules/SourceMapFileNameRule.ts

@@ -0,0 +1,20 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+export const SourceMapFileNameRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    let { sourceMapFileName }: { sourceMapFileName: string } = options;
+
+    if (sourceMapFileName) {
+        sourceMapFileName = sourceMapFileName
+            .replace(/^\/+/, '')
+            .split('.')[0];
+
+        options = {
+            ...options,
+            sourceMapFileName: `${sourceMapFileName}.js.map`
+        };
+    }
+
+    return options;
+};

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

@@ -0,0 +1,19 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+const STRING_ARRAY_ENCODING_OPTIONS: TInputOptions = {
+    stringArrayEncoding: 'base64'
+};
+
+export const StringArrayEncodingRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.stringArrayEncoding === true) {
+        options = {
+            ...options,
+            ...STRING_ARRAY_ENCODING_OPTIONS
+        };
+    }
+
+    return options;
+};

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

@@ -0,0 +1,22 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+const DISABLED_STRING_ARRAY_OPTIONS: TInputOptions = {
+    rotateStringArray: false,
+    stringArray: false,
+    stringArrayEncoding: false,
+    stringArrayThreshold: 0
+};
+
+export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (!options.stringArray) {
+        options = {
+            ...options,
+            ...DISABLED_STRING_ARRAY_OPTIONS
+        };
+    }
+
+    return options;
+};

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

@@ -0,0 +1,22 @@
+import { TInputOptions } from '../../types/options/TInputOptions';
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+const DISABLED_STRING_ARRAY_OPTIONS: TInputOptions = {
+    rotateStringArray: false,
+    stringArray: false,
+    stringArrayEncoding: false,
+    stringArrayThreshold: 0
+};
+
+export const StringArrayThresholdRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.stringArrayThreshold === 0) {
+        options = {
+            ...options,
+            ...DISABLED_STRING_ARRAY_OPTIONS
+        };
+    }
+
+    return options;
+};

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

@@ -6,10 +6,13 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0.75,
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0.4,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     rotateStringArray: true,
     seed: 0,
@@ -20,6 +23,6 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     sourceMapMode: SourceMapMode.Separate,
     stringArray: true,
     stringArrayEncoding: false,
-    stringArrayThreshold: 0.8,
-    unicodeEscapeSequence: true
+    stringArrayThreshold: 0.75,
+    unicodeEscapeSequence: false
 });

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

@@ -6,10 +6,13 @@ export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0,
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     rotateStringArray: false,
     seed: 0,
@@ -21,5 +24,5 @@ export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
     stringArray: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0,
-    unicodeEscapeSequence: true
+    unicodeEscapeSequence: false
 });

+ 1 - 0
src/types/cli/TCLISanitizer.d.ts

@@ -0,0 +1 @@
+export type TCLISanitizer = (value: string) => any;

+ 3 - 4
src/utils/CryptUtils.ts

@@ -16,7 +16,7 @@ export class CryptUtils {
         });
 
         for (
-            let block: number|undefined, charCode: number, idx: number = 0, map: string = chars;
+            let block: number | undefined, charCode: number, idx: number = 0, map: string = chars;
             string.charAt(idx | 0) || (map = '=', idx % 1);
             output += map.charAt(63 & block >> 8 - idx % 1 * 8)
         ) {
@@ -42,7 +42,7 @@ export class CryptUtils {
         const escapeRegExp: (s: string) => string = (s: string) =>
             s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
 
-        const randomMerge: (s1: string, s2: string) => string = function (s1: string, s2: string): string {
+        const randomMerge: (s1: string, s2: string) => string = (s1: string, s2: string): string => {
             let i1: number = -1,
                 i2: number = -1,
                 result: string = '';
@@ -64,7 +64,7 @@ export class CryptUtils {
         });
 
         let randomStringDiff: string = randomString.replace(
-            new RegExp('[' + escapeRegExp(str) + ']', 'g'),
+            new RegExp(`[${escapeRegExp(str)}]`, 'g'),
             '');
 
         const randomStringDiffArray: string[] = randomStringDiff.split('');
@@ -73,7 +73,6 @@ export class CryptUtils {
         randomStringDiff = randomStringDiffArray.join('');
 
         return [randomMerge(str, randomStringDiff), randomStringDiff];
-
     }
 
     /**

+ 29 - 79
test/dev/dev.ts

@@ -6,89 +6,39 @@ import { NO_CUSTOM_NODES_PRESET } from '../../src/options/presets/NoCustomNodes'
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            (function(){
-                var result = 1,
-                    term1 = 0,
-                    term2 = 1,
-                    i = 1;
-                while(i < 10)
-                {
-                    var test = 10;
-                    result = term1 + term2;
-                    console.log(result);
-                    term1 = term2;
-                    term2 = result;
-                    i++;
-                }
-        
-                console.log(test);
-                
-                var test = function (test) {
-                    console.log(test);
-                    
-                    if (true) {
-                        var test = 5
-                    }
-                    
-                    return test;
-                }
-                
-                console.log(test(1));
-                
-                function test2 (abc) {
-                    function test1 () {
-                      console.log('inside', abc.item);
-                    }
-                    
-                    console.log('тест', abc);
-                    
-                    var abc = {};
-                    
-                    return abc.item = 15, test1();
+        (function(){
+            if (true) {
+                var foo = function () {
+                    console.log('abc');
                 };
-                
-                var regexptest = /version\\/(\\d+)/i;
-                console.log(regexptest);
-                
-                test2(22);
-                console.log(105.4);
-                console.log(true, false);
-                
-                var sA = 'shorthand1';
-                var sB = 'shorthand2';
-                
-                console.log({sA, sB});
-                
-                try {
-                } catch (error) {
-                    console.log(error);
-                }
-                
-                function foo () {
-                    return function () {
-                        var sum1 = 10 + 20;
-                        var sum2 = 20 + 30;
-                        var sum3 = 30 + 50;
-                        var sub = sum3 - sum2;
-                        
-                        return sum1 + sub;
-                    }
-                }
-                
-                console.log(foo()());
-                
-                if (true) {
-                    console.log(\`1\`);
-                    console.log(\`2\`);
-                    console.log(\`3\`);
-                    console.log(\`4\`);
-                    console.log(\`5\`);
-                }
-            })();
+                var bar = function () {
+                    console.log('def');
+                };
+                var baz = function () {
+                    console.log('ghi');
+                };
+                var bark = function () {
+                    console.log('jkl');
+                };
+                var hawk = function () {
+                    console.log('mno');
+                };
+            
+                foo();
+                bar();
+                baz();
+                bark();
+                hawk();
+            }
+        })();
         `,
         {
             ...NO_CUSTOM_NODES_PRESET,
-            compact: false
+            compact: false,
+            stringArray: false,
+            stringArrayThreshold: 1,
+            deadCodeInjection: true,
+            deadCodeInjectionThreshold: 1
         }
     ).getObfuscatedCode();
 

+ 9 - 3
test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts

@@ -9,7 +9,9 @@ import { readFileAsString } from '../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 describe('ConsoleOutputDisableExpressionNode', () => {
-    const regExp = /(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*)*'\]\['(\\x[a-f0-9]*)*'\] *= *_0x([a-f0-9]){4,6};){7}/u;
+    const consoleLogRegExp: RegExp = /_0x([a-f0-9]){4,6}\['console'\]\['log'\] *= *_0x([a-f0-9]){4,6};/u;
+    const consoleErrorRegExp: RegExp = /_0x([a-f0-9]){4,6}\['console'\]\['error'\] *= *_0x([a-f0-9]){4,6};/u;
+    const consoleWarnRegExp: RegExp = /_0x([a-f0-9]){4,6}\['console'\]\['warn'\] *= *_0x([a-f0-9]){4,6};/u;
 
     it('should correctly append `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
@@ -20,7 +22,9 @@ describe('ConsoleOutputDisableExpressionNode', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(), regExp);
+        assert.match(obfuscationResult.getObfuscatedCode(), consoleLogRegExp);
+        assert.match(obfuscationResult.getObfuscatedCode(), consoleErrorRegExp);
+        assert.match(obfuscationResult.getObfuscatedCode(), consoleWarnRegExp);
     });
 
     it('should\'t append `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is not set', () => {
@@ -33,6 +37,8 @@ describe('ConsoleOutputDisableExpressionNode', () => {
             }
         );
 
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), regExp);
+        assert.notMatch(obfuscationResult.getObfuscatedCode(), consoleLogRegExp);
+        assert.notMatch(obfuscationResult.getObfuscatedCode(), consoleErrorRegExp);
+        assert.notMatch(obfuscationResult.getObfuscatedCode(), consoleWarnRegExp);
     });
 });

+ 25 - 9
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -117,9 +117,9 @@ describe('JavaScriptObfuscator', () => {
             );
         });
 
-        it('should obfuscate simple code with literal variable value', () => {
-            let pattern1: RegExp = /^var _0x(\w){4} *= *\['(\\[x|u]\d+)+'\];/,
-                pattern2: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/,
+        it('should obfuscate simple code with latin literal variable value', () => {
+            let stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/,
+                stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/,
                 obfuscatedCode1: string = JavaScriptObfuscator.obfuscate(
                     readFileAsString(__dirname + '/fixtures/simple-input-2.js'),
                     {
@@ -127,7 +127,15 @@ describe('JavaScriptObfuscator', () => {
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
-                ).getObfuscatedCode(),
+                ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode1, stringArrayLatinRegExp);
+            assert.match(obfuscatedCode1, stringArrayCallRegExp);
+        });
+
+        it('should obfuscate simple code with cyrillic literal variable value', () => {
+            let stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['(\\u\d+)+'\];/,
+                stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/,
                 obfuscatedCode2: string = JavaScriptObfuscator.obfuscate(
                     readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js'),
                     {
@@ -137,11 +145,8 @@ describe('JavaScriptObfuscator', () => {
                     }
                 ).getObfuscatedCode();
 
-            assert.match(obfuscatedCode1, pattern1);
-            assert.match(obfuscatedCode1, pattern2);
-
-            assert.match(obfuscatedCode2, pattern1);
-            assert.match(obfuscatedCode2, pattern2);
+            assert.match(obfuscatedCode2, stringArrayCyrillicRegExp);
+            assert.match(obfuscatedCode2, stringArrayCallRegExp);
         });
 
         it('should returns same code every time with same `seed`', function () {
@@ -206,6 +211,17 @@ describe('JavaScriptObfuscator', () => {
             );
         });
 
+        it('should mangle obfuscated code', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { mangle: true }
+            );
+            const mangleMatch: RegExp = /var *a *= *0x1/;
+
+            assert.match(obfuscationResult1.getObfuscatedCode(), mangleMatch);
+        });
+
         afterEach(() => {
             RandomGeneratorUtils.initializeRandomGenerator(0);
         });

+ 3 - 0
test/functional-tests/javascript-obfuscator/fixtures/mangle.js

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

+ 17 - 15
test/functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec.ts

@@ -10,14 +10,16 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('BlockStatementControlFlowTransformer', () => {
     describe('transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node', () => {
+        const switchCaseMapStringRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *\{'.*' *: *'(.*)'\};/;
+        const switchCaseMapVariableRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6}\['.*'\]\['split'\]\('\\x7c'\)/;
+
         describe('variant #1: 5 simple statements', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/input-1.js'),
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1,
-                    unicodeEscapeSequence: false
+                    controlFlowFlatteningThreshold: 1
                 }
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
@@ -32,9 +34,8 @@ describe('BlockStatementControlFlowTransformer', () => {
             const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
             const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
 
-            const switchCaseMapRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *'(.*?)'/;
-            const switchCaseMapMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapRegExp);
-            const switchCaseMapMatch: string = switchCaseMapMatches[1];
+            const switchCaseMapStringMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapStringRegExp);
+            const switchCaseMapMatch: string = switchCaseMapStringMatches[1];
             const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
 
             it('should save all statements', () => {
@@ -52,6 +53,7 @@ describe('BlockStatementControlFlowTransformer', () => {
 
             it('should create variable with order of switch cases sequence', () => {
                 assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
+                assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
             });
         });
 
@@ -77,9 +79,8 @@ describe('BlockStatementControlFlowTransformer', () => {
             const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
             const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
 
-            const switchCaseMapRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *'(.*?)'/;
-            const switchCaseMapMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapRegExp);
-            const switchCaseMapMatch: string = switchCaseMapMatches[1];
+            const switchCaseMapStringMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapStringRegExp);
+            const switchCaseMapMatch: string = switchCaseMapStringMatches[1];
             const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
 
             it('should save all statements', () => {
@@ -97,6 +98,7 @@ describe('BlockStatementControlFlowTransformer', () => {
 
             it('should create variable with order of switch cases sequence', () => {
                 assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
+                assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
             });
         });
 
@@ -111,7 +113,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *console\['(\\x[a-f0-9]*){3}'\]\(0x1\); *\} *\( *\) *\);$/;
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *console\['log'\]\(0x1\); *\} *\( *\) *\);$/;
 
             it('shouldn\'t transform block statement if statements length less than 5', () => {
                 assert.match(obfuscatedCode, statementRegExp);
@@ -129,7 +131,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *const *_0x([a-f0-9]){4,6} *= *0x1; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/;
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *const *_0x([a-f0-9]){4,6} *= *0x1; *console\['log'\]\(0x1\);/;
 
             it('shouldn\'t transform block statement if block statement contain variable declaration with `const` kind', () => {
                 assert.match(obfuscatedCode, statementRegExp);
@@ -147,7 +149,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *let *_0x([a-f0-9]){4,6} *= *0x1; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/;
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *let *_0x([a-f0-9]){4,6} *= *0x1; *console\['log'\]\(0x1\);/;
 
             it('shouldn\'t transform block statement if block statement contain variable declaration with `let` kind', () => {
                 assert.match(obfuscatedCode, statementRegExp);
@@ -165,7 +167,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *break; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/;
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *break; *console\['log'\]\(0x1\);/;
 
             it('shouldn\'t transform block statement if block statement contain break statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
@@ -183,7 +185,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *continue; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/;
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *continue; *console\['log'\]\(0x1\);/;
 
             it('shouldn\'t transform block statement if block statement contain continue statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
@@ -201,7 +203,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *function *_0x([a-f0-9]){4,6} *\( *\) *\{ *\} *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/;
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *function *_0x([a-f0-9]){4,6} *\( *\) *\{ *\} *console\['log'\]\(0x1\);/;
 
             it('shouldn\'t transform block statement if block statement contain function declaration', () => {
                 assert.match(obfuscatedCode, statementRegExp);
@@ -222,7 +224,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
 
             const regExp1: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/g;
-            const regExp2: RegExp = /\(function *\( *\) *\{ *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/g;
+            const regExp2: RegExp = /\(function *\( *\) *\{ *console\['log'\]\(0x1\);/g;
             const transformedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
             const untouchedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.length;
 

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

@@ -20,7 +20,7 @@ describe('BinaryExpressionControlFlowReplacer', () => {
                 }
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\]\(0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(0x1, *0x2\);/;
 
             it('should replace binary expression node by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
@@ -47,8 +47,8 @@ describe('BinaryExpressionControlFlowReplacer', () => {
                         }
                     );
                     const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(0x1, *0x2\);/;
-                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(0x2, *0x3\);/;
+                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(0x1, *0x2\);/;
+                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(0x2, *0x3\);/;
 
                     const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);

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

@@ -20,7 +20,7 @@ describe('CallExpressionControlFlowReplacer', () => {
                 }
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\]\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
 
             it('should replace call expression node by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
@@ -47,8 +47,8 @@ describe('CallExpressionControlFlowReplacer', () => {
                         }
                     );
                     const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
-                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(_0x([a-f0-9]){4,6}, *0x2, *0x3\);/;
+                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(_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{3}'\])\(_0x([a-f0-9]){4,6}, *0x2, *0x3\);/;
 
                     const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
@@ -78,7 +78,7 @@ describe('CallExpressionControlFlowReplacer', () => {
                 }
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\\x73\\x75\\x6d'\]\(0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['sum'\]\(0x1, *0x2\);/;
 
             it('shouldn\'t replace call expression node by call to control flow storage node if call expression callee is member expression node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);

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

@@ -20,7 +20,7 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                 }
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\]\(!!\[\], *!\[\]\);/;
+            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(!!\[\], *!\[\]\);/;
 
             it('should replace logical expression node by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
@@ -47,8 +47,8 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                         }
                     );
                     const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(!!\[\], *!\[\]\);/;
-                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(!\[\], *!!\[\]\);/;
+                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(!!\[\], *!\[\]\);/;
+                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(!\[\], *!!\[\]\);/;
 
                     const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
@@ -78,7 +78,7 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                 }
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['(\\x[a-f0-9]*){3}'\]\(!_0x([a-f0-9]){4,6}, *!_0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(!_0x([a-f0-9]){4,6}, *!_0x([a-f0-9]){4,6}\);/;
 
             it('should replace logical expression node with unary expression by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);

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

@@ -0,0 +1,33 @@
+import { assert } from 'chai';
+
+import { IObfuscationResult } from '../../../../../../src/interfaces/IObfuscationResult';
+
+import { NO_CUSTOM_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator';
+
+describe('StringLiteralControlFlowReplacer', () => {
+    describe('replace (literalNode: ESTree.Literal,parentNode: ESTree.Node,controlFlowStorage: IStorage <ICustomNode>)', () => {
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+            readFileAsString(__dirname + '/fixtures/input-1.js'),
+            {
+                ...NO_CUSTOM_NODES_PRESET,
+                controlFlowFlattening: true,
+                controlFlowFlatteningThreshold: 1
+            }
+        );
+        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+        const controlFlowStorageStringLiteralRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *\{'\w{3}' *: *'test'\};/;
+        const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\];/;
+
+        it('should add string literal node as property of control flow storage node', () => {
+            assert.match(obfuscatedCode, controlFlowStorageStringLiteralRegExp);
+        });
+
+        it('should replace string literal node by call to control flow storage node', () => {
+            assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/fixtures/input-1.js

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

+ 6 - 6
test/functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec.ts

@@ -12,15 +12,15 @@ describe('FunctionControlFlowTransformer', () => {
     const variableMatch: string = '_0x([a-f0-9]){4,6}';
     const rootControlFlowStorageNodeMatch: string = `` +
         `var *${variableMatch} *= *\\{` +
-            `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+            `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                 `return *${variableMatch} *\\+ *${variableMatch};` +
             `\\}` +
         `\\};` +
     ``;
     const innerControlFlowStorageNodeMatch: string = `` +
         `var *${variableMatch} *= *\\{` +
-            `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
-                `return *${variableMatch}\\['(\\\\x[a-f0-9]*){3}'\\]\\(${variableMatch}, *${variableMatch}\\);` +
+            `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+                `return *${variableMatch}\\['\\w{3}'\\]\\(${variableMatch}, *${variableMatch}\\);` +
             `\\}` +
         `\\};` +
     ``;
@@ -95,10 +95,10 @@ describe('FunctionControlFlowTransformer', () => {
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const regexp: RegExp = new RegExp(
                 `var *${variableMatch} *= *\\{` +
-                    `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+                    `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                         `return *${variableMatch} *\\+ *${variableMatch};` +
                     `\\}, *` +
-                    `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+                    `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                         `return *${variableMatch} *- *${variableMatch};` +
                     `\\}` +
                 `\\};`
@@ -130,7 +130,7 @@ describe('FunctionControlFlowTransformer', () => {
             const expectedValue: number = 0;
             const regExp: RegExp = new RegExp(
                 `var *[a-zA-Z]{6} *= *\\{` +
-                    `'(\\\\x[a-f0-9]*){3}' *: *function *_0x[0-9] *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+                    `'\\w{3}' *: *function *_0x[0-9] *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                         `return *${variableMatch} *\\+ *${variableMatch};` +
                     `\\}` +
                 `\\};`

+ 3 - 3
test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts

@@ -18,7 +18,7 @@ describe('MemberExpressionTransformer', () => {
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\['\\x6c\\x6f\\x67'\];/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\['log'\];/);
         });
 
         it('should replace member expression dot notation call by square brackets call to unicode array', () => {
@@ -31,7 +31,7 @@ describe('MemberExpressionTransformer', () => {
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['\\x6c\\x6f\\x67'\];/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['log'\];/);
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\[_0x([a-f0-9]){4}\('0x0'\)\];/);
         });
     });
@@ -47,7 +47,7 @@ describe('MemberExpressionTransformer', () => {
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['\\x6c\\x6f\\x67'\];/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['log'\];/);
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\[_0x([a-f0-9]){4}\('0x0'\)\];/);
         });
 

+ 2 - 2
test/functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec.ts

@@ -19,7 +19,7 @@ describe('MethodDefinitionTransformer', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /\['\\x62\\x61\\x72'\]\(\)\{\}/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /\['bar'\]\(\)\{\}/);
     });
 
     it('should replace method definition node `key` property with unicode array call', () => {
@@ -32,7 +32,7 @@ describe('MethodDefinitionTransformer', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['\\x62\\x61\\x72'\];/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['bar'\];/);
         assert.match(obfuscationResult.getObfuscatedCode(),  /\[_0x([a-f0-9]){4}\('0x0'\)\]\(\)\{\}/);
     });
 

+ 214 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts

@@ -0,0 +1,214 @@
+import { assert } from 'chai';
+
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
+
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
+
+describe('DeadCodeInjectionTransformer', () => {
+    const variableMatch: string = '_0x([a-f0-9]){4,6}';
+    const hexMatch: string = '0x[a-f0-9]';
+
+    describe('transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node', () => {
+        describe('variant #1 - 5 simple block statements', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/input-1.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 1,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+            const deadCodeMatch: string = `` +
+                `if *\\(${variableMatch}\\('${hexMatch}'\\) *[=|!]== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{`+
+                    `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                `\\} *else *\\{`+
+                    `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                `\\}` +
+            ``;
+            const regexp: RegExp = new RegExp(deadCodeMatch, 'g');
+            const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+
+            it('should replace block statements with condition with original block statements and dead code', () => {
+                assert.isNotNull(matches);
+                assert.equal(matches.length, 5);
+            });
+        });
+
+        describe('variant #2 - 4 simple block statements', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/block-statements-min-count.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 1,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+            const codeMatch: string = `` +
+                `var *${variableMatch} *= *function *\\(\\) *\\{` +
+                    `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                `\\};` +
+            ``;
+            const regexp: RegExp = new RegExp(codeMatch, 'g');
+            const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+
+            it('shouldn\'t add dead code if block statements count is less than 5', () => {
+                assert.isNotNull(matches);
+                assert.equal(matches.length, 4);
+            });
+        });
+
+        describe('variant #3 - deadCodeInjectionThreshold: 0', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/input-1.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 0,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+            const codeMatch: string = `` +
+                `var *${variableMatch} *= *function *\\(\\) *\\{` +
+                    `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                `\\};` +
+            ``;
+            const regexp: RegExp = new RegExp(codeMatch, 'g');
+            const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+
+            it('shouldn\'t add dead code if `deadCodeInjectionThreshold` option value is `0`', () => {
+                assert.isNotNull(matches);
+                assert.equal(matches.length, 5);
+            });
+        });
+
+        describe('variant #4 - break or continue statement in block statement', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/break-continue-statement.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 1,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            const functionMatch: string = `` +
+                `var *${variableMatch} *= *function *\\(\\) *\\{` +
+                    `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                `\\};` +
+            ``;
+            const loopMatch: string = `` +
+                `for *\\(var *${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *\\{` +
+                    `(?:continue|break);` +
+                `\\}` +
+            ``;
+
+            const functionRegExp: RegExp = new RegExp(functionMatch, 'g');
+            const loopRegExp: RegExp = new RegExp(loopMatch, 'g');
+
+            const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
+            const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
+
+            it('shouldn\'t add dead code if block statement contains `continue` or `break` statements', () => {
+                assert.isNotNull(functionMatches);
+                assert.isNotNull(loopMatches);
+
+                assert.equal(functionMatches.length, 4);
+                assert.equal(loopMatches.length, 2);
+            });
+        });
+
+        describe('variant #5 - chance of `IfStatement` variant', () => {
+            const samplesCount: number = 1000;
+            const delta: number = 0.1;
+            const expectedValue: number = 0.25;
+
+            const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
+            const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
+
+            const match1: string = `` +
+                `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
+                    `console.*` +
+                `\\} *else *\\{` +
+                    `${variableMatch}.*` +
+                `\\}` +
+            ``;
+            const match2: string = `` +
+                `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
+                    `console.*` +
+                `\\} *else *\\{` +
+                    `${variableMatch}.*` +
+                `\\}` +
+            ``;
+            const match3: string = `` +
+                `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
+                    `${variableMatch}.*` +
+                `\\} *else *\\{` +
+                    `console.*` +
+                `\\}` +
+            ``;
+            const match4: string = `` +
+                `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
+                    `${variableMatch}.*` +
+                `\\} *else *\\{` +
+                    `console.*` +
+                `\\}` +
+            ``;
+
+            const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
+            const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
+            const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
+            const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
+
+            let count1: number = 0;
+            let count2: number = 0;
+            let count3: number = 0;
+            let count4: number = 0;
+
+            for (let i = 0; i < samplesCount; i++) {
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js'),
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+                if (regExp1.test(obfuscatedCode)) {
+                    count1++;
+                } else if (regExp2.test(obfuscatedCode)) {
+                    count2++;
+                } else if (regExp3.test(obfuscatedCode)) {
+                    count3++;
+                } else if (regExp4.test(obfuscatedCode)) {
+                    count4++;
+                }
+            }
+
+            it('each of four `IfStatement` variant should have distribution close to `0.25`', () => {
+                assert.closeTo(count1 / samplesCount, expectedValue, delta);
+                assert.closeTo(count2 / samplesCount, expectedValue, delta);
+                assert.closeTo(count3 / samplesCount, expectedValue, delta);
+                assert.closeTo(count4 / samplesCount, expectedValue, delta);
+            });
+        });
+    });
+});

+ 21 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/block-statements-min-count.js

@@ -0,0 +1,21 @@
+(function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+        };
+        var bar = function () {
+            console.log('def');
+        };
+        var baz = function () {
+            console.log('ghi');
+        };
+        var bark = function () {
+            console.log('jkl');
+        };
+
+        foo();
+        bar();
+        baz();
+        bark();
+    }
+})();

+ 29 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/break-continue-statement.js

@@ -0,0 +1,29 @@
+(function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+        };
+        var bar = function () {
+            console.log('def');
+        };
+        var baz = function () {
+            console.log('ghi');
+        };
+        var bark = function () {
+            console.log('jkl');
+        };
+
+        for (var i = 0; i < 1; i++) {
+            continue;
+        }
+
+        for (var i = 0; i < 1; i++) {
+            break;
+        }
+
+        foo();
+        bar();
+        baz();
+        bark();
+    }
+})();

+ 25 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/if-statement-variants-distribution.js

@@ -0,0 +1,25 @@
+(function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+        };
+        var bar = function () {
+            alert('def');
+        };
+        var baz = function () {
+            alert('ghi');
+        };
+        var bark = function () {
+            alert('jkl');
+        };
+        var hawk = function () {
+            alert('mno');
+        };
+
+        foo();
+        bar();
+        baz();
+        bark();
+        hawk();
+    }
+})();

+ 25 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/input-1.js

@@ -0,0 +1,25 @@
+(function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+        };
+        var bar = function () {
+            console.log('def');
+        };
+        var baz = function () {
+            console.log('ghi');
+        };
+        var bark = function () {
+            console.log('jkl');
+        };
+        var hawk = function () {
+            console.log('mno');
+        };
+
+        foo();
+        bar();
+        baz();
+        bark();
+        hawk();
+    }
+})();

+ 1 - 1
test/functional-tests/node-transformers/obfuscating-transformers/catch-clause-transformer/CatchClauseTransformer.spec.ts

@@ -18,7 +18,7 @@ describe('CatchClauseTransformer', () => {
         );
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
         const paramNameRegExp: RegExp = /catch *\((_0x([a-f0-9]){4,6})\) *\{/;
-        const bodyParamNameRegExp: RegExp = /console\['\\x6c\\x6f\\x67'\]\((_0x([a-f0-9]){4,6})\);/;
+        const bodyParamNameRegExp: RegExp = /console\['log'\]\((_0x([a-f0-9]){4,6})\);/;
 
         it('should transform catch clause node', () => {
             assert.match(obfuscatedCode, paramNameRegExp);

+ 1 - 1
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts

@@ -22,7 +22,7 @@ describe('FunctionTransformer', () => {
             const functionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
                 .match(/var _0x[a-f0-9]{4,6} *= *function *\((_0x[a-f0-9]{4,6})\) *\{/);
             const functionBodyIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/console\['\\x6c\\x6f\\x67'\]\((_0x[a-f0-9]{4,6})\)/);
+                .match(/console\['log'\]\((_0x[a-f0-9]{4,6})\)/);
 
             const functionParamIdentifierName: string = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
             const functionBodyIdentifierName: string = (<RegExpMatchArray>functionBodyIdentifierMatch)[1];

+ 38 - 38
test/functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec.ts

@@ -10,59 +10,55 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('LiteralTransformer', () => {
     describe('transformation of literal node with string value', () => {
-        it('should replace literal node value with unicode escape sequence', () => {
+        it('should replace literal node value with value from string array', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 {
-                    ...NO_CUSTOM_NODES_PRESET
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'\\x74\\x65\\x73\\x74';$/);
+            assert.match(
+                obfuscationResult.getObfuscatedCode(),
+                /^var *_0x([a-f0-9]){4} *= *\['test'\];/
+            );
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
         });
 
-        it('should replace literal node value with unicode escape sequence from string array', () => {
+        it('shouldn\'t replace literal node value with value from string array if `stringArray` option is disabled', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
+                    ...NO_CUSTOM_NODES_PRESET
                 }
             );
 
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/
+                /^var *test *= *'test';/
             );
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
         });
 
-        it('should create only one item in string array for same literal node values', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/same-literal-values.js'),
+        it('should\'t throw an error when string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
+            assert.doesNotThrow(() => JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/error-when-non-latin.js'),
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
                     stringArrayThreshold: 1
                 }
-            );
-
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/
-            );
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
+            ));
         });
 
-        it('should replace literal node value with raw value from string array if `unicodeEscapeSequence` is disabled', () => {
+        it('should create only one item in string array for same literal node values', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
+                readFileAsString(__dirname + '/fixtures/same-literal-values.js'),
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
-                    stringArrayThreshold: 1,
-                    unicodeEscapeSequence: false
+                    stringArrayThreshold: 1
                 }
             );
 
@@ -73,31 +69,35 @@ describe('LiteralTransformer', () => {
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
         });
 
-        it('should replace literal node value with raw value from string array if `unicodeEscapeSequence` and `stringArray` are disabled', () => {
+        it('should replace literal node value with unicode escape sequence if `unicodeEscapeSequence` is enabled', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 {
                     ...NO_CUSTOM_NODES_PRESET,
-                    unicodeEscapeSequence: false
+                    unicodeEscapeSequence: true
+
                 }
             );
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *test *= *'test';/
-            );
+            assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'\\x74\\x65\\x73\\x74';$/);
         });
 
-        it('should\'t throw an error when string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
-            assert.doesNotThrow(() => JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/error-when-non-latin.js'),
+        it('should replace literal node value with unicode escape sequence from string array if `unicodeEscapeSequence` is enabled', () => {
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
                     stringArrayThreshold: 1,
-                    unicodeEscapeSequence: false
+                    unicodeEscapeSequence: true
                 }
-            ));
+            );
+
+            assert.match(
+                obfuscationResult.getObfuscatedCode(),
+                /^var *_0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/
+            );
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
         });
 
         it('shouldn\'t replace short literal node value with value from string array', () => {
@@ -110,7 +110,7 @@ describe('LiteralTransformer', () => {
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *'\\x74\\x65';/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *'te';/);
         });
 
         it('should replace literal node value with value from string array encoded using base64', () => {
@@ -126,7 +126,7 @@ describe('LiteralTransformer', () => {
 
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['\\x64\\x47\\x56\\x7a\\x64\\x41\\x3d\\x3d'\];/
+                /^var *_0x([a-f0-9]){4} *= *\['dGVzdA\\x3d\\x3d'\];/
             );
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
         });
@@ -144,7 +144,7 @@ describe('LiteralTransformer', () => {
 
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
-                /var *test *= *_0x([a-f0-9]){4}\('0x0', '(\\x[a-f0-9]*){4}'\);/
+                /var *test *= *_0x([a-f0-9]){4}\('0x0', '(?:\w|(?:\\x[a-f0-9]*)){4}'\);/
             );
         });
 
@@ -162,7 +162,7 @@ describe('LiteralTransformer', () => {
             );
 
             const regExp1: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/g;
-            const regExp2: RegExp = /var *test *= *'\\x74\\x65\\x73\\x74';/g;
+            const regExp2: RegExp = /var *test *= *'test';/g;
             const stringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
             const noStringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.length;
 

+ 3 - 14
test/functional-tests/node-transformers/obfuscating-transformers/object-expression-transformer/ObjectExpressionTransformer.spec.ts

@@ -9,18 +9,7 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('ObjectExpressionTransformer', () => {
-    it('should replace object expression node `key` property with literal value by unicode value', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/property-with-literal-value.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *\{'\\x66\\x6f\\x6f':0x0\};/);
-    });
-
-    it('should replace object expression node `key` property with identifier value by unicode value', () => {
+    it('should replace object expression node `key` property with identifier value by property with literal value', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             readFileAsString(__dirname + '/fixtures/property-with-identifier-value.js'),
             {
@@ -28,7 +17,7 @@ describe('ObjectExpressionTransformer', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *\{'\\x66\\x6f\\x6f':0x0\};/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *\{'foo':0x0\};/);
     });
 
     it('should correct convert shorthand ES6 object expression to non-shorthand object expression', () => {
@@ -41,7 +30,7 @@ describe('ObjectExpressionTransformer', () => {
 
         assert.match(
             obfuscationResult.getObfuscatedCode(),
-            /var *_0x[a-f0-9]{4,6} *= *\{'\\x61': *_0x[a-f0-9]{4,6}\, *'\\x62': *_0x[a-f0-9]{4,6}\};/
+            /var *_0x[a-f0-9]{4,6} *= *\{'a': *_0x[a-f0-9]{4,6}\, *'b': *_0x[a-f0-9]{4,6}\};/
         );
     });
 });

+ 16 - 16
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts

@@ -17,8 +17,8 @@ describe('VariableDeclarationTransformer', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4,6} *= *'\\x61\\x62\\x63';/);
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['\\x6c\\x6f\\x67'\]\(_0x([a-f0-9]){4,6}\);/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4,6} *= *'abc';/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\);/);
     });
 
     it('should not transform `variableDeclaration` node if parent block scope node is `Program` node', () => {
@@ -30,7 +30,7 @@ describe('VariableDeclarationTransformer', () => {
         );
 
         assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *0xa;/);
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['\\x6c\\x6f\\x67'\]\(test\);/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(test\);/);
     });
 
     it('should transform variable call (`identifier` node) outside of block scope of node in which this variable was declared with `var` kind', () => {
@@ -41,7 +41,7 @@ describe('VariableDeclarationTransformer', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['\\x6c\\x6f\\x67'\]\(_0x([a-f0-9]){4,6}\);/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\);/);
     });
 
     it('should not transform variable call (`identifier` node) outside of block scope of node in which this variable was declared with `let` kind', () => {
@@ -52,7 +52,7 @@ describe('VariableDeclarationTransformer', () => {
             }
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['\\x6c\\x6f\\x67'\]\(test\);/);
+        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(test\);/);
     });
 
     describe(`variable calls before variable declaration`, () => {
@@ -68,11 +68,11 @@ describe('VariableDeclarationTransformer', () => {
         });
 
         it('should transform variable call (`identifier` node name) before variable declaration if this call is inside function body', () => {
-            assert.match(obfuscationResult.getObfuscatedCode(),  /console\['\\x6c\\x6f\\x67'\]\(_0x([a-f0-9]){4,6}\['\\x69\\x74\\x65\\x6d'\]\);/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\['item'\]\);/);
         });
 
         it('should transform variable call (`identifier` node name) before variable declaration', () => {
-            assert.match(obfuscationResult.getObfuscatedCode(),  /console\['\\x6c\\x6f\\x67'\]\(_0x([a-f0-9]){4,6}\);/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\);/);
         });
     });
 
@@ -90,9 +90,9 @@ describe('VariableDeclarationTransformer', () => {
         const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
         const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/console\['\\x6c\\x6f\\x67'\]\((_0x[a-f0-9]{4,6})\)/);
+            .match(/console\['log'\]\((_0x[a-f0-9]{4,6})\)/);
         const objectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/return\{'\\x74':(_0x[a-f0-9]{4,6})\}/);
+            .match(/return\{'t':(_0x[a-f0-9]{4,6})\}/);
         const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/var *(_0x[a-f0-9]{4,6});/);
 
@@ -135,9 +135,9 @@ describe('VariableDeclarationTransformer', () => {
         const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
         const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/console\['\\x6c\\x6f\\x67'\]\((_0x[a-f0-9]{4,6})\)/);
+            .match(/console\['log'\]\((_0x[a-f0-9]{4,6})\)/);
         const objectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/return\{'\\x74':(_0x[a-f0-9]{4,6})\}/);
+            .match(/return\{'t':(_0x[a-f0-9]{4,6})\}/);
         const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/var *(_0x[a-f0-9]{4,6});/);
 
@@ -175,7 +175,7 @@ describe('VariableDeclarationTransformer', () => {
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var _0x([a-f0-9]){4,6} *= *\{'\\x74\\x65\\x73\\x74/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /var _0x([a-f0-9]){4,6} *= *\{'test/);
         });
 
         it('shouldn\'t replace computed member expression identifier', () => {
@@ -186,7 +186,7 @@ describe('VariableDeclarationTransformer', () => {
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /_0x([a-f0-9]){4,6}\['\\x74\\x65\\x73\\x74'\]/);
+            assert.match(obfuscationResult.getObfuscatedCode(),  /_0x([a-f0-9]){4,6}\['test'\]/);
         });
     });
 
@@ -200,8 +200,8 @@ describe('VariableDeclarationTransformer', () => {
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
         it('shouldn\'t transform object pattern variable declarator', () => {
-            const objectPatternVariableDeclaratorMatch: RegExp = /var *\{ *bar *\} *= *\{ *'\\x62\\x61\\x72' *: *'\\x66\\x6f\\x6f' *\};/;
-            const variableUsageMatch: RegExp = /console\['\\x6c\\x6f\\x67'\]\(bar\);/;
+            const objectPatternVariableDeclaratorMatch: RegExp = /var *\{ *bar *\} *= *\{ *'bar' *: *'foo' *\};/;
+            const variableUsageMatch: RegExp = /console\['log'\]\(bar\);/;
 
             assert.match(obfuscatedCode, objectPatternVariableDeclaratorMatch);
             assert.match(obfuscatedCode, variableUsageMatch);
@@ -218,7 +218,7 @@ describe('VariableDeclarationTransformer', () => {
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
         const objectPatternVariableDeclaratorMatch: RegExp = /var *\[ *(_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6}) *\] *= *\[0x1, *0x2\];/;
-        const variableUsageMatch: RegExp = /console\['\\x6c\\x6f\\x67'\]\((_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6})\);/;
+        const variableUsageMatch: RegExp = /console\['log'\]\((_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6})\);/;
 
         const objectPatternIdentifierName1: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![1];
         const objectPatternIdentifierName2: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![2];

+ 2 - 0
test/index.spec.ts

@@ -35,9 +35,11 @@ import './functional-tests/node-transformers/control-flow-transformers/function-
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec';
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec';
 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/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec';
+import './functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/catch-clause-transformer/CatchClauseTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/function-declaration-transformer/FunctionDeclarationTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec';

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

@@ -13,10 +13,11 @@ describe('JavaScriptObfuscator runtime eval', () => {
         const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             code,
             {
-                selfDefending: true,
+                controlFlowFlattening: true,
+                deadCodeInjection: true,
                 debugProtection: true,
-                stringArrayEncoding: 'rc4',
-                controlFlowFlattening: true
+                selfDefending: true,
+                stringArrayEncoding: 'rc4'
             }
         );
 

+ 58 - 1
test/unit-tests/node/node-utils/NodeUtils.spec.ts

@@ -24,6 +24,63 @@ describe('NodeUtils', () => {
         });
     });
 
+    describe('clone <T extends ESTree.Node> (astTree: T): T', () => {
+        let ifStatementNode1: ESTree.IfStatement,
+            ifStatementNode2: ESTree.IfStatement,
+            ifStatementBlockStatementNode1: ESTree.BlockStatement,
+            ifStatementBlockStatementNode2: ESTree.BlockStatement,
+            expressionStatementNode1: ESTree.ExpressionStatement,
+            expressionStatementNode2: ESTree.ExpressionStatement,
+            expressionStatementNode3: ESTree.ExpressionStatement,
+            expressionStatementNode4: ESTree.ExpressionStatement,
+            programNode1: ESTree.Program,
+            programNode2: ESTree.Program;
+
+        beforeEach(() => {
+            // actual AST tree
+            expressionStatementNode1 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+            expressionStatementNode2 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+
+            ifStatementBlockStatementNode1 = Nodes.getBlockStatementNode([
+                expressionStatementNode1,
+                expressionStatementNode2
+            ]);
+
+            ifStatementNode1 = Nodes.getIfStatementNode(
+                Nodes.getLiteralNode(true),
+                ifStatementBlockStatementNode1
+            );
+
+            programNode1 = Nodes.getProgramNode([
+                ifStatementNode1
+            ]);
+
+            // expected AST tree
+            expressionStatementNode3 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+            expressionStatementNode4 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+
+            ifStatementBlockStatementNode2 = Nodes.getBlockStatementNode([
+                expressionStatementNode3,
+                expressionStatementNode4
+            ]);
+
+            ifStatementNode2 = Nodes.getIfStatementNode(
+                Nodes.getLiteralNode(true),
+                ifStatementBlockStatementNode2
+            );
+
+            programNode2 = Nodes.getProgramNode([
+                ifStatementNode2
+            ]);
+
+            programNode2 = NodeUtils.parentize(programNode2);
+        });
+
+        it('should clone given AST-tree', () => {
+            assert.deepEqual(NodeUtils.clone(programNode1), programNode2);
+        });
+    });
+
     describe('convertCodeToStructure (code: string): ESTree.Node[]', () => {
         let code: string,
             identifierNode: ESTree.Identifier,
@@ -309,7 +366,7 @@ describe('NodeUtils', () => {
         });
     });
 
-    describe('parentize (node: ESTree.Node): void', () => {
+    describe('parentize <T extends ESTree.Node> (astTree: T): T', () => {
         let ifStatementNode: ESTree.IfStatement,
             ifStatementBlockStatementNode: ESTree.BlockStatement,
             expressionStatementNode1: ESTree.ExpressionStatement,

+ 56 - 0
test/unit-tests/options/options-normalizer/OptionsNormalizer.spec.ts

@@ -40,6 +40,62 @@ describe('OptionsNormalizer', () => {
             assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
         });
 
+        it('should normalize options preset: deadCodeInjectionRule', () => {
+            optionsPreset = {
+                ...DEFAULT_PRESET,
+                deadCodeInjection: true,
+                deadCodeInjectionThreshold: 0.4,
+                stringArray: false,
+                stringArrayThreshold: 0
+            };
+
+            expectedOptionsPreset = {
+                ...DEFAULT_PRESET,
+                deadCodeInjection: true,
+                deadCodeInjectionThreshold: 0.4,
+                stringArray: true,
+                stringArrayThreshold: 0.75
+            };
+
+            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        });
+
+        it('should normalize options preset: deadCodeInjectionRule. `stringArrayThreshold` option is not empty', () => {
+            optionsPreset = {
+                ...DEFAULT_PRESET,
+                deadCodeInjection: true,
+                deadCodeInjectionThreshold: 0.4,
+                stringArray: false,
+                stringArrayThreshold: 0.5
+            };
+
+            expectedOptionsPreset = {
+                ...DEFAULT_PRESET,
+                deadCodeInjection: true,
+                deadCodeInjectionThreshold: 0.4,
+                stringArray: true,
+                stringArrayThreshold: 0.5
+            };
+
+            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        });
+
+        it('should normalize options preset: deadCodeInjectionThresholdRule', () => {
+            optionsPreset = {
+                ...DEFAULT_PRESET,
+                deadCodeInjection: true,
+                deadCodeInjectionThreshold: 0
+            };
+
+            expectedOptionsPreset = {
+                ...DEFAULT_PRESET,
+                deadCodeInjection: false,
+                deadCodeInjectionThreshold: 0
+            };
+
+            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        });
+
         it('should normalize options preset: domainLockRule', () => {
             optionsPreset = {
                 ...DEFAULT_PRESET,

+ 9 - 9
tsconfig.json

@@ -1,19 +1,19 @@
 {
   "compilerOptions": {
-    "target": "ES6",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "importHelpers": true,
     "lib": [
       "es2017",
       "DOM"
     ],
-    "noEmitHelpers": true,
-    "importHelpers": true,
     "module": "commonjs",
-    "sourceMap": true,
-    "emitDecoratorMetadata": true,
-    "experimentalDecorators": true,
+    "noEmitHelpers": true,
+    "noImplicitThis": false,
+    "noUnusedLocals": true,
     "removeComments": true,
-    "noImplicitAny": true,
-    "strictNullChecks": true,
-    "noUnusedLocals": true
+    "sourceMap": true,
+    "strict": true,
+    "target": "ES6"
   }
 }

+ 5 - 0
tslint.json

@@ -63,15 +63,18 @@
     "no-inferrable-types": false,
     "no-internal-module": true,
     "no-invalid-this": true,
+    "no-misused-new": true,
     "no-null-keyword": false,
     "no-parameter-properties": true,
     "no-reference": true,
+    "no-reference-import": true,
     "no-require-imports": false,
     "no-shadowed-variable": true,
     "no-string-literal": true,
     "no-string-throw": true,
     "no-switch-case-fall-through": false,
     "no-trailing-whitespace": false,
+    "no-unnecessary-callback-wrapper": true,
     "no-unused-expression": true,
     "no-use-before-declare": true,
     "no-var-keyword": true,
@@ -86,7 +89,9 @@
       "check-whitespace"
     ],
     "one-variable-per-declaration": false,
+    "only-arrow-functions": [true, "allow-declarations"],
     "prefer-const": true,
+    "prefer-template": true,
     "quotemark": false,
     "radix": true,
     "semicolon": [true, "always"],

+ 2 - 2
webpack.config.js

@@ -23,7 +23,7 @@ module.exports = {
                 enforce: 'pre',
                 test: /\.ts$/,
                 loader: 'tslint-loader',
-                exclude: /(node_modules)/,
+                exclude: /(node_modules)/
             },
             {
                 test: /\.ts(x?)$/,
@@ -49,7 +49,7 @@ module.exports = {
         new CheckerPlugin()
     ],
     output: {
-        path: './dist',
+        path: __dirname + '/dist',
         filename: '[name].js',
         libraryTarget:  "commonjs2",
         library: "JavaScriptObfuscator"

Plik diff jest za duży
+ 303 - 313
yarn.lock


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików