/******************************************************************************* * 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.shrike.cg; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.Map; import com.ibm.wala.shrikeBT.ConstantInstruction; import com.ibm.wala.shrikeBT.Constants; import com.ibm.wala.shrikeBT.Disassembler; import com.ibm.wala.shrikeBT.LoadInstruction; import com.ibm.wala.shrikeBT.MethodData; import com.ibm.wala.shrikeBT.MethodEditor; import com.ibm.wala.shrikeBT.MethodEditor.Output; import com.ibm.wala.shrikeBT.ReturnInstruction; import com.ibm.wala.shrikeBT.ThrowInstruction; import com.ibm.wala.shrikeBT.Util; import com.ibm.wala.shrikeBT.analysis.Analyzer.FailureException; import com.ibm.wala.shrikeBT.analysis.ClassHierarchyStore; import com.ibm.wala.shrikeBT.analysis.Verifier; import com.ibm.wala.shrikeBT.shrikeCT.CTUtils; import com.ibm.wala.shrikeBT.shrikeCT.ClassInstrumenter; import com.ibm.wala.shrikeBT.shrikeCT.OfflineInstrumenter; import com.ibm.wala.shrikeCT.ClassReader; import com.ibm.wala.shrikeCT.ClassWriter; import com.ibm.wala.shrikeCT.ConstantPoolParser; import com.ibm.wala.shrikeCT.InvalidClassFileException; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.config.FileOfClasses; import com.ibm.wala.util.config.SetOfClasses; /** * Class files are taken as input arguments (or if there are none, from standard * input). The methods in those files are instrumented: we insert a * System.err.println() at ever method call, and a System.err.println() at every * method entry. * * The instrumented classes are placed in the directory "output" under the * current directory. Disassembled code is written to the file "report" under * the current directory. * * @author CHammer * @author Julian Dolby (dolby@us.ibm.com) * @since 10/18 */ public class DynamicCallGraph { private final static boolean disasm = true; private final static boolean verify = true; private static boolean patchExits = true; private static OfflineInstrumenter instrumenter; private static Class<?> runtime = Runtime.class; private static SetOfClasses filter; private static ClassHierarchyStore cha = new ClassHierarchyStore(); public static void main(String[] args) throws IOException, ClassNotFoundException, InvalidClassFileException, FailureException { ClassInstrumenter ci; Writer w = new BufferedWriter(new FileWriter("report", false)); for(int i = 0; i < args.length - 1; i++) { if ("--runtime".equals(args[i])) { runtime = Class.forName(args[i+1]); } else if ("--exclusions".equals(args[i])) { filter = new FileOfClasses(new FileInputStream(args[i+1])); } else if ("--dont-patch-exits".equals(args[i])) { patchExits = false; } else if ("--rt-jar".equals(args[i])) { System.err.println("using " + args[i+1] + " as stdlib"); OfflineInstrumenter libReader = new OfflineInstrumenter(true); libReader.addInputJar(new File(args[i+1])); while ((ci = libReader.nextClass()) != null) { CTUtils.addClassToHierarchy(cha, ci.getReader()); } } } instrumenter = new OfflineInstrumenter(true); args = instrumenter.parseStandardArgs(args); instrumenter.setPassUnmodifiedClasses(true); instrumenter.beginTraversal(); while ((ci = instrumenter.nextClass()) != null) { CTUtils.addClassToHierarchy(cha, ci.getReader()); } instrumenter.setClassHierarchyProvider(cha); instrumenter.beginTraversal(); while ((ci = instrumenter.nextClass()) != null) { doClass(ci, w); } instrumenter.close(); } private static void doClass(final ClassInstrumenter ci, Writer w) throws InvalidClassFileException, IOException, FailureException { final String className = ci.getReader().getName(); if (filter != null && filter.contains(className)) { return; } w.write("Class: " + className + "\n"); w.flush(); final ClassReader r = ci.getReader(); for (int m = 0; m < ci.getReader().getMethodCount(); m++) { final MethodData d = ci.visitMethod(m); // d could be null, e.g., if the method is abstract or native if (d != null) { if (filter != null && filter.contains(className + "." + ci.getReader().getMethodName(m))) { return; } w.write("Instrumenting " + ci.getReader().getMethodName(m) + " " + ci.getReader().getMethodType(m) + ":\n"); w.flush(); if (disasm) { w.write("Initial ShrikeBT code:\n"); (new Disassembler(d)).disassembleTo(w); w.flush(); } if (verify) { Verifier v = new Verifier(d); // v.setClassHierarchy(cha); v.verify(); } final MethodEditor me = new MethodEditor(d); me.beginPass(); final String theClass = r.getName(); final String theMethod = r.getMethodName(m).concat(r.getMethodType(m)); final boolean isConstructor = theMethod.contains("<init>"); final boolean nonStatic = !java.lang.reflect.Modifier.isStatic(r.getMethodAccessFlags(m)); me.insertAtStart(new MethodEditor.Patch() { @Override public void emitTo(MethodEditor.Output w) { w.emit(ConstantInstruction.makeString(theClass)); w.emit(ConstantInstruction.makeString(theMethod)); if (nonStatic && !isConstructor) w.emit(LoadInstruction.make(Constants.TYPE_Object, 0)); //load this else w.emit(Util.makeGet(runtime, "NULL_TAG")); // w.emit(ConstantInstruction.make(Constants.TYPE_null, null)); w.emit(Util.makeInvoke(runtime, "execution", new Class[] {Class.class, String.class, Object.class})); } }); if (patchExits) { me.addMethodExceptionHandler(null, new MethodEditor.Patch() { @Override public void emitTo(Output w) { w.emit(ConstantInstruction.makeString(theClass)); w.emit(ConstantInstruction.makeString(theMethod)); //if (nonStatic) // w.emit(LoadInstruction.make(Constants.TYPE_Object, 0)); //load this //else w.emit(Util.makeGet(runtime, "NULL_TAG")); // w.emit(ConstantInstruction.make(Constants.TYPE_null, null)); w.emit(ConstantInstruction.make(1)); // true w.emit(Util.makeInvoke(runtime, "termination", new Class[] {String.class, String.class, Object.class, boolean.class})); w.emit(ThrowInstruction.make(false)); } }); me.visitInstructions(new MethodEditor.Visitor() { @Override public void visitReturn(ReturnInstruction instruction) { insertBefore(new MethodEditor.Patch() { @Override public void emitTo(MethodEditor.Output w) { w.emit(ConstantInstruction.makeString(theClass)); w.emit(ConstantInstruction.makeString(theMethod)); if (nonStatic) w.emit(LoadInstruction.make(Constants.TYPE_Object, 0)); //load this else w.emit(Util.makeGet(runtime, "NULL_TAG")); // w.emit(ConstantInstruction.make(Constants.TYPE, null)); w.emit(ConstantInstruction.make(0)); // false w.emit(Util.makeInvoke(runtime, "termination", new Class[] {String.class, String.class, Object.class, boolean.class})); } }); } }); } // this updates the data d me.applyPatches(); if (disasm) { w.write("Final ShrikeBT code:\n"); (new Disassembler(d)).disassembleTo(w); w.flush(); } if (verify) { Verifier v = new Verifier(d); // v.setClassHierarchy(cha); v.verify(); } } } if (ci.isChanged()) { ClassWriter cw = new ClassWriter() { private final Map<Object, Integer> entries = HashMapFactory.make(); { ConstantPoolParser p = r.getCP(); for(int i = 1; i < p.getItemCount(); i++) { switch (p.getItemType(i)) { case CONSTANT_Integer: entries.put(new Integer(p.getCPInt(i)), i); break; case CONSTANT_Long: entries.put(new Long(p.getCPLong(i)), i); break; case CONSTANT_Float: entries.put(new Float(p.getCPFloat(i)), i); break; case CONSTANT_Double: entries.put(new Double(p.getCPDouble(i)), i); break; case CONSTANT_Utf8: entries.put(p.getCPUtf8(i), i); break; case CONSTANT_String: entries.put(new CWStringItem(p.getCPString(i), CONSTANT_String), i); break; case CONSTANT_Class: entries.put(new CWStringItem(p.getCPClass(i), CONSTANT_Class), i); break; case CONSTANT_MethodHandle: case CONSTANT_MethodType: case CONSTANT_InvokeDynamic: } } } private int findExistingEntry(Object o) { if (entries.containsKey(o)) { return entries.get(o); } else { return -1; } } @Override protected int addCPEntry(Object o, int size) { int entry = findExistingEntry(o); if (entry != -1) { return entry; } else { return super.addCPEntry(o, size); } } }; ci.emitClass(cw); instrumenter.outputModifiedClass(ci, cw); } } }