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 { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator'; import { IVisitor } from '../../interfaces/IVisitor'; import { AbstractNodeTransformer } from '../AbstractNodeTransformer'; import { Node } from '../../node/Node'; import { Nodes } from '../../node/Nodes'; import { NodeUtils } from '../../node/NodeUtils'; @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 {IRandomGenerator} randomGenerator * @param {IOptions} options */ constructor ( @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator, @inject(ServiceIdentifiers.IOptions) options: IOptions ) { super(randomGenerator, options); } /** * @return {IVisitor} */ public getVisitor (): IVisitor { return { leave: (node: ESTree.Node, parentNode: ESTree.Node) => { if (Node.isProgramNode(node)) { return this.transformNode(node, parentNode); } } }; } /** * @param {Program} programNode * @param {Node} parentNode * @returns {Node} */ public transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node { this.transformProgramNode(programNode); return programNode; } /** * @param {BlockStatement} blockStatementNode * @param {BlockStatement[]} collectedBlockStatements */ private 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 = this.randomGenerator.getRandomVariableName(6); } return node; } }); if (!isValidBlockStatementNode) { return; } collectedBlockStatements.push(clonedBlockStatementNode); } /** * @param {BlockStatement} blockStatementNode * @param {BlockStatement} randomBlockStatementNode * @returns {BlockStatement} */ private replaceBlockStatementNode ( blockStatementNode: ESTree.BlockStatement, randomBlockStatementNode: ESTree.BlockStatement ): ESTree.BlockStatement { const random1: boolean = this.randomGenerator.getMathRandom() > 0.5; const random2: boolean = this.randomGenerator.getMathRandom() > 0.5; const operator: ESTree.BinaryOperator = random1 ? '===' : '!=='; const leftString: string = this.randomGenerator.getRandomString(3); const rightString: string = random2 ? leftString : this.randomGenerator.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; } /** * @param {Program} programNode */ private transformProgramNode (programNode: ESTree.Program): void { estraverse.traverse(programNode, { enter: (node: ESTree.Node, parentNode: ESTree.Node): any => { if (!Node.isBlockStatementNode(node)) { return; } this.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) || this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold ) { return node; } const minInteger: number = 0; const maxInteger: number = this.collectedBlockStatements.length - 1; const randomIndex: number = this.randomGenerator.getRandomInteger(minInteger, maxInteger); const randomBlockStatementNode: ESTree.BlockStatement = this.collectedBlockStatements.splice(randomIndex, 1)[0]; if (randomBlockStatementNode === node) { return node; } return this.replaceBlockStatementNode(node, randomBlockStatementNode); } }); } }