UNPKG

js-confuser

Version:

JavaScript Obfuscation Tool.

499 lines (488 loc) 22.5 kB
"use strict"; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var t = _interopRequireWildcard(require("@babel/types")); var _order = require("../order"); var _astUtils = require("../utils/ast-utils"); var _constants = require("../constants"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var identifierMap = new Map(); identifierMap.set("undefined", function () { return t.unaryExpression("void", t.numericLiteral(0)); }); identifierMap.set("Infinity", function () { return t.binaryExpression("/", t.numericLiteral(1), t.numericLiteral(0)); }); /** * Minify removes unnecessary code and shortens the length for file size. * * - Dead code elimination * - Variable grouping * - Constant folding * - Shorten literals: True to !0, False to !1, Infinity to 1/0, Undefined to void 0 * - Remove unused variables, functions */ var _default = exports["default"] = function _default(_ref) { var Plugin = _ref.Plugin; var me = Plugin(_order.Order.Minify); return { visitor: { Program: function Program(path) { path.scope.crawl(); }, // var a; var b; -> var a,b; VariableDeclaration: { exit: function exit(path) { if (typeof path.key !== "number") return; var kind = path.node.kind; // get declaration after this var nextDeclaration = path.getSibling(path.key + 1); if (nextDeclaration.isVariableDeclaration({ kind: kind })) { var _nextDeclaration$node; // Add bindings back var addBindingsToScope = function addBindingsToScope(scope) { for (var name in bindings) { var binding = bindings[name]; if (binding) { binding.path = newBindingIdentifierPaths[name]; scope.bindings[name] = binding; } } }; var declarations = path.get("declarations"); // Preserve bindings! // This is important for dead code elimination var bindings = Object.create(null); var _iterator = _createForOfIteratorHelper(declarations), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var declaration = _step.value; for (var _i = 0, _Object$values = Object.values(declaration.getBindingIdentifierPaths()); _i < _Object$values.length; _i++) { var idPath = _Object$values[_i]; bindings[idPath.node.name] = idPath.scope.getBinding(idPath.node.name); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } (_nextDeclaration$node = nextDeclaration.node.declarations).unshift.apply(_nextDeclaration$node, _toConsumableArray(declarations.map(function (x) { return x.node; }))); var newBindingIdentifierPaths = nextDeclaration.getBindingIdentifierPaths(); // path.remove() unfortunately removes the bindings // We must perverse the entire binding object (referencePaths, constantViolations, etc) // and re-add them to the new scope path.remove(); if (kind === "var") { addBindingsToScope((0, _astUtils.getParentFunctionOrProgram)(path).scope); } addBindingsToScope(path.scope); } } }, // true -> !0, false -> !1 BooleanLiteral: { exit: function exit(path) { if (path.node.value) { path.replaceWith(t.unaryExpression("!", t.numericLiteral(0))); } else { path.replaceWith(t.unaryExpression("!", t.numericLiteral(1))); } } }, // !"" -> !1 UnaryExpression: { exit: function exit(path) { if (path.node.operator === "!") { var argument = path.get("argument"); if (argument.isNumericLiteral()) return; var value = argument.evaluateTruthy(); var parent = (0, _astUtils.getParentFunctionOrProgram)(path); if (parent && parent.node[_constants.UNSAFE]) return; if (value === undefined) return; path.replaceWith(t.unaryExpression("!", t.numericLiteral(value ? 1 : 0))); } } }, // "a" + "b" -> "ab" BinaryExpression: { exit: function exit(path) { if (path.node.operator !== "+") return; var left = path.get("left"); var right = path.get("right"); if (!left.isStringLiteral() || !right.isStringLiteral()) return; path.replaceWith(t.stringLiteral(left.node.value + right.node.value)); } }, // a["key"] -> a.key MemberExpression: { exit: function exit(path) { if (!path.node.computed) return; var property = path.get("property"); if (!property.isStringLiteral()) return; var key = property.node.value; if (!t.isValidIdentifier(key)) return; path.node.computed = false; path.node.property = t.identifier(key); } }, // {["key"]: 1} -> {key: 1} // {"key": 1} -> {key: 1} ObjectProperty: { exit: function exit(path) { var key = path.get("key"); if (path.node.computed && key.isStringLiteral()) { path.node.computed = false; } if (!path.node.computed && key.isStringLiteral() && t.isValidIdentifier(key.node.value)) { if (identifierMap.has(key.node.value)) { path.node.computed = true; key.replaceWith(identifierMap.get(key.node.value)()); } else { key.replaceWith(t.identifier(key.node.value)); } } } }, // (a); -> a; SequenceExpression: { exit: function exit(path) { if (path.node.expressions.length === 1) { path.replaceWith(path.node.expressions[0]); } } }, // ; -> () EmptyStatement: { exit: function exit(path) { path.remove(); } }, // console; -> (); ExpressionStatement: { exit: function exit(path) { if (path.get("expression").isIdentifier()) { var _path$parentPath, _path$parentPath2; // Preserve last expression of program for RGF if ((_path$parentPath = path.parentPath) !== null && _path$parentPath !== void 0 && _path$parentPath.isProgram() && ((_path$parentPath2 = path.parentPath) === null || _path$parentPath2 === void 0 ? void 0 : _path$parentPath2.get("body").at(-1)) === path) return; path.remove(); } } }, // undefined -> void 0 // Infinity -> 1/0 Identifier: { exit: function exit(path) { if (path.isReferencedIdentifier()) { if (identifierMap.has(path.node.name)) { (0, _astUtils.ensureComputedExpression)(path); path.replaceWith(identifierMap.get(path.node.name)()); } } } }, // true ? a : b -> a ConditionalExpression: { exit: function exit(path) { var testValue = path.get("test").evaluateTruthy(); if (testValue === undefined) return; path.replaceWith(testValue ? path.node.consequent : path.node.alternate); } }, // Remove unused functions FunctionDeclaration: { exit: function exit(path) { var id = path.get("id"); if (id.isIdentifier() && !id.node.name.startsWith(_constants.placeholderVariablePrefix) && !path.node[_constants.NO_REMOVE]) { var binding = path.scope.getBinding(id.node.name); if (binding && binding.constantViolations.length === 0 && binding.referencePaths.length === 0 && !binding.referenced) { path.remove(); } } } }, // var x=undefined -> var x // Remove unused variables // Simple destructuring VariableDeclarator: { exit: function exit(path) { if ((0, _astUtils.isUndefined)(path.get("init"))) { path.node.init = null; } var id = path.get("id"); var init = path.get("init"); // Simple array/object destructuring if (id.isArrayPattern() && init.isArrayExpression()) { var elements = id.get("elements"); var initElements = init.get("elements"); if (elements.length === 1 && initElements.length === 1) { id.replaceWith(elements[0]); init.replaceWith(initElements[0]); } } if (id.isObjectPattern() && init.isObjectExpression()) { var properties = id.get("properties"); var initProperties = init.get("properties"); if (properties.length === 1 && initProperties.length === 1) { var firstProperty = properties[0]; var firstInitProperty = initProperties[0]; if (firstProperty.isObjectProperty() && firstInitProperty.isObjectProperty()) { var firstKey = firstProperty.get("key"); var firstInitKey = firstInitProperty.get("key"); if (firstKey.isIdentifier() && firstInitKey.isIdentifier() && firstKey.node.name === firstInitKey.node.name) { id.replaceWith(firstProperty.node.value); init.replaceWith(firstInitProperty.node.value); } } } } // Remove unused variables // Can only remove if it's pure if (id.isIdentifier()) { // Do not remove variables in unsafe functions var fn = (0, _astUtils.getParentFunctionOrProgram)(path); if (fn.node[_constants.UNSAFE]) return; // Node explicitly marked as not to be removed if (id[_constants.NO_REMOVE]) return; var binding = path.scope.getBinding(id.node.name); if (binding && binding.constantViolations.length === 0 && binding.referencePaths.length === 0) { if (!init.node || init.isPure()) { path.remove(); } else if (path.parentPath.isVariableDeclaration() && path.parentPath.node.declarations.length === 1) { path.parentPath.replaceWith(t.expressionStatement(init.node)); } } } } }, // return undefined->return ReturnStatement: { exit: function exit(path) { if ((0, _astUtils.isUndefined)(path.get("argument"))) { path.node.argument = null; } } }, // while(true) {a();} -> while(true) a(); // for(;;) {a();} -> for(;;) a(); // with(a) {a();} -> with(a) a(); "While|For|WithStatement": { exit: function exit(_path) { var path = _path; var body = path.get("body"); if (body.isBlock() && body.node.body.length === 1) { body.replaceWith(body.node.body[0]); } } }, // if(a) a(); -> a && a(); // if(a) { return b; } -> if(a) return b; // if(a) { a(); } else { b(); } -> a ? a() : b(); // if(a) { return b; } else { return c; } -> return a ? b : c; IfStatement: { exit: function exit(path) { // BlockStatement to single statement var consequent = path.get("consequent"); var alternate = path.get("alternate"); var isMoveable = function isMoveable(node) { if (t.isDeclaration(node)) return false; return true; }; var testValue = path.get("test").evaluateTruthy(); var parent = (0, _astUtils.getParentFunctionOrProgram)(path); if (parent && parent.node[_constants.UNSAFE]) { testValue = undefined; } if (typeof testValue !== "undefined") { if (!alternate.node && consequent.isBlock() && consequent.node.body.length === 1 && isMoveable(consequent.node.body[0])) { consequent.replaceWith(consequent.node.body[0]); } if (alternate.node && alternate.isBlock() && alternate.node.body.length === 1 && isMoveable(alternate.node.body[0])) { alternate.replaceWith(alternate.node.body[0]); } } if (testValue === false) { // if(false){} -> () if (!alternate.node) { path.remove(); return; // if(false){a()}else{b()} -> b() } else { path.replaceWith(alternate.node); return; } // if(true){a()} -> {a()} } else if (testValue === true) { path.replaceWith(consequent.node); return; } function getResult(path) { if (!path.node) return null; if (path.isReturnStatement()) { return { returnPath: path, expressions: [] }; } if (path.isExpressionStatement()) { return { returnPath: null, expressions: [path.get("expression").node] }; } if (path.isBlockStatement()) { var expressions = []; var _iterator2 = _createForOfIteratorHelper(path.get("body")), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var statement = _step2.value; if (statement.isReturnStatement()) { return { returnPath: statement, expressions: expressions }; } else if (statement.isExpressionStatement()) { expressions.push(statement.get("expression").node); } else { return null; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return { returnPath: null, expressions: expressions }; } return null; } var consequentReturn = getResult(consequent); var alternateReturn = getResult(alternate); if (consequentReturn && alternateReturn) { if (consequentReturn.returnPath && alternateReturn.returnPath) { var createReturnArgument = function createReturnArgument(resultInfo) { return t.sequenceExpression([].concat(_toConsumableArray(resultInfo.expressions), [resultInfo.returnPath.node.argument || t.identifier("undefined")])); }; path.replaceWith(t.returnStatement(t.conditionalExpression(path.node.test, createReturnArgument(consequentReturn), createReturnArgument(alternateReturn)))); } else if (!consequentReturn.returnPath && !alternateReturn.returnPath) { var joinExpressions = function joinExpressions(expressions) { // condition?():() is invalid syntax // Just use 0 as a placeholder if (expressions.length === 0) return t.numericLiteral(0); // No need for sequence expression if there's only one expression if (expressions.length === 1) return expressions[0]; return t.sequenceExpression(expressions); }; path.replaceWith(t.conditionalExpression(path.node.test, joinExpressions(consequentReturn.expressions), joinExpressions(alternateReturn.expressions))); } } } }, // Remove unreachable code // Code after a return/throw/break/continue is unreachable // Remove implied returns // Remove code after if all branches are unreachable "Block|SwitchCase": { enter: function enter(path) { if (path.isProgram()) { path.scope.crawl(); } }, exit: function exit(path) { var statementList = path.isBlock() ? path.get("body") : path.get("consequent"); var impliedReturn; function isUnreachable(statementList) { var topLevel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var unreachableState = false; var _iterator3 = _createForOfIteratorHelper(statementList), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var statement = _step3.value; if (unreachableState) { statement.remove(); continue; } if (statement.isIfStatement()) { var consequent = statement.get("consequent"); var alternate = statement.get("alternate"); if ([consequent, alternate].every(function (x) { return x.node && x.isBlockStatement() && isUnreachable(x.get("body")); })) { unreachableState = true; if (!topLevel) { return true; } else { continue; } } } if (statement.isSwitchStatement()) { // Can only remove switch statements if all cases are unreachable // And all paths are exhausted var cases = statement.get("cases"); var hasDefaultCase = cases.some(function (x) { return !x.node.test; }); if (hasDefaultCase && cases.every(function (x) { return isUnreachable(x.get("consequent")); })) { unreachableState = true; if (!topLevel) { return true; } else { continue; } } } if (statement.isReturnStatement() || statement.isThrowStatement() || statement.isBreakStatement() || statement.isContinueStatement()) { unreachableState = true; if (!topLevel) { return true; } } if (topLevel) { if (statement == statementList.at(-1) && statement.isReturnStatement() && !statement.node.argument) { impliedReturn = statement; } } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } return false; } isUnreachable(statementList, true); if (impliedReturn) { var functionParent = path.getFunctionParent(); if (functionParent && t.isBlockStatement(functionParent.node.body) && functionParent.node.body === path.node) { impliedReturn.remove(); } } } } } }; };