/* * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. * */ package com.sun.classanalyzer; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.File; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import com.sun.tools.classfile.*; import com.sun.tools.classfile.ConstantPool.*; import static com.sun.tools.classfile.ConstantPool.*; import com.sun.tools.classfile.Instruction.TypeKind; import com.sun.tools.classfile.Type.*; /** * Generate the module config for the boot module with * a given set of roots (classes or methods) and exclude list. * * This tool does method-level dependency analysis starting * from the root set and follows references transitively as follows: * * * Limitation: * * * @author Mandy Chung */ public class BootAnalyzer { public static void main(String[] args) throws Exception { String jdkhome = null; String config = null; String output = "."; boolean printClassList = false; // process arguments int i = 0; while (i < args.length) { String arg = args[i++]; if (arg.equals("-jdkhome")) { if (i < args.length) { jdkhome = args[i++]; } else { usage(); } } else if (arg.equals("-config")) { config = args[i++]; } else if (arg.equals("-output")) { output = args[i++]; } else if (arg.equals("-classlist")) { printClassList = true; } else { usage(); } } if (jdkhome == null || config == null) { usage(); } File jre = new File(jdkhome, "jre"); if (jre.exists()) { ClassPath.setJDKHome(jdkhome); } else { File classes = new File(jdkhome, "classes"); if (classes.exists()) { ClassPath.setClassPath(classes.getCanonicalPath()); } else { throw new RuntimeException("Invalid jdkhome: " + jdkhome); } } parseConfigFile(config); followRoots(); // create output directory if it doesn't exist File dir = new File(output); if (!dir.isDirectory()) { if (!dir.exists()) { boolean created = dir.mkdir(); if (!created) { throw new RuntimeException("Unable to create `" + dir + "'"); } } } String bootmodule = "boot"; String bootconfig = resolve(dir, bootmodule, "config"); printBootConfig(bootconfig, bootmodule); List list = ModuleConfig.readConfigurationFile(bootconfig); Module module = Module.addModule(list.get(0)); for (Klass k : Klass.getAllClasses()) { module.addKlass(k); } module.fixupDependencies(); if (printClassList) { module.printClassListTo(resolve(dir, bootmodule, "classlist")); module.printSummaryTo(resolve(dir, bootmodule, "summary")); } } // print boot.config file as an input to the ClassAnalyzer private static void printBootConfig(String output, String bootmodule) throws IOException { File f = new File(output); PrintWriter writer = new PrintWriter(f); try { int count = 0; writer.format("module %s {%n", bootmodule); for (Klass k : Klass.getAllClasses()) { if (count++ == 0) { writer.format("%4s%7s %s", "", "include", k); } else { writer.format(",%n"); writer.format("%4s%7s %s", "", "", k); } } writer.format(";%n}%n"); } finally { writer.close(); } } private static String resolve(File dir, String mname, String suffix) { File f = new File(dir, mname + "." + suffix); return f.toString(); } static List methods = new LinkedList(); static Deque pending = new ArrayDeque(); static Deque interfaceMethodRefs = new ArrayDeque(); static Filter filter = new Filter(); private static void followRoots() throws IOException { MethodDescriptor md = null; while ((md = pending.poll()) != null) { if (!methods.contains(md)) { methods.add(md); if (md.classname.isEmpty()) { trace("Warning: class missing %s%n", md); continue; } if (filter.isExcluded(md.classname)) { trace("excluded %s%n", md); } else { KlassInfo kinfo = getKlassInfo(md.classname); if (kinfo.classname.contains("$")) { int pos = kinfo.classname.lastIndexOf('$'); String outer = kinfo.classname.substring(0, pos); if (!cache.containsKey(outer)) { trace(" include outer class %s%n", outer); getKlassInfo(outer).ensureParse(); } } kinfo.ensureParse(); if (md.methodname.length() > 0) { if (filter.isExcluded(md.name)) { trace("excluded %s%n", md); } else { if (md.interfaceMethodRef) { trace("interface methodref %s%n", md); interfaceMethodRefs.add(md); } else { List descriptors = kinfo.parse(md); if (descriptors.isEmpty()) { if (kinfo.getSuperclass() != null) { String sn = kinfo.getSuperclass().classname; MethodDescriptor superMD = new MethodDescriptor(sn + "." + md.methodname, md.descriptor, false); if (!methods.contains(superMD) && !pending.contains(superMD)) { trace(" delegated %s to %s%n", md, superMD); pending.add(superMD); } } else if (kinfo.isClass()) { trace(" %s (not found)%n", md); } else { trace(" %s (interface)%n", md); } } else { if (md.descriptor.equals("*")) { trace(" parsed %s : ", md.name); for (String s : descriptors) { trace(" %s", s); } trace("%n"); } } } } } } } if (pending.isEmpty()) { for (Klass k : Klass.getAllClasses()) { if (k.getFileSize() == 0) { getKlassInfo(k.getClassName()).ensureParse(); } } while ((md = interfaceMethodRefs.poll()) != null) { addSubClassMethods(md); } } } } static void addSubClassMethods(MethodDescriptor md) throws IOException { for (KlassInfo kinfo : getSubClasses(md.classname)) { String methodname = kinfo.classname + "." + md.methodname; MethodDescriptor other = new MethodDescriptor(methodname, md.descriptor, false); if (!methods.contains(other) && !pending.contains(other)) { trace("Warning: subclass from %s to %s%n", md.classname, other); pending.add(other); } } } private final static String privilegedActionInterf = "java.security.PrivilegedAction"; private final static String privilegedExceptionActionInterf = "java.security.PrivilegedExceptionAction"; static boolean isPrivilegedAction(String classname) { if (classname.isEmpty()) { return false; } KlassInfo kinfo = getKlassInfo(classname); for (KlassInfo ki : kinfo.getInterfaces()) { String interf = ki.classname; if (interf.equals(privilegedActionInterf) || interf.equals(privilegedExceptionActionInterf)) { return true; } } return false; } static Map cache = new HashMap(); static KlassInfo getKlassInfo(String classname) { classname = classname.replace('/', '.'); KlassInfo kinfo = cache.get(classname); if (kinfo == null) { kinfo = new KlassInfo(classname); cache.put(classname, kinfo); } return kinfo; } static class KlassInfo { final String classname; private ClassFileParser parser; private KlassInfo superclass; private List interfaces = new LinkedList(); KlassInfo(String classname) { this.classname = classname; } boolean isClass() { ensureParse(); return parser.classfile.isClass(); } KlassInfo getSuperclass() { ensureParse(); return superclass; } List getInterfaces() { ensureParse(); return java.util.Collections.unmodifiableList(interfaces); } void ensureParse() { try { getClassFileParser(); } catch (IOException e) { throw new RuntimeException(e); } } synchronized ClassFileParser getClassFileParser() throws IOException { if (parser == null) { parser = ClassPath.parserForClass(classname); if (parser != null) { parseClassFile(); List descriptors = parse(new MethodDescriptor(classname + ".", "()V", false)); } } return parser; } List parse(MethodDescriptor md) { ensureParse(); try { List descriptors = new LinkedList(); for (Method m : parser.classfile.methods) { String name = m.getName(parser.classfile.constant_pool); String desc = parser.constantPoolParser.getDescriptor(m.descriptor.index); if (name.equals(md.methodname)) { if (md.descriptor.equals("*") || md.descriptor.equals(desc)) { parseMethod(parser, m); descriptors.add(desc); } } } return descriptors; } catch (ConstantPoolException ex) { throw new RuntimeException(ex); } } private void parseClassFile() throws IOException { parser.parseClassInfo(); ClassFile classfile = parser.classfile; try { if (classfile.super_class > 0) { superclass = getKlassInfo(classfile.getSuperclassName()); } if (classfile.interfaces != null) { for (int i = 0; i < classfile.interfaces.length; i++) { interfaces.add(getKlassInfo(classfile.getInterfaceName(i))); } } } catch (ConstantPoolException ex) { throw new RuntimeException(ex); } } } static List getSubClasses(String classname) throws IOException { List result = new LinkedList(); List list = new LinkedList(); list.addAll(cache.values()); for (KlassInfo kinfo : list) { if (kinfo.getSuperclass() != null && classname.equals(kinfo.getSuperclass().classname)) { result.add(kinfo); } for (KlassInfo interf : kinfo.getInterfaces()) { if (classname.equals(interf.classname)) { result.add(kinfo); } } } return result; } private static void parseConfigFile(String config) throws IOException { FileInputStream in = new FileInputStream(config); try { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line; int lineNumber = 0; while ((line = reader.readLine()) != null) { lineNumber++; if ((line = line.trim()).length() > 0) { if (line.startsWith("#")) { continue; } String[] s = line.split("\\s+"); if ("exclude".equals(s[0])) { filter.exclude(s[1]); } else { String name = s[0].replace('/', '.'); if (name.length() > 0) { String classname = name.replace('/', '.'); if (s.length == 2) { // method name int pos = classname.lastIndexOf('.'); classname = classname.substring(0, pos); } KlassInfo kinfo = getKlassInfo(classname); if (kinfo.getClassFileParser() != null) { // class exists MethodDescriptor md = (s.length == 1) ? new MethodDescriptor(name) : new MethodDescriptor(name, s[1], false); if (!pending.contains(md)) { pending.add(md); } } else { // class not found trace("Class %s not found%n", classname); } } } } } } finally { in.close(); } } private static void parseMethod(ClassFileParser cfparser, Method m) { Klass.Method kmethod = cfparser.parseMethod(m); Code_attribute c_attr = (Code_attribute) m.attributes.get(Attribute.Code); if (c_attr != null) { LineNumberTable_attribute lineNumTable = (LineNumberTable_attribute) c_attr.attributes.get(Attribute.LineNumberTable); InstructorVisitor visitor = new InstructorVisitor(cfparser, lineNumTable); trace("parseMethod %s %s %n", cfparser.this_klass, kmethod); for (Instruction instr : c_attr.getInstructions()) { try { instr.accept(visitor, kmethod); } catch (ArrayIndexOutOfBoundsException e) { throw new RuntimeException("error at or after byte " + instr.getPC()); } } if (c_attr.exception_table_langth > 0) { for (int i = 0; i < c_attr.exception_table.length; i++) { Code_attribute.Exception_data handler = c_attr.exception_table[i]; int catch_type = handler.catch_type; if (catch_type > 0) { visitor.addConstantPoolRef(catch_type, kmethod, handler.start_pc); } } } } } static class MethodDescriptor { final String name; final String classname; final String methodname; final String descriptor; final boolean interfaceMethodRef; MethodDescriptor(String classname) { this.classname = classname.replace('/', '.'); this.name = this.classname; this.methodname = ""; this.descriptor = ""; this.interfaceMethodRef = false; if (this.classname.length() == 1) { throw new RuntimeException("invalid " + this); } } MethodDescriptor(String name, String descriptor, boolean interfaceMethodRef) { name = name.replace('/', '.'); this.name = name; int pos = name.lastIndexOf('.'); this.classname = name.substring(0, pos); this.methodname = name.substring(pos + 1, name.length()); this.descriptor = descriptor; this.interfaceMethodRef = interfaceMethodRef; if (this.classname.length() == 1) { throw new RuntimeException("invalid " + this); } } @Override public boolean equals(Object obj) { MethodDescriptor m = (MethodDescriptor) obj; return this.name.equals(m.name) && this.descriptor.equals(m.descriptor); } @Override public int hashCode() { int hash = 7; hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); hash = 97 * hash + (this.descriptor != null ? this.descriptor.hashCode() : 0); return hash; } public String toString() { if (descriptor.isEmpty()) { return name; } else { return name + " : " + descriptor; } } } static class Filter { private Set excludes = new TreeSet(); Filter exclude(String pattern) { excludes.add(pattern); return this; } boolean isExcluded(String klass) { for (String pattern : excludes) { if (matches(klass, pattern)) { return true; } } return false; } private boolean matches(String klass, String pattern) { int pos = klass.lastIndexOf('.'); String packageName = pos > 0 ? klass.substring(0, pos) : ""; if (pattern.endsWith("**")) { String p = pattern.substring(0, pattern.length() - 2); return klass.startsWith(p); } else if (pattern.endsWith("*")) { pos = pattern.lastIndexOf('.'); String pkg = pos > 0 ? pattern.substring(0, pos) : ""; if (packageName.equals(pkg)) { // package name has to be exact match String p = pattern.substring(0, pattern.length() - 1); return klass.startsWith(p); } else { return false; } } else { // exact match or inner class return klass.equals(pattern) || klass.startsWith(pattern + "$"); } } } static class InstructorVisitor implements Instruction.KindVisitor { private final ClassFileParser parser; private final LineNumberTable_attribute lineNumTable; InstructorVisitor(ClassFileParser parser, LineNumberTable_attribute lineNumTable) { this.parser = parser; this.lineNumTable = lineNumTable; } int getLineNumber(int pc) { if (lineNumTable != null) { int start_pc = 0; int lineno = 0; for (int i = 0; i < lineNumTable.line_number_table_length; i++) { int cur_start_pc = lineNumTable.line_number_table[i].start_pc; if (pc == 0 && cur_start_pc == 0) { return lineNumTable.line_number_table[i].line_number; } else if (pc >= start_pc && pc < cur_start_pc) { return lineno; } start_pc = cur_start_pc; lineno = lineNumTable.line_number_table[i].line_number; } } return 0; } void addConstantPoolRef(int index, Klass.Method m, int pc) { try { CPInfo cpInfo = parser.classfile.constant_pool.get(index); String name = cpInfo.accept(typeFinder, null); if (name != null) { trace(" %s %s at line %d%n", parser.constantPoolParser.tagName(index), name, getLineNumber(pc)); } } catch (InvalidIndex ex) { throw new RuntimeException(ex); } } public Void visitNoOperands(Instruction instr, Klass.Method m) { return null; } public Void visitArrayType(Instruction instr, TypeKind kind, Klass.Method m) { return null; } public Void visitBranch(Instruction instr, int offset, Klass.Method m) { return null; } public Void visitConstantPoolRef(Instruction instr, int index, Klass.Method m) { addConstantPoolRef(index, m, instr.getPC()); return null; } public Void visitConstantPoolRefAndValue(Instruction instr, int index, int value, Klass.Method m) { addConstantPoolRef(index, m, instr.getPC()); return null; } public Void visitLocal(Instruction instr, int index, Klass.Method m) { return null; } public Void visitLocalAndValue(Instruction instr, int index, int value, Klass.Method m) { return null; } public Void visitLookupSwitch(Instruction instr, int default_, int npairs, int[] matches, int[] offsets, Klass.Method m) { return null; } public Void visitTableSwitch(Instruction instr, int default_, int low, int high, int[] offsets, Klass.Method m) { return null; } public Void visitValue(Instruction instr, int value, Klass.Method m) { return null; } public Void visitUnknown(Instruction instr, Klass.Method m) { return null; } private ConstantPool.Visitor typeFinder = new ConstantPool.Visitor() { String getClassName(CPRefInfo info, Void p) { try { return parser.checkClassName(info.getClassName()).replace('/', '.'); } catch (ConstantPoolException ex) { throw new RuntimeException(ex); } } boolean addReferencedClass(String name) { if (Klass.findKlass(name) == null) { MethodDescriptor md = new MethodDescriptor(name); if (!methods.contains(md) && !pending.contains(md)) { pending.add(md); } return true; } return false; } private String privilegedActionClass = ""; void cachePrivilegedAction(String classname) { trace(" found PrivilegedAction %s%n", classname); privilegedActionClass = classname; } void doPrivilegedCall(String method) { if (privilegedActionClass.length() > 0) { MethodDescriptor md = new MethodDescriptor(privilegedActionClass + ".run", "*", false); if (!methods.contains(md) && !pending.contains(md)) { trace(" doPrivileged %s%n", md); pending.add(md); } } } private String addMethodDescriptor(CPRefInfo info, Void p) { try { String classname = getClassName(info, null); String method = classname + "." + info.getNameAndTypeInfo().getName(); String descriptor = info.getNameAndTypeInfo().getType(); if (method.endsWith(".") && isPrivilegedAction(classname)) { cachePrivilegedAction(classname); } if (method.equals("java.security.AccessController.doPrivileged")) { doPrivilegedCall(method); return method; } boolean interfaceMethodRef = info instanceof CONSTANT_InterfaceMethodref_info; MethodDescriptor md = new MethodDescriptor(method, descriptor, interfaceMethodRef); if (!methods.contains(md) && !pending.contains(md)) { pending.add(md); } return method; } catch (ConstantPoolException e) { throw new RuntimeException(e); } } public String visitClass(CONSTANT_Class_info info, Void p) { try { String classname = parser.checkClassName(info.getName()).replace('/', '.'); if (classname.length() > 0) { addReferencedClass(classname); } return classname; } catch (ConstantPoolException ex) { throw new RuntimeException(ex); } } public String visitDouble(CONSTANT_Double_info info, Void p) { // skip return null; } public String visitFieldref(CONSTANT_Fieldref_info info, Void p) { try { String classname = getClassName(info, p); if (classname.length() > 0) { addReferencedClass(classname); } String type = info.getNameAndTypeInfo().getType(); String fieldType = parser.checkClassName(type).replace('/', '.'); if (fieldType.length() > 0) { addReferencedClass(classname); } return parser.constantPoolParser.stringValue(info); } catch (ConstantPoolException e) { throw new RuntimeException(e); } } public String visitFloat(CONSTANT_Float_info info, Void p) { // skip return null; } public String visitInteger(CONSTANT_Integer_info info, Void p) { // skip return null; } public String visitInterfaceMethodref(CONSTANT_InterfaceMethodref_info info, Void p) { return addMethodDescriptor(info, p); } public String visitLong(CONSTANT_Long_info info, Void p) { // skip return null; } public String visitNameAndType(CONSTANT_NameAndType_info info, Void p) { // skip return null; } public String visitMethodref(CONSTANT_Methodref_info info, Void p) { return addMethodDescriptor(info, p); } public String visitString(CONSTANT_String_info info, Void p) { // skip return null; } public String visitUtf8(CONSTANT_Utf8_info info, Void p) { return null; } }; } static boolean traceOn = System.getProperty("classanalyzer.debug") != null; private static void trace(String format, Object... args) { if (traceOn) { System.out.format(format, args); } } private static void usage() { System.out.println("Usage: BootAnalyzer "); System.out.println("Options: "); System.out.println("\t-jdkhome where all jars will be parsed"); System.out.println("\t-config "); System.out.println("\t-output "); System.out.println("\t-classlist print class list and summary"); System.exit(-1); } }