/******************************************************************************* * Copyright (c) 2002,2006 IBM Corporation. * 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 *******************************************************************************/ package com.ibm.wala.shrikeBT.analysis; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import com.ibm.wala.shrikeBT.DupInstruction; import com.ibm.wala.shrikeBT.ExceptionHandler; import com.ibm.wala.shrikeBT.Instruction; import com.ibm.wala.shrikeBT.LoadInstruction; import com.ibm.wala.shrikeBT.MethodData; import com.ibm.wala.shrikeBT.StoreInstruction; import com.ibm.wala.shrikeBT.SwapInstruction; import com.ibm.wala.shrikeBT.Util; /** * @author roca */ public class Analyzer { // inputs protected boolean isStatic; protected String classType; protected String signature; protected Instruction[] instructions; protected ExceptionHandler[][] handlers; protected ClassHierarchyProvider hierarchy; // working protected int maxStack; protected int maxLocals; protected String[][] stacks; protected String[][] locals; protected int[] stackSizes; protected BitSet basicBlockStarts; protected int[][] backEdges; protected final static String[] noStrings = new String[0]; protected final static int[] noEdges = new int[0]; public Analyzer(boolean isStatic, String classType, String signature, Instruction[] instructions, ExceptionHandler[][] handlers) { this.classType = classType; this.isStatic = isStatic; this.signature = signature; this.instructions = instructions; this.handlers = handlers; } /** * Use class hierarchy information in 'h'. If this method is not called or h * provides only partial hierarchy information, the verifier behaves * optimistically. */ final public void setClassHierarchy(ClassHierarchyProvider h) { this.hierarchy = h; } private void addBackEdge(int from, int to) { int[] oldEdges = backEdges[from]; if (oldEdges == null) { backEdges[from] = new int[] { to }; } else if (oldEdges[oldEdges.length - 1] < 0) { int left = 1; int right = oldEdges.length - 1; while (true) { if (right - left < 2) { if (oldEdges[left] < 0) { break; } else { if (oldEdges[right] >= 0) throw new Error("Failed binary search"); left = right; break; } } else { int mid = (left + right) / 2; if (oldEdges[mid] < 0) { right = mid; } else { left = mid + 1; } } } oldEdges[left] = to; } else { int[] newEdges = new int[oldEdges.length * 2]; System.arraycopy(oldEdges, 0, newEdges, 0, oldEdges.length); newEdges[oldEdges.length] = to; for (int i = oldEdges.length + 1; i < newEdges.length; i++) { newEdges[i] = -1; } backEdges[from] = newEdges; } } final public int[][] getBackEdges() { if (backEdges != null) { return backEdges; } backEdges = new int[instructions.length][]; for (int i = 0; i < instructions.length; i++) { Instruction instr = instructions[i]; int[] targets = instr.getBranchTargets(); for (int j = 0; j < targets.length; j++) { addBackEdge(targets[j], i); } ExceptionHandler[] hs = handlers[i]; for (int j = 0; j < hs.length; j++) { addBackEdge(hs[j].getHandler(), i); } } for (int i = 0; i < backEdges.length; i++) { int[] back = backEdges[i]; if (back == null) { backEdges[i] = noEdges; } else if (back[back.length - 1] < 0) { int j = back.length; while (back[j - 1] < 0) { j--; } int[] newBack = new int[j]; System.arraycopy(back, 0, newBack, 0, newBack.length); backEdges[i] = newBack; } } return backEdges; } final public boolean isSubtypeOf(String t1, String t2) { return ClassHierarchy.isSubtypeOf(hierarchy, t1, t2) != ClassHierarchy.NO; } final public String findCommonSupertype(String t1, String t2) { return ClassHierarchy.findCommonSupertype(hierarchy, t1, t2); } final public BitSet getBasicBlockStarts() { if (basicBlockStarts != null) { return basicBlockStarts; } BitSet r = new BitSet(instructions.length); r.set(0); for (int i = 0; i < instructions.length; i++) { int[] targets = instructions[i].getBranchTargets(); for (int j = 0; j < targets.length; j++) { r.set(targets[j]); } } for (int i = 0; i < handlers.length; i++) { ExceptionHandler[] hs = handlers[i]; if (hs != null) { for (int j = 0; j < hs.length; j++) { r.set(hs[j].getHandler()); } } } basicBlockStarts = r; return r; } final public Instruction[] getInstructions() { return instructions; } private void getReachableRecursive(int from, BitSet reachable, boolean followHandlers, BitSet mask) { while (true) { // stop if we've already visited this instruction or if we've gone outside // the mask if (reachable.get(from) || (mask != null && !mask.get(from))) { return; } reachable.set(from); Instruction instr = instructions[from]; int[] targets = instr.getBranchTargets(); for (int i = 0; i < targets.length; i++) { getReachableRecursive(targets[i], reachable, followHandlers, mask); } if (followHandlers) { ExceptionHandler[] hs = handlers[from]; for (int i = 0; i < hs.length; i++) { getReachableRecursive(hs[i].getHandler(), reachable, followHandlers, mask); } } if (instr.isFallThrough()) { ++from; continue; } break; } } final public BitSet getReachableFrom(int from) { return getReachableFrom(from, true, null); } final public void getReachableFromUpdate(int from, BitSet reachable, boolean followHandlers, BitSet mask) { if (reachable == null) { throw new IllegalArgumentException("reachable is null"); } reachable.clear(); getReachableRecursive(from, reachable, followHandlers, mask); } final public BitSet getReachableFrom(int from, boolean followHandlers, BitSet mask) { BitSet reachable = new BitSet(); getReachableRecursive(from, reachable, followHandlers, mask); return reachable; } private void getReachingRecursive(int to, BitSet reaching, BitSet mask) { while (true) { // stop if we've already visited this instruction or if we've gone outside // the mask if (reaching.get(to) || (mask != null && !mask.get(to))) { return; } reaching.set(to); int[] targets = backEdges[to]; for (int i = 0; i < targets.length; i++) { getReachingRecursive(targets[i], reaching, mask); } if (to > 0 && instructions[to - 1].isFallThrough()) { --to; continue; } break; } } private void getReachingBase(int to, BitSet reaching, BitSet mask) { int[] targets = backEdges[to]; for (int i = 0; i < targets.length; i++) { getReachingRecursive(targets[i], reaching, mask); } if (to > 0 && instructions[to - 1].isFallThrough()) { getReachingRecursive(to - 1, reaching, mask); } } final public void getReachingToUpdate(int to, BitSet reaching, BitSet mask) { if (reaching == null) { throw new IllegalArgumentException("reaching is null"); } getBackEdges(); reaching.clear(); getReachingBase(to, reaching, mask); } final public BitSet getReachingTo(int to, BitSet mask) { getBackEdges(); BitSet reaching = new BitSet(); getReachingBase(to, reaching, mask); return reaching; } final public BitSet getReachingTo(int to) { return getReachingTo(to, null); } private void computeStackSizesAt(int[] stackSizes, int i, int size) throws FailureException { while (true) { if (stackSizes[i] >= 0) { if (size != stackSizes[i]) { throw new FailureException(i, "Stack size mismatch", null); } return; } stackSizes[i] = size; Instruction instr = instructions[i]; if (instr instanceof DupInstruction) { size += ((DupInstruction) instr).getSize(); } else if (instr instanceof SwapInstruction) { } else { size -= instr.getPoppedCount(); if (instr.getPushedWordSize() > 0) { size++; } } int[] targets = instr.getBranchTargets(); for (int j = 0; j < targets.length; j++) { computeStackSizesAt(stackSizes, targets[j], size); } ExceptionHandler[] hs = handlers[i]; for (int j = 0; j < hs.length; j++) { computeStackSizesAt(stackSizes, hs[j].getHandler(), 1); } if (!instr.isFallThrough()) { return; } i++; } } /** * This exception is thrown by verify() when it fails. */ public static final class FailureException extends Exception { private static final long serialVersionUID = -7663520961403117526L; private int offset; private String reason; private List path; FailureException(int offset, String reason, List path) { super(reason + " at offset " + offset); this.offset = offset; this.reason = reason; this.path = path; } /** * @return the index of the Instruction which failed to verify */ public int getOffset() { return offset; } /** * @return a description of the reason why verification failed */ public String getReason() { return reason; } /** * @return a list of PathElements describing how the type that caused the * error was propagated from its origin to the point of the error */ public List getPath() { return path; } void setPath(List path) { this.path = path; } /** * Print the path to the given stream, if there is one. */ public void printPath(Writer w) throws IOException { if (path != null) { for (int i = 0; i < path.size(); i++) { PathElement elem = path.get(i); String[] stack = elem.stack; String[] locals = elem.locals; w.write("Offset " + elem.index + ": ["); for (int j = 0; j < stack.length; j++) { if (j > 0) { w.write(","); } w.write(stack[j]); } w.write("], ["); for (int j = 0; j < locals.length; j++) { if (j > 0) { w.write(","); } w.write(locals[j] == null ? "?" : locals[j]); } w.write("]\n"); } } } } public static final class PathElement { int index; String[] stack; String[] locals; PathElement(int index, String[] stack, String[] locals) { this.stack = stack.clone(); this.locals = locals.clone(); this.index = index; } /** * @return the bytecode offset of the instruction causing a value transfer. */ public int getIndex() { return index; } /** * @return the types of the local variabls at the instruction. */ public String[] getLocals() { return locals; } /** * @return the types of the working stack at the instruction. */ public String[] getStack() { return stack; } } private String[] cutArray(String[] a, int len) { if (len == 0) { return noStrings; } else { String[] r = new String[len]; System.arraycopy(a, 0, r, 0, len); return r; } } private boolean mergeTypes(int i, String[] curStack, int curStackSize, String[] curLocals, int curLocalsSize, List path) throws FailureException { boolean changed = false; if (stacks[i] == null) { stacks[i] = cutArray(curStack, curStackSize); changed = true; } else { String[] st = stacks[i]; if (st.length != curStackSize) { throw new FailureException(i, "Stack size mismatch: " + st.length + ", " + curStackSize, path); } for (int j = 0; j < curStackSize; j++) { String t = findCommonSupertype(st[j], curStack[j]); if (t != st[j]) { if (t == null) { throw new FailureException(i, "Stack type mismatch at " + j + " (" + st[j] + " vs " + curStack[j] + ")", path); } st[j] = t; changed = true; } } } if (locals[i] == null) { locals[i] = cutArray(curLocals, curLocalsSize); changed = true; } else { String[] ls = locals[i]; for (int j = 0; j < ls.length; j++) { String t = findCommonSupertype(ls[j], curLocals[j]); if (t != ls[j]) { ls[j] = t; changed = true; } } } return changed; } /** * A PathElement describes a point where a value is moved from one location to * another. */ private void computeTypes(int i, TypeVisitor visitor, BitSet makeTypesAt, List path) throws FailureException { final String[] curStack = new String[maxStack]; final String[] curLocals = new String[maxLocals]; while (true) { if (path != null) { path.add(new PathElement(i, stacks[i], locals[i])); } int curStackSize = stacks[i].length; System.arraycopy(stacks[i], 0, curStack, 0, curStackSize); final int[] curLocalsSize = { locals[i].length }; System.arraycopy(locals[i], 0, curLocals, 0, curLocalsSize[0]); Instruction.Visitor localsUpdate = new Instruction.Visitor() { @Override public void visitLocalLoad(LoadInstruction instruction) { String t = curLocals[instruction.getVarIndex()]; curStack[0] = t; } @Override public void visitLocalStore(StoreInstruction instruction) { int index = instruction.getVarIndex(); curLocals[index] = curStack[0]; if (index >= curLocalsSize[0]) { curLocalsSize[0] = index + 1; } } }; boolean restart = false; while (true) { Instruction instr = instructions[i]; int popped = instr.getPoppedCount(); if (curStackSize < popped) { throw new FailureException(i, "Stack underflow", path); } if (visitor != null) { visitor.setState(i, path, curStack, curLocals); instr.visit(visitor); if (!visitor.shouldContinue()) { return; } } if (instr instanceof DupInstruction) { DupInstruction d = (DupInstruction) instr; int size = d.getSize(); System.arraycopy(curStack, popped, curStack, popped + size, curStackSize - popped); System.arraycopy(curStack, 0, curStack, popped, size); curStackSize += size; } else if (instr instanceof SwapInstruction) { String s = curStack[0]; curStack[0] = curStack[1]; curStack[1] = s; } else { String pushed = instr.getPushedType(curStack); if (pushed != null) { System.arraycopy(curStack, popped, curStack, 1, curStackSize - popped); curStack[0] = Util.getStackType(pushed); instr.visit(localsUpdate); // visit localLoad after pushing curStackSize -= popped - 1; } else { instr.visit(localsUpdate); // visit localStore before popping System.arraycopy(curStack, popped, curStack, 0, curStackSize - popped); curStackSize -= popped; } } int[] targets = instr.getBranchTargets(); for (int j = 0; j < targets.length; j++) { if (mergeTypes(targets[j], curStack, curStackSize, curLocals, curLocalsSize[0], path)) { computeTypes(targets[j], visitor, makeTypesAt, path); } } if (!instr.isFallThrough()) { break; } else { i++; if (makeTypesAt.get(i)) { if (mergeTypes(i, curStack, curStackSize, curLocals, curLocalsSize[0], path)) { restart = true; break; } if (path != null) { path.remove(path.size() - 1); } return; } } } if (!restart) { break; } } if (path != null) { path.remove(path.size() - 1); } } public int[] getStackSizes() throws FailureException { if (stackSizes != null) { return stackSizes; } stackSizes = new int[instructions.length]; for (int i = 0; i < stackSizes.length; i++) { stackSizes[i] = -1; } computeStackSizesAt(stackSizes, 0, 0); return stackSizes; } private void computeMaxLocals() { maxLocals = locals[0].length; for (int i = 0; i < instructions.length; i++) { Instruction instr = instructions[i]; if (instr instanceof LoadInstruction) { maxLocals = Math.max(maxLocals, ((LoadInstruction) instr).getVarIndex() + 1); } else if (instr instanceof StoreInstruction) { maxLocals = Math.max(maxLocals, ((StoreInstruction) instr).getVarIndex() + 1); } } } final protected void initTypeInfo() throws FailureException { stacks = new String[instructions.length][]; locals = new String[instructions.length][]; stacks[0] = noStrings; locals[0] = Util.getParamsTypesInLocals(isStatic ? null : classType, signature); int[] stackSizes = getStackSizes(); maxStack = 0; for (int i = 0; i < stackSizes.length; i++) { maxStack = Math.max(maxStack, stackSizes[i]); } computeMaxLocals(); } /** * Verify the method and compute types at every program point. * * @throws FailureException * the method contains invalid bytecode */ final public void computeTypes(TypeVisitor v, BitSet makeTypesAt, boolean wantPath) throws FailureException { initTypeInfo(); computeTypes(0, v, makeTypesAt, wantPath ? new ArrayList() : null); } public abstract class TypeVisitor extends Instruction.Visitor { public abstract void setState(int index, List path, String[] curStack, String[] curLocals); public abstract boolean shouldContinue(); } /** * @return an array indexed by instruction index; each entry is an array of * Strings giving the types of the locals at that instruction. */ final public String[][] getLocalTypes() { return locals; } /** * @return an array indexed by instruction index; each entry is an array of * Strings giving the types of the stack elements at that instruction. * The top of the stack is the last element of the array. */ final public String[][] getStackTypes() { return stacks; } protected Analyzer(MethodData info) { this(info.getIsStatic(), info.getClassType(), info.getSignature(), info.getInstructions(), info.getHandlers()); } public static Analyzer createAnalyzer(MethodData info) { if (info == null) { throw new IllegalArgumentException("info is null"); } return new Analyzer(info); } }