6558476: com/sun/tools/javac/Main.compile don't release file handles on return
authorjjg
Wed, 12 Aug 2009 10:34:13 -0700
changeset 3656 d4e34b76b0c3
parent 3655 f9ee66e780f4
child 3657 25fdd465d1dc
6558476: com/sun/tools/javac/Main.compile don't release file handles on return Reviewed-by: darcy
langtools/src/share/classes/com/sun/tools/javac/file/CloseableURLClassLoader.java
langtools/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java
langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java
langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java
langtools/test/tools/javac/T6558476.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/file/CloseableURLClassLoader.java	Wed Aug 12 10:34:13 2009 -0700
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.tools.javac.file;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.jar.JarFile;
+
+/**
+ * A URLClassLoader that also implements Closeable.
+ * Reflection is used to access internal data structures in the URLClassLoader,
+ * since no public API exists for this purpose. Therefore this code is somewhat
+ * fragile. Caveat emptor.
+ * @throws Error if the internal data structures are not as expected.
+ *
+ *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
+ *  you write code that depends on this, you do so at your own risk.
+ *  This code and its internal interfaces are subject to change or
+ *  deletion without notice.</b>
+ */
+class CloseableURLClassLoader
+        extends URLClassLoader implements Closeable {
+    CloseableURLClassLoader(URL[] urls, ClassLoader parent) throws Error {
+        super(urls, parent);
+        try {
+            getLoaders(); //proactive check that URLClassLoader is as expected
+        } catch (Throwable t) {
+            throw new Error("cannot create CloseableURLClassLoader", t);
+        }
+    }
+
+    /**
+     * Close any jar files that may have been opened by the class loader.
+     * Reflection is used to access the jar files in the URLClassLoader's
+     * internal data structures.
+     * @throws java.io.IOException if the jar files cannot be found for any
+     * reson, or if closing the jar file itself causes an IOException.
+     */
+    public void close() throws IOException {
+        try {
+            for (Object l: getLoaders()) {
+                if (l.getClass().getName().equals("sun.misc.URLClassPath$JarLoader")) {
+                    Field jarField = l.getClass().getDeclaredField("jar");
+                    JarFile jar = (JarFile) getField(l, jarField);
+                    //System.err.println("CloseableURLClassLoader: closing " + jar);
+                    jar.close();
+                }
+            }
+        } catch (Throwable t) {
+            IOException e = new IOException("cannot close class loader");
+            e.initCause(t);
+            throw e;
+        }
+    }
+
+    private ArrayList<?> getLoaders()
+            throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException
+    {
+        Field ucpField = URLClassLoader.class.getDeclaredField("ucp");
+        Object urlClassPath = getField(this, ucpField);
+        if (urlClassPath == null)
+            throw new AssertionError("urlClassPath not set in URLClassLoader");
+        Field loadersField = urlClassPath.getClass().getDeclaredField("loaders");
+        return (ArrayList<?>) getField(urlClassPath, loadersField);
+    }
+
+    private Object getField(Object o, Field f)
+            throws IllegalArgumentException, IllegalAccessException {
+        boolean prev = f.isAccessible();
+        try {
+            f.setAccessible(true);
+            return f.get(o);
+        } finally {
+            f.setAccessible(prev);
+        }
+    }
+
+}
--- a/langtools/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Wed Aug 12 07:54:30 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Wed Aug 12 10:34:13 2009 -0700
@@ -33,6 +33,7 @@
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.lang.ref.SoftReference;
+import java.lang.reflect.Constructor;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
@@ -76,6 +77,7 @@
 import com.sun.tools.javac.util.Log;
 import com.sun.tools.javac.util.Options;
 
+import java.io.Closeable;
 import static javax.tools.StandardLocation.*;
 import static com.sun.tools.javac.main.OptionName.*;
 
@@ -131,6 +133,7 @@
 
     protected boolean mmappedIO;
     protected boolean ignoreSymbolFile;
