diff --git a/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java b/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java index e03d72c6..d3ca0ae0 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java +++ b/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java @@ -28,58 +28,182 @@ package gr.gousiosg.javacg.stat; -import org.apache.bcel.classfile.Constant; -import org.apache.bcel.classfile.ConstantPool; -import org.apache.bcel.classfile.EmptyVisitor; -import org.apache.bcel.classfile.JavaClass; -import org.apache.bcel.classfile.Method; +import org.apache.bcel.classfile.*; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.MethodGen; +import java.io.PrintStream; + /** * The simplest of class visitors, invokes the method visitor class for each * method found. */ public class ClassVisitor extends EmptyVisitor { + private static final String OBJECT = Object.class.getName(); + private static final String ENUMERATION = Enum.class.getName(); private JavaClass clazz; private ConstantPoolGen constants; private String classReferenceFormat; - - public ClassVisitor(JavaClass jc) { + private PrintStream output; + private FormatEnumaration format; + private ConstantPool currentConstantPool; + + public ClassVisitor(JavaClass jc, PrintStream outputStream, FormatEnumaration format) { clazz = jc; + this.format = format; + this.output = outputStream; constants = new ConstantPoolGen(clazz.getConstantPool()); - classReferenceFormat = "C:" + clazz.getClassName() + " %s"; + switch (format) { + case TXT: + classReferenceFormat = "C:" + clazz.getClassName() + " %s"; + break; + case XML: + classReferenceFormat = "%s"; + break; + default: + throw new RuntimeException("Unsupported format "+format); + } + } public void visitJavaClass(JavaClass jc) { + switch (format) { + case XML: + output.println(""); + String[] names = jc.getInterfaceNames(); + if (names != null && names.length>0) { + output.println(""); + for (String name:names) { + output.print(""); + output.print(name); + output.println(""); + } + output.println(""); + } + if (OBJECT.equals(jc.getSuperclassName())==false) { + output.print(""); + output.print(jc.getSuperclassName()); + output.println(""); + } + break; + } jc.getConstantPool().accept(this); Method[] methods = jc.getMethods(); - for (int i = 0; i < methods.length; i++) + for (int i = 0; i < methods.length; i++) { methods[i].accept(this); + } + switch (format) { + case XML: + output.println(""); + break; + } } public void visitConstantPool(ConstantPool constantPool) { - for (int i = 0; i < constantPool.getLength(); i++) { - Constant constant = constantPool.getConstant(i); - if (constant == null) - continue; - if (constant.getTag() == 7) { - String referencedClass = - constantPool.constantToString(constant); - System.out.println(String.format(classReferenceFormat, - referencedClass)); - } + switch (format) { + case TXT: + for (int i = 0; i < constantPool.getLength(); i++) { + Constant constant = constantPool.getConstant(i); + if (constant == null) + continue; + if (constant.getTag() == 7) { + String referencedClass = + constantPool.constantToString(constant); + output.println(String.format(classReferenceFormat, + referencedClass)); + } + } + case XML: + for (int i = 0; i < constantPool.getLength(); i++) { + Constant constant = constantPool.getConstant(i); + if (constant == null) + continue; + if (constant.getTag() == 7) { + output.print(""); + String referencedClass = + constantPool.constantToString(constant); + output.print(xmlEscapeText(referencedClass)); + output.println(""); + } + } } } public void visitMethod(Method method) { MethodGen mg = new MethodGen(method, clazz.getClassName(), constants); - MethodVisitor visitor = new MethodVisitor(mg, clazz); + MethodVisitor visitor = new MethodVisitor(mg, clazz,output,format); visitor.start(); } public void start() { visitJavaClass(clazz); } + + /** + * Encode special charaters for XML + * @param t + * @return + */ + static String xmlEscapeText(String t) { + StringBuilder sb = null; + for(int i = 0; i < t.length(); i++){ + char c = t.charAt(i); + switch(c){ + case '<': + if (sb==null) { + sb = new StringBuilder(t.length()+10); + sb.append(t.substring(0,i)); + } + sb.append("<"); + break; + case '>': + if (sb==null) { + sb = new StringBuilder(t.length()+10); + sb.append(t.substring(0,i)); + } + sb.append(">"); + break; + case '\"': + if (sb==null) { + sb = new StringBuilder(t.length()+10); + sb.append(t.substring(0,i)); + } + sb.append("""); + break; + case '&': + if (sb==null) { + sb = new StringBuilder(t.length()+10); + sb.append(t.substring(0,i)); + } + sb.append("&"); + break; + case '\'': + if (sb==null) { + sb = new StringBuilder(t.length()+10); + sb.append(t.substring(0,i)); + } + sb.append("'"); + break; + default: + if(c>0x7e || c<0x20) { + if (sb==null) { + sb = new StringBuilder(t.length()+10); + sb.append(t.substring(0,i)); + } + sb.append("&#"+((int)c)+";"); + }else if (sb != null) { + sb.append(c); + } + } + } + if (sb==null) { + //re-use original string + return t; + } else { + return sb.toString(); + } + } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/FormatEnumaration.java b/src/main/java/gr/gousiosg/javacg/stat/FormatEnumaration.java new file mode 100644 index 00000000..c9afc34a --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/FormatEnumaration.java @@ -0,0 +1,5 @@ +package gr.gousiosg.javacg.stat; + +public enum FormatEnumaration { + TXT,XML +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java b/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java index 4d19408d..88af3917 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java +++ b/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java @@ -29,8 +29,12 @@ package gr.gousiosg.javacg.stat; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -46,9 +50,90 @@ public class JCallGraph { public static void main(String[] args) { + FormatEnumaration format = FormatEnumaration.TXT; + PrintStream outputStream = System.out; + List jars = new ArrayList<>(args.length); + if (args.length==0) { + printHelp(); + } + for (int i =0;i"); + outputStream.println(""); + break; + } + for (String arg : jars) { File f = new File(arg); @@ -68,13 +153,27 @@ public static void main(String[] args) { continue; cp = new ClassParser(arg,entry.getName()); - ClassVisitor visitor = new ClassVisitor(cp.parse()); + ClassVisitor visitor = new ClassVisitor(cp.parse(),outputStream,format); visitor.start(); } } + switch (format) { + case XML: + outputStream.println(""); + break; + } } catch (IOException e) { System.err.println("Error while processing jar: " + e.getMessage()); e.printStackTrace(); } } + + private static void printHelp() { + System.out.println("Constructs a callgraph out of a JAR archive. Can combine multiple archives into a single call graph"); + System.out.println("See https://github.com/gousiosg/java-callgraph for more details"); + System.out.println("Options:"); + System.out.println("-h, -help, -? This help message"); + System.out.println("-f, -format Set output format. Possible values 'xml' or 'txt' (default)"); + System.out.println("-o, -out File for output. Default - to console"); + } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java b/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java index 4dd0821b..9280414c 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java +++ b/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java @@ -31,6 +31,8 @@ import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.*; +import java.io.PrintStream; + /** * The simplest of method visitors, prints any invoked method * signature for all method invocations. @@ -43,22 +45,47 @@ public class MethodVisitor extends EmptyVisitor { private MethodGen mg; private ConstantPoolGen cp; private String format; + private PrintStream output; + private FormatEnumaration outputFormat; - public MethodVisitor(MethodGen m, JavaClass jc) { + public MethodVisitor(MethodGen m, JavaClass jc, PrintStream output, FormatEnumaration format) { visitedClass = jc; mg = m; cp = mg.getConstantPool(); - format = "M:" + visitedClass.getClassName() + ":" + mg.getName() + "(" + argumentList(mg.getArgumentTypes()) + ")" - + " " + "(%s)%s:%s(%s)"; + this.outputFormat = format; + this.output = output; + switch (outputFormat) { + case TXT: + this.format = "M:" + visitedClass.getClassName() + ":" + mg.getName() + "(" + argumentList(mg.getArgumentTypes(),false) + ")" + + " " + "(%s)%s:%s(%s)"; + break; + case XML: + this.format = ""; + break; + default: + throw new RuntimeException("Unsupported format "+outputFormat); + } } - private String argumentList(Type[] arguments) { + private String argumentList(Type[] arguments,boolean flat) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < arguments.length; i++) { - if (i != 0) { - sb.append(","); + if (flat || outputFormat==FormatEnumaration.TXT) { + if (i != 0) { + sb.append(","); + } + sb.append(arguments[i].toString()); + } else { + switch (outputFormat) { + case XML: + sb.append(""); + sb.append(arguments[i].getSignature()); + sb.append(""); + break; + default: + throw new RuntimeException("Unsupported format "+outputFormat); + } } - sb.append(arguments[i].toString()); } return sb.toString(); } @@ -66,13 +93,35 @@ private String argumentList(Type[] arguments) { public void start() { if (mg.isAbstract() || mg.isNative()) return; - for (InstructionHandle ih = mg.getInstructionList().getStart(); + switch (outputFormat) { + case XML: + output.print(""); + break; + } + for (InstructionHandle ih = mg.getInstructionList().getStart(); ih != null; ih = ih.getNext()) { Instruction i = ih.getInstruction(); - +// if (outputFormat== FormatEnumaration.XML && i instanceof ConstantPushInstruction) { +// ConstantPushInstruction pushInstruction = (ConstantPushInstruction)i; +// output.println(""); +// output.println(pushInstruction.getValue()); +// output.println(""); +// } if (!visitInstruction(i)) i.accept(this); } + switch (outputFormat) { + case XML: + output.println(""); + break; + } + } + + @Override + public void visitLCONST(LCONST obj) { + super.visitLCONST(obj); } private boolean visitInstruction(Instruction i) { @@ -82,29 +131,38 @@ private boolean visitInstruction(Instruction i) { && !(i instanceof ReturnInstruction)); } +// @Override +// public void visitLocalVariableInstruction(LocalVariableInstruction obj) { +// switch (outputFormat) { +// case XML: +// output.println(""+obj.toString(true)+""); +// break; +// } +// } + @Override public void visitINVOKEVIRTUAL(INVOKEVIRTUAL i) { - System.out.println(String.format(format,"M",i.getReferenceType(cp),i.getMethodName(cp),argumentList(i.getArgumentTypes(cp)))); + output.println(String.format(format,"M",i.getReferenceType(cp), ClassVisitor.xmlEscapeText(i.getMethodName(cp)),argumentList(i.getArgumentTypes(cp),true))); } @Override public void visitINVOKEINTERFACE(INVOKEINTERFACE i) { - System.out.println(String.format(format,"I",i.getReferenceType(cp),i.getMethodName(cp),argumentList(i.getArgumentTypes(cp)))); + output.println(String.format(format,"I",i.getReferenceType(cp),ClassVisitor.xmlEscapeText(i.getMethodName(cp)),argumentList(i.getArgumentTypes(cp),true))); } @Override public void visitINVOKESPECIAL(INVOKESPECIAL i) { - System.out.println(String.format(format,"O",i.getReferenceType(cp),i.getMethodName(cp),argumentList(i.getArgumentTypes(cp)))); + output.println(String.format(format,"O",i.getReferenceType(cp),ClassVisitor.xmlEscapeText(i.getMethodName(cp)),argumentList(i.getArgumentTypes(cp),true))); } @Override public void visitINVOKESTATIC(INVOKESTATIC i) { - System.out.println(String.format(format,"S",i.getReferenceType(cp),i.getMethodName(cp),argumentList(i.getArgumentTypes(cp)))); + output.println(String.format(format,"S",i.getReferenceType(cp),ClassVisitor.xmlEscapeText(i.getMethodName(cp)),argumentList(i.getArgumentTypes(cp),true))); } @Override public void visitINVOKEDYNAMIC(INVOKEDYNAMIC i) { - System.out.println(String.format(format,"D",i.getType(cp),i.getMethodName(cp), - argumentList(i.getArgumentTypes(cp)))); + output.println(String.format(format,"D",i.getType(cp),ClassVisitor.xmlEscapeText(i.getMethodName(cp)), + argumentList(i.getArgumentTypes(cp),true))); } }