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 <clinit>.</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 visitString(CONSTANT_String_info info, Void p) { |
|
793 // skip |
|
794 return null; |
|
795 } |
|
796 |
|
797 public String visitUtf8(CONSTANT_Utf8_info info, Void p) { |
|
798 return null; |
|
799 } |
|
800 }; |
|
801 } |
|
802 static boolean traceOn = System.getProperty("classanalyzer.debug") != null; |
|
803 |
|
804 private static void trace(String format, Object... args) { |
|
805 if (traceOn) { |
|
806 System.out.format(format, args); |
|
807 } |
|
808 } |
|
809 |
|
810 private static void usage() { |
|
811 System.out.println("Usage: BootAnalyzer <options>"); |
|
812 System.out.println("Options: "); |
|
813 System.out.println("\t-jdkhome <JDK home> where all jars will be parsed"); |
|
814 System.out.println("\t-config <roots for the boot module>"); |
|
815 System.out.println("\t-output <output dir>"); |
|
816 System.out.println("\t-classlist print class list and summary"); |
|
817 System.exit(-1); |
|
818 } |
|
819 } |
|