+    protected String classLoaderClass;
 
     /**
      * User provided charset (through javax.tools).
@@ -180,6 +183,7 @@
 
         mmappedIO = options.get("mmappedIO") != null;
         ignoreSymbolFile = options.get("ignore.symbol.file") != null;
+        classLoaderClass = options.get("procloader");
     }
 
     public JavaFileObject getFileForInput(String name) {
@@ -747,8 +751,40 @@
                 throw new AssertionError(e);
             }
         }
-        return new URLClassLoader(lb.toArray(new URL[lb.size()]),
-            getClass().getClassLoader());
+
+        URL[] urls = lb.toArray(new URL[lb.size()]);
+        ClassLoader thisClassLoader = getClass().getClassLoader();
+
+        // Bug: 6558476
+        // Ideally, ClassLoader should be Closeable, but before JDK7 it is not.
+        // On older versions, try the following, to get a closeable classloader.
+
+        // 1: Allow client to specify the class to use via hidden option
+        if (classLoaderClass != null) {
+            try {
+                Class<? extends ClassLoader> loader =
+                        Class.forName(classLoaderClass).asSubclass(ClassLoader.class);
+                Class<?>[] constrArgTypes = { URL[].class, ClassLoader.class };
+                Constructor<? extends ClassLoader> constr = loader.getConstructor(constrArgTypes);
+                return constr.newInstance(new Object[] { urls, thisClassLoader });
+            } catch (Throwable t) {
+                // ignore errors loading user-provided class loader, fall through
+            }
+        }
+
+        // 2: If URLClassLoader implements Closeable, use that.
+        if (Closeable.class.isAssignableFrom(URLClassLoader.class))
+            return new URLClassLoader(urls, thisClassLoader);
+
+        // 3: Try using private reflection-based CloseableURLClassLoader
+        try {
+            return new CloseableURLClassLoader(urls, thisClassLoader);
+        } catch (Throwable t) {
+            // ignore errors loading workaround class loader, fall through
+        }
+
+        // 4: If all else fails, use plain old standard URLClassLoader
+        return new URLClassLoader(urls, thisClassLoader);
     }
 
     public Iterable<JavaFileObject> list(Location location,
--- a/langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Wed Aug 12 07:54:30 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Wed Aug 12 10:34:13 2009 -0700
@@ -813,6 +813,9 @@
         } catch (Abort ex) {
             if (devVerbose)
                 ex.printStackTrace();
+        } finally {
+            if (procEnvImpl != null)
+                procEnvImpl.close();
         }
     }
 
@@ -936,7 +939,7 @@
     /**
      * Object to handle annotation processing.
      */
-    JavacProcessingEnvironment procEnvImpl = null;
+    private JavacProcessingEnvironment procEnvImpl = null;
 
     /**
      * Check if we should process annotations.
@@ -947,7 +950,8 @@
      * @param processors user provided annotation processors to bypass
      * discovery, {@code null} means that no processors were provided
      */
-    public void initProcessAnnotations(Iterable<? extends Processor> processors) {
+    public void initProcessAnnotations(Iterable<? extends Processor> processors)
+                throws IOException {
         // Process annotations if processing is not disabled and there
         // is at least one Processor available.
         Options options = Options.instance(context);
@@ -974,7 +978,8 @@
     }
 
     // TODO: called by JavacTaskImpl
-    public JavaCompiler processAnnotations(List<JCCompilationUnit> roots) throws IOException {
+    public JavaCompiler processAnnotations(List<JCCompilationUnit> roots)
+            throws IOException {
         return processAnnotations(roots, List.<String>nil());
     }
 
@@ -1061,10 +1066,14 @@
                         return this;
                 }
             }
