/* * Copyright 20089 Wishnu Prasetya. * * This file is part of T2. * T2 is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License (GPL) as published by the * Free Software Foundation; either version 3 of the License, or any * later version. * * T2 is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * A copy of the GNU General Public License can be found in T2 distribution. * If it is missing, see http://www.gnu.org/licenses. */ package Sequenic.T2ext.Instrumenter; import java.util.* ; import org.apache.bcel.* ; import org.apache.bcel.generic.*; import org.apache.bcel.classfile.* ; import Sequenic.T2ext.util.*; /** * Provide functionality to instrument a single method. It will construct a CFG * for the method, and inject sensors to it. * * @author Wishnu Prasetya * @author Original code by Maaike Gerritsen * */ public class MethodInstrumenter { /** * The method to which this CFG belongs to. */ java.lang.reflect.Method method ; /** * A unique name given to the method. This has the form of * * methodname_paramtype1_paramtype2 ... */ String methodUniqueName ; /** * The class to which the method belong to. Note that this is not necessarily * the class that declares the method. */ Class C ; /** * BCEL representation of a target class C; it allows a class file to * be analyzed, but not transformed. */ JavaClass clazz ; /** * BCEL interface to manipulate a class. */ ClassGen classGen ; /** * BCEL representation of the target method. */ Method mezod = null ; /** * A method generator; this will hold the new version of m: */ MethodGen mGen = null ; /** * If true will inject sensors, else it will only generate CFG * without injecting sensors. * * The default is false. */ boolean doInject = false ; boolean debug = false ; /** * Constructor * * @param tmethod target method * @param tclass target class, to which the method belongs to. */ public MethodInstrumenter(java.lang.reflect.Method tmethod, Class tclass){ method = tmethod ; C = tclass ; methodUniqueName = Sequenic.T2ext.util.util.completeMethodName(method) ; // Have BCEL to read the target class and method: try { clazz = Repository.lookupClass(C); } catch (Exception e) { throw new Error(e) ; } mezod = clazz.getMethod(method) ; // Set-up the class-gen to allow manipulation on the class: classGen = new ClassGen(clazz) ; // Set-up the method generator to construct a new (injected) m: mGen = new MethodGen(mezod,C.getName(),classGen.getConstantPool()) ; } /** * Same as the other class constructor, but we also pass a classGen. * This is convenient when we need to instrument multiple methods from * the same class. */ public MethodInstrumenter(java.lang.reflect.Method tmethod, Class tclass, ClassGen tclassGen){ method = tmethod ; C = tclass ; classGen = tclassGen ; methodUniqueName = Sequenic.T2ext.util.util.completeMethodName(method) ; clazz = classGen.getJavaClass() ; mezod = clazz.getMethod(method) ; // Set-up the method generator to construct a new (injected) m: mGen = new MethodGen(mezod,C.getName(),classGen.getConstantPool()) ; } /** * The instrumentation main method (below) needs to maintain lots of * information. This badly cluttered the code; so now I'm putting * all those info in this inner class. */ private class InstrumenterState { InstructionList il = mGen.getInstructionList() ; LineNumberTable lines = mezod.getLineNumberTable(); ConstantPoolGen cpoolGen = new ConstantPoolGen(clazz.getConstantPool()) ; InstructionHandle currentINSTR = il.getStart() ; ; Node currentBlock = null ; /** * Provide fresh number to number the blocks. */ int id_cnt = 0 ; /** * If (i,Z) is in pendingEdges, and node N is in Z, this means that * we have seen block N; it jumps ahead to i, * but i is yet to be processed, so it still has no block assigned. * */ Map> pendingEdges = new HashMap>() ; /** * (i,N) is in "blocks" means that we have seen block N, and it starts * at instruction i. */ Map blocks = new HashMap() ; /** * Separate data structure to keep track of the fall-through targets of * "switch" constructs. */ List switchFallThroughTargets = new LinkedList() ; /** * Contains pointers to the beginning of "assert" statements found so far. */ List assertionBegins = new LinkedList() ; /** * This is set to true when we enter an "assert" construct; and is set to false * again after its throw. */ boolean justSeenAnAssert = false ; /** * The index pointing to an injected variable in the target method that * will be used to hold a runID. */ Integer indexOf___runID = null ; InstrumenterState() { if(lines == null) { // Error or warning?? System.out.println("ERROR: Line numbers are missing in the class file!"); System.out.println(" Recompile it with -g (or with debug=\"true\" when using Ant)"); } } } /** * Will build the CFG of the target method AND inject sensors. */ public CFG doInstrumenting(){ CFG CFG = new CFG() ; CFG.C = C.getName() ; CFG.methodName = methodUniqueName ; CFG.setOriginalMethodName(method.getName()); // Create an inner object that will hold various data structures // we need for this algorithm. The creation will also innitialize // those data structures": InstrumenterState sigma = new InstrumenterState() ; if (debug) { System.out.println("CONSTANTS pool:") ; System.out.println(clazz.getConstantPool()) ; System.out.println("ORIGINAL instructions:") ; System.out.println(sigma.il) ; } while (sigma.currentINSTR != null) { // A start of a new block, make a node for it: sigma.currentBlock = new Node() ; CFG.nodes.add(sigma.currentBlock) ; sigma.currentBlock.id = sigma.id_cnt ; sigma.currentBlock.lineNr_start = sigma.lines.getSourceLine(sigma.currentINSTR.getPosition()); //System.out.println(">>> block added: " + currentBlock.id + " <-- " + currentINSTR) ; sigma.blocks.put(sigma.currentINSTR, sigma.currentBlock) ; InstructionHandle blockFirstINSTR = sigma.currentINSTR ; // Check constant use: Object constant = detectConstant(sigma) ; if (constant != null) sigma.currentBlock.constants.add(constant) ; // Special case if it is the starting block, it has to be linked from // the CFG: if (sigma.currentINSTR == sigma.il.getStart()) { // System.out.println("!!!") ; // sigma.currentBlock.isEntryNode = true ; CFG.startNode = sigma.currentBlock ; sigma.indexOf___runID = injectGetRunID(sigma) ; } // If there are pending edges jumping to the current instruction, // deal with it: // boolean thereIsAnIncomingEdge = unPendingEdge(sigma) ; unPendingEdge(sigma) ; // If there is no incoming edge, then this block must be an entry // node; mark it: // if (!thereIsAnIncomingEdge) currentBlock.isEntryNode = true ; // Scan forward until a block-end is encountered: while (! isBlockEnd(sigma)) { sigma.currentINSTR = sigma.currentINSTR.getNext() ; constant = detectConstant(sigma) ; if (constant != null) sigma.currentBlock.constants.add(constant) ; if (isAssertionBegin(sigma.currentINSTR,sigma)) { sigma.justSeenAnAssert = true ; sigma.assertionBegins.add(sigma.currentINSTR.getInstruction()) ; } } // Now we're at the block's end: sigma.currentBlock.lineNr_end = sigma.lines.getSourceLine(sigma.currentINSTR.getPosition()); // Depending on the instruction at the block-end, we'll do different things, // e.g. linking the children, if there are any... if (isBeforeJumpTarget(sigma.currentINSTR,sigma) && !isGOTO(sigma.currentINSTR) && (!isIF(sigma.currentINSTR) || isAssertionBegin(sigma.currentINSTR,sigma)) && !isSWITCH(sigma.currentINSTR) && !isRETURN(sigma.currentINSTR) && !isTHROW(sigma.currentINSTR)){ addPendingEdges(sigma.currentINSTR.getNext(),sigma) ; injectSensor(blockFirstINSTR,sigma) ; } if (isGOTO(sigma.currentINSTR)) { InstructionHandle jumpTarget = ((BranchHandle) sigma.currentINSTR).getTarget() ; boolean isBackEdge = linkBackEdge(jumpTarget,sigma) ; //System.out.println(">>> GOTO backedge :" + isBackEdge + " // " + jumpTarget) ; if (!isBackEdge) { addPendingEdges(jumpTarget,sigma) ; } // Now inject; note that we inject BEFORE the block's starting // instruction: injectSensor(blockFirstINSTR,sigma) ; } if (isIF(sigma.currentINSTR) && !isAssertionBegin(sigma.currentINSTR,sigma)) { sigma.currentBlock.kind = Node.BRANCH ; InstructionHandle jumpTarget = ((BranchHandle) sigma.currentINSTR).getTarget() ; boolean isBackEdge = linkBackEdge(jumpTarget,sigma) ; //System.out.println(">>> IF backedge :" + isBackEdge) ; if (!isBackEdge) { addPendingEdges(jumpTarget,sigma) ; } // Also add an edge to the next instruction: assert sigma.currentINSTR.getNext() != null ; addPendingEdges(sigma.currentINSTR.getNext(),sigma) ; // Now inject: injectSensor(blockFirstINSTR,sigma) ; } if (isSWITCH(sigma.currentINSTR)){ sigma.currentBlock.kind = Node.SWITCH ; Select currentSwitch = (Select) sigma.currentINSTR.getInstruction() ; InstructionHandle[] jumpTargets = currentSwitch.getTargets() ; //System.out.println(">>> SWITCH with " + jumpTargets.length + " targets.") ; for (int i=0; i>> switch from " + currentBlock.id + " to " + jumpTargets[i]) ; addPendingEdges(jumpTargets[i],sigma) ; sigma.switchFallThroughTargets.add(jumpTargets[i]) ; } // And handle the default-jump too: InstructionHandle defaultJumpTarget = currentSwitch.getTarget() ; //System.out.println(">>> !!!" + switchFallThroughTargets.contains(defaultJumpTarget)) ; assert defaultJumpTarget != null ; sigma.switchFallThroughTargets.add(defaultJumpTarget) ; addPendingEdges(defaultJumpTarget,sigma) ; // Now inject: injectSensor(blockFirstINSTR,sigma) ; } if (isTHROW(sigma.currentINSTR)) { // Exception is still a problem. The problem is that // BCEL doesn't seem to expose sufficient information // on where thrown exception may be handled by the // method exception handler. This should be in the // exception handler ... but it doesn't seem to expose // sufficient info. // So, for now a THROW will be treated as an exit node. // This is not inconsistent; though this means that // our coverage definition will be blind to what happen // after something in the target method throws an exception. // We can't capture the flow in our CFG. sigma.currentBlock.kind = Node.EXIT ; // Furthermore, if it is throwing an AssertionError, we'll treat // it as specification. The block should not be instrumented; // so we unlink it: if (sigma.justSeenAnAssert) { List parents = (List) ((Object) sigma.currentBlock.parents) ; for (Node N : parents) N.children.remove(sigma.currentBlock) ; sigma.currentBlock.parents.clear() ; CFG.nodes.remove(sigma.currentBlock) ; //System.out.println(">>> Unlinking block: " + currentBlock.id) ; // No injection! Because the block was unlinked. sigma.justSeenAnAssert = false ; } else { // else inject: injectSensor(blockFirstINSTR,sigma) ; } } if (isRETURN(sigma.currentINSTR)) { sigma.currentBlock.kind = Node.EXIT ; injectSensor(blockFirstINSTR,sigma) ; } // Ok done, now advance to the next block: sigma.currentINSTR = sigma.currentINSTR.getNext() ; sigma.id_cnt++ ; } // Done with the loop, now finalize the CFG, including // calculating prime paths: CFG.buildDerivedLists(); PrimePathCalculator.calculatePrimePath(CFG) ; //System.out.println(">>> kind of CFG startnode: " + CFG.startNode.kind) ; // CFG.simplePrint() ; // Ok done with constructing CFG and injection if (doInject){ // Now finalize the method transformation: sigma.il.setPositions(true) ; mGen.setMaxStack(); // Replace m with the new one: classGen.removeMethod(mezod); classGen.addMethod(mGen.getMethod()) ; } if (debug) { System.out.println("NEW CONSTANTS pool:") ; System.out.println(clazz.getConstantPool()) ; System.out.println("NEW instructions:") ; System.out.println(sigma.il) ; } //DONE! return CFG ; } /** * To link a back-edge. If it is not a back-edge no linking happens, and * it returns false. A back-edge here simply means a jump to a piece * of code that we have earlier seen. */ private boolean linkBackEdge(InstructionHandle jumpTarget, InstrumenterState sigma){ Node targetBlock = sigma.blocks.get(jumpTarget) ; //System.out.println(">>> jump from block " + currentBlock.id + " to block " + targetBlock ) ; if (targetBlock != null) { // Ok so we have a back-edge: sigma.currentBlock.children.add(targetBlock) ; targetBlock.parents.add(sigma.currentBlock) ; //currentBlock.hasBackEdge = targetBlock.id ; return true ; } return false ; } /** * This will add an edge from currentBlock to the jumptarget to the * pendingEdges list. */ private void addPendingEdges(InstructionHandle jumptarget, InstrumenterState sigma){ List sources = sigma.pendingEdges.get(jumptarget) ; if (sources==null) { sources = new LinkedList() ; sigma.pendingEdges.put(jumptarget,sources) ; } sources.add(sigma.currentBlock) ; } /** * Check if the current block has some pending edges jumping to * it; if so link these edges. Then remove them from the pending * list. True will be returned. * * Else do no linking, and return false. */ private boolean unPendingEdge(InstrumenterState sigma) { List targeters = sigma.pendingEdges.get(sigma.currentINSTR) ; if (targeters == null) return false ; if (targeters.isEmpty()) { sigma.pendingEdges.remove(sigma.currentINSTR) ; return false ; } // There are targeters: for (Node N : targeters) { sigma.currentBlock.parents.add(N) ; N.children.add(sigma.currentBlock) ; } sigma.pendingEdges.remove(sigma.currentINSTR) ; return true ; } /** * Given an instruction list, this will inject a sensor just BEFORE the instruction * pointed by h. To the sensor we will pass the unique name of the target * method, and also a node-id. */ protected void injectSensor( InstructionHandle blockBegin, InstrumenterState sigma //InstructionList il, //Map blocks, //Map> pendingEdges, //List switchFallThroughTargets, //InstructionHandle h, //int node_id, //Integer indexOf___runID ){ // If injection flag is false, do nothing: if (!doInject) return ; InstructionFactory ifact = new InstructionFactory(classGen); InstructionList sensorINSTRs = new InstructionList(); //ConstantPoolGen pgen = classGen.getConstantPool(); // Load parameters for the call to testtick; // pass x,y, and info through: assert sigma.indexOf___runID != null ; sensorINSTRs.append(new LLOAD((int)sigma.indexOf___runID)) ; sensorINSTRs.append(ifact.createConstant(sigma.currentBlock.id)) ; sensorINSTRs.append(ifact.createConstant(methodUniqueName)) ; sensorINSTRs.append(ifact.createInvoke("Sequenic.T2ext.Instrumenter.Sensor", "tick", Type.VOID, new Type[] { Type.LONG, Type.INT,Type.STRING }, Constants.INVOKESTATIC)); InstructionHandle k = sigma.il.insert(blockBegin,sensorINSTRs) ; // k should point to where the where the sensor is inserted, // Now redirect the jumps: sigma.il.redirectBranches(blockBegin,k) ; // Also re-align our working data structures: Node N = sigma.blocks.get(blockBegin) ; sigma.blocks.remove(blockBegin) ; if (N!=null) sigma.blocks.put(k,N) ; List Ts = sigma.pendingEdges.get(blockBegin) ; sigma.pendingEdges.remove(blockBegin) ; if (Ts!=null) sigma.pendingEdges.put(k,Ts) ; if (sigma.switchFallThroughTargets.contains(blockBegin)) { sigma.switchFallThroughTargets.remove(blockBegin) ; sigma.switchFallThroughTargets.add(k) ; } } /** * This will inject a call to Sensor.getRunID to get a fresh ID. ThisID will * be held in a local variable in the target method; so this variable * has to be injected as well. * * The method will return the index to the above mentioned local var; we will * need this info for the other injection method. * Perform this injection at the beginning of the target method. * */ protected Integer injectGetRunID(InstrumenterState sigma){ // If injection flag is false, do nothing: if (!doInject) return null ; LocalVariableGen lgen = mGen.addLocalVariable("___runID", Type.LONG, null, null); // Save the index of this injected local variable: Integer indexOf___runID = lgen.getIndex(); // Now inject code that will call Sensor.getRunID: InstructionList newINSTRs = new InstructionList(); InstructionFactory ifact = new InstructionFactory(classGen); newINSTRs.append(ifact.createInvoke("Sequenic.T2ext.Instrumenter.Sensor", "getRunID", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); newINSTRs.append(new LSTORE((int) indexOf___runID)); sigma.il.insert(sigma.currentINSTR,newINSTRs) ; return indexOf___runID ; } private boolean isBlockEnd(InstrumenterState sigma){ InstructionHandle instr = sigma.currentINSTR ; return isGOTO(instr) || (isIF(instr) && ! isAssertionBegin(instr,sigma)) || isRETURN(instr) || isSWITCH(instr) || isTHROW(instr) || isBeforeJumpTarget(instr,sigma) ; } private boolean isBeforeJumpTarget(InstructionHandle instr, InstrumenterState sigma){ if (instr.getNext() == null) return false ; return isJumpTarget(instr.getNext(),sigma) ; } /** * Return true in the instruction is a jump-target. */ private boolean isJumpTarget(InstructionHandle instr, InstrumenterState sigma){ if (sigma.switchFallThroughTargets.contains(instr)) return true ; InstructionTargeter[] targeters = instr.getTargeters() ; if (targeters==null || targeters.length<=0) return false ; for (int i=0; i>> assertion found = " + found) ; return found ; } /** * Returns true iff an IFNE instruction that immediately * follows a check to the $assertionsDisabled field. */ private boolean isAssertionBegin(InstructionHandle instr, InstrumenterState sigma){ if (! (instr.getInstruction() instanceof IFNE)) return false ; if (instr.getPrev() == null) return false ; //System.out.println(">>>") ; return isGETSTATICdisabledAssertion(instr.getPrev(),sigma) ; } /** * Returns true if this instruction is where an "assert" block * ends. */ /* private boolean isAssertionEnd(InstructionHandle instr, InstrumenterState sigma) { InstructionTargeter[] targeters = instr.getTargeters() ; if (targeters == null) return false ; for (int i=0; i