/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim.tools; import static kilim.analysis.Utils.dedent; import static kilim.analysis.Utils.indent; import static kilim.analysis.Utils.pn; import static kilim.analysis.Utils.resetIndentation; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import kilim.analysis.BasicBlock; import kilim.analysis.ClassFlow; import kilim.analysis.Frame; import kilim.analysis.MethodFlow; import kilim.analysis.TypeDesc; import kilim.analysis.Usage; import kilim.analysis.Value; import kilim.mirrors.Detector; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; /** * Used to dump the stack and locals at the beginning of each basic block * @author ram */ public class FlowAnalyzer { public static void main(String[] args) throws Exception { if (args.length == 0) { System.err.println("Usage <class name | jar file name> [methodName]"); System.exit(1); } String name = args[0]; if (name.endsWith(".jar")) { analyzeJar(name, Detector.DEFAULT); } else { analyzeClass(name, Detector.DEFAULT); } } private static void analyzeClass(String className, Detector detector) { try { pn("-------------------------------------------------"); pn("Class: " + className); System.out.flush(); ClassFlow cf = null; if (className.endsWith(".class")) { FileInputStream fis = null; try { fis = new FileInputStream(className); cf = new ClassFlow(fis, detector); } finally { if (fis != null) {fis.close();} } } if (cf == null) { cf = new ClassFlow(className, detector); } ArrayList<MethodFlow> flows = cf.analyze(true); for (MethodFlow flow: flows) { reportFlow(flow, className); } } catch (IOException e) { pn("##################################################"); stackTrace(e); } catch (Throwable ie) { pn("##################################################"); stackTrace(ie); } } private static void stackTrace(Throwable t) { PrintStream ps = new PrintStream(System.out); t.printStackTrace(ps); } private static void reportFlow(MethodFlow method, String className) { resetIndentation(); pn("Method : "+ className + '.' + method.name); pn("MaxStack: " + method.maxStack); pn("MaxLocals: " + method.maxLocals); ArrayList<BasicBlock> bbs = method.getBasicBlocks(); Collections.sort(bbs); indent(2); for (BasicBlock bb: bbs) { AbstractInsnNode ainode = bb.getInstruction(bb.startPos); if (ainode instanceof MethodInsnNode) { MethodInsnNode m = (MethodInsnNode)ainode; int n = getNumArgs(m); // This many will get consumed from stack pn("Call(" + n + "): " + m.owner + "." + m.name + m.desc); indent(2); pn("Inframe: "); indent(2); Frame f = bb.startFrame; pn(f.toString()); dedent(2); pn("Live locals:"); indent(2); Usage u = bb.getVarUsage(); pn(u.toString()); dedent(2); pn("Actual usage: " + uniqueItems(bb, f, u, n)); dedent(2); } } dedent(2); } private static String uniqueItems(BasicBlock bb, Frame f, Usage u, int nStack) { StringBuffer sb = new StringBuffer(80); int numNonConstants = 0; int numLive = 0; ArrayList<Value> set = new ArrayList<Value>(10); for (int i = 0; i < f.getMaxLocals(); i++) { if (u.isLiveIn(i)) { numLive++; Value v = f.getLocal(i); if (!set.contains(v)) set.add(v); } } nStack = f.getStackLen() - nStack; for (int i = 0; i < nStack; i++) { Value v = f.getStack(i); if (!set.contains(v)) set.add(v); } char[] sig = new char[set.size()]; // create canonical sig. Convert types to one of 'O', 'I', 'F', 'L', 'D' and // put in sorted order // Also count non constants while we are iterating anyway. for (int i = 0; i < set.size(); i++) { Value v = set.get(i); char c = v.getTypeDesc().charAt(0); switch (c) { case 'L': case '[': case 'N': c = 'O'; break; case 'I': case 'B': case 'S': case 'Z': case 'C': c = 'I'; break; case 'J': c = 'J'; break; case 'F': c = 'F'; break; case 'U': default: { c = 'U'; System.err.println("***************************************"); System.err.println("Undefined/unrecognized value " + v); System.err.println("BasicBlock:\n" + bb); break; } } sig[i] = c; if (v.getConstVal() == Value.NO_VAL) { numNonConstants++; } } Arrays.sort(sig); numLive += nStack; sb.append("avail: ").append(nStack + f.getMaxLocals()); sb.append(", live: " + numLive); sb.append(", unique: ").append(set.size()); sb.append(", unique non-const: ").append(numNonConstants); sb.append("\nState signature: ").append(set.size() == 0 ? "None" : new String(sig)); return sb.toString(); } private static int getNumArgs(MethodInsnNode m) { int ret = TypeDesc.getNumArgumentTypes(m.desc); if (m.getOpcode() != INVOKESTATIC) ret++; return ret; } public static void analyzeJar(String jarFile, Detector detector) { try { Enumeration<JarEntry> e = new JarFile(jarFile).entries(); while (e.hasMoreElements()) { ZipEntry en = (ZipEntry) e.nextElement(); String n = en.getName(); if (!n.endsWith(".class")) continue; n = n.substring(0, n.length() - 6).replace('/','.'); analyzeClass(n, detector); } } catch (Exception e) { e.printStackTrace(); } } }