Wed, 30 Apr 2014 23:26:43 +0100
8038023: Compiler crash ClassCastException
Summary: Add additional checks on results of ClassReader.readPool
Reviewed-by: vromero
1 /*
2 * Copyright (c) 2009, 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 */
26 package com.sun.tools.javac.nio;
28 import java.io.File;
29 import java.io.IOException;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.nio.charset.Charset;
33 import java.nio.file.Files;
34 import java.nio.file.FileSystem;
35 import java.nio.file.FileSystems;
36 import java.nio.file.FileVisitOption;
37 import java.nio.file.FileVisitResult;
38 import java.nio.file.Path;
39 import java.nio.file.SimpleFileVisitor;
40 import java.nio.file.attribute.BasicFileAttributes;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.EnumSet;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.LinkedHashSet;
49 import java.util.Map;
50 import java.util.Set;
51 import javax.lang.model.SourceVersion;
52 import javax.tools.FileObject;
53 import javax.tools.JavaFileManager;
54 import javax.tools.JavaFileObject;
55 import javax.tools.JavaFileObject.Kind;
56 import javax.tools.StandardLocation;
58 import static java.nio.file.FileVisitOption.*;
59 import static javax.tools.StandardLocation.*;
61 import com.sun.tools.javac.util.BaseFileManager;
62 import com.sun.tools.javac.util.Context;
63 import com.sun.tools.javac.util.List;
64 import com.sun.tools.javac.util.ListBuffer;
66 import static com.sun.tools.javac.main.Option.*;
69 // NOTE the imports carefully for this compilation unit.
70 //
71 // Path: java.nio.file.Path -- the new NIO type for which this file manager exists
72 //
73 // Paths: com.sun.tools.javac.file.Paths -- legacy javac type for handling path options
74 // The other Paths (java.nio.file.Paths) is not used
76 // NOTE this and related classes depend on new API in JDK 7.
77 // This requires special handling while bootstrapping the JDK build,
78 // when these classes might not yet have been compiled. To workaround
79 // this, the build arranges to make stubs of these classes available
80 // when compiling this and related classes. The set of stub files
81 // is specified in make/build.properties.
83 /**
84 * Implementation of PathFileManager: a JavaFileManager based on the use
85 * of java.nio.file.Path.
86 *
87 * <p>Just as a Path is somewhat analagous to a File, so too is this
88 * JavacPathFileManager analogous to JavacFileManager, as it relates to the
89 * support of FileObjects based on File objects (i.e. just RegularFileObject,
90 * not ZipFileObject and its variants.)
91 *
92 * <p>The default values for the standard locations supported by this file
93 * manager are the same as the default values provided by JavacFileManager --
94 * i.e. as determined by the javac.file.Paths class. To override these values,
95 * call {@link #setLocation}.
96 *
97 * <p>To reduce confusion with Path objects, the locations such as "class path",
98 * "source path", etc, are generically referred to here as "search paths".
99 *
100 * <p><b>This is NOT part of any supported API.
101 * If you write code that depends on this, you do so at your own risk.
102 * This code and its internal interfaces are subject to change or
103 * deletion without notice.</b>
104 */
105 public class JavacPathFileManager extends BaseFileManager implements PathFileManager {
106 protected FileSystem defaultFileSystem;
108 /**
109 * Create a JavacPathFileManager using a given context, optionally registering
110 * it as the JavaFileManager for that context.
111 */
112 public JavacPathFileManager(Context context, boolean register, Charset charset) {
113 super(charset);
114 if (register)
115 context.put(JavaFileManager.class, this);
116 pathsForLocation = new HashMap<Location, PathsForLocation>();
117 fileSystems = new HashMap<Path,FileSystem>();
118 setContext(context);
119 }
121 /**
122 * Set the context for JavacPathFileManager.
123 */
124 @Override
125 public void setContext(Context context) {
126 super.setContext(context);
127 }
129 @Override
130 public FileSystem getDefaultFileSystem() {
131 if (defaultFileSystem == null)
132 defaultFileSystem = FileSystems.getDefault();
133 return defaultFileSystem;
134 }
136 @Override
137 public void setDefaultFileSystem(FileSystem fs) {
138 defaultFileSystem = fs;
139 }
141 @Override
142 public void flush() throws IOException {
143 contentCache.clear();
144 }
146 @Override
147 public void close() throws IOException {
148 for (FileSystem fs: fileSystems.values())
149 fs.close();
150 }
152 @Override
153 public ClassLoader getClassLoader(Location location) {
154 nullCheck(location);
155 Iterable<? extends Path> path = getLocation(location);
156 if (path == null)
157 return null;
158 ListBuffer<URL> lb = new ListBuffer<URL>();
159 for (Path p: path) {
160 try {
161 lb.append(p.toUri().toURL());
162 } catch (MalformedURLException e) {
163 throw new AssertionError(e);
164 }
165 }
167 return getClassLoader(lb.toArray(new URL[lb.size()]));
168 }
170 @Override
171 public boolean isDefaultBootClassPath() {
172 return locations.isDefaultBootClassPath();
173 }
175 // <editor-fold defaultstate="collapsed" desc="Location handling">
177 public boolean hasLocation(Location location) {
178 return (getLocation(location) != null);
179 }
181 public Iterable<? extends Path> getLocation(Location location) {
182 nullCheck(location);
183 lazyInitSearchPaths();
184 PathsForLocation path = pathsForLocation.get(location);
185 if (path == null && !pathsForLocation.containsKey(location)) {
186 setDefaultForLocation(location);
187 path = pathsForLocation.get(location);
188 }
189 return path;
190 }
192 private Path getOutputLocation(Location location) {
193 Iterable<? extends Path> paths = getLocation(location);
194 return (paths == null ? null : paths.iterator().next());
195 }
197 public void setLocation(Location location, Iterable<? extends Path> searchPath)
198 throws IOException
199 {
200 nullCheck(location);
201 lazyInitSearchPaths();
202 if (searchPath == null) {
203 setDefaultForLocation(location);
204 } else {
205 if (location.isOutputLocation())
206 checkOutputPath(searchPath);
207 PathsForLocation pl = new PathsForLocation();
208 for (Path p: searchPath)
209 pl.add(p); // TODO -Xlint:path warn if path not found
210 pathsForLocation.put(location, pl);
211 }
212 }
214 private void checkOutputPath(Iterable<? extends Path> searchPath) throws IOException {
215 Iterator<? extends Path> pathIter = searchPath.iterator();
216 if (!pathIter.hasNext())
217 throw new IllegalArgumentException("empty path for directory");
218 Path path = pathIter.next();
219 if (pathIter.hasNext())
220 throw new IllegalArgumentException("path too long for directory");
221 if (!isDirectory(path))
222 throw new IOException(path + ": not a directory");
223 }
225 private void setDefaultForLocation(Location locn) {
226 Collection<File> files = null;
227 if (locn instanceof StandardLocation) {
228 switch ((StandardLocation) locn) {
229 case CLASS_PATH:
230 files = locations.userClassPath();
231 break;
232 case PLATFORM_CLASS_PATH:
233 files = locations.bootClassPath();
234 break;
235 case SOURCE_PATH:
236 files = locations.sourcePath();
237 break;
238 case CLASS_OUTPUT: {
239 String arg = options.get(D);
240 files = (arg == null ? null : Collections.singleton(new File(arg)));
241 break;
242 }
243 case SOURCE_OUTPUT: {
244 String arg = options.get(S);
245 files = (arg == null ? null : Collections.singleton(new File(arg)));
246 break;
247 }
248 }
249 }
251 PathsForLocation pl = new PathsForLocation();
252 if (files != null) {
253 for (File f: files)
254 pl.add(f.toPath());
255 }
256 if (!pl.isEmpty())
257 pathsForLocation.put(locn, pl);
258 }
260 private void lazyInitSearchPaths() {
261 if (!inited) {
262 setDefaultForLocation(PLATFORM_CLASS_PATH);
263 setDefaultForLocation(CLASS_PATH);
264 setDefaultForLocation(SOURCE_PATH);
265 inited = true;
266 }
267 }
268 // where
269 private boolean inited = false;
271 private Map<Location, PathsForLocation> pathsForLocation;
273 private static class PathsForLocation extends LinkedHashSet<Path> {
274 private static final long serialVersionUID = 6788510222394486733L;
275 }
277 // </editor-fold>
279 // <editor-fold defaultstate="collapsed" desc="FileObject handling">
281 @Override
282 public Path getPath(FileObject fo) {
283 nullCheck(fo);
284 if (!(fo instanceof PathFileObject))
285 throw new IllegalArgumentException();
286 return ((PathFileObject) fo).getPath();
287 }
289 @Override
290 public boolean isSameFile(FileObject a, FileObject b) {
291 nullCheck(a);
292 nullCheck(b);
293 if (!(a instanceof PathFileObject))
294 throw new IllegalArgumentException("Not supported: " + a);
295 if (!(b instanceof PathFileObject))
296 throw new IllegalArgumentException("Not supported: " + b);
297 return ((PathFileObject) a).isSameFile((PathFileObject) b);
298 }
300 @Override
301 public Iterable<JavaFileObject> list(Location location,
302 String packageName, Set<Kind> kinds, boolean recurse)
303 throws IOException {
304 // validatePackageName(packageName);
305 nullCheck(packageName);
306 nullCheck(kinds);
308 Iterable<? extends Path> paths = getLocation(location);
309 if (paths == null)
310 return List.nil();
311 ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
313 for (Path path : paths)
314 list(path, packageName, kinds, recurse, results);
316 return results.toList();
317 }
319 private void list(Path path, String packageName, final Set<Kind> kinds,
320 boolean recurse, final ListBuffer<JavaFileObject> results)
321 throws IOException {
322 if (!Files.exists(path))
323 return;
325 final Path pathDir;
326 if (isDirectory(path))
327 pathDir = path;
328 else {
329 FileSystem fs = getFileSystem(path);
330 if (fs == null)
331 return;
332 pathDir = fs.getRootDirectories().iterator().next();
333 }
334 String sep = path.getFileSystem().getSeparator();
335 Path packageDir = packageName.isEmpty() ? pathDir
336 : pathDir.resolve(packageName.replace(".", sep));
337 if (!Files.exists(packageDir))
338 return;
340 /* Alternate impl of list, superceded by use of Files.walkFileTree */
341 // Deque<Path> queue = new LinkedList<Path>();
342 // queue.add(packageDir);
343 //
344 // Path dir;
345 // while ((dir = queue.poll()) != null) {
346 // DirectoryStream<Path> ds = dir.newDirectoryStream();
347 // try {
348 // for (Path p: ds) {
349 // String name = p.getFileName().toString();
350 // if (isDirectory(p)) {
351 // if (recurse && SourceVersion.isIdentifier(name)) {
352 // queue.add(p);
353 // }
354 // } else {
355 // if (kinds.contains(getKind(name))) {
356 // JavaFileObject fe =
357 // PathFileObject.createDirectoryPathFileObject(this, p, pathDir);
358 // results.append(fe);
359 // }
360 // }
361 // }
362 // } finally {
363 // ds.close();
364 // }
365 // }
366 int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
367 Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
368 Files.walkFileTree(packageDir, opts, maxDepth,
369 new SimpleFileVisitor<Path>() {
370 @Override
371 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
372 Path name = dir.getFileName();
373 if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292?
374 return FileVisitResult.CONTINUE;
375 else
376 return FileVisitResult.SKIP_SUBTREE;
377 }
379 @Override
380 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
381 if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) {
382 JavaFileObject fe =
383 PathFileObject.createDirectoryPathFileObject(
384 JavacPathFileManager.this, file, pathDir);
385 results.append(fe);
386 }
387 return FileVisitResult.CONTINUE;
388 }
389 });
390 }
392 @Override
393 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
394 Iterable<? extends Path> paths) {
395 ArrayList<PathFileObject> result;
396 if (paths instanceof Collection<?>)
397 result = new ArrayList<PathFileObject>(((Collection<?>)paths).size());
398 else
399 result = new ArrayList<PathFileObject>();
400 for (Path p: paths)
401 result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p)));
402 return result;
403 }
405 @Override
406 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
407 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
408 }
410 @Override
411 public JavaFileObject getJavaFileForInput(Location location,
412 String className, Kind kind) throws IOException {
413 return getFileForInput(location, getRelativePath(className, kind));
414 }
416 @Override
417 public FileObject getFileForInput(Location location,
418 String packageName, String relativeName) throws IOException {
419 return getFileForInput(location, getRelativePath(packageName, relativeName));
420 }
422 private JavaFileObject getFileForInput(Location location, String relativePath)
423 throws IOException {
424 for (Path p: getLocation(location)) {
425 if (isDirectory(p)) {
426 Path f = resolve(p, relativePath);
427 if (Files.exists(f))
428 return PathFileObject.createDirectoryPathFileObject(this, f, p);
429 } else {
430 FileSystem fs = getFileSystem(p);
431 if (fs != null) {
432 Path file = getPath(fs, relativePath);
433 if (Files.exists(file))
434 return PathFileObject.createJarPathFileObject(this, file);
435 }
436 }
437 }
438 return null;
439 }
441 @Override
442 public JavaFileObject getJavaFileForOutput(Location location,
443 String className, Kind kind, FileObject sibling) throws IOException {
444 return getFileForOutput(location, getRelativePath(className, kind), sibling);
445 }
447 @Override
448 public FileObject getFileForOutput(Location location, String packageName,
449 String relativeName, FileObject sibling)
450 throws IOException {
451 return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling);
452 }
454 private JavaFileObject getFileForOutput(Location location,
455 String relativePath, FileObject sibling) {
456 Path dir = getOutputLocation(location);
457 if (dir == null) {
458 if (location == CLASS_OUTPUT) {
459 Path siblingDir = null;
460 if (sibling != null && sibling instanceof PathFileObject) {
461 siblingDir = ((PathFileObject) sibling).getPath().getParent();
462 }
463 return PathFileObject.createSiblingPathFileObject(this,
464 siblingDir.resolve(getBaseName(relativePath)),
465 relativePath);
466 } else if (location == SOURCE_OUTPUT) {
467 dir = getOutputLocation(CLASS_OUTPUT);
468 }
469 }
471 Path file;
472 if (dir != null) {
473 file = resolve(dir, relativePath);
474 return PathFileObject.createDirectoryPathFileObject(this, file, dir);
475 } else {
476 file = getPath(getDefaultFileSystem(), relativePath);
477 return PathFileObject.createSimplePathFileObject(this, file);
478 }
480 }
482 @Override
483 public String inferBinaryName(Location location, JavaFileObject fo) {
484 nullCheck(fo);
485 // Need to match the path semantics of list(location, ...)
486 Iterable<? extends Path> paths = getLocation(location);
487 if (paths == null) {
488 return null;
489 }
491 if (!(fo instanceof PathFileObject))
492 throw new IllegalArgumentException(fo.getClass().getName());
494 return ((PathFileObject) fo).inferBinaryName(paths);
495 }
497 private FileSystem getFileSystem(Path p) throws IOException {
498 FileSystem fs = fileSystems.get(p);
499 if (fs == null) {
500 fs = FileSystems.newFileSystem(p, null);
501 fileSystems.put(p, fs);
502 }
503 return fs;
504 }
506 private Map<Path,FileSystem> fileSystems;
508 // </editor-fold>
510 // <editor-fold defaultstate="collapsed" desc="Utility methods">
512 private static String getRelativePath(String className, Kind kind) {
513 return className.replace(".", "/") + kind.extension;
514 }
516 private static String getRelativePath(String packageName, String relativeName) {
517 return packageName.isEmpty()
518 ? relativeName : packageName.replace(".", "/") + "/" + relativeName;
519 }
521 private static String getBaseName(String relativePath) {
522 int lastSep = relativePath.lastIndexOf("/");
523 return relativePath.substring(lastSep + 1); // safe if "/" not found
524 }
526 private static boolean isDirectory(Path path) throws IOException {
527 BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
528 return attrs.isDirectory();
529 }
531 private static Path getPath(FileSystem fs, String relativePath) {
532 return fs.getPath(relativePath.replace("/", fs.getSeparator()));
533 }
535 private static Path resolve(Path base, String relativePath) {
536 FileSystem fs = base.getFileSystem();
537 Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator()));
538 return base.resolve(rp);
539 }
541 // </editor-fold>
543 }