UNPKG

ben-sb-shift-codegen

Version:

code generator for Shift format ASTs (forked for use in https://github.com/ben-sb/javascript-deobfuscator)

795 lines (677 loc) 25.9 kB
const objectAssign = require('object-assign'); const { keyword } = require('esutils'); const { Precedence, getPrecedence, escapeStringLiteral, Empty, Token, RawToken, NumberCodeRep, Paren, Bracket, Brace, NoIn, ContainsIn, Seq, Semi, CommaSep, SemiOp } = require('./coderep'); function p(node, precedence, a) { return getPrecedence(node) < precedence ? paren(a) : a; } function t(token, isRegExp = false) { return new Token(token, isRegExp); } function paren(rep) { return new Paren(rep); } function brace(rep) { return new Brace(rep); } function bracket(rep) { return new Bracket(rep); } function noIn(rep) { return new NoIn(rep); } function markContainsIn(state) { return state.containsIn ? new ContainsIn(state) : state; } function seq(...reps) { return new Seq(reps); } function semi() { return new Semi; } function semiOp() { return new SemiOp; } function empty() { return new Empty; } function commaSep(pieces) { return new CommaSep(pieces); } function getAssignmentExpr(state) { return state ? state.containsGroup ? paren(state) : state : empty(); } class MinimalCodeGen { parenToAvoidBeingDirective(element, original) { if (element && element.type === 'ExpressionStatement' && element.expression.type === 'LiteralStringExpression') { return seq(paren(original.children[0]), semiOp()); } return original; } regenerateArrowParams(element, original) { if (element.rest == null && element.items.length === 1 && element.items[0].type === 'BindingIdentifier') { // FormalParameters unconditionally include parentheses, but they're not necessary here return this.reduceBindingIdentifier(element.items[0]); } return original; } reduceArrayExpression(node, { elements }) { if (elements.length === 0) { return bracket(empty()); } let content = commaSep(elements.map(getAssignmentExpr)); if (elements.length > 0 && elements[elements.length - 1] == null) { content = seq(content, t(',')); } return bracket(content); } reduceAwaitExpression(node, { expression }) { return seq(t('await'), p(node.expression, getPrecedence(node), expression)); } reduceSpreadElement(node, { expression }) { return seq(t('...'), p(node.expression, Precedence.Assignment, expression)); } reduceSpreadProperty(node, { expression }) { return seq(t('...'), getAssignmentExpr(expression)); } reduceAssignmentExpression(node, { binding, expression }) { let leftCode = binding; let rightCode = expression; let containsIn = expression.containsIn; let startsWithCurly = binding.startsWithCurly; let startsWithLetSquareBracket = binding.startsWithLetSquareBracket; let startsWithFunctionOrClass = binding.startsWithFunctionOrClass; if (getPrecedence(node.expression) < getPrecedence(node)) { rightCode = paren(rightCode); containsIn = false; } return objectAssign(seq(leftCode, t('='), rightCode), { containsIn, startsWithCurly, startsWithLetSquareBracket, startsWithFunctionOrClass }); } reduceAssignmentTargetIdentifier(node) { let a = t(node.name); if (node.name === 'let') { a.startsWithLet = true; } return a; } reduceAssignmentTargetWithDefault(node, { binding, init }) { return seq(binding, t('='), p(node.init, Precedence.Assignment, init)); } reduceCompoundAssignmentExpression(node, { binding, expression }) { let leftCode = binding; let rightCode = expression; let containsIn = expression.containsIn; let startsWithCurly = binding.startsWithCurly; let startsWithLetSquareBracket = binding.startsWithLetSquareBracket; let startsWithFunctionOrClass = binding.startsWithFunctionOrClass; if (getPrecedence(node.expression) < getPrecedence(node)) { rightCode = paren(rightCode); containsIn = false; } return objectAssign(seq(leftCode, t(node.operator), rightCode), { containsIn, startsWithCurly, startsWithLetSquareBracket, startsWithFunctionOrClass }); } reduceBinaryExpression(node, { left, right }) { let leftCode = left; let startsWithCurly = left.startsWithCurly; let startsWithLetSquareBracket = left.startsWithLetSquareBracket; let startsWithFunctionOrClass = left.startsWithFunctionOrClass; let leftContainsIn = left.containsIn; let isRightAssociative = node.operator === '**'; if (getPrecedence(node.left) < getPrecedence(node) || isRightAssociative && (getPrecedence(node.left) === getPrecedence(node) || node.left.type === 'UnaryExpression')) { leftCode = paren(leftCode); startsWithCurly = false; startsWithLetSquareBracket = false; startsWithFunctionOrClass = false; leftContainsIn = false; } let rightCode = right; let rightContainsIn = right.containsIn; if (getPrecedence(node.right) < getPrecedence(node) || !isRightAssociative && getPrecedence(node.right) === getPrecedence(node)) { rightCode = paren(rightCode); rightContainsIn = false; } return objectAssign( seq(leftCode, t(node.operator), rightCode), { containsIn: leftContainsIn || rightContainsIn || node.operator === 'in', containsGroup: node.operator === ',', startsWithCurly, startsWithLetSquareBracket, startsWithFunctionOrClass, } ); } reduceBindingWithDefault(node, { binding, init }) { return seq(binding, t('='), p(node.init, Precedence.Assignment, init)); } reduceBindingIdentifier(node) { let a = t(node.name); if (node.name === 'let') { a.startsWithLet = true; } return a; } reduceArrayAssignmentTarget(node, { elements, rest }) { let content; if (elements.length === 0) { content = rest == null ? empty() : seq(t('...'), rest); } else { elements = elements.concat(rest == null ? [] : [seq(t('...'), rest)]); content = commaSep(elements.map(getAssignmentExpr)); if (elements.length > 0 && elements[elements.length - 1] == null) { content = seq(content, t(',')); } } return bracket(content); } reduceArrayBinding(node, { elements, rest }) { let content; if (elements.length === 0) { content = rest == null ? empty() : seq(t('...'), rest); } else { elements = elements.concat(rest == null ? [] : [seq(t('...'), rest)]); content = commaSep(elements.map(getAssignmentExpr)); if (elements.length > 0 && elements[elements.length - 1] == null) { content = seq(content, t(',')); } } return bracket(content); } reduceObjectAssignmentTarget(node, { properties, rest }) { let content = commaSep(properties); if (properties.length === 0) { content = rest == null ? empty() : seq(t('...'), rest); } else { content = rest == null ? content : seq(content, t(','), t('...'), rest); } let state = brace(content); state.startsWithCurly = true; return state; } reduceObjectBinding(node, { properties, rest }) { let content = commaSep(properties); if (properties.length === 0) { content = rest == null ? empty() : seq(t('...'), rest); } else { content = rest == null ? content : seq(content, t(','), t('...'), rest); } let state = brace(content); state.startsWithCurly = true; return state; } reduceAssignmentTargetPropertyIdentifier(node, { binding, init }) { if (node.init == null) return binding; return seq(binding, t('='), p(node.init, Precedence.Assignment, init)); } reduceAssignmentTargetPropertyProperty(node, { name, binding }) { return seq(name, t(':'), binding); } reduceBindingPropertyIdentifier(node, { binding, init }) { if (node.init == null) return binding; return seq(binding, t('='), p(node.init, Precedence.Assignment, init)); } reduceBindingPropertyProperty(node, { name, binding }) { return seq(name, t(':'), binding); } reduceBlock(node, { statements }) { return brace(seq(...statements)); } reduceBlockStatement(node, { block }) { return block; } reduceBreakStatement(node) { return seq(t('break'), node.label ? t(node.label) : empty(), semiOp()); } reduceCallExpression(node, { callee, arguments: args }) { const parenthizedArgs = args.map((a, i) => p(node.arguments[i], Precedence.Assignment, a)); return objectAssign( seq(p(node.callee, getPrecedence(node), callee), paren(commaSep(parenthizedArgs))), { startsWithCurly: callee.startsWithCurly, startsWithLet: callee.startsWithLet, startsWithLetSquareBracket: callee.startsWithLetSquareBracket, startsWithFunctionOrClass: callee.startsWithFunctionOrClass, } ); } reduceCatchClause(node, { binding, body }) { if (binding == null) { return seq(t('catch'), body); } return seq(t('catch'), paren(binding), body); } reduceClassDeclaration(node, { name, super: _super, elements }) { let state = seq(t('class'), node.name.name === '*default*' ? empty() : name); if (_super != null) { state = seq(state, t('extends'), p(node.super, Precedence.New, _super)); } state = seq(state, t('{'), ...elements, t('}')); return state; } reduceClassExpression(node, { name, super: _super, elements }) { let state = t('class'); if (name != null) { state = seq(state, name); } if (_super != null) { state = seq(state, t('extends'), p(node.super, Precedence.New, _super)); } state = seq(state, t('{'), ...elements, t('}')); state.startsWithFunctionOrClass = true; return state; } reduceClassElement(node, { method }) { if (!node.isStatic) return method; return seq(t('static'), method); } reduceComputedMemberAssignmentTarget(node, { object, expression }) { let startsWithLetSquareBracket = object.startsWithLetSquareBracket || node.object.type === 'IdentifierExpression' && node.object.name === 'let'; return objectAssign( seq(p(node.object, getPrecedence(node), object), bracket(expression)), { startsWithLet: object.startsWithLet, startsWithLetSquareBracket, startsWithCurly: object.startsWithCurly, startsWithFunctionOrClass: object.startsWithFunctionOrClass, } ); } reduceComputedMemberExpression(node, { object, expression }) { let startsWithLetSquareBracket = object.startsWithLetSquareBracket || node.object.type === 'IdentifierExpression' && node.object.name === 'let'; return objectAssign( seq(p(node.object, getPrecedence(node), object), bracket(expression)), { startsWithLet: object.startsWithLet, startsWithLetSquareBracket, startsWithCurly: object.startsWithCurly, startsWithFunctionOrClass: object.startsWithFunctionOrClass, } ); } reduceComputedPropertyName(node, { expression }) { return bracket(p(node.expression, Precedence.Assignment, expression)); } reduceConditionalExpression(node, { test, consequent, alternate }) { let containsIn = test.containsIn || alternate.containsIn; let startsWithCurly = test.startsWithCurly; let startsWithLetSquareBracket = test.startsWithLetSquareBracket; let startsWithFunctionOrClass = test.startsWithFunctionOrClass; return objectAssign( seq( p(node.test, Precedence.LogicalOR, test), t('?'), p(node.consequent, Precedence.Assignment, consequent), t(':'), p(node.alternate, Precedence.Assignment, alternate)), { containsIn, startsWithCurly, startsWithLetSquareBracket, startsWithFunctionOrClass, }); } reduceContinueStatement(node) { return seq(t('continue'), node.label ? t(node.label) : empty(), semiOp()); } reduceDataProperty(node, { name, expression }) { return seq(name, t(':'), getAssignmentExpr(expression)); } reduceDebuggerStatement(/* node */) { return seq(t('debugger'), semiOp()); } reduceDoWhileStatement(node, { body, test }) { return seq(t('do'), body, t('while'), paren(test), semiOp()); } reduceEmptyStatement(/* node */) { return semi(); } reduceExpressionStatement(node, { expression }) { let needsParens = expression.startsWithCurly || expression.startsWithLetSquareBracket || expression.startsWithFunctionOrClass; return seq(needsParens ? paren(expression) : expression, semiOp()); } reduceForInStatement(node, { left, right, body }) { left = node.left.type === 'VariableDeclaration' ? noIn(markContainsIn(left)) : left; return objectAssign( seq(t('for'), paren(seq(left.startsWithLet ? paren(left) : left, t('in'), right)), body), { endsWithMissingElse: body.endsWithMissingElse }); } reduceForOfStatement(node, { left, right, body }) { left = node.left.type === 'VariableDeclaration' ? noIn(markContainsIn(left)) : left; return objectAssign( seq(t('for'), paren(seq(left.startsWithLet ? paren(left) : left, t('of'), p(node.right, Precedence.Assignment, right))), body), { endsWithMissingElse: body.endsWithMissingElse }); } reduceForStatement(node, { init, test, update, body }) { if (init) { if (init.startsWithLetSquareBracket) { init = paren(init); } init = noIn(markContainsIn(init)); } return objectAssign( seq( t('for'), paren(seq(init ? init : empty(), semi(), test || empty(), semi(), update || empty())), body), { endsWithMissingElse: body.endsWithMissingElse, }); } reduceForAwaitStatement(node, { left, right, body }) { left = node.left.type === 'VariableDeclaration' ? noIn(markContainsIn(left)) : left; return objectAssign( seq(t('for'), t('await'), paren(seq(left.startsWithLet ? paren(left) : left, t('of'), p(node.right, Precedence.Assignment, right))), body), { endsWithMissingElse: body.endsWithMissingElse }); } reduceFunctionBody(node, { directives, statements }) { if (statements.length) { statements[0] = this.parenToAvoidBeingDirective(node.statements[0], statements[0]); } return brace(seq(...directives, ...statements)); } reduceFunctionDeclaration(node, { name, params, body }) { return seq(node.isAsync ? t('async') : empty(), t('function'), node.isGenerator ? t('*') : empty(), node.name.name === '*default*' ? empty() : name, params, body); } reduceFunctionExpression(node, { name, params, body }) { let state = seq(node.isAsync ? t('async') : empty(), t('function'), node.isGenerator ? t('*') : empty(), name ? name : empty(), params, body); state.startsWithFunctionOrClass = true; return state; } reduceFormalParameters(node, { items, rest }) { return paren(commaSep(items.concat(rest == null ? [] : [seq(t('...'), rest)]))); } reduceArrowExpression(node, { params, body }) { params = this.regenerateArrowParams(node.params, params); let containsIn = false; if (node.body.type !== 'FunctionBody') { if (body.startsWithCurly) { body = paren(body); } else if (body.containsIn) { containsIn = true; } } return objectAssign(seq(node.isAsync ? t('async') : empty(), params, t('=>'), p(node.body, Precedence.Assignment, body)), { containsIn }); } reduceGetter(node, { name, body }) { return seq(t('get'), name, paren(empty()), body); } reduceIdentifierExpression(node) { let a = t(node.name); if (node.name === 'let') { a.startsWithLet = true; } return a; } reduceIfStatement(node, { test, consequent, alternate }) { if (alternate && consequent.endsWithMissingElse) { consequent = brace(consequent); } return objectAssign( seq(t('if'), paren(test), consequent, alternate ? seq(t('else'), alternate) : empty()), { endsWithMissingElse: alternate ? alternate.endsWithMissingElse : true }); } reduceImport(node, { defaultBinding, namedImports }) { let bindings = []; if (defaultBinding != null) { bindings.push(defaultBinding); } if (namedImports.length > 0) { bindings.push(brace(commaSep(namedImports))); } if (bindings.length === 0) { return seq(t('import'), t(escapeStringLiteral(node.moduleSpecifier)), semiOp()); } return seq(t('import'), commaSep(bindings), t('from'), t(escapeStringLiteral(node.moduleSpecifier)), semiOp()); } reduceImportNamespace(node, { defaultBinding, namespaceBinding }) { return seq( t('import'), defaultBinding == null ? empty() : seq(defaultBinding, t(',')), t('*'), t('as'), namespaceBinding, t('from'), t(escapeStringLiteral(node.moduleSpecifier)), semiOp() ); } reduceImportSpecifier(node, { binding }) { if (node.name == null) return binding; return seq(t(node.name), t('as'), binding); } reduceExportAllFrom(node) { return seq(t('export'), t('*'), t('from'), t(escapeStringLiteral(node.moduleSpecifier)), semiOp()); } reduceExportFrom(node, { namedExports }) { return seq(t('export'), brace(commaSep(namedExports)), t('from'), t(escapeStringLiteral(node.moduleSpecifier)), semiOp()); } reduceExportLocals(node, { namedExports }) { return seq(t('export'), brace(commaSep(namedExports)), semiOp()); } reduceExport(node, { declaration }) { switch (node.declaration.type) { case 'FunctionDeclaration': case 'ClassDeclaration': break; default: declaration = seq(declaration, semiOp()); } return seq(t('export'), declaration); } reduceExportDefault(node, { body }) { body = body.startsWithFunctionOrClass ? paren(body) : body; switch (node.body.type) { case 'FunctionDeclaration': case 'ClassDeclaration': return seq(t('export default'), body); default: return seq(t('export default'), p(node.body, Precedence.Assignment, body), semiOp()); } } reduceExportFromSpecifier(node) { if (node.exportedName == null) return t(node.name); return seq(t(node.name), t('as'), t(node.exportedName)); } reduceExportLocalSpecifier(node, { name }) { if (node.exportedName == null) return name; return seq(name, t('as'), t(node.exportedName)); } reduceLabeledStatement(node, { body }) { return objectAssign(seq(t(node.label + ':'), body), { endsWithMissingElse: body.endsWithMissingElse }); } reduceLiteralBooleanExpression(node) { return t(node.value.toString()); } reduceLiteralNullExpression(/* node */) { return t('null'); } reduceLiteralInfinityExpression(/* node */) { return t('2e308'); } reduceLiteralNumericExpression(node) { return new NumberCodeRep(node.value); } reduceLiteralRegExpExpression(node) { return t(`/${node.pattern}/${node.global ? 'g' : ''}${node.ignoreCase ? 'i' : ''}${node.multiLine ? 'm' : ''}${node.dotAll ? 's' : ''}${node.unicode ? 'u' : ''}${node.sticky ? 'y' : ''}`, true); } reduceLiteralStringExpression(node) { return t(escapeStringLiteral(node.value)); } reduceMethod(node, { name, params, body }) { return seq(node.isAsync ? t('async') : empty(), node.isGenerator ? t('*') : empty(), name, params, body); } reduceModule(node, { directives, items }) { if (items.length) { items[0] = this.parenToAvoidBeingDirective(node.items[0], items[0]); } return seq(...directives, ...items); } reduceNewExpression(node, { callee, arguments: args }) { const parenthizedArgs = args.map((a, i) => p(node.arguments[i], Precedence.Assignment, a)); let calleeRep = getPrecedence(node.callee) === Precedence.Call ? paren(callee) : p(node.callee, getPrecedence(node), callee); return seq(t('new'), calleeRep, args.length === 0 ? empty() : paren(commaSep(parenthizedArgs))); } reduceNewTargetExpression() { return t('new.target'); } reduceObjectExpression(node, { properties }) { let state = brace(commaSep(properties)); state.startsWithCurly = true; return state; } reduceUpdateExpression(node, { operand }) { if (node.isPrefix) { return this.reduceUnaryExpression(...arguments); } return objectAssign( seq(p(node.operand, Precedence.New, operand), t(node.operator)), { startsWithCurly: operand.startsWithCurly, startsWithLetSquareBracket: operand.startsWithLetSquareBracket, startsWithFunctionOrClass: operand.startsWithFunctionOrClass, } ); } reduceUnaryExpression(node, { operand }) { return seq(t(node.operator), p(node.operand, getPrecedence(node), operand)); } reduceReturnStatement(node, { expression }) { return seq(t('return'), expression || empty(), semiOp()); } reduceScript(node, { directives, statements }) { if (statements.length) { statements[0] = this.parenToAvoidBeingDirective(node.statements[0], statements[0]); } return seq(...directives, ...statements); } reduceSetter(node, { name, param, body }) { return seq(t('set'), name, paren(param), body); } reduceShorthandProperty(node, { name }) { return name; } reduceStaticMemberAssignmentTarget(node, { object }) { const state = seq(p(node.object, getPrecedence(node), object), t('.'), t(node.property)); state.startsWithLet = object.startsWithLet; state.startsWithCurly = object.startsWithCurly; state.startsWithLetSquareBracket = object.startsWithLetSquareBracket; state.startsWithFunctionOrClass = object.startsWithFunctionOrClass; return state; } reduceStaticMemberExpression(node, { object }) { const state = seq(p(node.object, getPrecedence(node), object), t('.'), t(node.property)); state.startsWithLet = object.startsWithLet; state.startsWithCurly = object.startsWithCurly; state.startsWithLetSquareBracket = object.startsWithLetSquareBracket; state.startsWithFunctionOrClass = object.startsWithFunctionOrClass; return state; } reduceStaticPropertyName(node) { if (keyword.isIdentifierNameES6(node.value)) { return t(node.value); } let n = parseFloat(node.value); if (n >= 0 && n.toString() === node.value) { return new NumberCodeRep(n); } return t(escapeStringLiteral(node.value)); } reduceSuper() { return t('super'); } reduceSwitchCase(node, { test, consequent }) { return seq(t('case'), test, t(':'), seq(...consequent)); } reduceSwitchDefault(node, { consequent }) { return seq(t('default:'), seq(...consequent)); } reduceSwitchStatement(node, { discriminant, cases }) { return seq(t('switch'), paren(discriminant), brace(seq(...cases))); } reduceSwitchStatementWithDefault(node, { discriminant, preDefaultCases, defaultCase, postDefaultCases }) { return seq( t('switch'), paren(discriminant), brace(seq(...preDefaultCases, defaultCase, ...postDefaultCases))); } reduceTemplateExpression(node, { tag, elements }) { let state = node.tag == null ? empty() : p(node.tag, getPrecedence(node), tag); state = seq(state, t('`')); for (let i = 0, l = node.elements.length; i < l; ++i) { if (node.elements[i].type === 'TemplateElement') { state = seq( state, i > 0 ? t('}') : empty(), elements[i], i < l - 1 ? t('${') : empty() ); } else { state = seq(state, elements[i]); } } state = seq(state, t('`')); if (node.tag != null) { state.startsWithCurly = tag.startsWithCurly; state.startsWithLet = tag.startsWithLet; state.startsWithLetSquareBracket = tag.startsWithLetSquareBracket; state.startsWithFunctionOrClass = tag.startsWithFunctionOrClass; } return state; } reduceTemplateElement(node) { return new RawToken(node.rawValue); } reduceThisExpression(/* node */) { return t('this'); } reduceThrowStatement(node, { expression }) { return seq(t('throw'), expression, semiOp()); } reduceTryCatchStatement(node, { body, catchClause }) { return seq(t('try'), body, catchClause); } reduceTryFinallyStatement(node, { body, catchClause, finalizer }) { return seq(t('try'), body, catchClause || empty(), t('finally'), finalizer); } reduceYieldExpression(node, { expression }) { if (node.expression == null) return t('yield'); return objectAssign(seq(t('yield'), p(node.expression, getPrecedence(node), expression)), { containsIn: expression.containsIn }); } reduceYieldGeneratorExpression(node, { expression }) { return objectAssign(seq(t('yield'), t('*'), p(node.expression, getPrecedence(node), expression)), { containsIn: expression.containsIn }); } reduceDirective(node) { let delim = node.rawValue.match(/(^|[^\\])(\\\\)*"/) ? '\'' : '"'; return seq(t(delim + node.rawValue + delim), semiOp()); } reduceVariableDeclaration(node, { declarators }) { return seq(t(node.kind), commaSep(declarators)); } reduceVariableDeclarationStatement(node, { declaration }) { return seq(declaration, semiOp()); } reduceVariableDeclarator(node, { binding, init }) { let containsIn = init && init.containsIn && !init.containsGroup; if (init) { if (init.containsGroup) { init = paren(init); } else { init = markContainsIn(init); } } return objectAssign(init == null ? binding : seq(binding, t('='), init), { containsIn }); } reduceWhileStatement(node, { test, body }) { return objectAssign(seq(t('while'), paren(test), body), { endsWithMissingElse: body.endsWithMissingElse }); } reduceWithStatement(node, { object, body }) { return objectAssign( seq(t('with'), paren(object), body), { endsWithMissingElse: body.endsWithMissingElse }); } } module.exports = MinimalCodeGen;