1 /*
   2  * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  *
  23  */
  24 package com.sun.classanalyzer;
  25 
  26 import java.io.BufferedReader;
  27 import java.io.FileInputStream;
  28 import java.io.IOException;
  29 import java.io.InputStreamReader;
  30 import java.io.PrintWriter;
  31 import java.io.File;
  32 import java.util.ArrayDeque;
  33 import java.util.Deque;
  34 import java.util.HashMap;
  35 import java.util.LinkedList;
  36 import java.util.List;
  37 import java.util.Map;
  38 import java.util.Set;
  39 import java.util.TreeSet;
  40 
  41 import com.sun.tools.classfile.*;
  42 import com.sun.tools.classfile.ConstantPool.*;
  43 import static com.sun.tools.classfile.ConstantPool.*;
  44 import com.sun.tools.classfile.Instruction.TypeKind;
  45 import com.sun.tools.classfile.Type.*;
  46 
  47 /**
  48  * Generate the module config for the boot module with
  49  * a given set of roots (classes or methods) and exclude list.
  50  *
  51  * This tool does method-level dependency analysis starting
  52  * from the root set and follows references transitively as follows:
  53  * <ul>
  54  * <li>For a given class, it will parse the ClassFile to
  55  *     find its superclass and superinterfaces and also
  56  *     its static initializer &lt;clinit&gt;.</li>
  57  * <li>For each method, it will parse its Code attribute
  58  *     to look for a Methodref, Fieldref, and InterfaceMethodref.
  59  *     </li>
  60  * <li>For each Fieldref, it will include the type of
  61  *     the field in the dependency.</li>
  62  * <li>For each MethodRef, it will follow all references in
  63  *     that method.</li>
  64  * <li>For each InterfaceMethodref, it will follow all references in
  65  *     that method defined its implementation classes in
  66  *     the resulting dependency list.</li>
  67  * </ul>
  68  *
  69  * Limitation:
  70  * <ul>
  71  * <li>For each Methodref, it only parses the method of
  72  *     the specified type.  It doesn't analyze the class hierarchy
  73  *     and follow references of its subclasses since it ends up
  74  *     pulls in many unnecessary dependencies.  For now,
  75  *     the list of subclasses and methods need to be listed in
  76  *     the root set.</li>
  77  * </ul>
  78  *
  79  * @author Mandy Chung
  80  */
  81 public class BootAnalyzer {
  82 
  83     public static void main(String[] args) throws Exception {
  84         String jdkhome = null;
  85         String config = null;
  86         String output = ".";
  87         boolean printClassList = false;
  88 
  89         // process arguments
  90         int i = 0;
  91         while (i < args.length) {
  92             String arg = args[i++];
  93             if (arg.equals("-jdkhome")) {
  94                 if (i < args.length) {
  95                     jdkhome = args[i++];
  96                 } else {
  97                     usage();
  98                 }
  99             } else if (arg.equals("-config")) {
 100                 config = args[i++];
 101             } else if (arg.equals("-output")) {
 102                 output = args[i++];
 103             } else if (arg.equals("-classlist")) {
 104                 printClassList = true;
 105             } else {
 106                 usage();
 107             }
 108         }
 109 
 110 
 111 
 112         if (jdkhome == null || config == null) {
 113             usage();
 114         }
 115 
 116         File jre = new File(jdkhome, "jre");
 117         if (jre.exists()) {
 118             ClassPath.setJDKHome(jdkhome);
 119         } else {
 120             File classes = new File(jdkhome, "classes");
 121             if (classes.exists()) {
 122                 ClassPath.setClassPath(classes.getCanonicalPath());
 123             } else {
 124                 throw new RuntimeException("Invalid jdkhome: " + jdkhome);
 125             }
 126         }
 127 
 128         parseConfigFile(config);
 129         followRoots();
 130 
 131         // create output directory if it doesn't exist
 132         File dir = new File(output);
 133         if (!dir.isDirectory()) {
 134             if (!dir.exists()) {
 135                 boolean created = dir.mkdir();
 136                 if (!created) {
 137                     throw new RuntimeException("Unable to create `" + dir + "'");
 138                 }
 139             }
 140         }
 141 
 142         String bootmodule = "boot";
 143         String bootconfig = resolve(dir, bootmodule, "config");
 144         printBootConfig(bootconfig, bootmodule);
 145 
 146         List<ModuleConfig> list = ModuleConfig.readConfigurationFile(bootconfig);
 147         Module module = Module.addModule(list.get(0));
 148         for (Klass k : Klass.getAllClasses()) {
 149             module.addKlass(k);
 150         }
 151         module.fixupDependencies();
 152 
 153         if (printClassList) {
 154             module.printClassListTo(resolve(dir, bootmodule, "classlist"));
 155             module.printSummaryTo(resolve(dir, bootmodule, "summary"));
 156         }
 157     }
 158 
 159     // print boot.config file as an input to the ClassAnalyzer
 160     private static void printBootConfig(String output, String bootmodule) throws IOException {
 161 
 162         File f = new File(output);
 163         PrintWriter writer = new PrintWriter(f);
 164         try {
 165             int count = 0;
 166             writer.format("module %s {%n", bootmodule);
 167             for (Klass k : Klass.getAllClasses()) {
 168                 if (count++ == 0) {
 169                     writer.format("%4s%7s %s", "", "include", k);
 170                 } else {
 171                     writer.format(",%n");
 172                     writer.format("%4s%7s %s", "", "", k);
 173                 }
 174             }
 175             writer.format(";%n}%n");
 176         } finally {
 177             writer.close();
 178         }
 179     }
 180 
 181     private static String resolve(File dir, String mname, String suffix) {
 182         File f = new File(dir, mname + "." + suffix);
 183         return f.toString();
 184 
 185     }
 186     static List<MethodDescriptor> methods = new LinkedList<MethodDescriptor>();
 187     static Deque<MethodDescriptor> pending = new ArrayDeque<MethodDescriptor>();
 188     static Deque<MethodDescriptor> interfaceMethodRefs = new ArrayDeque<MethodDescriptor>();
 189     static Filter filter = new Filter();
 190 
 191     private static void followRoots() throws IOException {
 192         MethodDescriptor md = null;
 193 
 194         while ((md = pending.poll()) != null) {
 195             if (!methods.contains(md)) {
 196                 methods.add(md);
 197                 if (md.classname.isEmpty()) {
 198                     trace("Warning: class missing %s%n", md);
 199                     continue;
 200                 }
 201 
 202                 if (filter.isExcluded(md.classname)) {
 203                     trace("excluded %s%n", md);
 204                 } else {
 205                     KlassInfo kinfo = getKlassInfo(md.classname);
 206                     if (kinfo.classname.contains("$")) {
 207                         int pos = kinfo.classname.lastIndexOf('$');
 208                         String outer = kinfo.classname.substring(0, pos);
 209                         if (!cache.containsKey(outer)) {
 210                             trace("  include outer class %s%n", outer);
 211                             getKlassInfo(outer).ensureParse();
 212                         }
 213                     }
 214 
 215                     kinfo.ensureParse();
 216                     if (md.methodname.length() > 0) {
 217                         if (filter.isExcluded(md.name)) {
 218                             trace("excluded %s%n", md);
 219                         } else {
 220                             if (md.interfaceMethodRef) {
 221                                 trace("interface methodref %s%n", md);
 222                                 interfaceMethodRefs.add(md);
 223                             } else {
 224                                 List<String> descriptors = kinfo.parse(md);
 225                                 if (descriptors.isEmpty()) {
 226                                     if (kinfo.getSuperclass() != null) {
 227                                         String sn = kinfo.getSuperclass().classname;
 228                                         MethodDescriptor superMD = new MethodDescriptor(sn + "." + md.methodname, md.descriptor, false);
 229                                         if (!methods.contains(superMD) && !pending.contains(superMD)) {
 230                                             trace("  delegated %s to %s%n", md, superMD);
 231                                             pending.add(superMD);
 232                                         }
 233                                     } else if (kinfo.isClass()) {
 234                                         trace("  %s (not found)%n", md);
 235                                     } else {
 236                                         trace("  %s (interface)%n", md);
 237                                     }
 238                                 } else {
 239                                     if (md.descriptor.equals("*")) {
 240                                         trace("  parsed %s : ", md.name);
 241                                         for (String s : descriptors) {
 242                                             trace(" %s", s);
 243                                         }
 244                                         trace("%n");
 245                                     }
 246                                 }
 247                             }
 248                         }
 249                     }
 250                 }
 251             }
 252             if (pending.isEmpty()) {
 253                 for (Klass k : Klass.getAllClasses()) {
 254                     if (k.getFileSize() == 0) {
 255                         getKlassInfo(k.getClassName()).ensureParse();
 256                     }
 257                 }
 258                 while ((md = interfaceMethodRefs.poll()) != null) {
 259                     addSubClassMethods(md);
 260                 }
 261             }
 262         }
 263     }
 264 
 265     static void addSubClassMethods(MethodDescriptor md) throws IOException {
 266         for (KlassInfo kinfo : getSubClasses(md.classname)) {
 267             String methodname = kinfo.classname + "." + md.methodname;
 268             MethodDescriptor other = new MethodDescriptor(methodname, md.descriptor, false);
 269             if (!methods.contains(other) && !pending.contains(other)) {
 270                 trace("Warning: subclass from %s to %s%n", md.classname, other);
 271                 pending.add(other);
 272             }
 273         }
 274     }
 275     private final static String privilegedActionInterf = "java.security.PrivilegedAction";
 276     private final static String privilegedExceptionActionInterf = "java.security.PrivilegedExceptionAction";
 277 
 278     static boolean isPrivilegedAction(String classname) {
 279         if (classname.isEmpty()) {
 280             return false;
 281         }
 282         KlassInfo kinfo = getKlassInfo(classname);
 283         for (KlassInfo ki : kinfo.getInterfaces()) {
 284             String interf = ki.classname;
 285             if (interf.equals(privilegedActionInterf) ||
 286                     interf.equals(privilegedExceptionActionInterf)) {
 287                 return true;
 288             }
 289         }
 290         return false;
 291     }
 292     static Map<String, KlassInfo> cache = new HashMap<String, KlassInfo>();
 293 
 294     static KlassInfo getKlassInfo(String classname) {
 295         classname = classname.replace('/', '.');
 296 
 297         KlassInfo kinfo = cache.get(classname);
 298         if (kinfo == null) {
 299             kinfo = new KlassInfo(classname);
 300             cache.put(classname, kinfo);
 301         }
 302         return kinfo;
 303     }
 304 
 305     static class KlassInfo {
 306 
 307         final String classname;
 308         private ClassFileParser parser;
 309         private KlassInfo superclass;
 310         private List<KlassInfo> interfaces = new LinkedList<KlassInfo>();
 311 
 312         KlassInfo(String classname) {
 313             this.classname = classname;
 314         }
 315 
 316         boolean isClass() {
 317             ensureParse();
 318             return parser.classfile.isClass();
 319         }
 320 
 321         KlassInfo getSuperclass() {
 322             ensureParse();
 323             return superclass;
 324         }
 325 
 326         List<KlassInfo> getInterfaces() {
 327             ensureParse();
 328             return java.util.Collections.unmodifiableList(interfaces);
 329         }
 330 
 331         void ensureParse() {
 332             try {
 333                 getClassFileParser();
 334             } catch (IOException e) {
 335                 throw new RuntimeException(e);
 336             }
 337         }
 338 
 339         synchronized ClassFileParser getClassFileParser() throws IOException {
 340             if (parser == null) {
 341                 parser = ClassPath.parserForClass(classname);
 342                 if (parser != null) {
 343                     parseClassFile();
 344                     List<String> descriptors = parse(new MethodDescriptor(classname + ".<clinit>", "()V", false));
 345                 }
 346             }
 347             return parser;
 348         }
 349 
 350         List<String> parse(MethodDescriptor md) {
 351             ensureParse();
 352             try {
 353                 List<String> descriptors = new LinkedList<String>();
 354                 for (Method m : parser.classfile.methods) {
 355                     String name = m.getName(parser.classfile.constant_pool);
 356                     String desc = parser.constantPoolParser.getDescriptor(m.descriptor.index);
 357                     if (name.equals(md.methodname)) {
 358                         if (md.descriptor.equals("*") || md.descriptor.equals(desc)) {
 359                             parseMethod(parser, m);
 360                             descriptors.add(desc);
 361                         }
 362                     }
 363                 }
 364                 return descriptors;
 365             } catch (ConstantPoolException ex) {
 366                 throw new RuntimeException(ex);
 367             }
 368         }
 369 
 370         private void parseClassFile() throws IOException {
 371             parser.parseClassInfo();
 372 
 373             ClassFile classfile = parser.classfile;
 374             try {
 375                 if (classfile.super_class > 0) {
 376                     superclass = getKlassInfo(classfile.getSuperclassName());
 377                 }
 378                 if (classfile.interfaces != null) {
 379                     for (int i = 0; i < classfile.interfaces.length; i++) {
 380                         interfaces.add(getKlassInfo(classfile.getInterfaceName(i)));
 381                     }
 382                 }
 383             } catch (ConstantPoolException ex) {
 384                 throw new RuntimeException(ex);
 385             }
 386         }
 387     }
 388 
 389     static List<KlassInfo> getSubClasses(String classname) throws IOException {
 390         List<KlassInfo> result = new LinkedList<KlassInfo>();
 391         List<KlassInfo> list = new LinkedList<KlassInfo>();
 392         list.addAll(cache.values());
 393         for (KlassInfo kinfo : list) {
 394             if (kinfo.getSuperclass() != null && classname.equals(kinfo.getSuperclass().classname)) {
 395                 result.add(kinfo);
 396             }
 397             for (KlassInfo interf : kinfo.getInterfaces()) {
 398                 if (classname.equals(interf.classname)) {
 399                     result.add(kinfo);
 400                 }
 401             }
 402         }
 403         return result;
 404     }
 405 
 406     private static void parseConfigFile(String config) throws IOException {
 407         FileInputStream in = new FileInputStream(config);
 408         try {
 409             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
 410             String line;
 411             int lineNumber = 0;
 412             while ((line = reader.readLine()) != null) {
 413                 lineNumber++;
 414                 if ((line = line.trim()).length() > 0) {
 415                     if (line.startsWith("#")) {
 416                         continue;
 417                     }
 418 
 419                     String[] s = line.split("\\s+");
 420                     if ("exclude".equals(s[0])) {
 421                         filter.exclude(s[1]);
 422                     } else {
 423                         String name = s[0].replace('/', '.');
 424                         if (name.length() > 0) {
 425                             String classname = name.replace('/', '.');
 426                             if (s.length == 2) {
 427                                 // method name
 428                                 int pos = classname.lastIndexOf('.');
 429                                 classname = classname.substring(0, pos);
 430                             }
 431 
 432                             KlassInfo kinfo = getKlassInfo(classname);
 433                             if (kinfo.getClassFileParser() != null) {
 434                                 // class exists
 435                                 MethodDescriptor md = (s.length == 1) ? new MethodDescriptor(name) : new MethodDescriptor(name, s[1], false);
 436                                 if (!pending.contains(md)) {
 437                                     pending.add(md);
 438                                 }
 439                             } else {
 440                                 // class not found
 441                                 trace("Class %s not found%n", classname);
 442                             }
 443                         }
 444                     }
 445                 }
 446             }
 447 
 448         } finally {
 449             in.close();
 450         }
 451     }
 452 
 453     private static void parseMethod(ClassFileParser cfparser, Method m) {
 454         Klass.Method kmethod = cfparser.parseMethod(m);
 455         Code_attribute c_attr = (Code_attribute) m.attributes.get(Attribute.Code);
 456         if (c_attr != null) {
 457             LineNumberTable_attribute lineNumTable =
 458                     (LineNumberTable_attribute) c_attr.attributes.get(Attribute.LineNumberTable);
 459             InstructorVisitor visitor = new InstructorVisitor(cfparser, lineNumTable);
 460             trace("parseMethod %s %s %n", cfparser.this_klass, kmethod);
 461             for (Instruction instr : c_attr.getInstructions()) {
 462                 try {
 463                     instr.accept(visitor, kmethod);
 464                 } catch (ArrayIndexOutOfBoundsException e) {
 465                     throw new RuntimeException("error at or after byte " + instr.getPC());
 466                 }
 467 
 468             }
 469 
 470             if (c_attr.exception_table_langth > 0) {
 471                 for (int i = 0; i <
 472                         c_attr.exception_table.length; i++) {
 473                     Code_attribute.Exception_data handler = c_attr.exception_table[i];
 474                     int catch_type = handler.catch_type;
 475                     if (catch_type > 0) {
 476                         visitor.addConstantPoolRef(catch_type, kmethod, handler.start_pc);
 477                     }
 478 
 479                 }
 480             }
 481         }
 482     }
 483 
 484     static class MethodDescriptor {
 485 
 486         final String name;
 487         final String classname;
 488         final String methodname;
 489         final String descriptor;
 490         final boolean interfaceMethodRef;
 491 
 492         MethodDescriptor(String classname) {
 493             this.classname = classname.replace('/', '.');
 494             this.name = this.classname;
 495             this.methodname = "";
 496             this.descriptor = "";
 497             this.interfaceMethodRef = false;
 498             if (this.classname.length() == 1) {
 499                 throw new RuntimeException("invalid " + this);
 500             }
 501         }
 502 
 503         MethodDescriptor(String name, String descriptor, boolean interfaceMethodRef) {
 504             name = name.replace('/', '.');
 505             this.name = name;
 506             int pos = name.lastIndexOf('.');
 507             this.classname = name.substring(0, pos);
 508             this.methodname = name.substring(pos + 1, name.length());
 509             this.descriptor = descriptor;
 510             this.interfaceMethodRef = interfaceMethodRef;
 511             if (this.classname.length() == 1) {
 512                 throw new RuntimeException("invalid " + this);
 513             }
 514         }
 515 
 516         @Override
 517         public boolean equals(Object obj) {
 518             MethodDescriptor m = (MethodDescriptor) obj;
 519 
 520             return this.name.equals(m.name) &&
 521                     this.descriptor.equals(m.descriptor);
 522         }
 523 
 524         @Override
 525         public int hashCode() {
 526             int hash = 7;
 527             hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
 528             hash = 97 * hash + (this.descriptor != null ? this.descriptor.hashCode() : 0);
 529             return hash;
 530         }
 531 
 532         public String toString() {
 533             if (descriptor.isEmpty()) {
 534                 return name;
 535             } else {
 536                 return name + " : " + descriptor;
 537             }
 538         }
 539     }
 540 
 541     static class Filter {
 542 
 543         private Set<String> excludes = new TreeSet<String>();
 544 
 545         Filter exclude(String pattern) {
 546             excludes.add(pattern);
 547             return this;
 548         }
 549 
 550         boolean isExcluded(String klass) {
 551             for (String pattern : excludes) {
 552                 if (matches(klass, pattern)) {
 553                     return true;
 554                 }
 555             }
 556             return false;
 557         }
 558 
 559         private boolean matches(String klass, String pattern) {
 560             int pos = klass.lastIndexOf('.');
 561             String packageName = pos > 0 ? klass.substring(0, pos) : "<unnamed>";
 562             if (pattern.endsWith("**")) {
 563                 String p = pattern.substring(0, pattern.length() - 2);
 564                 return klass.startsWith(p);
 565             } else if (pattern.endsWith("*")) {
 566                 pos = pattern.lastIndexOf('.');
 567                 String pkg = pos > 0 ? pattern.substring(0, pos) : "<unnamed>";
 568                 if (packageName.equals(pkg)) {
 569                     // package name has to be exact match
 570                     String p = pattern.substring(0, pattern.length() - 1);
 571                     return klass.startsWith(p);
 572                 } else {
 573                     return false;
 574                 }
 575             } else {
 576                 // exact match or inner class
 577                 return klass.equals(pattern) || klass.startsWith(pattern + "$");
 578             }
 579         }
 580     }
 581 
 582     static class InstructorVisitor implements Instruction.KindVisitor<Void, Klass.Method> {
 583 
 584         private final ClassFileParser parser;
 585         private final LineNumberTable_attribute lineNumTable;
 586 
 587         InstructorVisitor(ClassFileParser parser, LineNumberTable_attribute lineNumTable) {
 588             this.parser = parser;
 589             this.lineNumTable = lineNumTable;
 590         }
 591 
 592         int getLineNumber(int pc) {
 593             if (lineNumTable != null) {
 594                 int start_pc = 0;
 595                 int lineno = 0;
 596                 for (int i = 0; i < lineNumTable.line_number_table_length; i++) {
 597                     int cur_start_pc = lineNumTable.line_number_table[i].start_pc;
 598                     if (pc == 0 && cur_start_pc == 0) {
 599                         return lineNumTable.line_number_table[i].line_number;
 600                     } else if (pc >= start_pc && pc < cur_start_pc) {
 601                         return lineno;
 602                     }
 603                     start_pc = cur_start_pc;
 604                     lineno = lineNumTable.line_number_table[i].line_number;
 605                 }
 606             }
 607             return 0;
 608         }
 609 
 610         void addConstantPoolRef(int index, Klass.Method m, int pc) {
 611             try {
 612                 CPInfo cpInfo = parser.classfile.constant_pool.get(index);
 613                 String name = cpInfo.accept(typeFinder, null);
 614                 if (name != null) {
 615                     trace("   %s %s at line %d%n", parser.constantPoolParser.tagName(index), name, getLineNumber(pc));
 616                 }
 617             } catch (InvalidIndex ex) {
 618                 throw new RuntimeException(ex);
 619             }
 620         }
 621 
 622         public Void visitNoOperands(Instruction instr, Klass.Method m) {
 623             return null;
 624         }
 625 
 626         public Void visitArrayType(Instruction instr, TypeKind kind, Klass.Method m) {
 627             return null;
 628         }
 629 
 630         public Void visitBranch(Instruction instr, int offset, Klass.Method m) {
 631             return null;
 632         }
 633 
 634         public Void visitConstantPoolRef(Instruction instr, int index, Klass.Method m) {
 635             addConstantPoolRef(index, m, instr.getPC());
 636             return null;
 637         }
 638 
 639         public Void visitConstantPoolRefAndValue(Instruction instr, int index, int value, Klass.Method m) {
 640             addConstantPoolRef(index, m, instr.getPC());
 641             return null;
 642         }
 643 
 644         public Void visitLocal(Instruction instr, int index, Klass.Method m) {
 645             return null;
 646         }
 647 
 648         public Void visitLocalAndValue(Instruction instr, int index, int value, Klass.Method m) {
 649             return null;
 650         }
 651 
 652         public Void visitLookupSwitch(Instruction instr, int default_, int npairs, int[] matches, int[] offsets, Klass.Method m) {
 653             return null;
 654         }
 655 
 656         public Void visitTableSwitch(Instruction instr, int default_, int low, int high, int[] offsets, Klass.Method m) {
 657             return null;
 658         }
 659 
 660         public Void visitValue(Instruction instr, int value, Klass.Method m) {
 661             return null;
 662         }
 663 
 664         public Void visitUnknown(Instruction instr, Klass.Method m) {
 665             return null;
 666         }
 667         private ConstantPool.Visitor<String, Void> typeFinder = new ConstantPool.Visitor<String, Void>() {
 668 
 669             String getClassName(CPRefInfo info, Void p) {
 670                 try {
 671                     return parser.checkClassName(info.getClassName()).replace('/', '.');
 672                 } catch (ConstantPoolException ex) {
 673                     throw new RuntimeException(ex);
 674                 }
 675             }
 676 
 677             boolean addReferencedClass(String name) {
 678                 if (Klass.findKlass(name) == null) {
 679                     MethodDescriptor md = new MethodDescriptor(name);
 680                     if (!methods.contains(md) && !pending.contains(md)) {
 681                         pending.add(md);
 682                     }
 683                     return true;
 684                 }
 685                 return false;
 686             }
 687             private String privilegedActionClass = "";
 688 
 689             void cachePrivilegedAction(String classname) {
 690                 trace("   found PrivilegedAction %s%n", classname);
 691                 privilegedActionClass = classname;
 692             }
 693 
 694             void doPrivilegedCall(String method) {
 695                 if (privilegedActionClass.length() > 0) {
 696                     MethodDescriptor md = new MethodDescriptor(privilegedActionClass + ".run", "*", false);
 697                     if (!methods.contains(md) && !pending.contains(md)) {
 698                         trace("   doPrivileged %s%n", md);
 699                         pending.add(md);
 700                     }
 701                 }
 702             }
 703 
 704             private String addMethodDescriptor(CPRefInfo info, Void p) {
 705                 try {
 706                     String classname = getClassName(info, null);
 707                     String method = classname + "." + info.getNameAndTypeInfo().getName();
 708                     String descriptor = info.getNameAndTypeInfo().getType();
 709 
 710                     if (method.endsWith(".<init>") && isPrivilegedAction(classname)) {
 711                         cachePrivilegedAction(classname);
 712                     }
 713                     if (method.equals("java.security.AccessController.doPrivileged")) {
 714                         doPrivilegedCall(method);
 715                         return method;
 716                     }
 717 
 718                     boolean interfaceMethodRef = info instanceof CONSTANT_InterfaceMethodref_info;
 719                     MethodDescriptor md = new MethodDescriptor(method, descriptor, interfaceMethodRef);
 720                     if (!methods.contains(md) && !pending.contains(md)) {
 721                         pending.add(md);
 722                     }
 723                     return method;
 724                 } catch (ConstantPoolException e) {
 725                     throw new RuntimeException(e);
 726                 }
 727             }
 728 
 729             public String visitClass(CONSTANT_Class_info info, Void p) {
 730                 try {
 731                     String classname = parser.checkClassName(info.getName()).replace('/', '.');
 732                     if (classname.length() > 0) {
 733                         addReferencedClass(classname);
 734                     }
 735                     return classname;
 736                 } catch (ConstantPoolException ex) {
 737                     throw new RuntimeException(ex);
 738                 }
 739             }
 740 
 741             public String visitDouble(CONSTANT_Double_info info, Void p) {
 742                 // skip
 743                 return null;
 744             }
 745 
 746             public String visitFieldref(CONSTANT_Fieldref_info info, Void p) {
 747                 try {
 748                     String classname = getClassName(info, p);
 749                     if (classname.length() > 0) {
 750                         addReferencedClass(classname);
 751                     }
 752 
 753                     String type = info.getNameAndTypeInfo().getType();
 754                     String fieldType = parser.checkClassName(type).replace('/', '.');
 755                     if (fieldType.length() > 0) {
 756                         addReferencedClass(classname);
 757                     }
 758                     return parser.constantPoolParser.stringValue(info);
 759                 } catch (ConstantPoolException e) {
 760                     throw new RuntimeException(e);
 761                 }
 762             }
 763 
 764             public String visitFloat(CONSTANT_Float_info info, Void p) {
 765                 // skip
 766                 return null;
 767             }
 768 
 769             public String visitInteger(CONSTANT_Integer_info info, Void p) {
 770                 // skip
 771                 return null;
 772             }
 773 
 774             public String visitInterfaceMethodref(CONSTANT_InterfaceMethodref_info info, Void p) {
 775                 return addMethodDescriptor(info, p);
 776             }
 777 
 778             public String visitLong(CONSTANT_Long_info info, Void p) {
 779                 // skip
 780                 return null;
 781             }
 782 
 783             public String visitNameAndType(CONSTANT_NameAndType_info info, Void p) {
 784                 // skip
 785                 return null;
 786             }
 787 
 788             public String visitMethodref(CONSTANT_Methodref_info info, Void p) {
 789                 return addMethodDescriptor(info, p);
 790             }
 791 
 792             public String visitModuleId(CONSTANT_ModuleId_info info, Void p) {
 793                 // skip
 794                 return null;
 795             }
 796 
 797             public String visitString(CONSTANT_String_info info, Void p) {
 798                 // skip
 799                 return null;
 800             }
 801 
 802             public String visitUtf8(CONSTANT_Utf8_info info, Void p) {
 803                 return null;
 804             }
 805         };
 806     }
 807     static boolean traceOn = System.getProperty("classanalyzer.debug") != null;
 808 
 809     private static void trace(String format, Object... args) {
 810         if (traceOn) {
 811             System.out.format(format, args);
 812         }
 813     }
 814 
 815     private static void usage() {
 816         System.out.println("Usage: BootAnalyzer <options>");
 817         System.out.println("Options: ");
 818         System.out.println("\t-jdkhome <JDK home> where all jars will be parsed");
 819         System.out.println("\t-config  <roots for the boot module>");
 820         System.out.println("\t-output  <output dir>");
 821         System.out.println("\t-classlist print class list and summary");
 822         System.exit(-1);
 823     }
 824 }