Browse Source

Refactoring. Added `mangle` option

sanex3339 8 years ago
parent
commit
26d580d832

+ 12 - 2
README.md

@@ -1,12 +1,12 @@
 <!--
   Title: JavaScript Obfuscator
-  Description: JavaScript obfuscator for Node.js.
+  Description: A powerful obfuscator for JavaScript and Node.js.
   Author: sanex3339
   -->
 
 # JavaScript obfuscator for Node.js
 
-JavaScript obfuscator for Node.js is a free obfuscator with a wide number of features which provides protection for your source code.
+JavaScript obfuscator is a powerful free obfuscator for JavaScript and Node.js with a wide number of features which provides protection for your source code.
 
 * has no limits or restrictions
 * runs on your local machine - does not send data to a server;
@@ -181,6 +181,7 @@ Following options are available for the JS Obfuscator:
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
+    mangle: false,
     reservedNames: [],
     rotateStringArray: true,
     seed: 0,
@@ -211,6 +212,7 @@ Following options are available for the JS Obfuscator:
     --debugProtection <boolean>
     --debugProtectionInterval <boolean>
     --disableConsoleOutput <boolean>
+    --mangle <boolean>
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
     --seed <number>
@@ -334,6 +336,11 @@ Locks the obfuscated source code so it only runs on specific domains and/or sub-
 ##### Multiple domains and sub-domains
 It's possible to lock your code to more than one domain or sub-domain. For instance, to lock it so the code only runs on **www.example.com** add `www.example.com`, to make it work on any sub-domain from example.com, use `.example.com`.
 
+### `mangle`
+Type: `boolean` Default: `false`
+
+Enables mangling of variable names.
+
 ### `reservedNames`
 Type: `string[]` Default: `[]`
 
@@ -470,6 +477,7 @@ Performance will 50-100% slower than without obfuscation
     debugProtection: true,
     debugProtectionInterval: true,
     disableConsoleOutput: true,
+	mangle: false,
     rotateStringArray: true,
     selfDefending: true,
     stringArray: true,
@@ -493,6 +501,7 @@ Performance will 30-35% slower than without obfuscation
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
+    mangle: false,
     rotateStringArray: true,
     selfDefending: true,
     stringArray: true,
@@ -514,6 +523,7 @@ Performance will slightly slower than without obfuscation
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
+    mangle: true,
     rotateStringArray: true,
     selfDefending: true,
     stringArray: true,

+ 76 - 55
dist/index.js

@@ -441,6 +441,7 @@ exports.RandomGeneratorUtils = RandomGeneratorUtils;
 "use strict";
 
 
+var tslib_1 = __webpack_require__(1);
 function initializable() {
     var initializeMethodKey = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'initialize';
 
@@ -452,21 +453,21 @@ function initializable() {
         };
         var initializeMethod = target[initializeMethodKey];
         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");
         }
-        var metadataPropertyKey = '_' + propertyKey;
+        var metadataPropertyKey = "_" + propertyKey;
         var propertyDescriptor = Object.getOwnPropertyDescriptor(target, metadataPropertyKey) || descriptor;
         var methodDescriptor = Object.getOwnPropertyDescriptor(target, initializeMethodKey) || descriptor;
         var originalMethod = methodDescriptor.value;
