/******************************************************************************* * 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.Arrays; import java.util.BitSet; import java.util.List; import com.ibm.wala.shrikeBT.Constants; import com.ibm.wala.shrikeBT.DupInstruction; import com.ibm.wala.shrikeBT.ExceptionHandler; import com.ibm.wala.shrikeBT.IInstruction; import com.ibm.wala.shrikeBT.IInvokeInstruction; import com.ibm.wala.shrikeBT.IInvokeInstruction.Dispatch; import com.ibm.wala.shrikeBT.ILoadInstruction; import com.ibm.wala.shrikeBT.IStoreInstruction; import com.ibm.wala.shrikeBT.LoadInstruction; import com.ibm.wala.shrikeBT.MethodData; import com.ibm.wala.shrikeBT.NewInstruction; import com.ibm.wala.shrikeBT.StoreInstruction; import com.ibm.wala.shrikeBT.SwapInstruction; import com.ibm.wala.shrikeBT.Util; /** * @author roca */ public class Analyzer { public static final String thisType = "THIS"; public static final String topType = "TOP"; // inputs final protected boolean isConstructor; final protected boolean isStatic; final protected String classType; final protected String signature; final protected IInstruction[] instructions; final 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 int[] instToBC; protected String[][] varTypes; protected final static String[] noStrings = new String[0]; protected final static int[] noEdges = new int[0]; public Analyzer(boolean isConstructor, boolean isStatic, String classType, String signature, IInstruction[] instructions, ExceptionHandler[][] handlers, int[] instToBC, String[][] vars) { if (instructions == null) { throw new IllegalArgumentException("null instructions"); } if (handlers == null) { throw new IllegalArgumentException("null handlers"); } for (IInstruction i : instructions) { if (i == null) { throw new IllegalArgumentException("null instruction is illegal"); } } this.classType = classType; this.isConstructor = isConstructor; this.isStatic = isStatic; this.signature = signature; this.instructions = instructions; this.handlers = handlers; this.instToBC = instToBC; this.varTypes = vars; } /** * 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++) { IInstruction 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; } private String patchType(String t) { if (t == thisType) { return classType; } else if (t != null && t.startsWith("#")) { return stripSharp(t); } else { return t; } } final public boolean isSubtypeOf(String t1, String t2) { return ClassHierarchy.isSubtypeOf(hierarchy, patchType(t1), patchType(t2)) != ClassHierarchy.NO; } private boolean isPrimitive(String type) { return type != null && (!type.startsWith("L") && !type.startsWith("[")); } final public String findCommonSupertype(String t1, String t2) { if (String.valueOf(t1).equals(String.valueOf(t2))) { return t1; } if (String.valueOf(t1).equals("L;") || String.valueOf(t2).equals("L;")) { System.err.println("++ " + t1 + " -- " + t2 + " ++"); if (String.valueOf(t1).equals("L;")) { return t2; } else { return t1; } } if (t1 == thisType || t2 == thisType || t1 == topType || t2 == topType) { return topType; } if (isPrimitive(t1) != isPrimitive(t2)) { return topType; } String x = ClassHierarchy.findCommonSupertype(hierarchy, patchType(t1), patchType(t2)); if (String.valueOf(t1).contains("groovy/lang/GroovyObject") || String.valueOf(t2).contains("groovy/lang/GroovyObject")) { System.err.println(t1 + " -- " + t2 + " --> " + x); } if ("L?;".equals(x)) { System.err.println(t1 + " -- " + t2); } return x; } 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 IInstruction[] getInstructions() { return instructions; } private void getReachableRecursive(int from, BitSet reachable, boolean followHandlers, BitSet mask) throws IllegalArgumentException { if (from < 0) { throw new IllegalArgumentException("from < 0"); } 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); IInstruction 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 BitSet getReachingTo(int to, BitSet mask) { getBackEdges(); BitSet reaching = new BitSet(); getReachingBase(to, reaching, mask); return reaching; } final 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; IInstruction 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; final private int offset; final private String reason; private List<PathElement> path; FailureException(int offset, String reason, List<PathElement> 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<PathElement> getPath() { return path; } void setPath(List<PathElement> 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 { final int index; final String[] stack; final 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<PathElement> path) throws FailureException { boolean a = mergeStackTypes(i, curStack, curStackSize, path); boolean b = mergeLocalTypes(i, curLocals, curLocalsSize, path); return a||b; } private boolean longType(String type) { return Constants.TYPE_long.equals(type) || Constants.TYPE_double.equals(type); } private boolean mergeStackTypes(int i, String[] curStack, int curStackSize, List<PathElement> 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; } } } return changed; } private boolean mergeLocalTypes(int i, String[] curLocals, int curLocalsSize, List<PathElement> path) throws FailureException { boolean changed = false; if (locals[i] == null) { locals[i] = cutArray(curLocals, curLocalsSize); changed = true; } else { String[] ls = locals[i]; for (int lj = 0, cj = 0; lj < ls.length; lj++, cj++) { String t = findCommonSupertype(ls[lj], curLocals[cj]); if (t != ls[lj]) { ls[lj] = t; if (longType(curLocals[lj]) != longType(ls[cj])) { System.err.println("merging " + curLocals[cj] + " and " + ls[lj] + " to " + t); if (ls.length > lj+1) { System.err.println("next " + curLocals[cj+1] + " and " + ls[lj+1]); } } changed = true; } } } return changed; } public static String stripSharp(String type) { return type.substring(type.lastIndexOf('#')+1); } /** * 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<PathElement> 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]); IInstruction.Visitor localsUpdate = new IInstruction.Visitor() { @Override public void visitInvoke(IInvokeInstruction instruction) { if (instruction.getInvocationCode() == Dispatch.SPECIAL) { if (instruction.getMethodName().equals("<init>")) { int sz = Util.getParamsTypes(instruction.getClassType(), instruction.getMethodSignature()).length; if (isConstructor) { if (thisType.equals(curStack[ sz-1 ])) { for(int i = 0; i < curLocals.length; i++) { if (thisType.equals(curLocals[i])) { curLocals[i] = classType; } } for(int i = 0; i < curStack.length; i++) { if (thisType.equals(curStack[i])) { curStack[i] = classType; } } } } if (curStack.length > sz && curStack[sz] != null && curStack[sz].startsWith("#")) { curStack[sz] = stripSharp(curStack[sz]); } } } } @Override public void visitLocalLoad(ILoadInstruction instruction) { String t = curLocals[instruction.getVarIndex()]; curStack[0] = t; } @Override public void visitLocalStore(IStoreInstruction instruction) { int index = instruction.getVarIndex(); String t = curStack[0]; curLocals[index] = t; if (longType(t) && curLocals.length > index+1) { curLocals[index+1] = null; } if (index >= curLocalsSize[0]) { curLocalsSize[0] = index + (longType(t) && curLocals.length > index+1? 2: 1); } } }; boolean restart = false; while (true) { IInstruction 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 (instr instanceof NewInstruction && ! pushed.startsWith("[")) { pushed = "#" + instToBC[i] + "#" + pushed; } 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; if (varTypes != null) { if (instr instanceof IStoreInstruction) { int local = ((IStoreInstruction)instr).getVarIndex(); if (Constants.TYPE_null.equals(curLocals[local])) { for(int idx : new int[]{i, i+1}) { int bc = instToBC[idx]; if (bc == 667 && local == 16) { System.err.println("got here"); } if (bc != -1 && varTypes != null && varTypes.length > bc && varTypes[bc] != null) { if (varTypes[bc].length > local && varTypes[bc][local] != null) { String declaredType = varTypes[bc][local]; curLocals[local] = declaredType; System.err.println("setting local " + local + " to " + declaredType + " at " + instToBC[i] + " in " + classType + " " + signature); } } } } } } } } ExceptionHandler[] handler = handlers[i]; for(int h = 0; h < handler.length; h++) { int target = handler[h].getHandler(); String cls = handler[h].getCatchClass(); if (cls == null) { cls = "Ljava/lang/Throwable;"; } String[] catchStack = new String[]{ cls }; if (mergeTypes(target, catchStack, 1, curLocals, curLocalsSize[0], path)) { computeTypes(target, visitor, makeTypesAt, path); } } //System.err.println(i + " -- " + Arrays.toString(curLocals) + " -- " + Arrays.toString(curStack)); int[] targets = instr.getBranchTargets(); for (int j = 0; j < targets.length; j++) { if (targets[j] == 29 && classType.contains("MetaClassImpl;") && signature.contains("(Ljava/lang/String;Lorg/codehaus/groovy/reflection/CachedClass;)")) { System.err.println("got here"); } 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++) { IInstruction 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][]; String thisType; if (isConstructor) { thisType = Analyzer.thisType; } else { thisType = classType; } stacks[0] = noStrings; locals[0] = Util.getParamsTypesInLocals(isStatic ? null : thisType, signature); if (isConstructor) { for(int i = 0; i < locals[0].length; i++) { if (classType.equals(locals[0][i])) { locals[0][i] = thisType; } } } 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<PathElement>() : null); } public abstract class TypeVisitor extends IInstruction.Visitor { public abstract void setState(int index, List<PathElement> 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.getName().equals("<init>"), info.getIsStatic(), info.getClassType(), info.getSignature(), info.getInstructions(), info.getHandlers(), info.getInstructionsToBytecodes(), null); } protected Analyzer(MethodData info, int[] instToBC, String[][] vars) { this(info.getName().equals("<init>"), info.getIsStatic(), info.getClassType(), info.getSignature(), info.getInstructions(), info.getHandlers(), instToBC, vars); } public static Analyzer createAnalyzer(MethodData info) { if (info == null) { throw new IllegalArgumentException("info is null"); } return new Analyzer(info); } }