/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package io.github.proxyhotswap.javassist.bytecode.analysis;
import io.github.proxyhotswap.javassist.CtClass;
import io.github.proxyhotswap.javassist.CtMethod;
import io.github.proxyhotswap.javassist.bytecode.BadBytecode;
import io.github.proxyhotswap.javassist.bytecode.MethodInfo;
import io.github.proxyhotswap.javassist.bytecode.stackmap.BasicBlock;
import java.util.ArrayList;
/**
* Represents the control flow graph of a given method.
*
* <p>To obtain the control flow graph, do the following:</p>
*
* <pre>CtMethod m = ...
* ControlFlow cf = new ControlFlow(m);
* Block[] blocks = cf.basicBlocks();
* </pre>
*
* <p><code>blocks</code> is an array of basic blocks in
* that method body.</p>
*
* @see io.github.proxyhotswap.javassist.CtMethod
* @see Block
* @see Frame
* @see Analyzer
* @author Shigeru Chiba
* @since 3.16
*/
public class ControlFlow {
private CtClass clazz;
private MethodInfo methodInfo;
private Block[] basicBlocks;
private Frame[] frames;
/**
* Constructs a control-flow analyzer for the given method.
*/
public ControlFlow(CtMethod method) throws BadBytecode {
this(method.getDeclaringClass(), method.getMethodInfo2());
}
/**
* Constructs a control-flow analyzer.
*/
public ControlFlow(CtClass ctclazz, MethodInfo minfo) throws BadBytecode {
clazz = ctclazz;
methodInfo = minfo;
frames = null;
basicBlocks = (Block[])new BasicBlock.Maker() {
protected BasicBlock makeBlock(int pos) {
return new Block(pos, methodInfo);
}
protected BasicBlock[] makeArray(int size) {
return new Block[size];
}
}.make(minfo);
int size = basicBlocks.length;
int[] counters = new int[size];
for (int i = 0; i < size; i++) {
Block b = basicBlocks[i];
b.index = i;
b.entrances = new Block[b.incomings()];
counters[i] = 0;
}
for (int i = 0; i < size; i++) {
Block b = basicBlocks[i];
for (int k = 0; k < b.exits(); k++) {
Block e = b.exit(k);
e.entrances[counters[e.index]++] = b;
}
ControlFlow.Catcher[] catchers = b.catchers();
for (int k = 0; k < catchers.length; k++) {
Block catchBlock = catchers[k].node;
catchBlock.entrances[counters[catchBlock.index]++] = b;
}
}
}
/**
* Returns all the basic blocks in the method body.
*/
public Block[] basicBlocks() {
return basicBlocks;
}
/**
* Returns the types of the local variables and stack frame entries
* available at the given position. If the byte at the position is
* not the first byte of an instruction, then this method returns
* null.
*
* @param pos the position.
*/
public Frame frameAt(int pos) throws BadBytecode {
if (frames == null)
frames = new Analyzer().analyze(clazz, methodInfo);
return frames[pos];
}
/**
* Constructs a dominator tree. This method returns an array of
* the tree nodes. The first element of the array is the root
* of the tree.
*
* <p> The order of the elements is the same as that
* of the elements in the <code>Block</code> array returned
* by the <code>basicBlocks</code>
* method. If a <code>Block</code> object is at the i-th position
* in the <code>Block</code> array, then
* the <code>Node</code> object referring to that
* <code>Block</code> object is at the i-th position in the
* array returned by this method.
* For every array element <code>node</code>, its index in the
* array is equivalent to <code>node.block().index()</code>.
*
* @return an array of the tree nodes, or null if the method is abstract.
* @see Node#block()
* @see Block#index()
*/
public Node[] dominatorTree() {
int size = basicBlocks.length;
if (size == 0)
return null;
Node[] nodes = new Node[size];
boolean[] visited = new boolean[size];
int[] distance = new int[size];
for (int i = 0; i < size; i++) {
nodes[i] = new Node(basicBlocks[i]);
visited[i] = false;
}
Access access = new Access(nodes) {
BasicBlock[] exits(Node n) { return n.block.getExit(); }
BasicBlock[] entrances(Node n) { return n.block.entrances; }
};
nodes[0].makeDepth1stTree(null, visited, 0, distance, access);
do {
for (int i = 0; i < size; i++)
visited[i] = false;
} while (nodes[0].makeDominatorTree(visited, distance, access));
Node.setChildren(nodes);
return nodes;
}
/**
* Constructs a post dominator tree. This method returns an array of
* the tree nodes. Note that the tree has multiple roots.
* The parent of the root nodes is null.
*
* <p> The order of the elements is the same as that
* of the elements in the <code>Block</code> array returned
* by the <code>basicBlocks</code>
* method. If a <code>Block</code> object is at the i-th position
* in the <code>Block</code> array, then
* the <code>Node</code> object referring to that
* <code>Block</code> object is at the i-th position in the
* array returned by this method.
* For every array element <code>node</code>, its index in the
* array is equivalent to <code>node.block().index()</code>.
*
* @return an array of the tree nodes, or null if the method is abstract.
* @see Node#block()
* @see Block#index()
*/
public Node[] postDominatorTree() {
int size = basicBlocks.length;
if (size == 0)
return null;
Node[] nodes = new Node[size];
boolean[] visited = new boolean[size];
int[] distance = new int[size];
for (int i = 0; i < size; i++) {
nodes[i] = new Node(basicBlocks[i]);
visited[i] = false;
}
Access access = new Access(nodes) {
BasicBlock[] exits(Node n) { return n.block.entrances; }
BasicBlock[] entrances(Node n) { return n.block.getExit(); }
};
int counter = 0;
for (int i = 0; i < size; i++)
if (nodes[i].block.exits() == 0)
counter = nodes[i].makeDepth1stTree(null, visited, counter, distance, access);
boolean changed;
do {
for (int i = 0; i < size; i++)
visited[i] = false;
changed = false;
for (int i = 0; i < size; i++)
if (nodes[i].block.exits() == 0)
if (nodes[i].makeDominatorTree(visited, distance, access))
changed = true;
} while (changed);
Node.setChildren(nodes);
return nodes;
}
/**
* Basic block.
* It is a sequence of contiguous instructions that do not contain
* jump/branch instructions except the last one.
* Since Java6 or later does not allow <code>JSR</code>,
* we deal with <code>JSR</code> as a non-branch instruction.
*/
public static class Block extends BasicBlock {
/**
* A field that can be freely used for storing extra data.
* A client program of this control-flow analyzer can append
* an additional attribute to a <code>Block</code> object.
* The Javassist library never accesses this field.
*/
public Object clientData = null;
int index;
MethodInfo method;
Block[] entrances;
Block(int pos, MethodInfo minfo) {
super(pos);
method = minfo;
}
protected void toString2(StringBuffer sbuf) {
super.toString2(sbuf);
sbuf.append(", incoming{");
for (int i = 0; i < entrances.length; i++)
sbuf.append(entrances[i].position).append(", ");
sbuf.append("}");
}
BasicBlock[] getExit() { return exit; }
/**
* Returns the position of this block in the array of
* basic blocks that the <code>basicBlocks</code> method
* returns.
*
* @see #basicBlocks()
*/
public int index() { return index; }
/**
* Returns the position of the first instruction
* in this block.
*/
public int position() { return position; }
/**
* Returns the length of this block.
*/
public int length() { return length; }
/**
* Returns the number of the control paths entering this block.
*/
public int incomings() { return incoming; }
/**
* Returns the block that the control may jump into this block from.
*/
public Block incoming(int n) {
return entrances[n];
}
/**
* Return the number of the blocks that may be executed
* after this block.
*/
public int exits() { return exit == null ? 0 : exit.length; }
/**
* Returns the n-th block that may be executed after this
* block.
*
* @param n an index in the array of exit blocks.
*/
public Block exit(int n) { return (Block)exit[n]; }
/**
* Returns catch clauses that will catch an exception thrown
* in this block.
*/
public Catcher[] catchers() {
ArrayList catchers = new ArrayList();
BasicBlock.Catch c = toCatch;
while (c != null) {
catchers.add(new Catcher(c));
c = c.next;
}
return (Catcher[])catchers.toArray(new Catcher[catchers.size()]);
}
}
static abstract class Access {
Node[] all;
Access(Node[] nodes) { all = nodes; }
Node node(BasicBlock b) { return all[((Block)b).index]; }
abstract BasicBlock[] exits(Node n);
abstract BasicBlock[] entrances(Node n);
}
/**
* A node of (post) dominator trees.
*/
public static class Node {
private Block block;
private Node parent;
private Node[] children;
Node(Block b) {
block = b;
parent = null;
}
/**
* Returns a <code>String</code> representation.
*/
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("Node[pos=").append(block().position());
sbuf.append(", parent=");
sbuf.append(parent == null ? "*" : Integer.toString(parent.block().position()));
sbuf.append(", children{");
for (int i = 0; i < children.length; i++)
sbuf.append(children[i].block().position()).append(", ");
sbuf.append("}]");
return sbuf.toString();
}
/**
* Returns the basic block indicated by this node.
*/
public Block block() { return block; }
/**
* Returns the parent of this node.
*/
public Node parent() { return parent; }
/**
* Returns the number of the children of this node.
*/
public int children() { return children.length; }
/**
* Returns the n-th child of this node.
*
* @param n an index in the array of children.
*/
public Node child(int n) { return children[n]; }
/*
* After executing this method, distance[] represents the post order of the tree nodes.
* It also represents distances from the root; a bigger number represents a shorter
* distance. parent is set to its parent in the depth first spanning tree.
*/
int makeDepth1stTree(Node caller, boolean[] visited, int counter, int[] distance, Access access) {
int index = block.index;
if (visited[index])
return counter;
visited[index] = true;
parent = caller;
BasicBlock[] exits = access.exits(this);
if (exits != null)
for (int i = 0; i < exits.length; i++) {
Node n = access.node(exits[i]);
counter = n.makeDepth1stTree(this, visited, counter, distance, access);
}
distance[index] = counter++;
return counter;
}
boolean makeDominatorTree(boolean[] visited, int[] distance, Access access) {
int index = block.index;
if (visited[index])
return false;
visited[index] = true;
boolean changed = false;
BasicBlock[] exits = access.exits(this);
if (exits != null)
for (int i = 0; i < exits.length; i++) {
Node n = access.node(exits[i]);
if (n.makeDominatorTree(visited, distance, access))
changed = true;
}
BasicBlock[] entrances = access.entrances(this);
if (entrances != null)
for (int i = 0; i < entrances.length; i++) {
if (parent != null) {
Node n = getAncestor(parent, access.node(entrances[i]), distance);
if (n != parent) {
parent = n;
changed = true;
}
}
}
return changed;
}
private static Node getAncestor(Node n1, Node n2, int[] distance) {
while (n1 != n2) {
if (distance[n1.block.index] < distance[n2.block.index])
n1 = n1.parent;
else
n2 = n2.parent;
if (n1 == null || n2 == null)
return null;
}
return n1;
}
private static void setChildren(Node[] all) {
int size = all.length;
int[] nchildren = new int[size];
for (int i = 0; i < size; i++)
nchildren[i] = 0;
for (int i = 0; i < size; i++) {
Node p = all[i].parent;
if (p != null)
nchildren[p.block.index]++;
}
for (int i = 0; i < size; i++)
all[i].children = new Node[nchildren[i]];
for (int i = 0; i < size; i++)
nchildren[i] = 0;
for (int i = 0; i < size; i++) {
Node n = all[i];
Node p = n.parent;
if (p != null)
p.children[nchildren[p.block.index]++] = n;
}
}
}
/**
* Represents a catch clause.
*/
public static class Catcher {
private Block node;
private int typeIndex;
Catcher(BasicBlock.Catch c) {
node = (Block)c.body;
typeIndex = c.typeIndex;
}
/**
* Returns the first block of the catch clause.
*/
public Block block() { return node; }
/**
* Returns the name of the exception type that
* this catch clause catches.
*/
public String type() {
if (typeIndex == 0)
return "java.lang.Throwable";
else
return node.method.getConstPool().getClassInfo(typeIndex);
}
}
}