/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.abc.semantics; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.graph.IBasicBlock; import org.apache.flex.abc.graph.IFlowgraph; import org.apache.flex.abc.graph.algorithms.DepthFirstPreorderIterator; import org.apache.flex.abc.graph.algorithms.DominatorTree; import org.apache.flex.abc.visitors.IFlowGraphVisitor; /** * A ControlFlowGraph represents the flow of control through a sequence of * instructions. The instructions are organized into a set of Blocks -- * sequences of instructions where normal control flow proceeds linearly from * one instruction to the next -- and a set of edges, representing the * discontinuous transfer of control points. */ public class ControlFlowGraph implements IFlowgraph { /** * Search direction in the initial block of a block-by-block search * (typically for debug information). */ private enum SearchDirection { Forward, Backward }; /** * Create a ControlFlowGraph from the instructions in the MethodBodyInfo. * @param mbi - the MethodBodyInfo of the method whose control flow graph is to be computed. */ ControlFlowGraph(MethodBodyInfo mbi) { this.mbi = mbi; this.startBlock = newBlock(); buildCfg(); } /** * This ControlFlowGraph's MethodBodyInfo. */ private final MethodBodyInfo mbi; /** * Blocks of the flow graph in entry order. */ private ArrayList<IBasicBlock> blocks = new ArrayList<IBasicBlock>(); /** * The entry point of this routine; known in * graph theory as the start block. */ private Block startBlock; /** * Blocks keyed by their Labels. Multiple Labels may map to a Block. */ private Map<Label, IBasicBlock> blocksByLabel = new HashMap<Label, IBasicBlock>(); /** * Catch targets defined in this method. */ private ArrayList<IBasicBlock> catchTargets = new ArrayList<IBasicBlock>(); /** * The graph's dominator tree, built on demand. */ private DominatorTree dominatorTree = null; /** * Build the CFG. */ private void buildCfg() { Block current_block = startBlock; boolean last_block_transferred_control = false; // Sort the labels by position. Collections.sort(mbi.getInstructionList().getActiveLabels()); Iterator<Label> labels = mbi.getInstructionList().getActiveLabels().iterator(); Label next_label = getNextLabel(labels); // Labels of a Block's successors, keyed by the "from" block. // These are turned into edges from the "from" block once all // blocks have been seen. Map<Block, Collection<Label>> successor_labels = new HashMap<Block, Collection<Label>>(); // Iterate through the method's instructions by position; the active Labels // have embedded position information that corresponds to this zero-based // enumeration of the instructions. List<Instruction> instructions = mbi.getInstructionList().getInstructions(); for (int i = 0; i < instructions.size(); i++) { if (i == next_label.getPosition() || last_block_transferred_control) { if ( current_block.size() > 0 ) { Block prev_block = current_block; current_block = newBlock(); if (prev_block.canFallThrough()) { // Add an edge from the prev block to the current block, since // control can fall through from that block to the current one. prev_block.addSuccessor(current_block); } } else { // If the first instruction is a label target, // then the start block will still be empty; // this is actually OK for the flowgraph, but // would create a special case empty block // and complicate clients' block processing. assert current_block == startBlock; } // Add catch targets to the CFG's mapping. if (i == next_label.getPosition() && this.mbi.isCatchTarget(next_label)) this.catchTargets.add(current_block); // Map all labels targeting this block. while (next_label.getPosition() == i) { blocksByLabel.put(next_label, current_block); next_label = getNextLabel(labels); } } Instruction insn = instructions.get(i); current_block.add(insn); // Begin a new block after any transfer of control. last_block_transferred_control = insn.isTransferOfControl(); if (insn.isBranch()) { Collection<Label> successors = successor_labels.get(current_block); if (successors == null) { successors = new ArrayList<Label>(); successor_labels.put(current_block, successors); } // The target may be a forward jump to a block we haven't seen, // just store the label for now, and we'll get its block later after // we've seen all the instructions. if (insn.getOpcode() == ABCConstants.OP_lookupswitch) { for (int j = 0; j < insn.getOperandCount(); j++) { if (insn.getOperand(j) instanceof Label) successors.add((Label)insn.getOperand(j)); } } else { successors.add(insn.getTarget()); } } } // We've seen all the instructions now, so we can compute the blocks that // each label corresponds to, and fill in the rest of the graph for (Map.Entry<Block, Collection<Label>> entry : successor_labels.entrySet()) for (Label target_label : entry.getValue()) { IBasicBlock target_block = getBlock(target_label); if ( target_block != null ) entry.getKey().addSuccessor(target_block); } } /** * Get a Label's target Block. * * @param l - the Label of interest. * @return the corresponding Block, or null if not found. */ public IBasicBlock getBlock(Label l) { return blocksByLabel.get(l); } /** * Get the start block. * @return the start block. */ public IBasicBlock getStartBlock() { return this.startBlock; } /** * Is the given Block a catch target? * @param b - the Block of interest. * @return true if the Block is a catch target. * @see MethodBodyInfo#isCatchTarget(Label) * which differs from this routine in that it uses * positional information, which is not valid if the * ControlFlowGraph has been edited. */ public boolean isCatchTarget(IBasicBlock b) { return this.catchTargets.contains(b); } /** * Get an iterator that will iterate over the blocks in the graph. * in depth-first preorder. This will traverse each edge in the graph * once, but may return the same block multiple times if multiple edges lead * to it. */ public Iterable<IBasicBlock> blocksInControlFlowOrder() { return new Iterable<IBasicBlock>() { @Override public Iterator<IBasicBlock> iterator() { return new DepthFirstPreorderIterator(ControlFlowGraph.this.getRoots()); } }; } /** * Start a new block. * * @return the new Block. */ private Block newBlock() { Block result = new Block(); blocks.add(result); return result; } /** * @param labels - an Iterator of Labels. * @return the next Label in the sequence, or a marker * Label if the sequence is exhausted. */ private Label getNextLabel(Iterator<Label> labels) { if (labels.hasNext()) return labels.next(); else return END_OF_LABEL_SEQUENCE; } private static final Label END_OF_LABEL_SEQUENCE = new Label(); /** * Get the graph's blocks in their original order. * * @return an immutable List that presents the blocks in entry order. */ public List<IBasicBlock> getBlocksInEntryOrder() { return Collections.unmodifiableList(this.blocks); } /** * Get the graph's blocks as a mutable list. * @return the blocks in a list. */ List<IBasicBlock> getBlocks() { return this.blocks; } /** * Walk a IFlowGraphVisitor over this CFG. * * @param visitor - the visitor. */ public void traverseGraph(IFlowGraphVisitor visitor) { for (IBasicBlock b : this.blocksInControlFlowOrder()) { if (visitor.visitBlock(b)) { for (Instruction i : b.getInstructions()) visitor.visitInstruction(i); visitor.visitEnd(b); } } } /** * Touch the graph's dominator tree and fetch it. * @return the graph's dominator tree. */ public DominatorTree getDominatorTree() { if ( this.dominatorTree == null ) this.dominatorTree = new DominatorTree(getRoots()); return this.dominatorTree; } /** * Synthesize this flowgraph's "roots." The flowgraph actually * has only one root, the start block, but the exception handlers * aren't reachable by normal control flow so for the moment they're * considered alternate roots. * @return the Collection of the start block and any exception handlers. */ private Collection<IBasicBlock> getRoots() { Collection<IBasicBlock> roots = new ArrayList<IBasicBlock>(); roots.add(this.startBlock); roots.addAll(this.catchTargets); return roots; } /** * Remove an unreachable block from the CFG. * @param b - the Block to remove. * @pre b must be unreachable. */ public void removeUnreachableBlock(IBasicBlock b) { assert(!isReachable(b)); // The CFG is naive about reachability // of exception-handling targets, so // this procedure may remove blocks // that were formerly "reachable." boolean removedFormerlyReachable = false; // Edit any exception-handlers that reference b. for ( ExceptionInfo ex: this.mbi.getExceptions() ) { if ( b.equals(getBlock(ex.getFrom())) ) { if ( b.equals(getBlock(ex.getTo())) ) { // This exception-handler is now dead. ex.setLive(false); this.catchTargets.remove(getBlock(ex.getTarget())); removedFormerlyReachable = true; } else { // Move the mapping of the From label // to the next block in the covered region. int bIdx = this.blocks.indexOf(b); assert bIdx >= 0 && bIdx < this.blocks.size(); this.blocksByLabel.put(ex.getFrom(), this.blocks.get(bIdx+1)); } } else if ( b.equals(getBlock(ex.getTo())) ) { if ( b.equals(getBlock(ex.getFrom())) ) { // This exception-handler is now dead. ex.setLive(false); this.catchTargets.remove(getBlock(ex.getTarget())); removedFormerlyReachable = true; } else { // Move the mapping of the To label // to the previous block in the covered region. int bIdx = this.blocks.indexOf(b); assert bIdx >= 1 && bIdx < this.blocks.size(); this.blocksByLabel.put(ex.getTo(), this.blocks.get(bIdx-1)); } } } if ( removedFormerlyReachable ) { // The dominator tree needs to be recomputed. this.dominatorTree = null; } // Finally, remove the block itself. this.blocks.remove(b); } /** * Is the given Block reachable? * @param b - the block of interest. * @return true if a path exists from * any "entry" block to b. */ public boolean isReachable(IBasicBlock b) { return getDominatorTree().topologicalTraversal().contains(b); } /** * Find the closest matching line number to the start of a block. * @param b the block of interest. * @return any initial debugline within the block, or the nearest * debugline in the preceeding (entry-order) blocks. */ public int findLineNumber(IBasicBlock b) { return findLineNumber(b, 0, SearchDirection.Forward); } /** * Find the nearest debugline instruction preceeding the given * (Block,offset) position and fetch its line number. * @param b - the Block of interest. * @param initialOffset - the start offset in the block. * @return the closest debugline instruction's line number, * or -1 if not found. */ public int findLineNumber(IBasicBlock b, int initialOffset) { return findLineNumber(b, b.size()-1, SearchDirection.Backward); } /** * Find the nearest debugline instruction preceeding the given * (Block,offset) position and fetch its line number. * @param b - the Block of interest. * @param initialOffset - the start offset in the block. * @param initialDirection Search the first block Forward or Backward. * @return the closest debugline instruction's line number, * or -1 if not found. */ private int findLineNumber(IBasicBlock b, int initialOffset, SearchDirection initialDirection) { assert(this.blocks.contains(b)); // Start searching in the block given; the offset may // be a pseudo-offset that specifies an initial forward // search through non-executable instructions. Instruction result = searchBlock(b, ABCConstants.OP_debugline, initialOffset, initialDirection); if ( result != null ) { // Found a good line number match; return it as a zero-based offset. return result.getImmediate() - 1; } else { // Search backwards through the instruction stream; bump any line // number found during this search since the dead block is probably // on the next line. for ( int i = this.blocks.indexOf(b) - 1; i >= 0 && result == null; i-- ) { IBasicBlock candidate = this.blocks.get(i); result = searchBlock( candidate, ABCConstants.OP_debugline, candidate.size() - 1, SearchDirection.Backward); } } return result != null? result.getImmediate(): -1; } /** * Find the nearest debugfile instruction to the start of * the given block and fetch its source path. * @param b - the Block of interest. * @return the closest debugfile instruction's source path, * or null if not found. */ public String findSourcePath(IBasicBlock b) { return findSourcePath(b, 0, SearchDirection.Forward); } /** * Find the nearest instruction preceeding the given * (Block,offset) position and fetch its source path. * @param b - the Block of interest. * @param initialOffset - the start offset in the block. * @return the closest debugfile instruction's source path, * or null if not found. */ public String findSourcePath(IBasicBlock b, int initialOffset) { return findSourcePath(b, initialOffset, SearchDirection.Backward); } /** * Find the nearest debugline instruction to the given (Block, offset) * position and fetch its source path. * @param b the block of interest. * @param initialOffset the start offset in the block. * @param initialDirection Search the first block Forward or Backward. * @return the closest debugfile instruction's source path, * or null if not found. */ private String findSourcePath(IBasicBlock b, int initialOffset, SearchDirection initialDirection) { assert(this.blocks.contains(b)); Instruction result = searchBlock(b, ABCConstants.OP_debugfile, initialOffset, initialDirection); // Search backwards through the instruction stream if necessary. if ( result == null ) { for ( int i = this.blocks.indexOf(b) - 1; i >= 0 && result == null; i-- ) { IBasicBlock candidate = this.blocks.get(i); result = searchBlock(candidate, ABCConstants.OP_debugfile, candidate.size() - 1, SearchDirection.Backward); } } if (result == null) return null; // The debug filename can sometimes be in the format: sourcepath;package;filename so // need to translate that back to an absolute path by replacing the ;'s with system // path separators, making sure to normailize path separators. String debugFilename = result.getOperand(0).toString(); char otherSeparator = File.separatorChar == '/' ? '\\' : '/'; debugFilename = debugFilename.replace(otherSeparator, File.separatorChar); return debugFilename.replace(';', File.separatorChar); } /** * Find an instruction in the given block, working * forwards or backwards from the specified offset. * @param b the block of interest. * @param opcode the opcode of the desired Instruction. * @param initialOffset the offset at which to begin searching. * @param direction Forward or Backward. * @return the first instruction encountered with the specified * opcode, or null if none found or the offset is out of range. */ private Instruction searchBlock(IBasicBlock b, int opcode, int initialOffset, SearchDirection direction) { Instruction result = null; int blockSize = b.size(); assert initialOffset >= 0 && initialOffset < blockSize: String.format("invalid initialOffset %d", initialOffset); int offset = initialOffset; while ( result == null && offset >= 0 && offset < blockSize ) { Instruction candidate = b.get(offset); if ( candidate.getOpcode() == opcode ) result = candidate; else if ( direction == SearchDirection.Forward ) offset++; else offset--; } return result; } }