Java tutorial
/* * JBoss, Home of Professional Open Source * Copyright 2009-10 Red Hat and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * * @authors Andrew Dinn */ package org.jboss.byteman.agent.adapter.cfg; import org.jboss.byteman.rule.type.TypeHelper; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.jboss.byteman.agent.Transformer; import org.objectweb.asm.Type; import java.util.*; /** * A control flow graph (cfg) for use by trigger method adapters.<p/> * A trigger method adapter is required to notify the CFG each time an instruction or label is visited and * each time a try catch block is notified. It is also required to notify the CFG when trigger coe generartion * begins and ends. The cfg allows the trigger method adapter to identify whether or not trigger code is * within the scope of one or more synchronized blocks, allowing it to protect the trigger call with try catch * handlers which ensure that any open monitor enters are rounded off with a corresponding monitor exit. * <p/> * A cfg is constructed dynamically as the code is visited in order to enable trigger insertion to be performed * during a single pass of the bytecode. See {@link org.jboss.byteman.agent.adapter.RuleTriggerMethodAdapter} * for an example of how the methods provided by this class are invoked during visiting of the method byte code. * Methods provided for driving CFG construction include: * <ul> * <li> non-control instruction visit: * {@link CFG#add(int)}, {@link CFG#add(int, int)}, * {@link CFG#add(int, int, int)} {@link CFG#add(int, int[])} {@link CFG#add(int, String)}, * {@link CFG#add(int, String, String, String)}, {@link CFG#add(int, String, int)}, * <li> control instruction visit: * {@link CFG#split(org.objectweb.asm.Label)}, {@link CFG#split(org.objectweb.asm.Label, org.objectweb.asm.Label)}, * {@link CFG#split(org.objectweb.asm.Label, org.objectweb.asm.Label, org.objectweb.asm.Label)}, * {@link CFG#split(org.objectweb.asm.Label, org.objectweb.asm.Label, org.objectweb.asm.Label[])}, * <li> label visit: * {@link CFG#visitLabel(org.objectweb.asm.Label)}, * <li> try/catch block visit: * {@link CFG#visitTryCatchBlock(org.objectweb.asm.Label, org.objectweb.asm.Label, org.objectweb.asm.Label, String)}, * <li> trigger region demarcation: * {@link CFG#visitTriggerStart(org.objectweb.asm.Label)}, {@link CFG#visitTriggerEnd(org.objectweb.asm.Label)}, * <li> code visit end demarcation: * {@link org.jboss.byteman.agent.adapter.cfg.CFG#visitMaxs()}, {@link org.jboss.byteman.agent.adapter.cfg.CFG#visitEnd()}, * </ul> * <p/> * The cfg maintains the current instruction sequence for the method in encoded form as it is being generated. * The cfg models both the linear instruction sequence and the directed graph of control flow through that sequence. * It splits the instruction stream at control flow branch points, grouping instructions into basic blocks. A * successor link relation between blocks retains the linear instruction sequence. Control flow links between * basic blocks define the graph structure. The cfg correlates labels with i) blocks and ii) instruction offsets * within those blocks as the labels are visited during bytecode visiting. It also tracks the locations within * blocks of try catch regions and their handlers and of monitor enter and exit instructions. * <p/> * The lock propagation algorithm employed to track the extent of monitor enter/exit pairs and try/catch blocks is * the most complex aspect of this implementation, mainly because it has to be done in a single pass. This means * that the end location of a try catch block or the location of the (one or more) monitor exit(s) associated with * a monitor enter may not be known when a trigger point is reached. This algorithm is described below in detail. * First an explanation of the CFG organization is provided. * </p> * <h3>Control flow graph model</h3> * The bytecode sequence is segmented into basic blocks at control flow branches ensuring there is no <em>explicit</em> * control flow internal to a block. The only way <em>normal</em> control can flow from one block to another is via a * switch/goto/branch instruction occuring at the end of the block. So, basic blocks are the nodes of the CFG * and the links in the graph identify these control flow transitions. * <p> * Normal control flow linkage is explicitly * represented in the blocks as a list containing the labels of the target blocks. Labels are used rather than * handles on the block themselves so that forward links to blocks which have not yet been generated can be * modelled. Labels are resolved to the relevant block and instruction index as they are visited during walking * of the bytecode. * </p> * The outgoing control flow link count can be obtained by calling method * {@link BBlock#nOuts()}. The label of the block to which control is transferred can be identified by calling * method {@link BBlock#nthOut(int)}. Note that valid link indices run from 1 to nOuts() (see below). Once * a label has been visited it can be resolved to a {@link CodeLocation} by calling method * {@link CFG#getLocation(org.objectweb.asm.Label)}. The returned value identifies both a block and an instruction * offset in the block. * <p/> * Several caveats apply to this simple picture. Firstly, blocks ending in return or throw have no control flow -- they * pass control back to the caller rather than to another basic block. So, the count returned by {@link BBlock#nOuts()} * will be 0 for such blocks. * <p/> * Secondly, all blocks except the last have a distinguished link which identifies the block successor link * relationship. The successor block can be obtained by supplying value 0 as argument to method * {@link BBlock#nthOut(int)}. This link is <em>additional</em> to any control flow links and it is <em>not</em> * included in the count returned by {@link BBlock#nOuts()}. Note that where there is a control flow link to the * next block in line (e.g. where the block ends in an ifXX instruction) the label employed for the distinguished 0 * link will also appear in the set of control flow links (as link 1 in the case of an ifXX instruction). * <p/> * The final caveat is that this graph model does not identify control flow which occurs as a consequence of * generated exceptions. * </p> * <h3>Exceptional Control Flow</h3> * Exception control flow is modelled independently from normal flow because it relates to a segment of the * instruction sequence rather than individual instructions. A specific exception flow is associated with a each * try catch block and the target of the flow is the start of the handler block. The cfg maintains a list of * {@link TryCatchDetails} which identify the location of the try/catch start, its end and the associated handler * start location. Once again labels are used so as to allow modelling of forward references to code locations * which have not yet been generated. * <p/> * Note that handler start labels always refer to a code location which is at the start of a basic block. Start * and end labels for a given try/catch block may refer to code locations offset into their containing basic block * and possibly in distinct blocks. * <p/> * Methods {@link #tryCatchStart(org.objectweb.asm.Label)}, {@link #tryCatchEnd(org.objectweb.asm.Label)} * and {@link #tryCatchHandlerStart(org.objectweb.asm.Label)} can be called to determine whether a given label * identifies, respectively, the start of a try catch block, the end of a try catch block or the start of a handler * block. Methods {@link #tryCatchStartDetails(org.objectweb.asm.Label)} {@link #tryCatchEndDetails(org.objectweb.asm.Label)}, * and {@link #tryCatchHandlerStartDetails(org.objectweb.asm.Label)} can be used to retrieve the associated * {@link TryCatchDetails} information. * </p> * <h3>Label Resolution</h3> * The cfg relies upon its adapter client to notify it whenever a label is visited during a walk of the bytecode. * This allows it to associate labels with the basic blocks and instruction offsets within those blocks. The cfg * provides method {@link CFG#getBlock(org.objectweb.asm.Label)} to resolve the primary label for a block (i.e. the * one supplied as argument to a split call) to the associated block. It also provides method * {@link CFG#getBlockInstructionIdx(org.objectweb.asm.Label)} to resolve a label to a {@link CodeLocation} i.e. * block and instruction index within a block. Both methods return null if the label has not yet been visited. * <p/> * Method {@link CFG#getContains(BBlock)} is also provided to obtain a list of all labels contained within a * specific block. There may be more than one label which resolves to a location within a specific block. For * example, the handler start label associated with a try/catch handler is contained in the handler block at * offset 0 but is never the primary label for the block. Iteration over the contained set is used internally * in the cfg to resolve equivalent labels. * <h3>lock propagation algorithm</h3> * The cfg tracks the occurence of monitor enter and monitor exit instructions as they are encountered during * the bytecode walk. Note that the relationship between enter and exit instructions is 1 to many. For any given * monitor enter there are one or more exits associated with the normal control flow path and zero * or more alternative exits associated with exception control flow paths. The association between monitor * entry and monitor exit instructions is made available via methods {@link CFG#getPairedEnter(CodeLocation)}, * and {@link CFG#getPairedExit(CodeLocation, BBlock)} 9note that a given enter will never have more than one * exit in any given block). * <p/> * The cfg associates monitor enters and exits with their enclosing block, allowing it to identify the start and/or * end of synchronized regions within a specific block. This information can be propagated along control flow links * to identify outstanding monitor enters at any point in a given control flow path. Whenever a block is created * it is associated with a set of open enter instructions i.e. enter instructions occurring along all control flow * paths to the block for which no corresponding exit has been executed. * <p/> * <ul> * <li>For the initial block the open enters list is empty. * <p/> * <li>For a block reached by normal control flow the open enters list can be derived from any of the feed blocks * which transfer control to it. It is computed by adding and removing entries to/from the feed block's open * enters list according to the order the enters or exits appear in the block. Any feed block is valid because * every enter must have a single corresponding exit on each valid path through the bytecode. Two paths to the * same block cannot introduce different enters and exits without breaking this invariant. Also, enters and exits * must be strictly nested so the set of open monitors can be tracked using a simple stack model. * <p/> * The algorithm propagates open enters along normal control flow paths whenever a split instruction is invoked * (splitting the instruction stream into a new block). The work is done in method {@link CFG#carryForward()}. This * method identifies the current block's open enters list (how will emerge below), updates it with any enters and * exits performed in the block and then, for each each outgoing control link, associates the new list with the * linked block by inserting the list into a hash table keyed by the block label. Clearly, if the current block was * itself arrived at via normal control flow then its open enters list will already be available in the hash table. * Handler blocks require a different lookup. *<li> * Computing the open enters list for a handler block which is the target of exception control flow is also done in * method {@link CFG#carryForward()}. This requires identifying all try/catch regions which enclose the block * and tagging the corresponding {@link TryCatchDetails} object with the location of any monitor enter instructions * which are open at some point in the try catch region. If this is done for every block encountered during * the bytecode walk then at the point where the handler block is split all enter instructions which are still open * somewhere within the try/catch region will be listed in the {@link TryCatchDetails}. So, at the split point * the old block can be tested to see if it is labelled as a try/catch handler target and, if so, its open enters * list can be looked up by locating the {@link TryCatchDetails} associated with the handler start label. * </ul> * Note that we still need to worry about nested try/catch regions shadowing outer try/catch handlers with a more * specific exception type e.g. when a try region with a catch-all handler type is embedded within a try region with * a specific exception type or, less commonly, when the inner block employs a super type of the outer block. In these * cases exception flow from the inner region cannot reach the handler for the outer region. This means we must * dispense with propagation of open monitor enters to the outer region since they will be closed by the inner * handler. * TODO -- implement this last check */ public class CFG { /** * Type identifying execute exceptions thrown by runtime */ public final static Type EXECUTE_EXCEPTION_TYPE = Type .getType(TypeHelper.externalizeType("org.jboss.byteman.rule.exception.ExecuteException")); /** * Type identifying return exceptions thrown by runtime */ public final static Type EARLY_RETURN_EXCEPTION_TYPE = Type .getType(TypeHelper.externalizeType("org.jboss.byteman.rule.exception.EarlyReturnException")); /** * Type identifying throw exceptions thrown by runtime */ public final static Type THROW_EXCEPTION_TYPE = Type .getType(TypeHelper.externalizeType("org.jboss.byteman.rule.exception.ThrowException")); /** * name of type identifying execute exceptions thrown by runtime */ public final static String EXECUTE_EXCEPTION_TYPE_NAME = EXECUTE_EXCEPTION_TYPE.getInternalName(); /** * name of type identifying return exceptions thrown by runtime */ public final static String EARLY_RETURN_EXCEPTION_TYPE_NAME = EARLY_RETURN_EXCEPTION_TYPE.getInternalName(); /** * name of type identifying throw exceptions thrown by runtime */ public final static String THROW_EXCEPTION_TYPE_NAME = THROW_EXCEPTION_TYPE.getInternalName(); /** * the name of the method for which this is a CFG */ private String methodName; /** * the label of the first basic block in the code */ private BBlock entry; /** * the current basic block */ private BBlock current; /** * a counter used to number bblocks in code order */ private int nextIdx; /** * a mapping from the start label of a basic block to the associated block */ private Map<Label, BBlock> blocks; /** * a mapping from each label to its enclosing basic block and instruction offset */ private Map<Label, CodeLocation> labelLocations; /** * a map identifying the containment relationship between a basic block and labels which identify * instructions located within the block - the first entry is the block label itself */ private Map<BBlock, FanOut> contains; /** * a list of names employed in the bytecode */ private List<String> names; /** * a map from labels which identify the start of a code injection sequence to details of the labels * which locate the sequence and its exception handlers */ private Map<Label, TriggerDetails> triggerStarts; /** * a map from labels which identify the end of a code injection sequence to details of the labels * which locate the sequence and its exception handlers */ private Map<Label, TriggerDetails> triggerEnds; /** * details of the last trigger section encountered set when a trigger start label is notified */ private TriggerDetails latestTrigger; /** * a map from try catch block start labels to the corresponding try catch block details -- the value * is a list because the code reader will reuse teh same label when two try catch blocks start at the * same bytecode */ private Map<Label, List<TryCatchDetails>> tryCatchStarts; /** * a map from try catch block end labels to the corresponding try catch block details -- the value * is a list because the code reader will reuse the same label when two try catch blocks end at the * same bytecode */ private Map<Label, List<TryCatchDetails>> tryCatchEnds; /** * a map from try catch block handler labels to the corresponding try catch block details -- the value * is a list because the code reader will reuse the same label when two handler blocks start at the * same bytecode */ private Map<Label, List<TryCatchDetails>> tryCatchHandlers; /** * a list of all try catch blocks which are started but not ended. this is updated as tryStart and tryEnd * labels are visited. */ private List<TryCatchDetails> currentTryCatchStarts; /** * a map from block labels to any unclosed monitor enter instructions outstanding when the block is entered. * this is only valid for blocks which are arrived at via conventional control flow i.e. not direct targets * of try catch handler exceptions. */ private Map<Label, List<CodeLocation>> openMonitorEnters; /** * a map from monitor enter instructions to the monitor exit insructions which close them. this is a list * because an enter may have corresponding exits in exception handler blocks as well as the exit which * is executed via normal control flow. Note that the latter is always the first entry in the list. */ private Map<CodeLocation, List<CodeLocation>> monitorPairs; /** * an inverse map from each monitor exit instruction to the monitor enter insructions it closes. */ private Map<CodeLocation, CodeLocation> inverseMonitorPairs; /** * construct a CFG labelling the initial block with a given label * @param methodName the name of the method fro which this is a CFG * @param start a label for the entry block of the CFG */ public CFG(String methodName, Label start) { this.methodName = methodName; this.nextIdx = 0; this.entry = this.current = new BBlock(this, start, nextIdx++); blocks = new HashMap<Label, BBlock>(); contains = new HashMap<BBlock, FanOut>(); labelLocations = new HashMap<Label, CodeLocation>(); names = new ArrayList<String>(); triggerStarts = new HashMap<Label, TriggerDetails>(); triggerEnds = new HashMap<Label, TriggerDetails>(); latestTrigger = null; tryCatchStarts = new HashMap<Label, List<TryCatchDetails>>(); tryCatchEnds = new HashMap<Label, List<TryCatchDetails>>(); tryCatchHandlers = new HashMap<Label, List<TryCatchDetails>>(); openMonitorEnters = new HashMap<Label, List<CodeLocation>>(); monitorPairs = new HashMap<CodeLocation, List<CodeLocation>>(); inverseMonitorPairs = new HashMap<CodeLocation, CodeLocation>(); blocks.put(start, current); setLocation(start); contains.put(current, new FanOut(start)); openMonitorEnters.put(start, new LinkedList<CodeLocation>()); currentTryCatchStarts = new LinkedList<TryCatchDetails>(); } /* * routines for managing and querying the CFG structure */ /** * aopend an instruction to the current block * @param instruction */ public void add(int instruction) { current.append(instruction); } /** * append an instruction with one operand to the current block * @param instruction * @param operand */ public void add(int instruction, int operand) { current.append(instruction, operand); } /** * append an instruction with two operands to the current block * @param instruction * @param operand1 * @param operand2 */ public void add(int instruction, int operand1, int operand2) { current.append(instruction, operand1, operand2); } /** * append an operand with more than two operands ot the current block * @param instruction * @param operands */ public void add(int instruction, int[] operands) { current.append(instruction, operands); } /** * append an instruction with a String operand to the current block * @param instruction * @param name */ public void add(int instruction, String name) { int idx = names.indexOf(name); if (idx < 0) { idx = names.size(); names.add(name); } current.append(instruction, idx); } /** * append a multiarray create instruction to the current block * @param instruction * @param name the name of the array base type * @param dims the number of array dimensions */ public void add(int instruction, String name, int dims) { int idx = names.indexOf(name); if (idx < 0) { idx = names.size(); names.add(name); } current.append(instruction, idx, dims); } /** * append a field or method instruction with 3 String operands to the current block * @param instruction * @param name */ public void add(int instruction, String owner, String name, String desc) { int idx1 = names.indexOf(owner); if (idx1 < 0) { idx1 = names.size(); names.add(owner); } int idx2 = names.indexOf(name); if (idx2 < 0) { idx2 = names.size(); names.add(name); } int idx3 = names.indexOf(desc); if (idx3 < 0) { idx3 = names.size(); names.add(desc); } current.append(instruction, idx1, idx2, idx3); } /** * set the location of a label to the next instruction offset in the current block * @param label the label whose location is to be set */ public CodeLocation setLocation(Label label) { CodeLocation location = nextLocation(); labelLocations.put(label, location); return location; } /** * return the location of the label if known or null if it has not yet been reached. note that if this * returns non-null then the label's offset in the generated bytecode can be safely retrieved but if it * returns null then attempting to retrieve the offset will generate an exception. * @param label the label whose location is desired * @return the label's location if it has been reached otherwise null */ public CodeLocation getLocation(Label label) { return labelLocations.get(label); } /** * test whether the location of a label is known yet * @param label the label whose location is desired * @return true if the label's location has been reached otherwise false */ public boolean hasLocation(Label label) { return (labelLocations.get(label) != null); } /** * return a location which will identify the next instruction added to the current block * @return the location of the next instruction added to the current block */ public CodeLocation nextLocation() { return new CodeLocation(current, current.getInstructionCount()); } /** * return the block containing a label if known * * @param label the label whose containing block is desired * @return the label's location if it has been reached otherwise null */ public BBlock getBlock(Label label) { CodeLocation location = labelLocations.get(label); if (location == null) { // may not have generated code for this label yet return null; } return location.getBlock(); } /** * return a link object listing all the labels contained in a given block * @param block the block whose labels are being sought * @return the associated set of labels */ public FanOut getContains(BBlock block) { return contains.get(block); } /** * add a label to the list of labels contained in a given block * @param block the block whose containslist is to be updated * @param label the label to be added to the list */ private void addContains(BBlock block, Label label) { FanOut containsFanOut = contains.get(block); if (containsFanOut == null) { containsFanOut = new FanOut(block.getLabel()); contains.put(block, containsFanOut); } containsFanOut.append(label); } /** * retrieve the list of monitor enter locations open at the start of a given block * @param label the label of the block * @return the list of open monitor enter locations */ public List<CodeLocation> getOpenMonitorEnters(Label label) { return openMonitorEnters.get(label); } /** * retrieve the list of monitor enter locations open at the start of a given block * @param block the block * @return the list of open monitor enter locations in reverse order of appearance in the bytecode */ public List<CodeLocation> getOpenMonitorEnters(BBlock block) { List<CodeLocation> blockMonitorEnters = null; // if this is a handler target block then it will have an attached list of handler starts // the open enters list can be constructed by combining the lists attached to the try catch // details. Iterator<TryCatchDetails> iterator = block.getHandlerStarts(); if (iterator.hasNext()) { // at least one try/catch targets this block as a handler blockMonitorEnters = new LinkedList<CodeLocation>(); while (iterator.hasNext()) { TryCatchDetails details = iterator.next(); details.addOpenLocations(blockMonitorEnters); } return blockMonitorEnters; } // ok that failed so look for a control flow label with the propagated information // any of them will do since they all *must* have the same open monitor list FanOut fanOut = getContains(block); int count = fanOut.getToCount(); for (int i = 0; i < count; i++) { Label l = fanOut.getTo(i); CodeLocation loc = getLocation(l); if (loc.getInstructionIdx() > 0) { // nothing open on entry to this block break; } // see if we have an inherited list blockMonitorEnters = getOpenMonitorEnters(l); if (blockMonitorEnters != null) { return new LinkedList<CodeLocation>(blockMonitorEnters); } } // just return an empty list return new LinkedList<CodeLocation>(); } /** * retrieve the list of monitor enter locations associated with a trigger block. this is called * when we are inserting try catch handlers for trigger locations to determine whether they need * to perform any monitor exit operations before executing the normal trigger exception handling code. * @param triggerDetails the trigger being checked * @return the list of locations for monitor enters open at the trigger start */ public Iterator<CodeLocation> getOpenMonitors(TriggerDetails triggerDetails) { // there should be at least one try catch handlers associated with the trigger start label List<TryCatchDetails> tryCatchDetails = tryCatchStartDetails(triggerDetails.getStart()); // all 3 handler should have the same open monitor enters list return tryCatchDetails.get(0).getOpenEnters(); } /** * pair a monitor enter instruction with an associated monitor exit instructions * @param enter * @param exit */ void addMonitorPair(CodeLocation enter, CodeLocation exit) { List<CodeLocation> paired = monitorPairs.get(enter); if (paired == null) { paired = new LinkedList<CodeLocation>(); monitorPairs.put(enter, paired); } if (!paired.contains(exit)) { paired.add(exit); // we also need to be able to query this relationship in reverse order CodeLocation inverse = inverseMonitorPairs.put(exit, enter); } } /** * locate a monitor exit instruction in block associated with a given monitor enter * @param enter */ private CodeLocation getPairedExit(CodeLocation enter, BBlock block) { List<CodeLocation> paired = monitorPairs.get(enter); if (paired != null) { Iterator<CodeLocation> iter = paired.iterator(); while (iter.hasNext()) { CodeLocation location = iter.next(); if (location.getBlock() == block) { return location; } } } return null; } /** * locate the monitor enter instruction associated with a given monitor exit * @param exit */ public CodeLocation getPairedEnter(CodeLocation exit) { return inverseMonitorPairs.get(exit); } /** * return the index of the local var at which this monitorenter saved its lock object */ public int getSavedMonitorIdx(CodeLocation open) { // this should identify a monitorexit instruction preceded by an aload N instruction BBlock block = open.getBlock(); int instructionIdx = open.getInstructionIdx(); if (instructionIdx <= 0) { System.out.println("getSavedMonitorIdx : unexpected! close pair has invalid index " + instructionIdx + " in method " + methodName); } int instruction = block.getInstruction(instructionIdx); if (instruction != Opcodes.MONITORENTER) { System.out.println("getSavedMonitorIdx : unexpected! close pair instruction " + instruction + " is not MONITOREXIT in method " + methodName); } instructionIdx--; instruction = block.getInstruction(instructionIdx); // normally the monitorenter is preceded by a DUP ASTORE pair to save the monitor object // however, if an AT SYNCHRONIZE trigger has been injected before the MONITORENTER then // there may be a call to Rule.execute between the ASTORE and the MONITORENTER if (instruction == Opcodes.INVOKESTATIC) { // we can safely skip backwards to the last ASTORE because the trigger sequence will not // use an ASTORE while (instruction != Opcodes.ASTORE && instructionIdx > 0) { // skip backwards until we find the required ASTORE instructionIdx--; instruction = block.getInstruction(instructionIdx); } } if (instruction != Opcodes.ASTORE) { System.out.println("getSavedMonitorIdx : unexpected! close pair preceding instruction " + instruction + " is not ASTORE in method " + methodName); return -1; } int varIdx = block.getInstructionArg(instructionIdx, 0); if (varIdx < 0) { System.out.println( "getSavedMonitorIdx : unexpected! close pair preceding ASTORE instruction has invalid index " + varIdx + " in method " + methodName); } return varIdx; } /** * test whether there are any open monitorenters with no corresponding monitorexit on a path to * the current instruction * * @return true if there are open monitorenters otherwise false */ public boolean inOpenMonitor() { List<CodeLocation> currentOpenEnters = currentOpenEnters(false); return currentOpenEnters.size() > 0; } /** * return a list of all open monitorenters with no corresponding monitorexit on a path to * the current instruction * * @param dumpOk true if it is appropriate todump the cfg at this point * @return true if there are open monitorenters otherwise false */ private List<CodeLocation> currentOpenEnters(boolean dumpOk) { // any opens which were not closed in this block will have been retained in // the monitorEnters list in reverse order of appearance. // // any opens which were closed in this block will have been installed as a pair with their // corresponding close. Iterator<CodeLocation> entersIter = current.getMonitorEnters(); // any closes which occurred in this block will have been retained in // the monitorExits list Iterator<CodeLocation> exitsIter = current.getMonitorExits(); // opens which have been propagated to this block can be identified by calling getOpenMonitorEnters(current) List<CodeLocation> openEnters = getOpenMonitorEnters(current); Iterator<CodeLocation> openEntersIter = openEnters.iterator(); // if we are dumping debug info then do it now if (Transformer.isDumpCFGPartial() && dumpOk) { List<TryCatchDetails> active = current.getActiveTryStarts(); int openEntersCount = openEnters.size(); System.out.print("Carry forward open monitors for " + current.getBlockIdx() + " ==> {"); String sepr = ""; for (int i = 0; i < openEntersCount; i++) { System.out.print(sepr); System.out.print(openEnters.get(i)); sepr = ", "; } System.out.println("}"); int activeTryStartsCount = active.size(); System.out.print("active try starts for " + current.getBlockIdx() + " ==> {"); sepr = ""; for (int i = 0; i < activeTryStartsCount; i++) { TryCatchDetails details = active.get(i); CodeLocation start = getLocation(details.getStart()); CodeLocation end = getLocation(details.getEnd()); System.out.print(sepr); System.out.print(start); if (end != null) { System.out.print(":"); System.out.print(end); } sepr = ", "; } System.out.println("}"); int currentTryStartsCount = currentTryCatchStarts.size(); System.out.print("current try starts for " + current.getBlockIdx() + " ==> {"); sepr = ""; for (int i = 0; i < currentTryStartsCount; i++) { TryCatchDetails details = currentTryCatchStarts.get(i); CodeLocation start = getLocation(details.getStart()); CodeLocation end = getLocation(details.getEnd()); System.out.print(sepr); System.out.print(start); if (end != null) { System.out.print(":"); System.out.print(end); } sepr = ", "; } System.out.println("}"); } // find any exits which have not been closed List<CodeLocation> nonLocalExits = new LinkedList<CodeLocation>(); while (exitsIter.hasNext()) { CodeLocation exit = exitsIter.next(); // see if this one is paired off with a local enter CodeLocation enter = getPairedEnter(exit); if (enter == null || enter.getBlock() != current) { // this is a non-local exit matching a a propagated enter nonLocalExits.add(exit); } } // now pair off propagated enters with any non local exits exitsIter = nonLocalExits.iterator(); while (exitsIter.hasNext() && openEntersIter.hasNext()) { CodeLocation exit = exitsIter.next(); CodeLocation enter = openEntersIter.next(); // we may have already done this but it is idempotent addMonitorPair(enter, exit); } // sanity check if (exitsIter.hasNext()) { System.out.println("exits unaccounted for in block B" + current.getBlockIdx()); } // any left over values are still open at the end of this block so accumulate them // keeping them in reverse order of appearance LinkedList<CodeLocation> newOpenEnters = new LinkedList<CodeLocation>(); while (entersIter.hasNext()) { newOpenEnters.add(entersIter.next()); } if (openEntersIter != null) { while (openEntersIter.hasNext()) { newOpenEnters.add(openEntersIter.next()); } } return newOpenEnters; } /** * forward details of open monitor and try catch block locations from the current * block to its reachable labels. This is always called just before splitting the current block. */ private void carryForward() { if (Transformer.isDumpCFGPartial()) { int blockIdx = current.getBlockIdx(); if (blockIdx == 0) { System.out.println("Intermediate Control Flow Graph for " + methodName); } System.out.println("Carry forward for block " + blockIdx); } Label label = current.getLabel(); int nOuts = current.nOuts(); // identify which try start regions overlap this block // // the current try start list will include try catch regions which subsume the whole of this block // however, some regions may have partially overlapped so we need to add them too. to do this we need // to add any starts which have block offset greater than 0 List<TryCatchDetails> active = new ArrayList<TryCatchDetails>(currentTryCatchStarts); Iterator<TryCatchDetails> ends = current.getTryEnds(); while (ends.hasNext()) { TryCatchDetails details = ends.next(); CodeLocation location = getLocation(details.getEnd()); if (location.getInstructionIdx() > 0) { active.add(details); } } // any remaining starts are active somewhere in the block and hence indicate // possible exception control flow current.setActiveTryStarts(active); if (Transformer.isDumpCFGPartial()) { System.out.println(current); } // compute the list of monitorenters which are still open at the current instruction List<CodeLocation> newOpenEnters = currentOpenEnters(true); int newOpenCount = newOpenEnters.size(); // ok, now attach the list to all blocks reachable via normal control flow // first the blocks reachable via jump links // n.b. link 0 is the next block in line, if it is reachable then it will also appear as a later link // so we start the iteration from 1 for (int i = 1; i <= nOuts; i++) { label = current.nthOut(i); List<CodeLocation> blockOpenEnters = openMonitorEnters.get(label); if (blockOpenEnters == null) { openMonitorEnters.put(label, newOpenEnters); if (Transformer.isDumpCFGPartial()) { System.out.print("open monitors " + label + " ==> {"); String sepr = ""; for (int j = 0; j < newOpenCount; j++) { CodeLocation l = newOpenEnters.get(j); System.out.print(sepr); System.out.print("BB"); System.out.print(l.getBlock().getBlockIdx()); System.out.print("."); System.out.print(l.getInstructionIdx()); sepr = ", "; } System.out.println("}"); } } else { // sanity check // this should contain the same locations as our current list! int openCount = blockOpenEnters.size(); if (openCount != newOpenCount) { System.out.println("CFG.carryForward: unexpected! invalid open enters count for block " + label + " in method " + methodName); } for (int j = 0; j < newOpenCount && j < openCount; j++) { CodeLocation l1 = blockOpenEnters.get(j); CodeLocation l2 = newOpenEnters.get(j); if (l1.getBlock() != l2.getBlock()) { System.out.println("CFG.carryForward: unexpected! invalid open enters block for block " + label + " at index " + j + " in method " + methodName); } if (l1.getInstructionIdx() != l2.getInstructionIdx()) { System.out.println( "CFG.carryForward: unexpected! invalid open enters instruction index for block " + label + " at index " + j + " in method " + methodName); } } } } if (Transformer.isDumpCFGPartial()) { System.out.println(); } List<TryCatchDetails> activeTryStarts = current.getActiveTryStarts(); if (activeTryStarts != null) { // now propagate open monitors to handler blocks reachable via exceptions thrown from this block // we need to be conservative here. a handler block may not actually be reachable because another // try catch block with a generic exception type shadows it. // check each monitor exited in the current block to see if it's extent overlaps an active try catch // anywhere in the current block Iterator<CodeLocation> exitIter = current.getMonitorExits(); while (exitIter.hasNext()) { CodeLocation exit = exitIter.next(); CodeLocation enter = getPairedEnter(exit); Iterator<TryCatchDetails> activeStartsIter = activeTryStarts.iterator(); while (activeStartsIter.hasNext()) { TryCatchDetails activeRegion = activeStartsIter.next(); // skip if we know about this one already if (activeRegion.containsOpenEnter(enter)) { // ignore continue; } // if the handler has already been visited then this try catch returns to this same // block or one of its predecessors. that's not a real overlap since these try catches // are only added to ensure that exceptions in the handler code reenter the handler CodeLocation handlerStart = getLocation(activeRegion.getHandler()); if (handlerStart != null) { continue; } // ok let's look for an overlap -- we will definitely have a start location // but there may not be an end location yet CodeLocation tryStart = getLocation(activeRegion.getStart()); CodeLocation tryEnd = getLocation(activeRegion.getEnd()); int containment = computeContainment(tryStart, tryEnd, enter, exit, OVERLAPS); if (containment == OVERLAPS) { // the enter/exit overlaps the try region but we still need to check whether // it lies in a shadowing region List<TryCatchDetails> shadowRegions = activeRegion.getShadowRegions(); boolean isShadow = false; if (shadowRegions != null) { Iterator<TryCatchDetails> shadowRegionsIter = shadowRegions.iterator(); while (shadowRegionsIter.hasNext() && !isShadow) { TryCatchDetails shadowRegion = shadowRegionsIter.next(); CodeLocation shadowStart = getLocation(shadowRegion.getStart()); CodeLocation shadowEnd = getLocation(shadowRegion.getEnd()); containment = computeContainment(shadowStart, shadowEnd, enter, exit, CONTAINS); // we will not see UNKNOWN in this case as exit is known if (containment == CONTAINS) { // this region encloses the enter/exit so there is no need to propagate it if (Transformer.isDumpCFGPartial()) { System.out.println("ignoring open enter " + enter + " for region " + tryStart + ":" + (tryEnd != null ? tryEnd.toString() : "??") + " shadowed by region " + shadowStart + ":" + (shadowEnd != null ? shadowEnd.toString() : "??")); } isShadow = true; } } } if (!isShadow) { // ok, we need to add the enter to this region's open enter list if (Transformer.isDumpCFGPartial()) { System.out.println("propagating enter " + enter + " to try handler for " + tryStart + ":" + (tryEnd != null ? tryEnd.toString() : "??")); } activeRegion.addOpenEnter(enter); } } } } // for each monitor enter open at the end of the block to see if it's extent overlaps an active try catch // anywhere in the current block Iterator<CodeLocation> newOpenEntersIter = newOpenEnters.iterator(); while (newOpenEntersIter.hasNext()) { CodeLocation enter = newOpenEntersIter.next(); Iterator<TryCatchDetails> activeStartsIter = activeTryStarts.iterator(); while (activeStartsIter.hasNext()) { TryCatchDetails activeRegion = activeStartsIter.next(); // skip if we know about this one already if (activeRegion.containsOpenEnter(enter)) { // ignore continue; } // ok let's look for an overlap there may not be an end location yet // but we know the try start must precede the monitor exit because we have not // reached the exit and we know the try is active somewhere in this block CodeLocation tryStart = getLocation(activeRegion.getStart()); CodeLocation tryEnd = getLocation(activeRegion.getEnd()); int containment = computeContainment(tryStart, tryEnd, enter, null, OVERLAPS); if (containment == OVERLAPS) { // the enter/exit overlaps the try region but we still need to check whether // it lies in a shadowing region List<TryCatchDetails> shadowRegions = activeRegion.getShadowRegions(); boolean isShadow = false; if (shadowRegions != null) { Iterator<TryCatchDetails> shadowRegionsIter = shadowRegions.iterator(); while (shadowRegionsIter.hasNext() && !isShadow) { TryCatchDetails shadowRegion = shadowRegionsIter.next(); CodeLocation shadowStart = getLocation(shadowRegion.getStart()); CodeLocation shadowEnd = getLocation(shadowRegion.getEnd()); containment = computeContainment(shadowStart, shadowEnd, enter, null, CONTAINS); if (containment == CONTAINS) { // ok, the inner shadowing region overlaps the enter/exit if (Transformer.isDumpCFGPartial()) { System.out.println("ignoring open enter " + enter + " for region " + tryStart + ":" + (tryEnd != null ? tryEnd.toString() : "??") + " shadowed by region " + shadowStart + ":" + (shadowEnd != null ? shadowEnd.toString() : "??")); } isShadow = true; } else if (containment == UNKNOWN) { // this region may shadow the outer region but we don't know because we // have not yet seen an exit or a tryEnd for the inner region -- we will // find out when a monitor exit or tryEnd is reached so just delay for now if (Transformer.isDumpCFGPartial()) { System.out.println("ignoring open enter " + enter + " for region " + tryStart + ":" + (tryEnd != null ? tryEnd.toString() : "??") + " potentially shadowed by region " + shadowStart + ":" + (shadowEnd != null ? shadowEnd.toString() : "??")); } isShadow = true; } } } if (!isShadow) { // ok, we need to add the enter to this regions open enter list if (Transformer.isDumpCFGPartial()) { System.out.println("propagating enter " + enter + " to try handler for " + tryStart + ":" + (tryEnd != null ? tryEnd.toString() : "??")); } activeRegion.addOpenEnter(enter); } } } } } } /** * flag value passed to request a check for an overlap and returned to notify an overlap */ private final static int OVERLAPS = 1; /** * flag value passed to request a check for a containment and returned to notify a containment */ private final static int CONTAINS = 2; /** * flag value returned to notify that a containment cannot yet be computed */ private final static int UNKNOWN = 4; /** * compute whether the the region defined by a given enter and exit location pair overlaps or is contained within * the region defined by a try start and end location pair when both regions ar erestricted to the current block * @param tryStart the location of the start of the try region which will already have been visited * @param tryEnd the location of the end of the try region which may be null because the end point has not been * yet visited * @param enter the location of the start of the monitor region which will already have been visited * @param exit the location of an exit corresponding to the enter which may be null because the exit has not * yet been visited * @param flags OVERLAPS if an overlap is being checked for or CONTAINS if a containment is being checked for * @return OVERLAPS if the monitor region overlaps the try region and an overlap is being checked * for or CONTAINS if the monitor region is definitely contained within the try region and containment is being * checked for or UNKNOWN if the monitor region cannot yet be determined to be contained within the try region * and containment is being checked or 0 if there is no overlap and and an overlap is being checked * for or 0 if there is definitely no containment and containment is being checked for. */ private int computeContainment(CodeLocation tryStart, CodeLocation tryEnd, CodeLocation enter, CodeLocation exit, int flags) { int result = 0; // we are only interested in overlaps or containment in the current block so // restrict the region start to be inside this block if (tryStart.getBlockIdx() < current.getBlockIdx()) { tryStart = new CodeLocation(current, 0); } if (enter.getBlockIdx() < current.getBlockIdx()) { enter = new CodeLocation(current, 0); } // tryStart and enter will both be non-null but either or both of exit and tryEnd can null indicating that // they are both greater than or equal to the current position. by checking specifically for these cases // we can avoid having to create code locations if (exit == null) { if (tryEnd == null) { // there must at least be an OVERLAP since we have started both regions and have not exited either of // them. however we cannot determine containment yet if ((flags & CONTAINS) != 0) { return UNKNOWN; } return OVERLAPS; } else { // the tryEnd precedes the exit. if it also precedes the enter then there is no overlap if (tryEnd.compareTo(enter) <= 0) { return 0; } // the enter is contained in the try region so we have an overlap. we cannot have containment // because the exit lies beyond the try end if ((flags & CONTAINS) == 0) { return OVERLAPS; } else { return 0; } } } else if (tryEnd == null) { // well, we have an exit which precedes the try end. if it also precedes the try start then there // is no overlap if (exit.compareTo(tryStart) < 0) { return 0; } // the exit is contained in the try region so we have an overlap. if we are not interested in // containment then we are done if ((flags & CONTAINS) == 0) { return OVERLAPS; } // for containment we need the enter to be at or after the try start -- actually we also allow it // to be in the same block as the try start but precede it by one instruction because the enter // is only exposed starting from the instruction which follows it if (tryStartMayContainEnter(tryStart, enter)) { return CONTAINS; } // no containment return 0; } else { // we have a start and end for both regions. we can rule out any overlap/containment if the exit // precedes the try start or the enter follows the try end if (exit.compareTo(tryStart) < 0 || tryEnd.compareTo(enter) <= 0) { return 0; } // we must at least have an overlap. if we are not interested in // containment then we are done if ((flags & CONTAINS) == 0) { return OVERLAPS; } // for containment we need the exit to preceded the tryEnd and the enter to be at or after the try // start -- actually we also allow it to be in the same block as the try start but precede it by // one instruction because the enter is only exposed starting from the instruction which follows it // first check the exit against the try end if (exit.compareTo(tryEnd) >= 0) { // no containment return 0; } if (tryStartMayContainEnter(tryStart, enter)) { return CONTAINS; } // no containment return 0; } } /** * check whether the instructions exposed by a monitor enter may be contained within the scope of a * tryStart. this is possible if the try start begins before the first instruction which follows the * enter i.e. if the try start has a lower block idx than the enter or has the same block idx and an * instruction offset less than or equal to the enter instruction idx + 1. * @param enter * @param tryStart * @return */ private boolean tryStartMayContainEnter(CodeLocation tryStart, CodeLocation enter) { int enterBlockIdx = enter.getBlockIdx(); int tryStartBlockIdx = tryStart.getBlockIdx(); if (tryStartBlockIdx < enterBlockIdx) { return true; } if (tryStartBlockIdx == enterBlockIdx) { int enterInsnIdx = enter.getInstructionIdx(); int tryStartInsnIdx = tryStart.getInstructionIdx(); if (tryStartInsnIdx <= enterInsnIdx + 1) { return true; } } return false; } /** * split the graph at a control-flow dead-end using the label provided to identify the new current * block. the caller is obliged to call visitLabel immediately after calling this method to ensure * that the current block label is indexed appropriately. * * @param newStart the label to be used to identify the new current block */ public void split(Label newStart) { current.append(newStart); // carry forward any open monitor enter locations and update the open try start list carryForward(); current = new BBlock(this, newStart, nextIdx++); blocks.put(newStart, this.current); } /** * split the graph at a control-flow goto point using the labels provided to identify the new current * block and the goto target. the caller is obliged to call visitLabel immediately after calling this * method to ensure that the current block label is indexed appropriately. * * @param newStart the label to be used to identify the new current block * @param out the target of the GOTO */ public void split(Label newStart, Label out) { current.append(newStart); current.append(out); // carry forward any open monitor enter locations and update the open try start list carryForward(); current = new BBlock(this, newStart, nextIdx++); blocks.put(newStart, this.current); } /** * split the graph at a control-flow if branch point using the labels provided to identify the new current * block the if branch target and the else branch target. the caller is obliged to call visitLabel * immediately after calling this method to ensure that the current block label is indexed appropriately. * * @param newStart the label to be used to identify the new current block * @param out the target of the if branch * @param out2 the target of the else branch which probably ought to be the same label as passed for the * current block (IF instructions assume drop-through) */ public void split(Label newStart, Label out, Label out2) { current.append(newStart); current.append(out); current.append(out2); // carry forward any open monitor enter locations and update the open try start list carryForward(); current = new BBlock(this, newStart, nextIdx++); blocks.put(newStart, current); } /** * split the graph at a control-flow switch branch point using the labels provided to identify the new * current block, the switch case default branch target and the rest of the switch case branch targets. * the caller is obliged to call visitLabel immediately after calling this method to ensure that the * current block label is indexed appropriately. * * @param newStart the label to be used to identify the new current block * @param dflt the switch case default branch target * @param labels the other switch case branch targets */ public void split(Label newStart, Label dflt, Label[] labels) { current.append(newStart); current.append(dflt); for (int i = 0; i < labels.length; i++) { current.append(labels[i]); } // carry forward any open monitor enter locations and update the open try start list carryForward(); current = new BBlock(this, newStart, nextIdx++); blocks.put(newStart, this.current); } /** * test if a label marks the start of a try catch block * @param label the label to be tested * @return true if the label marks the start of a try catch block otherwise false */ public boolean tryCatchStart(Label label) { return tryCatchStarts.containsKey(label); } /** * test if a label marks the end of a try catch block * @param label the label to be tested * @return true if the label marks the start of a try catch block otherwise false */ public boolean tryCatchEnd(Label label) { return tryCatchEnds.containsKey(label); } /** * test if a label marks the start of the handler for a try catch block * @param label the label to be tested * @return true if the label marks the start of a try catch block otherwise false */ public boolean tryCatchHandlerStart(Label label) { return tryCatchHandlers.containsKey(label); } /** * return the list of details of try catch blocks which start at this label * @param label * @return */ public List<TryCatchDetails> tryCatchStartDetails(Label label) { return tryCatchStarts.get(label); } /** * return the list of details of try catch blocks which end at this label * @param label * @return */ public List<TryCatchDetails> tryCatchEndDetails(Label label) { return tryCatchEnds.get(label); } /** * return the list of details of try catch blocks whose handler startsend at this label * @param label * @return */ public List<TryCatchDetails> tryCatchHandlerStartDetails(Label label) { return tryCatchHandlers.get(label); } /** * test if a label marks the start of a trigger block * @param label the label to be tested * @return true if the label marks the start of a trigger block otherwise false */ public boolean triggerStart(Label label) { return triggerStarts.containsKey(label); } /** * test if a label marks the end of a trigger block * @param label the label to be tested * @return true if the label marks the start of a trigger block otherwise false */ public boolean triggerEnd(Label label) { return triggerEnds.containsKey(label); } /** * return details of any trigger block which starts at this label * @param label * @return */ public TriggerDetails triggerStartDetails(Label label) { return triggerStarts.get(label); } /** * return the list of details of try catch blocks which end at this label * @param label * @return */ public TriggerDetails triggerEndDetails(Label label) { return triggerEnds.get(label); } /** * return an iterator ovver all known trigger detailsd * @return */ public Iterator<TriggerDetails> triggerDetails() { return triggerStarts.values().iterator(); } /** * notify the CFG that a label has been visited by the method visitor and hence its position will now * be resolved * @param label */ public void visitLabel(Label label) { // record the label's location in the current block addContains(current, label); // now we need to add a block location for this label CodeLocation location = setLocation(label); // if this is a try catch block start, end or handler label then we need to update the list // maintained in each block. in the former two cases we also need to update the set of currently // open try starts List<TryCatchDetails> newStarts = tryCatchStartDetails(label); if (newStarts != null) { current.addTryStarts(newStarts); currentTryCatchStarts.addAll(newStarts); } List<TryCatchDetails> newEnds = tryCatchEndDetails(label); if (newEnds != null) { current.addTryEnds(newEnds); currentTryCatchStarts.removeAll(newEnds); } List<TryCatchDetails> newhandlers = tryCatchHandlerStartDetails(label); if (newhandlers != null) { current.addHandlerStarts(newhandlers); } if (newStarts != null) { // we need to identify whether any of the new try catch regions shadows any outer try catch regions // and add them to the shadow list for those outer regions. shadowing occurs if the inner region // has a catch type which is the same as or a superclass of the outer region catch type or // if the inner region is a catch all. we cannot guarantee to detect superclass relations as // that requires code loading. but we can detect shadowing for same type and catch alls // TODO extend this to cope with superclass shadowing // currentTryStarts contains all tryStarts which have not yet been closed Iterator<TryCatchDetails> currentStartsIter = currentTryCatchStarts.iterator(); while (currentStartsIter.hasNext()) { TryCatchDetails currentStart = currentStartsIter.next(); if (newStarts.contains(currentStart)) { // this was just added so no shadowing occurs continue; } Iterator<TryCatchDetails> newStartsIter = newStarts.iterator(); while (newStartsIter.hasNext()) { TryCatchDetails newStart = newStartsIter.next(); // TODO extend this to cope with superclass shadowing if (newStart.getType() == null || newStart.getType().equals(currentStart.getType())) { currentStart.addShadowRegion(newStart); } } } } } /** * notify the CFG that a label which represents the start of a trigger injection sequence has just been visited * by the method visitor. * @param label */ public void visitTriggerStart(Label label) { // we normally only see one trigger start for a given trigger adapter run but in the // case of an AT EXIT trigger adapter we may see multiple trigger starts latestTrigger = new TriggerDetails(this, label); triggerStarts.put(label, latestTrigger); } /** * notify the CFG that a label which represents the end of a trigger injection sequence has just been visited * by the method visitor. * @param label */ public void visitTriggerEnd(Label label) { // we normally only see one trigger end for a given trigger adaoter run but in the // case of an AT EXIT trigger adapter we may see multiple trigger ends latestTrigger.setEnd(label); triggerEnds.put(label, latestTrigger); latestTrigger = null; } /** * notify the CFG of the location of a try catch block. note that this does not mean that the code * generator has been notified of this information. these are normally notified to the method visitor * before visiting the code. this is problematic if we want to insert our trigger point try catch * blocks because we need to order them before any enclosing try catch blocks with wider scope. so the * method visitor calls this routine up front but only notifies the try catch block to its super when * the end label for the try catch block is reached. * * @param start * @param end * @param handler * @param type */ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { // hmm, we need to store this info so we can track it later boolean isTriggerHandler = triggerStarts.containsKey(start); TryCatchDetails details = new TryCatchDetails(this, start, end, handler, type, isTriggerHandler); // each label should only ever correspond to a single code location but we may see the same label // associated with more than one tryCatchBlock notification so the index links are 1:m List<TryCatchDetails> detailsList = tryCatchStarts.get(start); if (detailsList == null) { detailsList = new LinkedList<TryCatchDetails>(); tryCatchStarts.put(start, detailsList); } detailsList.add(details); detailsList = tryCatchEnds.get(end); if (detailsList == null) { detailsList = new LinkedList<TryCatchDetails>(); tryCatchEnds.put(end, detailsList); } detailsList.add(details); detailsList = tryCatchHandlers.get(handler); if (detailsList == null) { detailsList = new LinkedList<TryCatchDetails>(); tryCatchHandlers.put(handler, detailsList); } detailsList.add(details); } /** * check if the current block is a byteman-generated handler i.e. one which was created to * catch an exception thrown by the byteman runtime. n.b. a byteman handler only ever spans * one block. * @return true if the current block is a byteman-generated handler */ public boolean inBytemanHandler() { // we just need to check whether the current block has a handler with a byteman exception type Iterator<TryCatchDetails> handlerStarts = current.getHandlerStarts(); while (handlerStarts.hasNext()) { TryCatchDetails details = handlerStarts.next(); // any trigger we have planted will be tagged as such if (details.isTriggerHandler()) { return true; } // handlers planted by previous transforms will not be tagged but will be for a byteman exception type String typeName = details.getType(); // n.b. handlers for finally blocks will have a null type name if (typeName != null) { if (typeName.equals(CFG.EARLY_RETURN_EXCEPTION_TYPE_NAME) || typeName.equals(CFG.EXECUTE_EXCEPTION_TYPE_NAME) || typeName.equals(CFG.THROW_EXCEPTION_TYPE_NAME)) { return true; } } } return false; } /** * return true if the current block is a rethrow handler i.e. one which was created to close a monitor * exit instruction and then rethrow an exception. n.b. this must only be called when the next instruction * to be added to the byetcode sequence is an ATHROW * @return true if the current block is a rethrow handler */ public boolean inRethrowHandler() { int nextIdx = current.getInstructionCount(); // a compiler generated rethrow block always has the same format // astore N1 // aload N2 // monitorexit // aload N1 // athrow if ((nextIdx >= 4) && current.getInstruction(nextIdx - 1) == Opcodes.ALOAD && current.getInstruction(nextIdx - 2) == Opcodes.MONITOREXIT && current.getInstruction(nextIdx - 3) == Opcodes.ALOAD && current.getInstruction(nextIdx - 4) == Opcodes.ASTORE && current.getInstructionArg(nextIdx - 1, 0) == current.getInstructionArg(nextIdx - 4, 0)) { return true; } // a rethrow block generated by byteman has the simpler format // aload N1 // monitorexit // athrow if ((nextIdx >= 2) && current.getInstruction(nextIdx - 1) == Opcodes.MONITOREXIT && current.getInstruction(nextIdx - 2) == Opcodes.ALOAD) { return true; } return false; } /** * check if the current block is a byteman-generated trigger section. this can be checked by testing whether * there is an open try catch for one of the Byteman exception types * @return true if the current block is a byteman-generated trigger section */ public boolean inBytemanTrigger() { // if we are in the middle of injecting a trigger then latestTrigger will be non null if (latestTrigger != null) { return true; } // if we are in a previously injected trigger then we will be in the scope of a try catch // which hanldes one of the Byteman generated exceptions Iterator<TryCatchDetails> currentTryStarts = currentTryCatchStarts.iterator(); while (currentTryStarts.hasNext()) { TryCatchDetails details = currentTryStarts.next(); // any trigger we have planted will be tagged as such if (details.isTriggerHandler()) { return true; } // handlers planted by previous transforms will not be tagged but will be for a byteman exception type String typeName = details.getType(); if (typeName.equals(CFG.EARLY_RETURN_EXCEPTION_TYPE_NAME) || typeName.equals(CFG.EXECUTE_EXCEPTION_TYPE_NAME) || typeName.equals(CFG.THROW_EXCEPTION_TYPE_NAME)) { return true; } } return false; } /** * this can be called when the code generator call visiMaxs but it does nothing just now */ public void visitMaxs() { // nothing to do just here } public void visitEnd() { // we don't need to do anything here to but for now just dump the CFG if we are verbose if (Transformer.isDumpCFG()) { System.out.println(this); } } /** * generate a string representation of the CFG * @return */ public String toString() { StringBuffer buf = new StringBuffer(); buf.append("Control Flow Graph for "); buf.append(methodName); buf.append("\n"); BBlock next = entry; while (next != null) { next.printTo(buf); next = blocks.get(next.next()); } return buf.toString(); } /** * return the index of the label in its enclosing block's instruction sequence of -1 if the * label has not yet been visited. the index can be used to lookup the insruction following * the label. * @param label * @return */ public int getBlockInstructionIdx(Label label) { CodeLocation location = labelLocations.get(label); if (location == null) { // may not have generated code for this label yet return -1; } return location.getInstructionIdx(); } public String getName(int nameIdx) { return names.get(nameIdx); } }