Thu, 12 Jan 2012 15:28:34 +0000
7123100: javac fails with java.lang.StackOverflowError
Summary: Inference of under-constrained type-variables creates erroneous recursive wildcard types
Reviewed-by: jjg
1 /*
2 * Copyright (c) 2001, 2011, 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 */
26 package com.sun.tools.javadoc;
28 import java.io.File;
29 import java.io.IOException;
30 import java.util.Collection;
31 import java.util.EnumSet;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.Set;
35 import javax.tools.JavaFileManager.Location;
36 import javax.tools.JavaFileObject;
37 import javax.tools.StandardJavaFileManager;
38 import javax.tools.StandardLocation;
40 import com.sun.tools.javac.code.Symbol.CompletionFailure;
41 import com.sun.tools.javac.comp.Annotate;
42 import com.sun.tools.javac.tree.JCTree;
43 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
44 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
45 import com.sun.tools.javac.util.Abort;
46 import com.sun.tools.javac.util.Context;
47 import com.sun.tools.javac.util.List;
48 import com.sun.tools.javac.util.ListBuffer;
49 import com.sun.tools.javac.util.Position;
52 /**
53 * This class could be the main entry point for Javadoc when Javadoc is used as a
54 * component in a larger software system. It provides operations to
55 * construct a new javadoc processor, and to run it on a set of source
56 * files.
57 * @author Neal Gafter
58 */
59 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
60 DocEnv docenv;
62 final Context context;
63 final Messager messager;
64 final JavadocClassReader reader;
65 final JavadocEnter enter;
66 final Annotate annotate;
68 /**
69 * Construct a new JavaCompiler processor, using appropriately
70 * extended phases of the underlying compiler.
71 */
72 protected JavadocTool(Context context) {
73 super(context);
74 this.context = context;
75 messager = Messager.instance0(context);
76 reader = JavadocClassReader.instance0(context);
77 enter = JavadocEnter.instance0(context);
78 annotate = Annotate.instance(context);
79 }
81 /**
82 * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
83 */
84 protected boolean keepComments() {
85 return true;
86 }
88 /**
89 * Construct a new javadoc tool.
90 */
91 public static JavadocTool make0(Context context) {
92 Messager messager = null;
93 try {
94 // force the use of Javadoc's class reader
95 JavadocClassReader.preRegister(context);
97 // force the use of Javadoc's own enter phase
98 JavadocEnter.preRegister(context);
100 // force the use of Javadoc's own member enter phase
101 JavadocMemberEnter.preRegister(context);
103 // force the use of Javadoc's own todo phase
104 JavadocTodo.preRegister(context);
106 // force the use of Messager as a Log
107 messager = Messager.instance0(context);
109 return new JavadocTool(context);
110 } catch (CompletionFailure ex) {
111 messager.error(Position.NOPOS, ex.getMessage());
112 return null;
113 }
114 }
116 public RootDocImpl getRootDocImpl(String doclocale,
117 String encoding,
118 ModifierFilter filter,
119 List<String> javaNames,
120 List<String[]> options,
121 boolean breakiterator,
122 List<String> subPackages,
123 List<String> excludedPackages,
124 boolean docClasses,
125 boolean legacyDoclet,
126 boolean quiet) throws IOException {
127 docenv = DocEnv.instance(context);
128 docenv.showAccess = filter;
129 docenv.quiet = quiet;
130 docenv.breakiterator = breakiterator;
131 docenv.setLocale(doclocale);
132 docenv.setEncoding(encoding);
133 docenv.docClasses = docClasses;
134 docenv.legacyDoclet = legacyDoclet;
135 reader.sourceCompleter = docClasses ? null : this;
137 ListBuffer<String> names = new ListBuffer<String>();
138 ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<JCCompilationUnit>();
139 ListBuffer<JCCompilationUnit> packTrees = new ListBuffer<JCCompilationUnit>();
141 try {
142 StandardJavaFileManager fm = (StandardJavaFileManager) docenv.fileManager;
143 for (List<String> it = javaNames; it.nonEmpty(); it = it.tail) {
144 String name = it.head;
145 if (!docClasses && name.endsWith(".java") && new File(name).exists()) {
146 JavaFileObject fo = fm.getJavaFileObjects(name).iterator().next();
147 docenv.notice("main.Loading_source_file", name);
148 JCCompilationUnit tree = parse(fo);
149 classTrees.append(tree);
150 } else if (isValidPackageName(name)) {
151 names = names.append(name);
152 } else if (name.endsWith(".java")) {
153 docenv.error(null, "main.file_not_found", name);
154 } else {
155 docenv.error(null, "main.illegal_package_name", name);
156 }
157 }
159 if (!docClasses) {
160 // Recursively search given subpackages. If any packages
161 //are found, add them to the list.
162 Map<String,List<JavaFileObject>> packageFiles =
163 searchSubPackages(subPackages, names, excludedPackages);
165 // Parse the packages
166 for (List<String> packs = names.toList(); packs.nonEmpty(); packs = packs.tail) {
167 // Parse sources ostensibly belonging to package.
168 String packageName = packs.head;
169 parsePackageClasses(packageName, packageFiles.get(packageName), packTrees, excludedPackages);
170 }
172 if (messager.nerrors() != 0) return null;
174 // Enter symbols for all files
175 docenv.notice("main.Building_tree");
176 enter.main(classTrees.toList().appendList(packTrees.toList()));
177 }
178 } catch (Abort ex) {}
180 if (messager.nerrors() != 0)
181 return null;
183 if (docClasses)
184 return new RootDocImpl(docenv, javaNames, options);
185 else
186 return new RootDocImpl(docenv, listClasses(classTrees.toList()), names.toList(), options);
187 }
189 /** Is the given string a valid package name? */
190 boolean isValidPackageName(String s) {
191 int index;
192 while ((index = s.indexOf('.')) != -1) {
193 if (!isValidClassName(s.substring(0, index))) return false;
194 s = s.substring(index+1);
195 }
196 return isValidClassName(s);
197 }
199 /**
200 * search all directories in path for subdirectory name. Add all
201 * .java files found in such a directory to args.
202 */
203 private void parsePackageClasses(String name,
204 Iterable<JavaFileObject> files,
205 ListBuffer<JCCompilationUnit> trees,
206 List<String> excludedPackages)
207 throws IOException {
208 if (excludedPackages.contains(name)) {
209 return;
210 }
212 boolean hasFiles = false;
213 docenv.notice("main.Loading_source_files_for_package", name);
215 if (files == null) {
216 Location location = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
217 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
218 ListBuffer<JavaFileObject> lb = new ListBuffer<JavaFileObject>();
219 for (JavaFileObject fo: docenv.fileManager.list(
220 location, name, EnumSet.of(JavaFileObject.Kind.SOURCE), false)) {
221 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
222 String simpleName = getSimpleName(binaryName);
223 if (isValidClassName(simpleName)) {
224 lb.append(fo);
225 }
226 }
227 files = lb.toList();
228 }
230 for (JavaFileObject fo : files) {
231 // messager.notice("main.Loading_source_file", fn);
232 trees.append(parse(fo));
233 hasFiles = true;
234 }
236 if (!hasFiles) {
237 messager.warning(null, "main.no_source_files_for_package",
238 name.replace(File.separatorChar, '.'));
239 }
240 }
242 /**
243 * Recursively search all directories in path for subdirectory name.
244 * Add all packages found in such a directory to packages list.
245 */
246 private Map<String,List<JavaFileObject>> searchSubPackages(
247 List<String> subPackages,
248 ListBuffer<String> packages,
249 List<String> excludedPackages)
250 throws IOException {
251 Map<String,List<JavaFileObject>> packageFiles =
252 new HashMap<String,List<JavaFileObject>>();
254 Map<String,Boolean> includedPackages = new HashMap<String,Boolean>();
255 includedPackages.put("", true);
256 for (String p: excludedPackages)
257 includedPackages.put(p, false);
259 StandardLocation path = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
260 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
262 searchSubPackages(subPackages,
263 includedPackages,
264 packages, packageFiles,
265 path,
266 EnumSet.of(JavaFileObject.Kind.SOURCE));
268 return packageFiles;
269 }
271 private void searchSubPackages(List<String> subPackages,
272 Map<String,Boolean> includedPackages,
273 ListBuffer<String> packages,
274 Map<String, List<JavaFileObject>> packageFiles,
275 StandardLocation location, Set<JavaFileObject.Kind> kinds)
276 throws IOException {
277 for (String subPackage: subPackages) {
278 if (!isIncluded(subPackage, includedPackages))
279 continue;
281 for (JavaFileObject fo: docenv.fileManager.list(location, subPackage, kinds, true)) {
282 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
283 String packageName = getPackageName(binaryName);
284 String simpleName = getSimpleName(binaryName);
285 if (isIncluded(packageName, includedPackages) && isValidClassName(simpleName)) {
286 List<JavaFileObject> list = packageFiles.get(packageName);
287 list = (list == null ? List.of(fo) : list.prepend(fo));
288 packageFiles.put(packageName, list);
289 if (!packages.contains(packageName))
290 packages.add(packageName);
291 }
292 }
293 }
294 }
296 private String getPackageName(String name) {
297 int lastDot = name.lastIndexOf(".");
298 return (lastDot == -1 ? "" : name.substring(0, lastDot));
299 }
301 private String getSimpleName(String name) {
302 int lastDot = name.lastIndexOf(".");
303 return (lastDot == -1 ? name : name.substring(lastDot + 1));
304 }
306 private boolean isIncluded(String packageName, Map<String,Boolean> includedPackages) {
307 Boolean b = includedPackages.get(packageName);
308 if (b == null) {
309 b = isIncluded(getPackageName(packageName), includedPackages);
310 includedPackages.put(packageName, b);
311 }
312 return b;
313 }
315 /**
316 * Recursively search all directories in path for subdirectory name.
317 * Add all packages found in such a directory to packages list.
318 */
319 private void searchSubPackage(String packageName,
320 ListBuffer<String> packages,
321 List<String> excludedPackages,
322 Collection<File> pathnames) {
323 if (excludedPackages.contains(packageName))
324 return;
326 String packageFilename = packageName.replace('.', File.separatorChar);
327 boolean addedPackage = false;
328 for (File pathname : pathnames) {
329 File f = new File(pathname, packageFilename);
330 String filenames[] = f.list();
331 // if filenames not null, then found directory
332 if (filenames != null) {
333 for (String filename : filenames) {
334 if (!addedPackage
335 && (isValidJavaSourceFile(filename) ||
336 isValidJavaClassFile(filename))
337 && !packages.contains(packageName)) {
338 packages.append(packageName);
339 addedPackage = true;
340 } else if (isValidClassName(filename) &&
341 (new File(f, filename)).isDirectory()) {
342 searchSubPackage(packageName + "." + filename,
343 packages, excludedPackages, pathnames);
344 }
345 }
346 }
347 }
348 }
350 /**
351 * Return true if given file name is a valid class file name.
352 * @param file the name of the file to check.
353 * @return true if given file name is a valid class file name
354 * and false otherwise.
355 */
356 private static boolean isValidJavaClassFile(String file) {
357 if (!file.endsWith(".class")) return false;
358 String clazzName = file.substring(0, file.length() - ".class".length());
359 return isValidClassName(clazzName);
360 }
362 /**
363 * Return true if given file name is a valid Java source file name.
364 * @param file the name of the file to check.
365 * @return true if given file name is a valid Java source file name
366 * and false otherwise.
367 */
368 private static boolean isValidJavaSourceFile(String file) {
369 if (!file.endsWith(".java")) return false;
370 String clazzName = file.substring(0, file.length() - ".java".length());
371 return isValidClassName(clazzName);
372 }
374 /** Are surrogates supported?
375 */
376 final static boolean surrogatesSupported = surrogatesSupported();
377 private static boolean surrogatesSupported() {
378 try {
379 boolean b = Character.isHighSurrogate('a');
380 return true;
381 } catch (NoSuchMethodError ex) {
382 return false;
383 }
384 }
386 /**
387 * Return true if given file name is a valid class name
388 * (including "package-info").
389 * @param clazzname the name of the class to check.
390 * @return true if given class name is a valid class name
391 * and false otherwise.
392 */
393 public static boolean isValidClassName(String s) {
394 if (s.length() < 1) return false;
395 if (s.equals("package-info")) return true;
396 if (surrogatesSupported) {
397 int cp = s.codePointAt(0);
398 if (!Character.isJavaIdentifierStart(cp))
399 return false;
400 for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
401 cp = s.codePointAt(j);
402 if (!Character.isJavaIdentifierPart(cp))
403 return false;
404 }
405 } else {
406 if (!Character.isJavaIdentifierStart(s.charAt(0)))
407 return false;
408 for (int j=1; j<s.length(); j++)
409 if (!Character.isJavaIdentifierPart(s.charAt(j)))
410 return false;
411 }
412 return true;
413 }
415 /**
416 * From a list of top level trees, return the list of contained class definitions
417 */
418 List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
419 ListBuffer<JCClassDecl> result = new ListBuffer<JCClassDecl>();
420 for (JCCompilationUnit t : trees) {
421 for (JCTree def : t.defs) {
422 if (def.hasTag(JCTree.Tag.CLASSDEF))
423 result.append((JCClassDecl)def);
424 }
425 }
426 return result.toList();
427 }
429 }