Wed, 12 Aug 2009 10:34:13 -0700
6558476: com/sun/tools/javac/Main.compile don't release file handles on return
Reviewed-by: darcy
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/classes/com/sun/tools/javac/file/CloseableURLClassLoader.java Wed Aug 12 10:34:13 2009 -0700 1.3 @@ -0,0 +1,105 @@ 1.4 +/* 1.5 + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Sun designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Sun in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 1.25 + * CA 95054 USA or visit www.sun.com if you need additional information or 1.26 + * have any questions. 1.27 + */ 1.28 + 1.29 +package com.sun.tools.javac.file; 1.30 + 1.31 +import java.io.Closeable; 1.32 +import java.io.IOException; 1.33 +import java.lang.reflect.Field; 1.34 +import java.net.URL; 1.35 +import java.net.URLClassLoader; 1.36 +import java.util.ArrayList; 1.37 +import java.util.jar.JarFile; 1.38 + 1.39 +/** 1.40 + * A URLClassLoader that also implements Closeable. 1.41 + * Reflection is used to access internal data structures in the URLClassLoader, 1.42 + * since no public API exists for this purpose. Therefore this code is somewhat 1.43 + * fragile. Caveat emptor. 1.44 + * @throws Error if the internal data structures are not as expected. 1.45 + * 1.46 + * <p><b>This is NOT part of any API supported by Sun Microsystems. If 1.47 + * you write code that depends on this, you do so at your own risk. 1.48 + * This code and its internal interfaces are subject to change or 1.49 + * deletion without notice.</b> 1.50 + */ 1.51 +class CloseableURLClassLoader 1.52 + extends URLClassLoader implements Closeable { 1.53 + CloseableURLClassLoader(URL[] urls, ClassLoader parent) throws Error { 1.54 + super(urls, parent); 1.55 + try { 1.56 + getLoaders(); //proactive check that URLClassLoader is as expected 1.57 + } catch (Throwable t) { 1.58 + throw new Error("cannot create CloseableURLClassLoader", t); 1.59 + } 1.60 + } 1.61 + 1.62 + /** 1.63 + * Close any jar files that may have been opened by the class loader. 1.64 + * Reflection is used to access the jar files in the URLClassLoader's 1.65 + * internal data structures. 1.66 + * @throws java.io.IOException if the jar files cannot be found for any 1.67 + * reson, or if closing the jar file itself causes an IOException. 1.68 + */ 1.69 + public void close() throws IOException { 1.70 + try { 1.71 + for (Object l: getLoaders()) { 1.72 + if (l.getClass().getName().equals("sun.misc.URLClassPath$JarLoader")) { 1.73 + Field jarField = l.getClass().getDeclaredField("jar"); 1.74 + JarFile jar = (JarFile) getField(l, jarField); 1.75 + //System.err.println("CloseableURLClassLoader: closing " + jar); 1.76 + jar.close(); 1.77 + } 1.78 + } 1.79 + } catch (Throwable t) { 1.80 + IOException e = new IOException("cannot close class loader"); 1.81 + e.initCause(t); 1.82 + throw e; 1.83 + } 1.84 + } 1.85 + 1.86 + private ArrayList<?> getLoaders() 1.87 + throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException 1.88 + { 1.89 + Field ucpField = URLClassLoader.class.getDeclaredField("ucp"); 1.90 + Object urlClassPath = getField(this, ucpField); 1.91 + if (urlClassPath == null) 1.92 + throw new AssertionError("urlClassPath not set in URLClassLoader"); 1.93 + Field loadersField = urlClassPath.getClass().getDeclaredField("loaders"); 1.94 + return (ArrayList<?>) getField(urlClassPath, loadersField); 1.95 + } 1.96 + 1.97 + private Object getField(Object o, Field f) 1.98 + throws IllegalArgumentException, IllegalAccessException { 1.99 + boolean prev = f.isAccessible(); 1.100 + try { 1.101 + f.setAccessible(true); 1.102 + return f.get(o); 1.103 + } finally { 1.104 + f.setAccessible(prev); 1.105 + } 1.106 + } 1.107 + 1.108 +}
2.1 --- a/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java Wed Aug 12 07:54:30 2009 -0700 2.2 +++ b/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java Wed Aug 12 10:34:13 2009 -0700 2.3 @@ -33,6 +33,7 @@ 2.4 import java.io.InputStream; 2.5 import java.io.OutputStreamWriter; 2.6 import java.lang.ref.SoftReference; 2.7 +import java.lang.reflect.Constructor; 2.8 import java.net.MalformedURLException; 2.9 import java.net.URI; 2.10 import java.net.URL; 2.11 @@ -76,6 +77,7 @@ 2.12 import com.sun.tools.javac.util.Log; 2.13 import com.sun.tools.javac.util.Options; 2.14 2.15 +import java.io.Closeable; 2.16 import static javax.tools.StandardLocation.*; 2.17 import static com.sun.tools.javac.main.OptionName.*; 2.18 2.19 @@ -131,6 +133,7 @@ 2.20 2.21 protected boolean mmappedIO; 2.22 protected boolean ignoreSymbolFile; 2.23 + protected String classLoaderClass; 2.24 2.25 /** 2.26 * User provided charset (through javax.tools). 2.27 @@ -180,6 +183,7 @@ 2.28 2.29 mmappedIO = options.get("mmappedIO") != null; 2.30 ignoreSymbolFile = options.get("ignore.symbol.file") != null; 2.31 + classLoaderClass = options.get("procloader"); 2.32 } 2.33 2.34 public JavaFileObject getFileForInput(String name) { 2.35 @@ -747,8 +751,40 @@ 2.36 throw new AssertionError(e); 2.37 } 2.38 } 2.39 - return new URLClassLoader(lb.toArray(new URL[lb.size()]), 2.40 - getClass().getClassLoader()); 2.41 + 2.42 + URL[] urls = lb.toArray(new URL[lb.size()]); 2.43 + ClassLoader thisClassLoader = getClass().getClassLoader(); 2.44 + 2.45 + // Bug: 6558476 2.46 + // Ideally, ClassLoader should be Closeable, but before JDK7 it is not. 2.47 + // On older versions, try the following, to get a closeable classloader. 2.48 + 2.49 + // 1: Allow client to specify the class to use via hidden option 2.50 + if (classLoaderClass != null) { 2.51 + try { 2.52 + Class<? extends ClassLoader> loader = 2.53 + Class.forName(classLoaderClass).asSubclass(ClassLoader.class); 2.54 + Class<?>[] constrArgTypes = { URL[].class, ClassLoader.class }; 2.55 + Constructor<? extends ClassLoader> constr = loader.getConstructor(constrArgTypes); 2.56 + return constr.newInstance(new Object[] { urls, thisClassLoader }); 2.57 + } catch (Throwable t) { 2.58 + // ignore errors loading user-provided class loader, fall through 2.59 + } 2.60 + } 2.61 + 2.62 + // 2: If URLClassLoader implements Closeable, use that. 2.63 + if (Closeable.class.isAssignableFrom(URLClassLoader.class)) 2.64 + return new URLClassLoader(urls, thisClassLoader); 2.65 + 2.66 + // 3: Try using private reflection-based CloseableURLClassLoader 2.67 + try { 2.68 + return new CloseableURLClassLoader(urls, thisClassLoader); 2.69 + } catch (Throwable t) { 2.70 + // ignore errors loading workaround class loader, fall through 2.71 + } 2.72 + 2.73 + // 4: If all else fails, use plain old standard URLClassLoader 2.74 + return new URLClassLoader(urls, thisClassLoader); 2.75 } 2.76 2.77 public Iterable<JavaFileObject> list(Location location,
3.1 --- a/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java Wed Aug 12 07:54:30 2009 -0700 3.2 +++ b/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java Wed Aug 12 10:34:13 2009 -0700 3.3 @@ -813,6 +813,9 @@ 3.4 } catch (Abort ex) { 3.5 if (devVerbose) 3.6 ex.printStackTrace(); 3.7 + } finally { 3.8 + if (procEnvImpl != null) 3.9 + procEnvImpl.close(); 3.10 } 3.11 } 3.12 3.13 @@ -936,7 +939,7 @@ 3.14 /** 3.15 * Object to handle annotation processing. 3.16 */ 3.17 - JavacProcessingEnvironment procEnvImpl = null; 3.18 + private JavacProcessingEnvironment procEnvImpl = null; 3.19 3.20 /** 3.21 * Check if we should process annotations. 3.22 @@ -947,7 +950,8 @@ 3.23 * @param processors user provided annotation processors to bypass 3.24 * discovery, {@code null} means that no processors were provided 3.25 */ 3.26 - public void initProcessAnnotations(Iterable<? extends Processor> processors) { 3.27 + public void initProcessAnnotations(Iterable<? extends Processor> processors) 3.28 + throws IOException { 3.29 // Process annotations if processing is not disabled and there 3.30 // is at least one Processor available. 3.31 Options options = Options.instance(context); 3.32 @@ -974,7 +978,8 @@ 3.33 } 3.34 3.35 // TODO: called by JavacTaskImpl 3.36 - public JavaCompiler processAnnotations(List<JCCompilationUnit> roots) throws IOException { 3.37 + public JavaCompiler processAnnotations(List<JCCompilationUnit> roots) 3.38 + throws IOException { 3.39 return processAnnotations(roots, List.<String>nil()); 3.40 } 3.41 3.42 @@ -1061,10 +1066,14 @@ 3.43 return this; 3.44 } 3.45 } 3.46 - JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols); 3.47 - if (c != this) 3.48 - annotationProcessingOccurred = c.annotationProcessingOccurred = true; 3.49 - return c; 3.50 + try { 3.51 + JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols); 3.52 + if (c != this) 3.53 + annotationProcessingOccurred = c.annotationProcessingOccurred = true; 3.54 + return c; 3.55 + } finally { 3.56 + procEnvImpl.close(); 3.57 + } 3.58 } catch (CompletionFailure ex) { 3.59 log.error("cant.access", ex.sym, ex.getDetailValue()); 3.60 return this;
4.1 --- a/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Wed Aug 12 07:54:30 2009 -0700 4.2 +++ b/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Wed Aug 12 10:34:13 2009 -0700 4.3 @@ -136,6 +136,8 @@ 4.4 */ 4.5 Source source; 4.6 4.7 + private ClassLoader processorClassLoader; 4.8 + 4.9 /** 4.10 * JavacMessages object used for localization 4.11 */ 4.12 @@ -203,7 +205,7 @@ 4.13 JavaFileManager fileManager = context.get(JavaFileManager.class); 4.14 try { 4.15 // If processorpath is not explicitly set, use the classpath. 4.16 - ClassLoader processorCL = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH) 4.17 + processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH) 4.18 ? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH) 4.19 : fileManager.getClassLoader(CLASS_PATH); 4.20 4.21 @@ -213,9 +215,9 @@ 4.22 * provider mechanism to create the processor iterator. 4.23 */ 4.24 if (processorNames != null) { 4.25 - processorIterator = new NameProcessIterator(processorNames, processorCL, log); 4.26 + processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log); 4.27 } else { 4.28 - processorIterator = new ServiceIterator(processorCL, log); 4.29 + processorIterator = new ServiceIterator(processorClassLoader, log); 4.30 } 4.31 } catch (SecurityException e) { 4.32 /* 4.33 @@ -1019,9 +1021,11 @@ 4.34 /** 4.35 * Free resources related to annotation processing. 4.36 */ 4.37 - public void close() { 4.38 + public void close() throws IOException { 4.39 filer.close(); 4.40 discoveredProcs = null; 4.41 + if (processorClassLoader != null && processorClassLoader instanceof Closeable) 4.42 + ((Closeable) processorClassLoader).close(); 4.43 } 4.44 4.45 private List<ClassSymbol> getTopLevelClasses(List<? extends JCCompilationUnit> units) {
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/test/tools/javac/T6558476.java Wed Aug 12 10:34:13 2009 -0700 5.3 @@ -0,0 +1,101 @@ 5.4 +/* 5.5 + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. 5.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5.7 + * 5.8 + * This code is free software; you can redistribute it and/or modify it 5.9 + * under the terms of the GNU General Public License version 2 only, as 5.10 + * published by the Free Software Foundation. 5.11 + * 5.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 5.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 5.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 5.15 + * version 2 for more details (a copy is included in the LICENSE file that 5.16 + * accompanied this code). 5.17 + * 5.18 + * You should have received a copy of the GNU General Public License version 5.19 + * 2 along with this work; if not, write to the Free Software Foundation, 5.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 5.21 + * 5.22 + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 5.23 + * CA 95054 USA or visit www.sun.com if you need additional information or 5.24 + * have any questions. 5.25 + */ 5.26 + 5.27 +/* 5.28 + * @test 5.29 + * @run main/othervm -Xmx512m -Xms512m T6558476 5.30 + */ 5.31 + 5.32 +import java.io.File; 5.33 +import java.io.FileInputStream; 5.34 +import java.io.FileOutputStream; 5.35 +import java.io.IOException; 5.36 +import java.util.Random; 5.37 + 5.38 +import com.sun.tools.javac.Main; 5.39 + 5.40 +public class T6558476 { 5.41 + private static File copyFileTo(File file, File directory) throws IOException { 5.42 + File newFile = new File(directory, file.getName()); 5.43 + FileInputStream fis = null; 5.44 + FileOutputStream fos = null; 5.45 + try { 5.46 + fis = new FileInputStream(file); 5.47 + fos = new FileOutputStream(newFile); 5.48 + byte buff[] = new byte[1024]; 5.49 + int val; 5.50 + while ((val = fis.read(buff)) > 0) 5.51 + fos.write(buff, 0, val); 5.52 + } finally { 5.53 + if (fis != null) 5.54 + fis.close(); 5.55 + if (fos != null) 5.56 + fos.close(); 5.57 + } 5.58 + return newFile; 5.59 + } 5.60 + 5.61 + private static String generateJavaClass(String className) { 5.62 + StringBuffer sb = new StringBuffer(); 5.63 + sb.append("import sun.net.spi.nameservice.dns.DNSNameService;\n"); 5.64 + sb.append("public class "); 5.65 + sb.append(className); 5.66 + sb.append(" {\n"); 5.67 + sb.append(" public void doStuff() {\n"); 5.68 + sb.append(" DNSNameService dns = null;\n"); 5.69 + sb.append(" }\n"); 5.70 + sb.append("}\n"); 5.71 + return sb.toString(); 5.72 + } 5.73 + 5.74 + public static void main(String[] args) throws IOException { 5.75 + File javaHomeDir = new File(System.getProperty("java.home")); 5.76 + File tmpDir = new File(System.getProperty("java.io.tmpdir")); 5.77 + File outputDir = new File(tmpDir, "outputDir" + new Random().nextInt(65536)); 5.78 + outputDir.mkdir(); 5.79 + outputDir.deleteOnExit(); 5.80 + 5.81 + File dnsjarfile = new File(javaHomeDir, "lib" + File.separator + "ext" + File.separator + "dnsns.jar"); 5.82 + File tmpJar = copyFileTo(dnsjarfile, outputDir); 5.83 + String className = "TheJavaFile"; 5.84 + File javaFile = new File(outputDir, className + ".java"); 5.85 + javaFile.deleteOnExit(); 5.86 + FileOutputStream fos = new FileOutputStream(javaFile); 5.87 + fos.write(generateJavaClass(className).getBytes()); 5.88 + fos.close(); 5.89 + 5.90 + int rc = Main.compile(new String[]{"-d", outputDir.getPath(), 5.91 + "-classpath", 5.92 + tmpJar.getPath(), 5.93 + javaFile.getAbsolutePath()}); 5.94 + if (rc != 0) { 5.95 + throw new Error("Couldn't compile the file (exit code=" + rc + ")"); 5.96 + } 5.97 + 5.98 + if (tmpJar.delete()) { 5.99 + System.out.println("jar file successfully deleted"); 5.100 + } else { 5.101 + throw new Error("Error deleting file \"" + tmpJar.getPath() + "\""); 5.102 + } 5.103 + } 5.104 +}