Thu, 14 Feb 2013 09:43:00 -0800
8006225: tools/jdeps/Basic.java failes with AssertionError
Reviewed-by: alanb
1 /*
2 * Copyright (c) 2012, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package com.sun.tools.jdeps;
27 import com.sun.tools.classfile.ClassFile;
28 import com.sun.tools.classfile.ConstantPoolException;
29 import com.sun.tools.classfile.Dependencies;
30 import com.sun.tools.classfile.Dependencies.ClassFileError;
31 import com.sun.tools.classfile.Dependency;
32 import java.io.*;
33 import java.text.MessageFormat;
34 import java.util.*;
35 import java.util.regex.Pattern;
37 /**
38 * Implementation for the jdeps tool for static class dependency analysis.
39 */
40 class JdepsTask {
41 class BadArgs extends Exception {
42 static final long serialVersionUID = 8765093759964640721L;
43 BadArgs(String key, Object... args) {
44 super(JdepsTask.getMessage(key, args));
45 this.key = key;
46 this.args = args;
47 }
49 BadArgs showUsage(boolean b) {
50 showUsage = b;
51 return this;
52 }
53 final String key;
54 final Object[] args;
55 boolean showUsage;
56 }
58 static abstract class Option {
59 Option(boolean hasArg, String... aliases) {
60 this.hasArg = hasArg;
61 this.aliases = aliases;
62 }
64 boolean isHidden() {
65 return false;
66 }
68 boolean matches(String opt) {
69 for (String a : aliases) {
70 if (a.equals(opt)) {
71 return true;
72 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
73 return true;
74 }
75 }
76 return false;
77 }
79 boolean ignoreRest() {
80 return false;
81 }
83 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
84 final boolean hasArg;
85 final String[] aliases;
86 }
88 static abstract class HiddenOption extends Option {
89 HiddenOption(boolean hasArg, String... aliases) {
90 super(hasArg, aliases);
91 }
93 boolean isHidden() {
94 return true;
95 }
96 }
98 static Option[] recognizedOptions = {
99 new Option(false, "-h", "-?", "--help") {
100 void process(JdepsTask task, String opt, String arg) {
101 task.options.help = true;
102 }
103 },
104 new Option(false, "-s", "--summary") {
105 void process(JdepsTask task, String opt, String arg) {
106 task.options.showSummary = true;
107 task.options.verbose = Analyzer.Type.SUMMARY;
108 }
109 },
110 new Option(false, "-v", "--verbose") {
111 void process(JdepsTask task, String opt, String arg) {
112 task.options.verbose = Analyzer.Type.VERBOSE;
113 }
114 },
115 new Option(true, "-V", "--verbose-level") {
116 void process(JdepsTask task, String opt, String arg) throws BadArgs {
117 if ("package".equals(arg)) {
118 task.options.verbose = Analyzer.Type.PACKAGE;
119 } else if ("class".equals(arg)) {
120 task.options.verbose = Analyzer.Type.CLASS;
121 } else {
122 throw task.new BadArgs("err.invalid.arg.for.option", opt);
123 }
124 }
125 },
126 new Option(true, "-c", "--classpath") {
127 void process(JdepsTask task, String opt, String arg) {
128 task.options.classpath = arg;
129 }
130 },
131 new Option(true, "-p", "--package") {
132 void process(JdepsTask task, String opt, String arg) {
133 task.options.packageNames.add(arg);
134 }
135 },
136 new Option(true, "-e", "--regex") {
137 void process(JdepsTask task, String opt, String arg) {
138 task.options.regex = arg;
139 }
140 },
141 new Option(false, "-P", "--profile") {
142 void process(JdepsTask task, String opt, String arg) {
143 task.options.showProfile = true;
144 }
145 },
146 new Option(false, "-R", "--recursive") {
147 void process(JdepsTask task, String opt, String arg) {
148 task.options.depth = 0;
149 }
150 },
151 new HiddenOption(true, "-d", "--depth") {
152 void process(JdepsTask task, String opt, String arg) throws BadArgs {
153 try {
154 task.options.depth = Integer.parseInt(arg);
155 } catch (NumberFormatException e) {
156 throw task.new BadArgs("err.invalid.arg.for.option", opt);
157 }
158 }
159 },
160 new Option(false, "--version") {
161 void process(JdepsTask task, String opt, String arg) {
162 task.options.version = true;
163 }
164 },
165 new HiddenOption(false, "--fullversion") {
166 void process(JdepsTask task, String opt, String arg) {
167 task.options.fullVersion = true;
168 }
169 },
170 };
172 private static final String PROGNAME = "jdeps";
173 private final Options options = new Options();
174 private final List<String> classes = new ArrayList<String>();
176 private PrintWriter log;
177 void setLog(PrintWriter out) {
178 log = out;
179 }
181 /**
182 * Result codes.
183 */
184 static final int EXIT_OK = 0, // Completed with no errors.
185 EXIT_ERROR = 1, // Completed but reported errors.
186 EXIT_CMDERR = 2, // Bad command-line arguments
187 EXIT_SYSERR = 3, // System error or resource exhaustion.
188 EXIT_ABNORMAL = 4;// terminated abnormally
190 int run(String[] args) {
191 if (log == null) {
192 log = new PrintWriter(System.out);
193 }
194 try {
195 handleOptions(args);
196 if (options.help) {
197 showHelp();
198 }
199 if (options.version || options.fullVersion) {
200 showVersion(options.fullVersion);
201 }
202 if (classes.isEmpty() && !options.wildcard) {
203 if (options.help || options.version || options.fullVersion) {
204 return EXIT_OK;
205 } else {
206 showHelp();
207 return EXIT_CMDERR;
208 }
209 }
210 if (options.regex != null && options.packageNames.size() > 0) {
211 showHelp();
212 return EXIT_CMDERR;
213 }
214 if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
215 showHelp();
216 return EXIT_CMDERR;
217 }
218 boolean ok = run();
219 return ok ? EXIT_OK : EXIT_ERROR;
220 } catch (BadArgs e) {
221 reportError(e.key, e.args);
222 if (e.showUsage) {
223 log.println(getMessage("main.usage.summary", PROGNAME));
224 }
225 return EXIT_CMDERR;
226 } catch (IOException e) {
227 return EXIT_ABNORMAL;
228 } finally {
229 log.flush();
230 }
231 }
233 private final List<Archive> sourceLocations = new ArrayList<Archive>();
234 private boolean run() throws IOException {
235 findDependencies();
236 Analyzer analyzer = new Analyzer(options.verbose);
237 analyzer.run(sourceLocations);
238 if (options.verbose == Analyzer.Type.SUMMARY) {
239 printSummary(log, analyzer);
240 } else {
241 printDependencies(log, analyzer);
242 }
243 return true;
244 }
246 private boolean isValidClassName(String name) {
247 if (!Character.isJavaIdentifierStart(name.charAt(0))) {
248 return false;
249 }
250 for (int i=1; i < name.length(); i++) {
251 char c = name.charAt(i);
252 if (c != '.' && !Character.isJavaIdentifierPart(c)) {
253 return false;
254 }
255 }
256 return true;
257 }
259 private void findDependencies() throws IOException {
260 Dependency.Finder finder = Dependencies.getClassDependencyFinder();
261 Dependency.Filter filter;
262 if (options.regex != null) {
263 filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
264 } else if (options.packageNames.size() > 0) {
265 filter = Dependencies.getPackageFilter(options.packageNames, false);
266 } else {
267 filter = new Dependency.Filter() {
268 public boolean accepts(Dependency dependency) {
269 return !dependency.getOrigin().equals(dependency.getTarget());
270 }
271 };
272 }
274 List<Archive> archives = new ArrayList<Archive>();
275 Deque<String> roots = new LinkedList<String>();
276 for (String s : classes) {
277 File f = new File(s);
278 if (f.exists()) {
279 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
280 } else {
281 if (isValidClassName(s)) {
282 roots.add(s);
283 } else {
284 warning("warn.invalid.arg", s);
285 }
286 }
287 }
289 List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
290 if (options.wildcard) {
291 // include all archives from classpath to the initial list
292 archives.addAll(getClassPathArchives(options.classpath));
293 } else {
294 classpaths.addAll(getClassPathArchives(options.classpath));
295 }
296 classpaths.addAll(PlatformClassPath.getArchives());
298 // add all archives to the source locations for reporting
299 sourceLocations.addAll(archives);
300 sourceLocations.addAll(classpaths);
302 // Work queue of names of classfiles to be searched.
303 // Entries will be unique, and for classes that do not yet have
304 // dependencies in the results map.
305 Deque<String> deque = new LinkedList<String>();
306 Set<String> doneClasses = new HashSet<String>();
308 // get the immediate dependencies of the input files
309 for (Archive a : archives) {
310 for (ClassFile cf : a.reader().getClassFiles()) {
311 String classFileName;
312 try {
313 classFileName = cf.getName();
314 } catch (ConstantPoolException e) {
315 throw new ClassFileError(e);
316 }
318 if (!doneClasses.contains(classFileName)) {
319 doneClasses.add(classFileName);
320 }
321 for (Dependency d : finder.findDependencies(cf)) {
322 if (filter.accepts(d)) {
323 String cn = d.getTarget().getName();
324 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
325 deque.add(cn);
326 }
327 a.addClass(d.getOrigin(), d.getTarget());
328 }
329 }
330 }
331 }
333 // add Archive for looking up classes from the classpath
334 // for transitive dependency analysis
335 Deque<String> unresolved = roots;
336 int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
337 do {
338 String name;
339 while ((name = unresolved.poll()) != null) {
340 if (doneClasses.contains(name)) {
341 continue;
342 }
343 ClassFile cf = null;
344 for (Archive a : classpaths) {
345 cf = a.reader().getClassFile(name);
346 if (cf != null) {
347 String classFileName;
348 try {
349 classFileName = cf.getName();
350 } catch (ConstantPoolException e) {
351 throw new ClassFileError(e);
352 }
353 if (!doneClasses.contains(classFileName)) {
354 // if name is a fully-qualified class name specified
355 // from command-line, this class might already be parsed
356 doneClasses.add(classFileName);
357 for (Dependency d : finder.findDependencies(cf)) {
358 if (depth == 0) {
359 // ignore the dependency
360 a.addClass(d.getOrigin());
361 break;
362 } else if (filter.accepts(d)) {
363 a.addClass(d.getOrigin(), d.getTarget());
364 String cn = d.getTarget().getName();
365 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
366 deque.add(cn);
367 }
368 }
369 }
370 }
371 break;
372 }
373 }
374 if (cf == null) {
375 doneClasses.add(name);
376 }
377 }
378 unresolved = deque;
379 deque = new LinkedList<String>();
380 } while (!unresolved.isEmpty() && depth-- > 0);
381 }
383 private void printSummary(final PrintWriter out, final Analyzer analyzer) {
384 Analyzer.Visitor visitor = new Analyzer.Visitor() {
385 public void visit(String origin, String profile) {
386 if (options.showProfile) {
387 out.format("%-30s -> %s%n", origin, profile);
388 }
389 }
390 public void visit(Archive origin, Archive target) {
391 if (!options.showProfile) {
392 out.format("%-30s -> %s%n", origin, target);
393 }
394 }
395 };
396 analyzer.visitSummary(visitor);
397 }
399 private void printDependencies(final PrintWriter out, final Analyzer analyzer) {
400 Analyzer.Visitor visitor = new Analyzer.Visitor() {
401 private String pkg = "";
402 public void visit(String origin, String target) {
403 if (!origin.equals(pkg)) {
404 pkg = origin;
405 out.format(" %s (%s)%n", origin, analyzer.getArchiveName(origin));
406 }
407 Archive source = analyzer.getArchive(target);
408 String profile = options.showProfile ? analyzer.getProfile(target) : "";
409 out.format(" -> %-50s %s%n", target,
410 PlatformClassPath.contains(source)
411 ? profile
412 : analyzer.getArchiveName(target));
413 }
414 public void visit(Archive origin, Archive target) {
415 out.format("%s -> %s%n", origin, target);
416 }
417 };
418 analyzer.visit(visitor);
419 }
421 public void handleOptions(String[] args) throws BadArgs {
422 // process options
423 for (int i=0; i < args.length; i++) {
424 if (args[i].charAt(0) == '-') {
425 String name = args[i];
426 Option option = getOption(name);
427 String param = null;
428 if (option.hasArg) {
429 if (name.startsWith("--") && name.indexOf('=') > 0) {
430 param = name.substring(name.indexOf('=') + 1, name.length());
431 } else if (i + 1 < args.length) {
432 param = args[++i];
433 }
434 if (param == null || param.isEmpty() || param.charAt(0) == '-') {
435 throw new BadArgs("err.missing.arg", name).showUsage(true);
436 }
437 }
438 option.process(this, name, param);
439 if (option.ignoreRest()) {
440 i = args.length;
441 }
442 } else {
443 // process rest of the input arguments
444 for (; i < args.length; i++) {
445 String name = args[i];
446 if (name.charAt(0) == '-') {
447 throw new BadArgs("err.option.after.class", name).showUsage(true);
448 }
449 if (name.equals("*") || name.equals("\"*\"")) {
450 options.wildcard = true;
451 } else {
452 classes.add(name);
453 }
454 }
455 }
456 }
457 }
459 private Option getOption(String name) throws BadArgs {
460 for (Option o : recognizedOptions) {
461 if (o.matches(name)) {
462 return o;
463 }
464 }
465 throw new BadArgs("err.unknown.option", name).showUsage(true);
466 }
468 private void reportError(String key, Object... args) {
469 log.println(getMessage("error.prefix") + " " + getMessage(key, args));
470 }
472 private void warning(String key, Object... args) {
473 log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
474 }
476 private void showHelp() {
477 log.println(getMessage("main.usage", PROGNAME));
478 for (Option o : recognizedOptions) {
479 String name = o.aliases[0].substring(1); // there must always be at least one name
480 name = name.charAt(0) == '-' ? name.substring(1) : name;
481 if (o.isHidden() || name.equals("h")) {
482 continue;
483 }
484 log.println(getMessage("main.opt." + name));
485 }
486 }
488 private void showVersion(boolean full) {
489 log.println(version(full ? "full" : "release"));
490 }
492 private String version(String key) {
493 // key=version: mm.nn.oo[-milestone]
494 // key=full: mm.mm.oo[-milestone]-build
495 if (ResourceBundleHelper.versionRB == null) {
496 return System.getProperty("java.version");
497 }
498 try {
499 return ResourceBundleHelper.versionRB.getString(key);
500 } catch (MissingResourceException e) {
501 return getMessage("version.unknown", System.getProperty("java.version"));
502 }
503 }
505 static String getMessage(String key, Object... args) {
506 try {
507 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
508 } catch (MissingResourceException e) {
509 throw new InternalError("Missing message: " + key);
510 }
511 }
513 private static class Options {
514 boolean help;
515 boolean version;
516 boolean fullVersion;
517 boolean showFlags;
518 boolean showProfile;
519 boolean showSummary;
520 boolean wildcard;
521 String regex;
522 String classpath = "";
523 int depth = 1;
524 Analyzer.Type verbose = Analyzer.Type.PACKAGE;
525 Set<String> packageNames = new HashSet<String>();
526 }
528 private static class ResourceBundleHelper {
529 static final ResourceBundle versionRB;
530 static final ResourceBundle bundle;
532 static {
533 Locale locale = Locale.getDefault();
534 try {
535 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
536 } catch (MissingResourceException e) {
537 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
538 }
539 try {
540 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
541 } catch (MissingResourceException e) {
542 throw new InternalError("version.resource.missing");
543 }
544 }
545 }
547 private List<Archive> getArchives(List<String> filenames) throws IOException {
548 List<Archive> result = new ArrayList<Archive>();
549 for (String s : filenames) {
550 File f = new File(s);
551 if (f.exists()) {
552 result.add(new Archive(f, ClassFileReader.newInstance(f)));
553 } else {
554 warning("warn.file.not.exist", s);
555 }
556 }
557 return result;
558 }
560 private List<Archive> getClassPathArchives(String paths) throws IOException {
561 List<Archive> result = new ArrayList<Archive>();
562 if (paths.isEmpty()) {
563 return result;
564 }
565 for (String p : paths.split(File.pathSeparator)) {
566 if (p.length() > 0) {
567 File f = new File(p);
568 if (f.exists()) {
569 result.add(new Archive(f, ClassFileReader.newInstance(f)));
570 }
571 }
572 }
573 return result;
574 }
575 }