WALA/com.ibm.wala.dalvik/src/com/ibm/wala/dalvik/classLoader/DexCFG.java

714 lines
27 KiB
Java

/*******************************************************************************
* Copyright (c) 2002 - 2006, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Steve Suh <suhsteve@gmail.com> - Modified to handle dalvik instructions
*******************************************************************************/
package com.ibm.wala.dalvik.classLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import com.ibm.wala.cfg.AbstractCFG;
import com.ibm.wala.cfg.BytecodeCFG;
import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.classLoader.BytecodeLanguage;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.JavaLanguage;
import com.ibm.wala.dalvik.dex.instructions.Instruction;
import com.ibm.wala.dalvik.dex.instructions.Invoke;
import com.ibm.wala.dalvik.dex.instructions.Return;
import com.ibm.wala.dalvik.dex.instructions.Throw;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.shrikeBT.ExceptionHandler;
import com.ibm.wala.shrikeCT.InvalidClassFileException;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.ArrayIterator;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.graph.impl.NodeWithNumber;
import com.ibm.wala.util.intset.BitVector;
import com.ibm.wala.util.shrike.ShrikeUtil;
import com.ibm.wala.util.warnings.Warning;
import com.ibm.wala.util.warnings.Warnings;
public class DexCFG extends AbstractCFG<Instruction, DexCFG.BasicBlock> implements BytecodeCFG {
private static final boolean DEBUG = false;
private int[] instruction2Block;
private final DexIMethod dexMethod;
private final Context context;
private static int totalEdges = 0;
/**
* Cache this here for efficiency
*/
private final int hashBase;
/**
* Set of Shrike {@link ExceptionHandler} objects that cover this method.
*/
final private Set<ExceptionHandler> exceptionHandlers = HashSetFactory.make(10);
protected DexCFG(DexIMethod method, Context context) throws IllegalArgumentException {
super(method);
if (method == null) {
throw new IllegalArgumentException("method cannot be null");
}
this.dexMethod = method;
this.context = context;
this.hashBase = method.hashCode() * 9967;
makeBasicBlocks();
init();
computeI2BMapping();
computeEdges();
}
public DexIMethod getDexMethod() {
return dexMethod;
}
public static int getTotalEdges() {
return totalEdges;
}
@Override
public int hashCode() {
return 9511 * getMethod().hashCode();
}
@Override
public boolean equals(Object o) {
//return (o instanceof DexCFG) && getMethod().equals(((DexCFG) o).getMethod());
return o instanceof DexCFG && ((DexCFG)o).dexMethod.equals(dexMethod) && ((DexCFG)o).context.equals(context);
}
public Instruction[] getInstructions() {
return (Instruction[])dexMethod.getDexInstructions();
}
/**
* Compute a mapping from instruction to basic block. Also, compute the blocks that end with a 'normal' return.
*/
private void computeI2BMapping() {
instruction2Block = new int[getInstructions().length];
for (Iterator<BasicBlock> it = iterator(); it.hasNext();) {
final BasicBlock b = it.next();
for (int j = b.getFirstInstructionIndex(); j <= b.getLastInstructionIndex(); j++) {
instruction2Block[j] = getNumber(b);
}
}
}
/**
* Compute outgoing edges in the control flow graph.
*/
private void computeEdges() {
for (Iterator<BasicBlock> it = iterator(); it.hasNext();) {
BasicBlock b = it.next();
if (b.equals(exit())) {
continue;
} else if (b.equals(entry())) {
BasicBlock bb0 = getBlockForInstruction(0);
assert bb0 != null;
addNormalEdge(b, bb0);
} else {
b.computeOutgoingEdges();
}
}
}
private void makeBasicBlocks() {
ExceptionHandler[][] handlers;
try {
handlers = dexMethod.getHandlers();
} catch (InvalidClassFileException e) {
e.printStackTrace();
Assertions.UNREACHABLE();
handlers = null;
}
boolean[] r = new boolean[getInstructions().length];
boolean[] catchers = new boolean[getInstructions().length];
// we initially start with both the entry and exit block.
@SuppressWarnings("unused")
int blockCount = 2;
// Compute r so r[i] == true iff instruction i begins a basic block.
// While doing so count the number of blocks.
r[0] = true;
Instruction[] instructions = getInstructions();
for (int i = 0; i < instructions.length; i++) {
int[] targets = instructions[i].getBranchTargets();
// if there are any targets, then break the basic block here.
// also break the basic block after a return
if (targets.length > 0 || !instructions[i].isFallThrough()) {
if (i + 1 < instructions.length && !r[i + 1]) {
r[i + 1] = true;
blockCount++;
}
}
for (int j = 0; j < targets.length; j++) {
if (!r[targets[j]]) {
r[targets[j]] = true;
blockCount++;
}
}
if (instructions[i].isPEI()) {
ExceptionHandler[] hs = handlers[i];
// break the basic block here.
if (i + 1 < instructions.length && !r[i + 1]) {
r[i + 1] = true;
blockCount++;
}
if (hs != null && hs.length > 0) {
for (int j = 0; j < hs.length; j++) {
exceptionHandlers.add(hs[j]);
if (!r[hs[j].getHandler()]) {
// we have not discovered the catch block yet.
// form a new basic block
r[hs[j].getHandler()] = true;
blockCount++;
}
catchers[hs[j].getHandler()] = true;
}
}
}
}
BasicBlock entry = new BasicBlock(-1);
addNode(entry);
int j = 1;
for (int i = 0; i < r.length; i++) {
if (r[i]) {
BasicBlock b = new BasicBlock(i);
addNode(b);
if (catchers[i]) {
setCatchBlock(j);
}
j++;
}
}
BasicBlock exit = new BasicBlock(-1);
addNode(exit);
}
/**
* Return an instruction's basic block in the CFG given the index of the instruction in the CFG's instruction array.
*/
public BasicBlock getBlockForInstruction(int index) {
return getNode(instruction2Block[index]);
}
public final class BasicBlock extends NodeWithNumber implements IBasicBlock<Instruction> {
/**
* The number of the ShrikeBT instruction that begins this block.
*/
final private int startIndex;
public BasicBlock(int startIndex) {
this.startIndex = startIndex;
}
public boolean isCatchBlock() {
return DexCFG.this.isCatchBlock(getNumber());
}
private void computeOutgoingEdges() {
if (DEBUG) {
System.err.println("Block " + this + ": computeOutgoingEdges()");
}
Instruction last = getInstructions()[getLastInstructionIndex()];
int[] targets = last.getBranchTargets();
for (int i = 0; i < targets.length; i++) {
BasicBlock b = getBlockForInstruction(targets[i]);
addNormalEdgeTo(b);
}
addExceptionalEdges(last);
if (last.isFallThrough()) {
BasicBlock next = getNode(getNumber() + 1);
addNormalEdgeTo(next);
}
if (last instanceof Return) {
// link each return instruction to the exit block.
BasicBlock exit = exit();
addNormalEdgeTo(exit);
}
}
/**
* Add any exceptional edges generated by the last instruction in a basic block.
*
* @param last the last instruction in a basic block.
*/
protected void addExceptionalEdges(Instruction last) {
IClassHierarchy cha = getMethod().getClassHierarchy();
if (last.isPEI()) {
Collection<TypeReference> exceptionTypes = null;
boolean goToAllHandlers = false;
ExceptionHandler[] hs = getExceptionHandlers();
if (last instanceof Throw) {
// this class does not have the type information needed
// to determine what the athrow throws. So, add an
// edge to all reachable handlers. Better information can
// be obtained later with SSA type propagation.
// TODO: consider pruning to only the exception types that
// this method either catches or allocates, since these are
// the only types that can flow to an athrow.
goToAllHandlers = true;
} else {
if (hs != null && hs.length > 0) {
IClassLoader loader = getMethod().getDeclaringClass().getClassLoader();
BytecodeLanguage l = (BytecodeLanguage) loader.getLanguage();
//exceptionTypes = l.getImplicitExceptionTypes(last);
exceptionTypes = getImplicitExceptionTypes(last);
if (last instanceof Invoke) {
Invoke call = (Invoke) last;
exceptionTypes = HashSetFactory.make(exceptionTypes);
MethodReference target = MethodReference.findOrCreate(l, loader.getReference(), call.clazzName, call
.methodName, call.descriptor);
try {
exceptionTypes.addAll(l.inferInvokeExceptions(target, cha));
} catch (InvalidClassFileException e) {
e.printStackTrace();
Assertions.UNREACHABLE();
}
}
}
}
if (hs != null && hs.length > 0) {
// found a handler for this PEI
// create a mutable copy
if (!goToAllHandlers) {
exceptionTypes = HashSetFactory.make(exceptionTypes);
}
for (int j = 0; j < hs.length; j++) {
if (DEBUG) {
System.err.println(" handler " + hs[j]);
}
BasicBlock b = getBlockForInstruction(hs[j].getHandler());
if (DEBUG) {
System.err.println(" target " + b);
}
if (goToAllHandlers) {
// add an edge to the catch block.
if (DEBUG) {
System.err.println(" gotoAllHandlers " + b);
}
addExceptionalEdgeTo(b);
} else {
TypeReference caughtException = null;
if (hs[j].getCatchClass() != null) {
ClassLoaderReference loader = DexCFG.this.getMethod().getDeclaringClass().getReference().getClassLoader();
caughtException = ShrikeUtil.makeTypeReference(loader, hs[j].getCatchClass());
if (DEBUG) {
System.err.println(" caughtException " + caughtException);
}
IClass caughtClass = cha.lookupClass(caughtException);
if (caughtClass == null) {
// conservatively add the edge, and raise a warning
addExceptionalEdgeTo(b);
Warnings.add(FailedExceptionResolutionWarning.create(caughtException));
// null out caughtException, to avoid attempting to process it
caughtException = null;
}
} else {
if (DEBUG) {
System.err.println(" catchClass() == null");
}
// hs[j].getCatchClass() == null.
// this means that the handler catches all exceptions.
// add the edge and null out all types
if (!exceptionTypes.isEmpty()) {
addExceptionalEdgeTo(b);
exceptionTypes.clear();
caughtException = null;
}
}
if (caughtException != null) {
IClass caughtClass = cha.lookupClass(caughtException);
// the set "caught" should be the set of exceptions that MUST
// have been caught by the handlers in scope
ArrayList<TypeReference> caught = new ArrayList<TypeReference>(exceptionTypes.size());
// check if we should add an edge to the catch block.
for (TypeReference t : exceptionTypes) {
if (t != null) {
IClass klass = cha.lookupClass(t);
if (klass == null) {
Warnings.add(FailedExceptionResolutionWarning.create(caughtException));
// conservatively add an edge
addExceptionalEdgeTo(b);
} else {
boolean subtype1 = cha.isSubclassOf(klass, caughtClass);
if (subtype1 || cha.isSubclassOf(caughtClass, klass)) {
// add the edge and null out the type from the array
addExceptionalEdgeTo(b);
if (subtype1) {
caught.add(t);
}
}
}
}
}
exceptionTypes.removeAll(caught);
}
}
}
// if needed, add an edge to the exit block.
if (exceptionTypes == null || !exceptionTypes.isEmpty()) {
BasicBlock exit = exit();
addExceptionalEdgeTo(exit);
}
} else {
// found no handler for this PEI ... link to the exit block.
BasicBlock exit = exit();
addExceptionalEdgeTo(exit);
}
}
}
/**
* @param pei a potentially-excepting instruction
* @return the exception types that pei may throw, independent of the class hierarchy. null if none.
*
* Notes
* <ul>
* <li>this method will <em>NOT</em> return the exception type explicitly thrown by an athrow
* <li>this method will <em>NOT</em> return the exception types that a called method may throw
* <li>this method ignores OutOfMemoryError
* <li>this method ignores linkage errors
* <li>this method ignores IllegalMonitorState exceptions
* </ul>
*
* @throws IllegalArgumentException if pei is null
*/
public Collection<TypeReference> getImplicitExceptionTypes(Instruction pei) {
if (pei == null) {
throw new IllegalArgumentException("pei is null");
}
switch (((Instruction) pei).getOpcode()) {
//TODO: Make sure all the important cases and exceptions are covered.
case AGET:
case AGET_WIDE:
case AGET_OBJECT:
case AGET_BOOLEAN:
case AGET_BYTE:
case AGET_CHAR:
case AGET_SHORT:
// case OP_iaload:
// case OP_laload:
// case OP_faload:
// case OP_daload:
// case OP_aaload:
// case OP_baload:
// case OP_caload:
// case OP_saload:
case APUT:
case APUT_WIDE:
// case APUT_OBJECT:
case APUT_BOOLEAN:
case APUT_BYTE:
case APUT_CHAR:
case APUT_SHORT:
// case OP_iastore:
// case OP_lastore:
// case OP_fastore:
// case OP_dastore:
// case OP_bastore:
// case OP_castore:
// case OP_sastore:
return JavaLanguage.getArrayAccessExceptions();
case APUT_OBJECT:
//case OP_aastore:
return JavaLanguage.getAaStoreExceptions();
case IGET:
case IGET_WIDE:
case IGET_OBJECT:
case IGET_BOOLEAN:
case IGET_BYTE:
case IGET_CHAR:
case IGET_SHORT:
// case OP_getfield:
case IPUT:
case IPUT_WIDE:
case IPUT_OBJECT:
case IPUT_BOOLEAN:
case IPUT_BYTE:
case IPUT_CHAR:
case IPUT_SHORT:
// case OP_putfield:
//Shrike imp does not include the static invoke calls, so likewise will do the same
case INVOKE_VIRTUAL:
case INVOKE_SUPER:
case INVOKE_DIRECT:
//case INVOKE_STATIC:
case INVOKE_INTERFACE:
case INVOKE_VIRTUAL_RANGE:
case INVOKE_SUPER_RANGE:
case INVOKE_DIRECT_RANGE:
//case INVOKE_STATIC_RANGE:
case INVOKE_INTERFACE_RANGE:
// case OP_invokevirtual:
// case OP_invokespecial:
// case OP_invokeinterface:
return JavaLanguage.getNullPointerException();
case DIV_INT:
case DIV_INT_2ADDR:
case DIV_INT_LIT16:
case DIV_INT_LIT8:
case REM_INT:
case REM_INT_2ADDR:
case REM_INT_LIT16:
case REM_INT_LIT8:
case DIV_LONG:
case DIV_LONG_2ADDR:
case REM_LONG:
case REM_LONG_2ADDR:
// case OP_idiv:
// case OP_irem:
// case OP_ldiv:
// case OP_lrem:
return JavaLanguage.getArithmeticException();
case NEW_INSTANCE:
//case OP_new:
return JavaLanguage.getNewScalarExceptions();
case NEW_ARRAY:
case FILLED_NEW_ARRAY:
case FILLED_NEW_ARRAY_RANGE:
// case OP_newarray:
// case OP_anewarray:
// case OP_multianewarray:
return JavaLanguage.getNewArrayExceptions();
case ARRAY_LENGTH:
// case OP_arraylength:
return JavaLanguage.getNullPointerException();
case THROW:
// case OP_athrow:
// N.B: the caller must handle the explicitly-thrown exception
return JavaLanguage.getNullPointerException();
case CHECK_CAST:
// case OP_checkcast:
return JavaLanguage.getClassCastException();
case MONITOR_ENTER:
case MONITOR_EXIT:
// case OP_monitorenter:
// case OP_monitorexit:
// we're currently ignoring MonitorStateExceptions, since J2EE stuff
// should be
// logically single-threaded
return JavaLanguage.getNullPointerException();
//I Don't think dalvik has to worry about this?
// case OP_ldc_w:
// if (((ConstantInstruction) pei).getType().equals(TYPE_Class))
// return JavaLanguage.getClassNotFoundException();
// else
// return null;
case SGET:
case SGET_BOOLEAN:
case SGET_BYTE:
case SGET_CHAR:
case SGET_OBJECT:
case SGET_SHORT:
case SGET_WIDE:
case SPUT:
case SPUT_BOOLEAN:
case SPUT_BYTE:
case SPUT_CHAR:
case SPUT_OBJECT:
case SPUT_SHORT:
case SPUT_WIDE:
// case OP_getstatic:
// case OP_putstatic:
return JavaLanguage.getExceptionInInitializerError();
default:
return Collections.emptySet();
}
}
private ExceptionHandler[] getExceptionHandlers() {
ExceptionHandler[][] handlers;
try {
handlers = dexMethod.getHandlers();
} catch (InvalidClassFileException e) {
e.printStackTrace();
Assertions.UNREACHABLE();
handlers = null;
}
ExceptionHandler[] hs = handlers[getLastInstructionIndex()];
return hs;
}
private void addNormalEdgeTo(BasicBlock b) {
totalEdges++;
addNormalEdge(this, b);
}
private void addExceptionalEdgeTo(BasicBlock b) {
totalEdges++;
addExceptionalEdge(this, b);
}
public int getLastInstructionIndex() {
if (this == entry() || this == exit()) {
// these are the special end blocks
return -2;
}
if (getNumber() == (getMaxNumber() - 1)) {
// this is the last non-exit block
return getInstructions().length - 1;
} else {
BasicBlock next = getNode(getNumber() + 1);
return next.getFirstInstructionIndex() - 1;
}
}
public int getFirstInstructionIndex() {
return startIndex;
}
@Override
public String toString() {
return "BB[Dex]" + getNumber() + " - " + dexMethod.getDeclaringClass().getReference().getName() + "." + dexMethod.getName();
}
/*
* @see com.ibm.wala.cfg.BasicBlock#isExitBlock()
*/
public boolean isExitBlock() {
return this == DexCFG.this.exit();
}
/*
* @see com.ibm.wala.cfg.BasicBlock#isEntryBlock()
*/
public boolean isEntryBlock() {
return this == DexCFG.this.entry();
}
/*
* @see com.ibm.wala.cfg.BasicBlock#getMethod()
*/
public IMethod getMethod() {
return DexCFG.this.getMethod();
}
@Override
public int hashCode() {
return hashBase + getNumber();
}
@Override
public boolean equals(Object o) {
return (o instanceof BasicBlock) && ((BasicBlock) o).getMethod().equals(getMethod())
&& ((BasicBlock) o).getNumber() == getNumber();
}
/*
* @see com.ibm.wala.cfg.BasicBlock#getNumber()
*/
public int getNumber() {
return getGraphNodeId();
}
public Iterator<Instruction> iterator() {
return new ArrayIterator<Instruction>(getInstructions(), getFirstInstructionIndex(), getLastInstructionIndex());
}
}
@Override
public String toString() {
StringBuffer s = new StringBuffer("");
BitVector catches = this.getCatchBlocks();
for (Iterator<BasicBlock> it = iterator(); it.hasNext();) {
BasicBlock bb = it.next();
s.append("BB").append(getNumber(bb));
if (catches.contains(bb.getNumber())) {
s.append("<Handler>");
}
s.append("\n");
for (int j = bb.getFirstInstructionIndex(); j <= bb.getLastInstructionIndex(); j++) {
s.append(" ").append(j).append(" ").append(getInstructions()[j]).append("\n");
}
Iterator<BasicBlock> succNodes = getSuccNodes(bb);
while (succNodes.hasNext()) {
s.append(" -> BB").append(getNumber(succNodes.next())).append("\n");
}
}
return s.toString();
}
public int getMaxStackHeight() {
return dexMethod.getMaxStackHeight();
}
public int getMaxLocals() {
return dexMethod.getMaxLocals();
}
public Set<ExceptionHandler> getExceptionHandlers() {
return exceptionHandlers;
}
/*
* @see com.ibm.wala.cfg.ControlFlowGraph#getProgramCounter(int)
*/
public int getProgramCounter(int index) {
return dexMethod.getAddressFromIndex(index);
// return dexMethod.getInstructionFromIndex(index).pc;
}
/**
* A warning when we fail to resolve the type of an exception
*/
private static class FailedExceptionResolutionWarning extends Warning {
final TypeReference T;
FailedExceptionResolutionWarning(TypeReference T) {
super(Warning.MODERATE);
this.T = T;
}
@Override
public String getMsg() {
return getClass().toString() + " : " + T;
}
public static FailedExceptionResolutionWarning create(TypeReference T) {
return new FailedExceptionResolutionWarning(T);
}
}
}