فهرست منبع

Switched from `escodegen` to `escodegen-wallaby`

sanex3339 8 سال پیش
والد
کامیت
735bbb4b3c
82فایلهای تغییر یافته به همراه2371 افزوده شده و 1134 حذف شده
  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
 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`.
 * increased runtime performance with `rc4` `stringArrayEncoding`.
 
 
 v0.9.3
 v0.9.3
@@ -10,11 +15,11 @@ v0.9.3
 
 
 v0.9.2
 v0.9.2
 ---
 ---
-* fixed https://github.com/javascript-obfuscator/javascript-obfuscator/pull/42
+* Removed coverage dir from npm package
 
 
 v0.9.1
 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
 v0.9.0
 ---
 ---

+ 171 - 42
README.md

@@ -1,12 +1,12 @@
 <!--
 <!--
   Title: JavaScript Obfuscator
   Title: JavaScript Obfuscator
-  Description: JavaScript obfuscator for Node.js.
+  Description: A powerful obfuscator for JavaScript and Node.js.
   Author: sanex3339
   Author: sanex3339
   -->
   -->
 
 
 # JavaScript obfuscator for Node.js
 # 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
 * has no limits or restrictions
 * runs on your local machine - does not send data to a server;
 * 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!*
 *NOTE! the README on the master branch might not match that of the latest stable release!*
 
 
 ## :warning: Important
 ## :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.
 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,
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0.75,
     controlFlowFlatteningThreshold: 0.75,
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0.4,
     debugProtection: false,
     debugProtection: false,
     debugProtectionInterval: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     disableConsoleOutput: true,
+    mangle: false,
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
     seed: 0,
     seed: 0,
@@ -189,8 +192,8 @@ Following options are available for the JS Obfuscator:
     sourceMapMode: 'separate',
     sourceMapMode: 'separate',
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: false,
     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>
     --compact <boolean>
     --controlFlowFlattening <boolean>
     --controlFlowFlattening <boolean>
     --controlFlowFlatteningThreshold <number>
     --controlFlowFlatteningThreshold <number>
+    --deadCodeInjection <boolean>
+    --deadCodeInjectionThreshold <number>
     --debugProtection <boolean>
     --debugProtection <boolean>
     --debugProtectionInterval <boolean>
     --debugProtectionInterval <boolean>
     --disableConsoleOutput <boolean>
     --disableConsoleOutput <boolean>
+    --mangle <boolean>
     --reservedNames <list> (comma separated)
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
     --rotateStringArray <boolean>
     --seed <number>
     --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`.
 `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`
 ### `debugProtection`
 Type: `boolean` Default: `false`
 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
 ##### 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`.
 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`
 ### `reservedNames`
 Type: `string[]` Default: `[]`
 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`.
 `stringArrayThreshold: 0` equals to `stringArray: false`.
 
 
 ### `unicodeEscapeSequence`
 ### `unicodeEscapeSequence`
-Type: `boolean` Default: `true`
+Type: `boolean` Default: `false`
 
 
 Allows to enable/disable string conversion to unicode escape sequence.
 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
 ## Preset Options
 ### High obfuscation, low performance
 ### High obfuscation, low performance
@@ -458,18 +579,21 @@ Performance will 50-100% slower than without obfuscation
 
 
 ```javascript
 ```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
 ```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
 ```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
 }
 }
 ```
 ```
 
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 318 - 233
dist/index.js


