6558476: com/sun/tools/javac/Main.compile don't release file handles on return

Wed, 12 Aug 2009 10:34:13 -0700

author
jjg
date
Wed, 12 Aug 2009 10:34:13 -0700
changeset 372
7dbb79875a63
parent 371
71680973d8ec
child 373
b055a5ea0dad

6558476: com/sun/tools/javac/Main.compile don't release file handles on return
Reviewed-by: darcy

src/share/classes/com/sun/tools/javac/file/CloseableURLClassLoader.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/tools/javac/file/JavacFileManager.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/tools/javac/main/JavaCompiler.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java file | annotate | diff | comparison | revisions
test/tools/javac/T6558476.java file | annotate | diff | comparison | revisions
     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 +}

mercurial