-            JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols);
-            if (c != this)
-                annotationProcessingOccurred = c.annotationProcessingOccurred = true;
-            return c;
+            try {
+                JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols);
+                if (c != this)
+                    annotationProcessingOccurred = c.annotationProcessingOccurred = true;
+                return c;
+            } finally {
+                procEnvImpl.close();
+            }
         } catch (CompletionFailure ex) {
             log.error("cant.access", ex.sym, ex.getDetailValue());
             return this;
--- a/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Wed Aug 12 07:54:30 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Wed Aug 12 10:34:13 2009 -0700
@@ -136,6 +136,8 @@
      */
     Source source;
 
+    private ClassLoader processorClassLoader;
+
     /**
      * JavacMessages object used for localization
      */
@@ -203,7 +205,7 @@
             JavaFileManager fileManager = context.get(JavaFileManager.class);
             try {
                 // If processorpath is not explicitly set, use the classpath.
-                ClassLoader processorCL = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
+                processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
                     ? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
                     : fileManager.getClassLoader(CLASS_PATH);
 
@@ -213,9 +215,9 @@
                  * provider mechanism to create the processor iterator.
                  */
                 if (processorNames != null) {
-                    processorIterator = new NameProcessIterator(processorNames, processorCL, log);
+                    processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log);
                 } else {
-                    processorIterator = new ServiceIterator(processorCL, log);
+                    processorIterator = new ServiceIterator(processorClassLoader, log);
                 }
             } catch (SecurityException e) {
                 /*
@@ -1019,9 +1021,11 @@
     /**
      * Free resources related to annotation processing.
      */
-    public void close() {
+    public void close() throws IOException {
         filer.close();
         discoveredProcs = null;
+        if (processorClassLoader != null && processorClassLoader instanceof Closeable)
+            ((Closeable) processorClassLoader).close();
     }
 
     private List<ClassSymbol> getTopLevelClasses(List<? extends JCCompilationUnit> units) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/T6558476.java	Wed Aug 12 10:34:13 2009 -0700
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @run main/othervm -Xmx512m -Xms512m  T6558476
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+import com.sun.tools.javac.Main;
+
+public class T6558476 {
+    private static File copyFileTo(File file, File directory) throws IOException {
+        File newFile = new File(directory, file.getName());
+        FileInputStream fis = null;
+        FileOutputStream fos = null;
+        try {
+            fis = new FileInputStream(file);
+            fos = new FileOutputStream(newFile);
+            byte buff[] = new byte[1024];
+            int val;
+            while ((val = fis.read(buff)) > 0)
+                fos.write(buff, 0, val);
+        } finally {
+            if (fis != null)
+                fis.close();
+            if (fos != null)
+                fos.close();
+        }
+        return newFile;
+    }
+
+    private static String generateJavaClass(String className) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("import sun.net.spi.nameservice.dns.DNSNameService;\n");
+        sb.append("public class ");
+        sb.append(className);
+        sb.append(" {\n");
+        sb.append("  public void doStuff() {\n");
+        sb.append("    DNSNameService dns = null;\n");
+        sb.append("  }\n");
+        sb.append("}\n");
+        return sb.toString();
+    }
+
+    public static void main(String[] args) throws IOException {
+        File javaHomeDir = new File(System.getProperty("java.home"));
+        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+        File outputDir = new File(tmpDir, "outputDir" + new Random().nextInt(65536));
+        outputDir.mkdir();
+        outputDir.deleteOnExit();
+
+        File dnsjarfile = new File(javaHomeDir, "lib" + File.separator + "ext" + File.separator + "dnsns.jar");
+        File tmpJar = copyFileTo(dnsjarfile, outputDir);
+        String className = "TheJavaFile";
+        File javaFile = new File(outputDir, className + ".java");
+        javaFile.deleteOnExit();
+        FileOutputStream fos = new FileOutputStream(javaFile);
+        fos.write(generateJavaClass(className).getBytes());
+        fos.close();
+
+        int rc = Main.compile(new String[]{"-d", outputDir.getPath(),
+                    "-classpath",
+                    tmpJar.getPath(),
+                    javaFile.getAbsolutePath()});
+        if (rc != 0) {
+            throw new Error("Couldn't compile the file (exit code=" + rc + ")");
+        }
+
+        if (tmpJar.delete()) {
+            System.out.println("jar file successfully deleted");
+        } else {
+            throw new Error("Error deleting file \"" + tmpJar.getPath() + "\"");
+        }
+    }
+}