-        Object.defineProperty(target, propertyKey, Object.assign({}, propertyDescriptor, { get: function get() {
+        Object.defineProperty(target, propertyKey, tslib_1.__assign({}, propertyDescriptor, { get: function get() {
                 if (this[metadataPropertyKey] === undefined) {
-                    throw new Error('Property `' + propertyKey + '` is not initialized! Initialize it first!');
+                    throw new Error("Property `" + propertyKey + "` is not initialized! Initialize it first!");
                 }
                 return this[metadataPropertyKey];
             }, set: function set(newVal) {
                 this[metadataPropertyKey] = newVal;
             } }));
-        Object.defineProperty(target, initializeMethodKey, Object.assign({}, methodDescriptor, { value: function value() {
+        Object.defineProperty(target, initializeMethodKey, tslib_1.__assign({}, methodDescriptor, { value: function value() {
                 originalMethod.apply(this, arguments);
                 if (this[propertyKey]) {}
             } }));
@@ -1306,7 +1307,7 @@ var _createClass = (function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-__webpack_require__(132);
+__webpack_require__(133);
 var ServiceIdentifiers_1 = __webpack_require__(2);
 var InversifyContainerFacade_1 = __webpack_require__(45);
 var JavaScriptObfuscatorCLI_1 = __webpack_require__(44);
@@ -1541,6 +1542,7 @@ exports.NO_CUSTOM_NODES_PRESET = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     rotateStringArray: false,
     seed: 0,
@@ -1957,6 +1959,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     rotateStringArray: true,
     seed: 0,
@@ -2121,6 +2124,7 @@ var inversify_1 = __webpack_require__(0);
 var ServiceIdentifiers_1 = __webpack_require__(2);
 var esprima = __webpack_require__(36);
 var escodegen = __webpack_require__(23);
+var esmangle = __webpack_require__(129);
 var JavaScriptObfuscatorInternal = JavaScriptObfuscatorInternal_1 = function () {
     function JavaScriptObfuscatorInternal(obfuscator, sourceMapCorrector, options) {
         _classCallCheck(this, JavaScriptObfuscatorInternal);
@@ -2146,10 +2150,12 @@ var JavaScriptObfuscatorInternal = JavaScriptObfuscatorInternal_1 = function ()
                 escodegenParams.sourceMap = 'sourceMap';
                 escodegenParams.sourceContent = sourceCode;
             }
-            escodegenParams.format = {
-                compact: this.options.compact
-            };
-            var generatorOutput = escodegen.generate(astTree, escodegenParams);
+            if (this.options.mangle) {
+                astTree = esmangle.mangle(astTree);
+            }
+            var generatorOutput = escodegen.generate(astTree, Object.assign({}, escodegenParams, { format: {
+                    compact: this.options.compact
+                } }));
             generatorOutput.map = generatorOutput.map ? generatorOutput.map.toString() : '';
             return generatorOutput;
         }
@@ -2267,7 +2273,8 @@ var Obfuscator = Obfuscator_1 = function () {
                 _this.obfuscationEventEmitter.once(customNodeGroup.getAppendEvent(), customNodeGroup.appendCustomNodes.bind(customNodeGroup));
             });
             this.obfuscationEventEmitter.emit(ObfuscationEvents_1.ObfuscationEvents.BeforeObfuscation, astTree, stackTraceData);
-            astTree = this.transformAstTree(astTree, [].concat(_toConsumableArray(this.options.deadCodeInjection ? Obfuscator_1.deadCodeInjectionTransformersList : []), _toConsumableArray(this.options.controlFlowFlattening ? Obfuscator_1.controlFlowTransformersList : [])));
+            astTree = this.transformAstTree(astTree, [].concat(_toConsumableArray(this.options.deadCodeInjection ? Obfuscator_1.deadCodeInjectionTransformersList : [])));
+            astTree = this.transformAstTree(astTree, [].concat(_toConsumableArray(this.options.controlFlowFlattening ? Obfuscator_1.controlFlowTransformersList : [])));
             astTree = this.transformAstTree(astTree, [].concat(_toConsumableArray(Obfuscator_1.convertingTransformersList), _toConsumableArray(Obfuscator_1.obfuscatingTransformersList)));
             this.obfuscationEventEmitter.emit(ObfuscationEvents_1.ObfuscationEvents.AfterObfuscation, astTree, stackTraceData);
             return astTree;
@@ -2404,8 +2411,8 @@ var _createClass = (function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-var fs = __webpack_require__(130);
-var mkdirp = __webpack_require__(131);
+var fs = __webpack_require__(131);
+var mkdirp = __webpack_require__(132);
 var path = __webpack_require__(37);
 
 var CLIUtils = function () {
@@ -2546,7 +2553,7 @@ var JavaScriptObfuscatorCLI = function () {
         value: function configureCommands() {
             this.commands = new commander.Command().version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version').usage('<inputPath> [options]').option('-o, --output <path>', 'Output path for obfuscated code').option('--compact <boolean>', 'Disable one line output code compacting', JavaScriptObfuscatorCLI.parseBoolean).option('--controlFlowFlattening <boolean>', 'Enables control flow flattening', JavaScriptObfuscatorCLI.parseBoolean).option('--controlFlowFlatteningThreshold <number>', 'The probability that the control flow flattening transformation will be applied to the node', parseFloat).option('--deadCodeInjection <boolean>', 'Enables dead code injection', JavaScriptObfuscatorCLI.parseBoolean).option('--deadCodeInjectionThreshold <number>', 'The probability that the dead code injection transformation will be applied to the node', parseFloat).option('--debugProtection <boolean>', 'Disable browser Debug panel (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean).option('--debugProtectionInterval <boolean>', 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean).option('--disableConsoleOutput <boolean>', 'Allow console.log, console.info, console.error and console.warn messages output into browser console', JavaScriptObfuscatorCLI.parseBoolean).option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', function (value) {
                 return value.split(',');
-            }).option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', function (value) {
+            }).option('--mangle <boolean>', 'Enables mangling of variable names', JavaScriptObfuscatorCLI.parseBoolean).option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', function (value) {
                 return value.split(',');
             }).option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--seed <number>', 'Sets seed for random generator. This is useful for creating repeatable results.', parseFloat).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`').option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`').option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--stringArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--stringArrayEncoding <boolean|string> [true, false, base64, rc4]', 'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed', JavaScriptObfuscatorCLI.parseStringArrayEncoding).option('--stringArrayThreshold <number>', 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--unicodeEscapeSequence <boolean>', 'Allows to enable/disable string conversion to unicode escape sequence', JavaScriptObfuscatorCLI.parseBoolean).parse(this.rawArguments);
             this.commands.on('--help', function () {
@@ -4045,7 +4052,7 @@ var NodeCallsControllerFunctionNode = function (_AbstractCustomNode_) {
             if (this.appendEvent === ObfuscationEvents_1.ObfuscationEvents.AfterObfuscation) {
                 return JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(format(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate(), {
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                }), Object.assign({}, NoCustomNodes_1.NO_CUSTOM_NODES_PRESET, { seed: this.options.seed })).getObfuscatedCode();
+                }), tslib_1.__assign({}, NoCustomNodes_1.NO_CUSTOM_NODES_PRESET, { seed: this.options.seed })).getObfuscatedCode();
             }
             return format(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate(), {
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
@@ -4259,7 +4266,7 @@ var StringArrayCallsWrapper = function (_AbstractCustomNode_) {
                 decodeNodeTemplate: decodeNodeTemplate,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayName: this.stringArrayName
-            }), Object.assign({}, NoCustomNodes_1.NO_CUSTOM_NODES_PRESET, { seed: this.options.seed })).getObfuscatedCode();
+            }), tslib_1.__assign({}, NoCustomNodes_1.NO_CUSTOM_NODES_PRESET, { seed: this.options.seed })).getObfuscatedCode();
         }
     }, {
         key: "getDecodeStringArrayTemplate",
@@ -4440,7 +4447,7 @@ var StringArrayRotateFunctionNode = function (_AbstractCustomNode_) {
                 stringArrayName: this.stringArrayName,
                 stringArrayRotateValue: Utils_1.Utils.decToHex(this.stringArrayRotateValue),
                 whileFunctionName: whileFunctionName
-            }), Object.assign({}, NoCustomNodes_1.NO_CUSTOM_NODES_PRESET, { seed: this.options.seed })).getObfuscatedCode();
+            }), tslib_1.__assign({}, NoCustomNodes_1.NO_CUSTOM_NODES_PRESET, { seed: this.options.seed })).getObfuscatedCode();
         }
     }]);
 
@@ -4573,7 +4580,7 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
 
 var tslib_1 = __webpack_require__(1);
 var inversify_1 = __webpack_require__(0);
-var events_1 = __webpack_require__(129);
+var events_1 = __webpack_require__(130);
 inversify_1.decorate(inversify_1.injectable(), events_1.EventEmitter);
 var ObfuscationEventEmitter = function (_events_1$EventEmitte) {
     _inherits(ObfuscationEventEmitter, _events_1$EventEmitte);
@@ -5199,6 +5206,8 @@ var MethodDefinitionTransformer_1;
 "use strict";
 
 
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@@ -5255,11 +5264,17 @@ var TemplateLiteralTransformer = TemplateLiteralTransformer_1 = function (_Abstr
                 nodes.unshift(Nodes_1.Nodes.getLiteralNode(''));
             }
             if (nodes.length > 1) {
-                var root = Nodes_1.Nodes.getBinaryExpressionNode('+', nodes.shift(), nodes.shift());
-                nodes.forEach(function (node) {
-                    root = Nodes_1.Nodes.getBinaryExpressionNode('+', root, node);
-                });
-                return root;
+                var _ret = function () {
+                    var root = Nodes_1.Nodes.getBinaryExpressionNode('+', nodes.shift(), nodes.shift());
+                    nodes.forEach(function (node) {
+                        root = Nodes_1.Nodes.getBinaryExpressionNode('+', root, node);
+                    });
+                    return {
+                        v: root
+                    };
+                }();
+
+                if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v;
             }
             return nodes[0];
         }
@@ -5338,14 +5353,20 @@ var DeadCodeInjectionTransformer = DeadCodeInjectionTransformer_1 = function (_A
 
             estraverse.traverse(programNode, {
                 enter: function enter(node, parentNode) {
-                    return DeadCodeInjectionTransformer_1.collectBlockStatementNodes(node, _this3.collectedBlockStatements);
+                    if (!Node_1.Node.isBlockStatementNode(node)) {
+                        return;
+                    }
+                    DeadCodeInjectionTransformer_1.collectBlockStatementNodes(node, _this3.collectedBlockStatements);
                 }
             });
-            if (this.collectedBlockStatements.length < 10) {
+            if (this.collectedBlockStatements.length < DeadCodeInjectionTransformer_1.minCollectedBlockStatementsCount) {
                 return;
             }
             estraverse.replace(programNode, {
                 leave: function leave(node, parentNode) {
+                    if (!_this3.collectedBlockStatements.length) {
+                        return estraverse.VisitorOption.Break;
+                    }
                     if (!Node_1.Node.isBlockStatementNode(node) || RandomGeneratorUtils_1.RandomGeneratorUtils.getMathRandom() > _this3.options.deadCodeInjectionThreshold) {
                         return node;
                     }
@@ -5354,47 +5375,39 @@ var DeadCodeInjectionTransformer = DeadCodeInjectionTransformer_1 = function (_A
                     if (randomBlockStatementNode === node) {
                         return node;
                     }
-                    return DeadCodeInjectionTransformer_1.replaceBlockStatementNodes(node, randomBlockStatementNode);
+                    return DeadCodeInjectionTransformer_1.replaceBlockStatementNode(node, randomBlockStatementNode);
                 }
             });
         }
     }], [{
         key: "collectBlockStatementNodes",
-        value: function collectBlockStatementNodes(targetNode, collectedBlockStatements) {
-            if (!Node_1.Node.isBlockStatementNode(targetNode) || !DeadCodeInjectionTransformer_1.isValidBlockStatementNode(targetNode)) {
-                return;
-            }
-            var clonedBlockStatementNode = NodeUtils_1.NodeUtils.clone(targetNode);
+        value: function collectBlockStatementNodes(blockStatementNode, collectedBlockStatements) {
+            var clonedBlockStatementNode = NodeUtils_1.NodeUtils.clone(blockStatementNode);
+            var nestedBlockStatementsCount = 0,
+                isValidBlockStatementNode = true;
             estraverse.replace(clonedBlockStatementNode, {
                 enter: function enter(node, parentNode) {
-                    if (Node_1.Node.isIdentifierNode(node)) {
+                    if (Node_1.Node.isBlockStatementNode(node)) {
+                        nestedBlockStatementsCount++;
+                    }
+                    if (nestedBlockStatementsCount > DeadCodeInjectionTransformer_1.maxNestedBlockStatementsCount || Node_1.Node.isBreakStatementNode(node) || Node_1.Node.isContinueStatementNode(node)) {
+                        isValidBlockStatementNode = false;
+                        return estraverse.VisitorOption.Break;
+                    }
+                    if (Node_1.Node.isIdentifierNode(node) && !Node_1.Node.isMemberExpressionNode(parentNode)) {
                         node.name = RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomVariableName(6);
                     }
                     return node;
                 }
             });
+            if (!isValidBlockStatementNode) {
+                return;
+            }
             collectedBlockStatements.push(clonedBlockStatementNode);
         }
     }, {
-        key: "isValidBlockStatementNode",
-        value: function isValidBlockStatementNode(blockStatementNode) {
-            var blockStatementsCount = 0,
-                isValidBlockStatementNode = true;
-            estraverse.traverse(blockStatementNode, {
-                enter: function enter(node, parentNode) {
-                    if (blockStatementNode !== node && Node_1.Node.isBlockStatementNode(node)) {
-                        blockStatementsCount++;
-                    }
-                    if (blockStatementsCount > DeadCodeInjectionTransformer_1.maxNestedBlockStatementsCount || Node_1.Node.isBreakStatementNode(node) || Node_1.Node.isContinueStatementNode(node)) {
-                        isValidBlockStatementNode = false;
-                    }
-                }
-            });
-            return isValidBlockStatementNode;
-        }
-    }, {
-        key: "replaceBlockStatementNodes",
-        value: function replaceBlockStatementNodes(blockStatementNode, randomBlockStatementNode) {
+        key: "replaceBlockStatementNode",
+        value: function replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode) {
             var random1 = RandomGeneratorUtils_1.RandomGeneratorUtils.getMathRandom() > 0.5;
             var random2 = RandomGeneratorUtils_1.RandomGeneratorUtils.getMathRandom() > 0.5;
             var operator = random1 ? '===' : '!==';
@@ -5418,6 +5431,7 @@ var DeadCodeInjectionTransformer = DeadCodeInjectionTransformer_1 = function (_A
     return DeadCodeInjectionTransformer;
 }(AbstractNodeTransformer_1.AbstractNodeTransformer);
 DeadCodeInjectionTransformer.maxNestedBlockStatementsCount = 4;
+DeadCodeInjectionTransformer.minCollectedBlockStatementsCount = 5;
 DeadCodeInjectionTransformer = DeadCodeInjectionTransformer_1 = tslib_1.__decorate([inversify_1.injectable(), tslib_1.__param(0, inversify_1.inject(ServiceIdentifiers_1.ServiceIdentifiers.IOptions)), tslib_1.__metadata("design:paramtypes", [Object])], DeadCodeInjectionTransformer);
 exports.DeadCodeInjectionTransformer = DeadCodeInjectionTransformer;
 var DeadCodeInjectionTransformer_1;
@@ -6387,6 +6401,7 @@ tslib_1.__decorate([class_validator_1.IsBoolean(), tslib_1.__metadata("design:ty
 tslib_1.__decorate([class_validator_1.IsArray(), class_validator_1.ArrayUnique(), class_validator_1.IsString({
     each: true
 }), tslib_1.__metadata("design:type", Array)], Options.prototype, "domainLock", void 0);
+tslib_1.__decorate([class_validator_1.IsBoolean(), tslib_1.__metadata("design:type", Boolean)], Options.prototype, "mangle", void 0);
 tslib_1.__decorate([class_validator_1.IsArray(), class_validator_1.ArrayUnique(), class_validator_1.IsString({
     each: true
 }), tslib_1.__metadata("design:type", Array)], Options.prototype, "reservedNames", void 0);
@@ -6738,7 +6753,7 @@ var StackTraceAnalyzer = StackTraceAnalyzer_1 = function () {
                 if (!calleeData) {
                     return;
                 }
-                stackTraceData.push(Object.assign({}, calleeData, { stackTrace: _this2.analyzeRecursive(calleeData.callee.body) }));
+                stackTraceData.push(tslib_1.__assign({}, calleeData, { stackTrace: _this2.analyzeRecursive(calleeData.callee.body) }));
             });
         }
     }], [{
@@ -7500,24 +7515,30 @@ module.exports = require("commander");
 /* 129 */
 /***/ (function(module, exports) {
 
-module.exports = require("events");
+module.exports = require("esmangle");
 
 /***/ }),
 /* 130 */
 /***/ (function(module, exports) {
 
-module.exports = require("fs");
+module.exports = require("events");
 
 /***/ }),
 /* 131 */
 /***/ (function(module, exports) {
 
-module.exports = require("mkdirp");
+module.exports = require("fs");
 
 /***/ }),
 /* 132 */
 /***/ (function(module, exports) {
 
+module.exports = require("mkdirp");
+
+/***/ }),
+/* 133 */
+/***/ (function(module, exports) {
+
 module.exports = require("reflect-metadata");
 
 /***/ })

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "class-validator": "0.7.0",
     "commander": "2.9.0",
     "escodegen": "1.8.1",
+    "esmangle": "^1.0.1",
     "esprima": "3.1.3",
     "estraverse": "4.2.0",
     "inversify": "3.3.0",

+ 10 - 4
src/JavaScriptObfuscatorInternal.ts

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

+ 7 - 3
src/Obfuscator.ts

@@ -136,13 +136,17 @@ export class Obfuscator implements IObfuscator {
 
         this.obfuscationEventEmitter.emit(ObfuscationEvents.BeforeObfuscation, astTree, stackTraceData);
 
-        // first pass transformers: dead code injection and control flow flattening transformers
+        // 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.deadCodeInjection ? Obfuscator.deadCodeInjectionTransformersList : [],
             ...this.options.controlFlowFlattening ? Obfuscator.controlFlowTransformersList : []
         ]);
 
-        // second pass: nodes obfuscation
+        // third pass: converting and obfuscating transformers
         astTree = this.transformAstTree(astTree, [
             ...Obfuscator.convertingTransformersList,
             ...Obfuscator.obfuscatingTransformersList

+ 4 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -194,6 +194,10 @@ export class JavaScriptObfuscatorCLI {
                 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)',
                 (value: string) => value.split(',')
             )
+            .option(
+                '--mangle <boolean>', 'Enables mangling of variable names',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
             .option(
                 '--reservedNames <list>',
                 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)',

+ 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;
+}

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

@@ -11,6 +11,7 @@ export interface IOptions {
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
+    readonly mangle: boolean;
     readonly reservedNames: string[];
     readonly rotateStringArray: boolean;
     readonly seed: number;

+ 56 - 38
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts

@@ -18,12 +18,17 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
     /**
      * @type {number}
      */
-    private static maxNestedBlockStatementsCount: number = 4;
+    private static readonly maxNestedBlockStatementsCount: number = 4;
+
+    /**
+     * @type {number}
+     */
+    private static readonly minCollectedBlockStatementsCount: number = 5;
 
     /**
      * @type {ESTree.BlockStatement[]}
      */
-    private collectedBlockStatements: ESTree.BlockStatement[] = [];
+    private readonly collectedBlockStatements: ESTree.BlockStatement[] = [];
 
     /**
      * @param options
@@ -35,54 +40,58 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param targetNode
+     * @param blockStatementNode
      * @param collectedBlockStatements
      */
-    private static collectBlockStatementNodes (targetNode: ESTree.Node, collectedBlockStatements: ESTree.BlockStatement[]): void {
-        if (!Node.isBlockStatementNode(targetNode) || !DeadCodeInjectionTransformer.isValidBlockStatementNode(targetNode)) {
-            return;
-        }
-
-        const clonedBlockStatementNode: ESTree.BlockStatement = <ESTree.BlockStatement>NodeUtils.clone(targetNode);
-
-        estraverse.replace(clonedBlockStatementNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (Node.isIdentifierNode(node)) {
-                    node.name = RandomGeneratorUtils.getRandomVariableName(6);
-                }
-
-                return node;
-            }
-        });
-
-        collectedBlockStatements.push(clonedBlockStatementNode);
-    }
+    private static collectBlockStatementNodes (
+        blockStatementNode: ESTree.BlockStatement,
+        collectedBlockStatements: ESTree.BlockStatement[]
+    ): void {
+        const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode);
 
-    /**
-     * @param blockStatementNode
-     * @return {boolean}
-     */
-    private static isValidBlockStatementNode (blockStatementNode: ESTree.BlockStatement): boolean {
-        let blockStatementsCount: number = 0,
+        let nestedBlockStatementsCount: number = 0,
             isValidBlockStatementNode: boolean = true;
 
-        estraverse.traverse(blockStatementNode, {
+        estraverse.replace(clonedBlockStatementNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (blockStatementNode !== node && Node.isBlockStatementNode(node)) {
-                    blockStatementsCount++;
+                /**
+                 * 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 (
-                    blockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
+                    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;
             }
         });
 
-        return isValidBlockStatementNode;
+        if (!isValidBlockStatementNode) {
+            return;
+        }
+
+        collectedBlockStatements.push(clonedBlockStatementNode);
     }
 
     /**
@@ -90,7 +99,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
      * @param randomBlockStatementNode
      * @return {ESTree.BlockStatement}
      */
-    private static replaceBlockStatementNodes (
+    private static replaceBlockStatementNode (
         blockStatementNode: ESTree.BlockStatement,
         randomBlockStatementNode: ESTree.BlockStatement
     ): ESTree.BlockStatement {
@@ -158,16 +167,25 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
      */
     private transformProgramNode (programNode: ESTree.Program): void {
         estraverse.traverse(programNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any =>
-                DeadCodeInjectionTransformer.collectBlockStatementNodes(node, this.collectedBlockStatements)
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (!Node.isBlockStatementNode(node)) {
+                    return;
+                }
+
+                DeadCodeInjectionTransformer.collectBlockStatementNodes(node, this.collectedBlockStatements);
+            }
         });
 
-        if (this.collectedBlockStatements.length < 10) {
+        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
@@ -182,7 +200,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
                     return node;
                 }
 
-                return DeadCodeInjectionTransformer.replaceBlockStatementNodes(node, randomBlockStatementNode);
+                return DeadCodeInjectionTransformer.replaceBlockStatementNode(node, randomBlockStatementNode);
             }
         });
     }

+ 3 - 3
src/node/NodeUtils.ts

@@ -44,8 +44,8 @@ export class NodeUtils {
      * @param astTree
      * @return {ESTree.Node}
      */
-    public static clone (astTree: ESTree.Node): ESTree.Node {
-        const cloneRecursive: (node: ESTree.Node) => ESTree.Node = (node: 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
@@ -69,7 +69,7 @@ export class NodeUtils {
                     copy[property] = clonedValue;
                 });
 
-            return <ESTree.Node>copy;
+            return <T>copy;
         };
 
         return NodeUtils.parentize(cloneRecursive(astTree));

+ 6 - 0
src/options/Options.ts

@@ -99,6 +99,12 @@ export class Options implements IOptions {
     })
     public readonly domainLock: string[];
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly mangle: boolean;
+
     /**
      * @type {string[]}
      */

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

@@ -12,6 +12,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     rotateStringArray: true,
     seed: 0,

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

@@ -12,6 +12,7 @@ export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    mangle: false,
     reservedNames: [],
     rotateStringArray: false,
     seed: 0,

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

@@ -199,6 +199,17 @@ describe('JavaScriptObfuscator', () => {
             assert.notEqual(obfuscationResult3.getObfuscatedCode(), obfuscationResult4.getObfuscatedCode());
         });
 
+        it('should mangle obfuscated code', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { mangle: true }
+            );
+            const mangleMatch: RegExp = /var *a *= *0x1/;
+
+            assert.match(obfuscationResult1.getObfuscatedCode(), mangleMatch);
+        });
+
         afterEach(() => {
             RandomGeneratorUtils.initializeRandomGenerator(0);
         });

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

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