+ 26 - 25
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "0.9.4",
+  "version": "0.10.0-beta.3",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",
@@ -19,47 +19,48 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
   },
   },
   "dependencies": {
   "dependencies": {
-    "chance": "1.0.6",
-    "class-validator": "0.6.8",
+    "chance": "1.0.8",
+    "class-validator": "0.7.0",
     "commander": "2.9.0",
     "commander": "2.9.0",
     "escodegen-wallaby": "1.6.11",
     "escodegen-wallaby": "1.6.11",
+    "esmangle": "^1.0.1",
     "esprima": "3.1.3",
     "esprima": "3.1.3",
     "estraverse": "4.2.0",
     "estraverse": "4.2.0",
-    "inversify": "3.1.0",
+    "inversify": "4.1.0",
     "mkdirp": "0.5.1",
     "mkdirp": "0.5.1",
     "reflect-metadata": "0.1.10",
     "reflect-metadata": "0.1.10",
-    "source-map-support": "0.4.11",
+    "source-map-support": "0.4.15",
     "string-template": "1.0.0",
     "string-template": "1.0.0",
-    "tslib": "1.6.0"
+    "tslib": "1.7.0"
   },
   },
   "devDependencies": {
   "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/commander": "2.3.31",
     "@types/escodegen": "0.0.6",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "2.1.33",
     "@types/esprima": "2.1.33",
     "@types/estraverse": "0.0.6",
     "@types/estraverse": "0.0.6",
-    "@types/estree": "0.0.34",
+    "@types/estree": "0.0.35",
     "@types/mkdirp": "0.3.29",
     "@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",
     "@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-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",
     "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"
     "webpack-node-externals": "1.5.4"
   },
   },
   "repository": {
   "repository": {

+ 1 - 1
scripts/tslint

@@ -1,3 +1,3 @@
 #!/bin/bash
 #!/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 esprima from 'esprima';
 import * as escodegen from 'escodegen-wallaby';
 import * as escodegen from 'escodegen-wallaby';
+import * as esmangle from 'esmangle';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
@@ -83,11 +84,16 @@ export class JavaScriptObfuscatorInternal implements IJavaScriptObfuscator {
             escodegenParams.sourceContent = sourceCode;
             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() : '';
         generatorOutput.map = generatorOutput.map ? generatorOutput.map.toString() : '';
 
 

+ 21 - 5
src/Obfuscator.ts

@@ -43,6 +43,13 @@ export class Obfuscator implements IObfuscator {
         NodeTransformers.TemplateLiteralTransformer
         NodeTransformers.TemplateLiteralTransformer
     ];
     ];
 
 
+    /**
+     * @type {NodeTransformers[]}
+     */
+    private static readonly deadCodeInjectionTransformersList: NodeTransformers[] = [
+        NodeTransformers.DeadCodeInjectionTransformer
+    ];
+
     /**
     /**
      * @type {NodeTransformers[]}
      * @type {NodeTransformers[]}
      */
      */
@@ -129,12 +136,17 @@ export class Obfuscator implements IObfuscator {
 
 
         this.obfuscationEventEmitter.emit(ObfuscationEvents.BeforeObfuscation, astTree, stackTraceData);
         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, [
         astTree = this.transformAstTree(astTree, [
             ...Obfuscator.convertingTransformersList,
             ...Obfuscator.convertingTransformersList,
             ...Obfuscator.obfuscatingTransformersList
             ...Obfuscator.obfuscatingTransformersList
@@ -153,6 +165,10 @@ export class Obfuscator implements IObfuscator {
         astTree: ESTree.Program,
         astTree: ESTree.Program,
         nodeTransformers: NodeTransformers[]
         nodeTransformers: NodeTransformers[]
     ): ESTree.Program {
     ): ESTree.Program {
+        if (!nodeTransformers.length) {
+            return astTree;
+        }
+
         const enterVisitors: IVisitor[] = [];
         const enterVisitors: IVisitor[] = [];
         const leaveVisitors: IVisitor[] = [];
         const leaveVisitors: IVisitor[] = [];
         const nodeTransformersLength: number = nodeTransformers.length;
         const nodeTransformersLength: number = nodeTransformers.length;

+ 1 - 1
src/SourceMapCorrector.ts

@@ -79,5 +79,5 @@ export class SourceMapCorrector implements ISourceMapCorrector {
         }
         }
 
 
         return `${obfuscatedCode}\n${sourceMappingUrl}`;
         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 * as path from 'path';
 
 
 import { TInputOptions } from '../types/options/TInputOptions';
 import { TInputOptions } from '../types/options/TInputOptions';
-import { TStringArrayEncoding } from '../types/options/TStringArrayEncoding';
 
 
 import { IObfuscationResult } from '../interfaces/IObfuscationResult';
 import { IObfuscationResult } from '../interfaces/IObfuscationResult';
 
 
-import { SourceMapMode } from '../enums/SourceMapMode';
-import { StringArrayEncoding } from '../enums/StringArrayEncoding';
-
 import { DEFAULT_PRESET } from '../options/presets/Default';
 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 { CLIUtils } from './CLIUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 
 
@@ -55,51 +55,6 @@ export class JavaScriptObfuscatorCLI {
         return CLIUtils.getPackageConfig().version;
         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 {
     public run (): void {
         this.configureCommands();
         this.configureCommands();
 
 
@@ -152,38 +107,52 @@ export class JavaScriptObfuscatorCLI {
             .option(
             .option(
                 '--compact <boolean>',
                 '--compact <boolean>',
                 'Disable one line output code compacting',
                 'Disable one line output code compacting',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--controlFlowFlattening <boolean>',
                 '--controlFlowFlattening <boolean>',
                 'Enables control flow flattening',
                 'Enables control flow flattening',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--controlFlowFlatteningThreshold <number>',
                 '--controlFlowFlatteningThreshold <number>',
                 'The probability that the control flow flattening transformation will be applied to the node',
                 'The probability that the control flow flattening transformation will be applied to the node',
                 parseFloat
                 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(
             .option(
                 '--debugProtection <boolean>',
                 '--debugProtection <boolean>',
                 'Disable browser Debug panel (can cause DevTools enabled browser freeze)',
                 'Disable browser Debug panel (can cause DevTools enabled browser freeze)',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--debugProtectionInterval <boolean>',
                 '--debugProtectionInterval <boolean>',
                 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)',
                 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--disableConsoleOutput <boolean>',
                 '--disableConsoleOutput <boolean>',
                 'Allow console.log, console.info, console.error and console.warn messages output into browser console',
                 'Allow console.log, console.info, console.error and console.warn messages output into browser console',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--domainLock <list>',
                 '--domainLock <list>',
                 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)',
                 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)',
                 (value: string) => value.split(',')
                 (value: string) => value.split(',')
             )
             )
+            .option(
+                '--mangle <boolean>', 'Enables mangling of variable names',
+                BooleanSanitizer
+            )
             .option(
             .option(
                 '--reservedNames <list>',
                 '--reservedNames <list>',
                 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)',
                 '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(
             .option(
                 '--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation',
                 '--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--seed <number>',
                 '--seed <number>',
@@ -201,12 +170,12 @@ export class JavaScriptObfuscatorCLI {
             .option(
             .option(
                 '--selfDefending <boolean>',
                 '--selfDefending <boolean>',
                 'Disables self-defending for obfuscated code',
                 'Disables self-defending for obfuscated code',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--sourceMap <boolean>',
                 '--sourceMap <boolean>',
                 'Enables source map generation',
                 'Enables source map generation',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--sourceMapBaseUrl <string>',
                 '--sourceMapBaseUrl <string>',
@@ -219,17 +188,17 @@ export class JavaScriptObfuscatorCLI {
             .option(
             .option(
                 '--sourceMapMode <string> [inline, separate]',
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',
                 'Specify source map output mode',
-                JavaScriptObfuscatorCLI.parseSourceMapMode
+                SourceMapModeSanitizer
             )
             )
             .option(
             .option(
                 '--stringArray <boolean>',
                 '--stringArray <boolean>',
                 'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
                 'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .option(
             .option(
                 '--stringArrayEncoding <boolean|string> [true, false, base64, rc4]',
                 '--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',
                 'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed',
-                JavaScriptObfuscatorCLI.parseStringArrayEncoding
+                StringArrayEncodingSanitizer
             )
             )
             .option(
             .option(
                 '--stringArrayThreshold <number>',
                 '--stringArrayThreshold <number>',
@@ -239,7 +208,7 @@ export class JavaScriptObfuscatorCLI {
             .option(
             .option(
                 '--unicodeEscapeSequence <boolean>',
                 '--unicodeEscapeSequence <boolean>',
                 'Allows to enable/disable string conversion to unicode escape sequence',
                 'Allows to enable/disable string conversion to unicode escape sequence',
-                JavaScriptObfuscatorCLI.parseBoolean
+                BooleanSanitizer
             )
             )
             .parse(this.rawArguments);
             .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'),
     ICalleeDataExtractor: Symbol('ICalleeDataExtractor'),
     ICustomNodeGroup: Symbol('ICustomNodeGroup'),
     ICustomNodeGroup: Symbol('ICustomNodeGroup'),
     IControlFlowReplacer: Symbol('IControlFlowReplacer'),
     IControlFlowReplacer: Symbol('IControlFlowReplacer'),
+    IDeadCodeInjectionReplacer: Symbol('IDeadCodeInjectionReplacer'),
     IJavaScriptObfuscator: Symbol('IJavaScriptObfuscator'),
     IJavaScriptObfuscator: Symbol('IJavaScriptObfuscator'),
     INodeTransformer: Symbol('INodeTransformer'),
     INodeTransformer: Symbol('INodeTransformer'),
     IObfuscationEventEmitter: Symbol('IObfuscationEventEmitter'),
     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 { StringArrayCallsWrapper } from '../../../custom-nodes/string-array-nodes/StringArrayCallsWrapper';
 import { StringArrayNode } from '../../../custom-nodes/string-array-nodes/StringArrayNode';
 import { StringArrayNode } from '../../../custom-nodes/string-array-nodes/StringArrayNode';
 import { StringArrayRotateFunctionNode } from '../../../custom-nodes/string-array-nodes/StringArrayRotateFunctionNode';
 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) => {
 export const customNodesModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // custom nodes
     // custom nodes
@@ -102,6 +104,14 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .toConstructor(StringArrayRotateFunctionNode)
         .toConstructor(StringArrayRotateFunctionNode)
         .whenTargetNamed(CustomNodes.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
     // node groups
     bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
     bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
         .to(ConsoleOutputCustomNodeGroup)
         .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 { BinaryExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer';
 import { CallExpressionControlFlowReplacer } from '../../../node-transformers/control-flow-transformers/control-flow-replacers/CallExpressionControlFlowReplacer';
 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 { 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) => {
 export const controlFlowTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
     bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
@@ -22,6 +23,10 @@ export const controlFlowTransformersModule: interfaces.ContainerModule = new Con
         .to(LogicalExpressionControlFlowReplacer)
         .to(LogicalExpressionControlFlowReplacer)
         .whenTargetNamed(ControlFlowReplacers.LogicalExpressionControlFlowReplacer);
         .whenTargetNamed(ControlFlowReplacers.LogicalExpressionControlFlowReplacer);
 
 
+    bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
+        .to(StringLiteralControlFlowReplacer)
+        .whenTargetNamed(ControlFlowReplacers.StringLiteralControlFlowReplacer);
+
     bind<IControlFlowReplacer>(ServiceIdentifiers.Factory__IControlFlowReplacer)
     bind<IControlFlowReplacer>(ServiceIdentifiers.Factory__IControlFlowReplacer)
         .toFactory<IControlFlowReplacer>((context: interfaces.Context) => {
         .toFactory<IControlFlowReplacer>((context: interfaces.Context) => {
             const cache: Map <ControlFlowReplacers, IControlFlowReplacer> = new Map();
             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 { FunctionControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/FunctionControlFlowTransformer';
 
 
 import { BlockStatementControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer';
 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 { CatchClauseTransformer } from '../../../node-transformers/obfuscating-transformers/CatchClauseTransformer';
 import { FunctionDeclarationTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionDeclarationTransformer';
 import { FunctionDeclarationTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionDeclarationTransformer';
 import { FunctionTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionTransformer';
 import { FunctionTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionTransformer';
@@ -25,6 +26,10 @@ export const nodeTransformersModule: interfaces.ContainerModule = new ContainerM
         .to(BlockStatementControlFlowTransformer)
         .to(BlockStatementControlFlowTransformer)
         .whenTargetNamed(NodeTransformers.BlockStatementControlFlowTransformer);
         .whenTargetNamed(NodeTransformers.BlockStatementControlFlowTransformer);
 
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(DeadCodeInjectionTransformer)
+        .whenTargetNamed(NodeTransformers.DeadCodeInjectionTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(FunctionControlFlowTransformer)
         .to(FunctionControlFlowTransformer)
         .whenTargetNamed(NodeTransformers.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,
                 ...NO_CUSTOM_NODES_PRESET,
-                seed: this.options.seed
+                seed: this.options.seed,
+                unicodeEscapeSequence: true
             }
             }
         ).getObfuscatedCode();
         ).getObfuscatedCode();
     }
     }

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

@@ -1,3 +1,5 @@
+import * as escodegen from 'escodegen';
+
 import { IGeneratorOutput } from '../interfaces/IGeneratorOutput';
 import { IGeneratorOutput } from '../interfaces/IGeneratorOutput';
 
 
 declare module 'escodegen' {
 declare module 'escodegen' {
@@ -6,5 +8,5 @@ declare module 'escodegen' {
      * @param options
      * @param options
      * @returns IGeneratorOutput
      * @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 {
 ): (target: IInitializable, propertyKey: string | symbol) => any {
     const decoratorName: string = Object.keys(this)[0];
     const decoratorName: string = Object.keys(this)[0];
 
 
-    return (target: IInitializable, propertyKey: string | symbol): any => {
+    return (target: IInitializable, propertyKey: string | symbol): PropertyDescriptor => {
         const descriptor: PropertyDescriptor = {
         const descriptor: PropertyDescriptor = {
             configurable: true,
             configurable: true,
             enumerable: true
             enumerable: true
         };
         };
-        const initializeMethod: any = (<any>target)[initializeMethodKey];
+        const initializeMethod: Function = target[initializeMethodKey];
 
 
         if (!initializeMethod || typeof initializeMethod !== 'function') {
         if (!initializeMethod || typeof initializeMethod !== 'function') {
            throw new Error(`\`${initializeMethodKey}\` method with initialization logic not found. \`@${decoratorName}\` decorator requires \`${initializeMethodKey}\` method`);
            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 {
 export enum ControlFlowReplacers {
     BinaryExpressionControlFlowReplacer,
     BinaryExpressionControlFlowReplacer,
     CallExpressionControlFlowReplacer,
     CallExpressionControlFlowReplacer,
-    LogicalExpressionControlFlowReplacer
+    LogicalExpressionControlFlowReplacer,
+    StringLiteralControlFlowReplacer,
 }
 }

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

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

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

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

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

@@ -1,4 +1,6 @@
 export interface IInitializable {
 export interface IInitializable {
+    [key: string]: any;
+
     /**
     /**
      * @param args
      * @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 compact: boolean;
     readonly controlFlowFlattening: boolean;
     readonly controlFlowFlattening: boolean;
     readonly controlFlowFlatteningThreshold: number;
     readonly controlFlowFlatteningThreshold: number;
+    readonly deadCodeInjection: boolean;
+    readonly deadCodeInjectionThreshold: number;
     readonly debugProtection: boolean;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
     readonly domainLock: string[];
+    readonly mangle: boolean;
     readonly reservedNames: string[];
     readonly reservedNames: string[];
     readonly rotateStringArray: boolean;
     readonly rotateStringArray: boolean;
     readonly seed: number;
     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([
     private static readonly controlFlowReplacersMap: Map <string, ControlFlowReplacers> = new Map([
         [NodeType.BinaryExpression, ControlFlowReplacers.BinaryExpressionControlFlowReplacer],
         [NodeType.BinaryExpression, ControlFlowReplacers.BinaryExpressionControlFlowReplacer],
         [NodeType.CallExpression, ControlFlowReplacers.CallExpressionControlFlowReplacer],
         [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
                 const controlFlowReplacerName: ControlFlowReplacers = <ControlFlowReplacers>FunctionControlFlowTransformer
                     .controlFlowReplacersMap.get(node.type);
                     .controlFlowReplacersMap.get(node.type);
 
 
+                if (controlFlowReplacerName === undefined) {
+                    return node;
+                }
+
                 return {
                 return {
                     ...this.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage),
                     ...this.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage),
                     parentNode
                     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);
         const storageKeysForCurrentId: string[] | undefined = storageKeysById.get(replacerId);
 
 
         if (
         if (
-            RandomGeneratorUtils.getMathRandom() > usingExistingIdentifierChance &&
+            RandomGeneratorUtils.getMathRandom() < usingExistingIdentifierChance &&
             storageKeysForCurrentId &&
             storageKeysForCurrentId &&
             storageKeysForCurrentId.length
             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 { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import * as escodegen from 'escodegen-wallaby';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -11,17 +10,13 @@ import { NodeType } from '../../enums/NodeType';
 
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { Node } from '../../node/Node';
 import { Node } from '../../node/Node';
-import { Utils } from '../../utils/Utils';
 
 
 /**
 /**
  * replaces:
  * replaces:
- *     var object = { 'PSEUDO': 1 };
- *
- * or:
  *     var object = { PSEUDO: 1 };
  *     var object = { PSEUDO: 1 };
  *
  *
  * on:
  * on:
- *     var object = { '\u0050\u0053\u0045\u0055\u0044\u004f': 1 };
+ *     var object = { 'PSEUDO': 1 };
  */
  */
 @injectable()
 @injectable()
 export class ObjectExpressionTransformer extends AbstractNodeTransformer {
 export class ObjectExpressionTransformer extends AbstractNodeTransformer {
@@ -34,21 +29,6 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
         super(options);
         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
      * @param node
      * @returns {ESTree.Literal}
      * @returns {ESTree.Literal}
@@ -57,11 +37,7 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
         return {
         return {
             type: NodeType.Literal,
             type: NodeType.Literal,
             value: node.name,
             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;
                     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);
                     property.key = ObjectExpressionTransformer.transformIdentifierPropertyKey(property.key);
                 }
                 }
             });
             });

+ 35 - 0
src/node/NodeUtils.ts

@@ -40,6 +40,41 @@ export class NodeUtils {
         return astTree;
         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
      * @param code
      * @returns {TStatement[]}
      * @returns {TStatement[]}

+ 7 - 1
src/node/Nodes.ts

@@ -189,13 +189,19 @@ export class Nodes {
     /**
     /**
      * @param test
      * @param test
      * @param consequent
      * @param consequent
+     * @param alternate
      * @returns {ESTree.IfStatement}
      * @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 {
         return {
             type: NodeType.IfStatement,
             type: NodeType.IfStatement,
             test,
             test,
             consequent,
             consequent,
+            ...alternate && {alternate},
             obfuscatedNode: false
             obfuscatedNode: false
         };
         };
     }
     }

+ 18 - 0
src/options/Options.ts

@@ -59,6 +59,18 @@ export class Options implements IOptions {
     @Max(1)
     @Max(1)
     public readonly controlFlowFlatteningThreshold: number;
     public readonly controlFlowFlatteningThreshold: number;
 
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly deadCodeInjection: boolean;
+
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly deadCodeInjectionThreshold: number;
+
     /**
     /**
      * @type {boolean}
      * @type {boolean}
      */
      */
@@ -87,6 +99,12 @@ export class Options implements IOptions {
     })
     })
     public readonly domainLock: string[];
     public readonly domainLock: string[];
 
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly mangle: boolean;
+
     /**
     /**
      * @type {string[]}
      * @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 { 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 {
 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[]}
      * @type {TOptionsNormalizerRule[]}
      */
      */
     private static readonly normalizerRules: 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;
         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,
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0.75,
     controlFlowFlatteningThreshold: 0.75,
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0.4,
     debugProtection: false,
     debugProtection: false,
     debugProtectionInterval: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     domainLock: [],
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
     seed: 0,
     seed: 0,
@@ -20,6 +23,6 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     sourceMapMode: SourceMapMode.Separate,
     sourceMapMode: SourceMapMode.Separate,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: false,
     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,
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0,
     controlFlowFlatteningThreshold: 0,
+    deadCodeInjection: false,
+    deadCodeInjectionThreshold: 0,
     debugProtection: false,
     debugProtection: false,
     debugProtectionInterval: false,
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     disableConsoleOutput: false,
     domainLock: [],
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: false,
     rotateStringArray: false,
     seed: 0,
     seed: 0,
@@ -21,5 +24,5 @@ export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
     stringArray: false,
     stringArray: false,
     stringArrayEncoding: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0,
     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 (
         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);
             string.charAt(idx | 0) || (map = '=', idx % 1);
             output += map.charAt(63 & block >> 8 - idx % 1 * 8)
             output += map.charAt(63 & block >> 8 - idx % 1 * 8)
         ) {
         ) {
@@ -42,7 +42,7 @@ export class CryptUtils {
         const escapeRegExp: (s: string) => string = (s: string) =>
         const escapeRegExp: (s: string) => string = (s: string) =>
             s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
             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,
             let i1: number = -1,
                 i2: number = -1,
                 i2: number = -1,
                 result: string = '';
                 result: string = '';
@@ -64,7 +64,7 @@ export class CryptUtils {
         });
         });
 
 
         let randomStringDiff: string = randomString.replace(
         let randomStringDiff: string = randomString.replace(
-            new RegExp('[' + escapeRegExp(str) + ']', 'g'),
+            new RegExp(`[${escapeRegExp(str)}]`, 'g'),
             '');
             '');
 
 
         const randomStringDiffArray: string[] = randomStringDiff.split('');
         const randomStringDiffArray: string[] = randomStringDiff.split('');
@@ -73,7 +73,6 @@ export class CryptUtils {
         randomStringDiff = randomStringDiffArray.join('');
         randomStringDiff = randomStringDiffArray.join('');
 
 
         return [randomMerge(str, randomStringDiff), randomStringDiff];
         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(
     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,
             ...NO_CUSTOM_NODES_PRESET,
-            compact: false
+            compact: false,
+            stringArray: false,
+            stringArrayThreshold: 1,
+            deadCodeInjection: true,
+            deadCodeInjectionThreshold: 1
         }
         }
     ).getObfuscatedCode();
     ).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';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 
 describe('ConsoleOutputDisableExpressionNode', () => {
 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', () => {
     it('should correctly append `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
         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', () => {
     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(
                 obfuscatedCode1: string = JavaScriptObfuscator.obfuscate(
                     readFileAsString(__dirname + '/fixtures/simple-input-2.js'),
                     readFileAsString(__dirname + '/fixtures/simple-input-2.js'),
                     {
                     {
@@ -127,7 +127,15 @@ describe('JavaScriptObfuscator', () => {
                         stringArray: true,
                         stringArray: true,
                         stringArrayThreshold: 1
                         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(
                 obfuscatedCode2: string = JavaScriptObfuscator.obfuscate(
                     readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js'),
                     readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js'),
                     {
                     {
@@ -137,11 +145,8 @@ describe('JavaScriptObfuscator', () => {
                     }
                     }
                 ).getObfuscatedCode();
                 ).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 () {
         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(() => {
         afterEach(() => {
             RandomGeneratorUtils.initializeRandomGenerator(0);
             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('BlockStatementControlFlowTransformer', () => {
     describe('transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node', () => {
     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', () => {
         describe('variant #1: 5 simple statements', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/input-1.js'),
                 readFileAsString(__dirname + '/fixtures/input-1.js'),
                 {
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     ...NO_CUSTOM_NODES_PRESET,
                     controlFlowFlattening: true,
                     controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1,
-                    unicodeEscapeSequence: false
+                    controlFlowFlatteningThreshold: 1
                 }
                 }
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
@@ -32,9 +34,8 @@ describe('BlockStatementControlFlowTransformer', () => {
             const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
             const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
             const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
             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();
             const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
 
 
             it('should save all statements', () => {
             it('should save all statements', () => {
@@ -52,6 +53,7 @@ describe('BlockStatementControlFlowTransformer', () => {
 
 
             it('should create variable with order of switch cases sequence', () => {
             it('should create variable with order of switch cases sequence', () => {
                 assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
                 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 switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
             const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
             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();
             const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
 
 
             it('should save all statements', () => {
             it('should save all statements', () => {
@@ -97,6 +98,7 @@ describe('BlockStatementControlFlowTransformer', () => {
 
 
             it('should create variable with order of switch cases sequence', () => {
             it('should create variable with order of switch cases sequence', () => {
                 assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
                 assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
+                assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
             });
             });
         });
         });
 
 
@@ -111,7 +113,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('shouldn\'t transform block statement if statements length less than 5', () => {
                 assert.match(obfuscatedCode, statementRegExp);
                 assert.match(obfuscatedCode, statementRegExp);
@@ -129,7 +131,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('shouldn\'t transform block statement if block statement contain variable declaration with `const` kind', () => {
                 assert.match(obfuscatedCode, statementRegExp);
                 assert.match(obfuscatedCode, statementRegExp);
@@ -147,7 +149,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('shouldn\'t transform block statement if block statement contain variable declaration with `let` kind', () => {
                 assert.match(obfuscatedCode, statementRegExp);
                 assert.match(obfuscatedCode, statementRegExp);
@@ -165,7 +167,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('shouldn\'t transform block statement if block statement contain break statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
                 assert.match(obfuscatedCode, statementRegExp);
@@ -183,7 +185,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('shouldn\'t transform block statement if block statement contain continue statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
                 assert.match(obfuscatedCode, statementRegExp);
@@ -201,7 +203,7 @@ describe('BlockStatementControlFlowTransformer', () => {
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('shouldn\'t transform block statement if block statement contain function declaration', () => {
                 assert.match(obfuscatedCode, statementRegExp);
                 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 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 transformedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
             const untouchedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.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 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', () => {
             it('should replace binary expression node by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
@@ -47,8 +47,8 @@ describe('BinaryExpressionControlFlowReplacer', () => {
                         }
                         }
                     );
                     );
                     const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
                     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 firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
                     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 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', () => {
             it('should replace call expression node by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
@@ -47,8 +47,8 @@ describe('CallExpressionControlFlowReplacer', () => {
                         }
                         }
                     );
                     );
                     const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
                     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 firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
@@ -78,7 +78,7 @@ describe('CallExpressionControlFlowReplacer', () => {
                 }
                 }
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             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);
                 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 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', () => {
             it('should replace logical expression node by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
@@ -47,8 +47,8 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                         }
                         }
                     );
                     );
                     const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
                     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 firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
                     const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
@@ -78,7 +78,7 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                 }
                 }
             );
             );
             const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             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', () => {
             it('should replace logical expression node with unary expression by call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
                 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 variableMatch: string = '_0x([a-f0-9]){4,6}';
     const rootControlFlowStorageNodeMatch: string = `` +
     const rootControlFlowStorageNodeMatch: string = `` +
         `var *${variableMatch} *= *\\{` +
         `var *${variableMatch} *= *\\{` +
-            `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+            `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                 `return *${variableMatch} *\\+ *${variableMatch};` +
                 `return *${variableMatch} *\\+ *${variableMatch};` +
             `\\}` +
             `\\}` +
         `\\};` +
         `\\};` +
     ``;
     ``;
     const innerControlFlowStorageNodeMatch: string = `` +
     const innerControlFlowStorageNodeMatch: string = `` +
         `var *${variableMatch} *= *\\{` +
         `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 obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const regexp: RegExp = new RegExp(
             const regexp: RegExp = new RegExp(
                 `var *${variableMatch} *= *\\{` +
                 `var *${variableMatch} *= *\\{` +
-                    `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+                    `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                         `return *${variableMatch} *\\+ *${variableMatch};` +
                         `return *${variableMatch} *\\+ *${variableMatch};` +
                     `\\}, *` +
                     `\\}, *` +
-                    `'(\\\\x[a-f0-9]*){3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
+                    `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
                         `return *${variableMatch} *- *${variableMatch};` +
                         `return *${variableMatch} *- *${variableMatch};` +
                     `\\}` +
                     `\\}` +
                 `\\};`
                 `\\};`
@@ -130,7 +130,7 @@ describe('FunctionControlFlowTransformer', () => {
             const expectedValue: number = 0;
             const expectedValue: number = 0;
             const regExp: RegExp = new RegExp(
             const regExp: RegExp = new RegExp(
                 `var *[a-zA-Z]{6} *= *\\{` +
                 `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};` +
                         `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', () => {
         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'\)\];/);
             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'\)\];/);
             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', () => {
     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'\)\]\(\)\{\}/);
         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 obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
         const paramNameRegExp: RegExp = /catch *\((_0x([a-f0-9]){4,6})\) *\{/;
         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', () => {
         it('should transform catch clause node', () => {
             assert.match(obfuscatedCode, paramNameRegExp);
             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
             const functionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
                 .match(/var _0x[a-f0-9]{4,6} *= *function *\((_0x[a-f0-9]{4,6})\) *\{/);
                 .match(/var _0x[a-f0-9]{4,6} *= *function *\((_0x[a-f0-9]{4,6})\) *\{/);
             const functionBodyIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             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 functionParamIdentifierName: string = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
             const functionBodyIdentifierName: string = (<RegExpMatchArray>functionBodyIdentifierMatch)[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('LiteralTransformer', () => {
     describe('transformation of literal node with string value', () => {
     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(
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 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(
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 {
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
+                    ...NO_CUSTOM_NODES_PRESET
                 }
                 }
             );
             );
 
 
             assert.match(
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
                 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,
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
                     stringArray: true,
                     stringArrayThreshold: 1
                     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(
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
+                readFileAsString(__dirname + '/fixtures/same-literal-values.js'),
                 {
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
                     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'\);/);
             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(
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 readFileAsString(__dirname + '/fixtures/simple-input.js'),
                 {
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     ...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,
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
                     stringArray: true,
                     stringArrayThreshold: 1,
                     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', () => {
         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', () => {
         it('should replace literal node value with value from string array encoded using base64', () => {
@@ -126,7 +126,7 @@ describe('LiteralTransformer', () => {
 
 
             assert.match(
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
                 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'\);/);
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
         });
         });
@@ -144,7 +144,7 @@ describe('LiteralTransformer', () => {
 
 
             assert.match(
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
                 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 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 stringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
             const noStringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.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';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 
 describe('ObjectExpressionTransformer', () => {
 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(
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             readFileAsString(__dirname + '/fixtures/property-with-identifier-value.js'),
             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', () => {
     it('should correct convert shorthand ES6 object expression to non-shorthand object expression', () => {
@@ -41,7 +30,7 @@ describe('ObjectExpressionTransformer', () => {
 
 
         assert.match(
         assert.match(
             obfuscationResult.getObfuscatedCode(),
             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', () => {
     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(),  /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', () => {
     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', () => {
     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`, () => {
     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', () => {
         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', () => {
         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
         const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
             .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
         const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
         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
         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
         const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/var *(_0x[a-f0-9]{4,6});/);
             .match(/var *(_0x[a-f0-9]{4,6});/);
 
 
@@ -135,9 +135,9 @@ describe('VariableDeclarationTransformer', () => {
         const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
         const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
             .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
         const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
         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
         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
         const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
             .match(/var *(_0x[a-f0-9]{4,6});/);
             .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', () => {
         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();
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
 
         it('shouldn\'t transform object pattern variable declarator', () => {
         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, objectPatternVariableDeclaratorMatch);
             assert.match(obfuscatedCode, variableUsageMatch);
             assert.match(obfuscatedCode, variableUsageMatch);
@@ -218,7 +218,7 @@ describe('VariableDeclarationTransformer', () => {
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
 
         const objectPatternVariableDeclaratorMatch: RegExp = /var *\[ *(_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6}) *\] *= *\[0x1, *0x2\];/;
         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 objectPatternIdentifierName1: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![1];
         const objectPatternIdentifierName2: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![2];
         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/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/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/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/member-expression-transformer/MemberExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.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/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/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-declaration-transformer/FunctionDeclarationTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.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(
         const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             code,
             code,
             {
             {
-                selfDefending: true,
+                controlFlowFlattening: true,
+                deadCodeInjection: true,
                 debugProtection: 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[]', () => {
     describe('convertCodeToStructure (code: string): ESTree.Node[]', () => {
         let code: string,
         let code: string,
             identifierNode: ESTree.Identifier,
             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,
         let ifStatementNode: ESTree.IfStatement,
             ifStatementBlockStatementNode: ESTree.BlockStatement,
             ifStatementBlockStatementNode: ESTree.BlockStatement,
             expressionStatementNode1: ESTree.ExpressionStatement,
             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);
             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', () => {
         it('should normalize options preset: domainLockRule', () => {
             optionsPreset = {
             optionsPreset = {
                 ...DEFAULT_PRESET,
                 ...DEFAULT_PRESET,

+ 9 - 9
tsconfig.json

@@ -1,19 +1,19 @@
 {
 {
   "compilerOptions": {
   "compilerOptions": {
-    "target": "ES6",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "importHelpers": true,
     "lib": [
     "lib": [
       "es2017",
       "es2017",
       "DOM"
       "DOM"
     ],
     ],
-    "noEmitHelpers": true,
-    "importHelpers": true,
     "module": "commonjs",
     "module": "commonjs",
-    "sourceMap": true,
-    "emitDecoratorMetadata": true,
-    "experimentalDecorators": true,
+    "noEmitHelpers": true,
+    "noImplicitThis": false,
+    "noUnusedLocals": true,
     "removeComments": 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-inferrable-types": false,
     "no-internal-module": true,
     "no-internal-module": true,
     "no-invalid-this": true,
     "no-invalid-this": true,
+    "no-misused-new": true,
     "no-null-keyword": false,
     "no-null-keyword": false,
     "no-parameter-properties": true,
     "no-parameter-properties": true,
     "no-reference": true,
     "no-reference": true,
+    "no-reference-import": true,
     "no-require-imports": false,
     "no-require-imports": false,
     "no-shadowed-variable": true,
     "no-shadowed-variable": true,
     "no-string-literal": true,
     "no-string-literal": true,
     "no-string-throw": true,
     "no-string-throw": true,
     "no-switch-case-fall-through": false,
     "no-switch-case-fall-through": false,
     "no-trailing-whitespace": false,
     "no-trailing-whitespace": false,
+    "no-unnecessary-callback-wrapper": true,
     "no-unused-expression": true,
     "no-unused-expression": true,
     "no-use-before-declare": true,
     "no-use-before-declare": true,
     "no-var-keyword": true,
     "no-var-keyword": true,
@@ -86,7 +89,9 @@
       "check-whitespace"
       "check-whitespace"
     ],
     ],
     "one-variable-per-declaration": false,
     "one-variable-per-declaration": false,
+    "only-arrow-functions": [true, "allow-declarations"],
     "prefer-const": true,
     "prefer-const": true,
+    "prefer-template": true,
     "quotemark": false,
     "quotemark": false,
     "radix": true,
     "radix": true,
     "semicolon": [true, "always"],
     "semicolon": [true, "always"],

+ 2 - 2
webpack.config.js

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

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 303 - 313
yarn.lock


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است