# HG changeset patch # User ksrini # Date 1299548382 28800 # Node ID 6970d9fb8e026dc27f85005a2123be6b51b861b2 # Parent cb9493a80341e712a188d0041b443af36d8c53bb 7021927: javac: regression in performance Reviewed-by: jjg diff -r cb9493a80341 -r 6970d9fb8e02 src/share/classes/com/sun/tools/javac/file/JavacFileManager.java --- a/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java Mon Mar 07 13:45:06 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/file/JavacFileManager.java Mon Mar 07 17:39:42 2011 -0800 @@ -89,7 +89,7 @@ private FSInfo fsInfo; - private boolean useZipFileIndex; + private boolean contextUseOptimizedZip; private ZipFileIndexCache zipFileIndexCache; private final File uninited = new File("U N I N I T E D"); @@ -164,8 +164,8 @@ fsInfo = FSInfo.instance(context); - useZipFileIndex = options.isSet("useOptimizedZip"); - if (useZipFileIndex) + contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true); + if (contextUseOptimizedZip) zipFileIndexCache = ZipFileIndexCache.getSharedInstance(); mmappedIO = options.isSet("mmappedIO"); @@ -471,9 +471,27 @@ private static final RelativeDirectory symbolFilePrefix = new RelativeDirectory("META-INF/sym/rt.jar/"); + /* + * This method looks for a ZipFormatException and takes appropriate + * evasive action. If there is a failure in the fast mode then we + * fail over to the platform zip, and allow it to deal with a potentially + * non compliant zip file. + */ + protected Archive openArchive(File zipFilename) throws IOException { + try { + return openArchive(zipFilename, contextUseOptimizedZip); + } catch (IOException ioe) { + if (ioe instanceof ZipFileIndex.ZipFormatException) { + return openArchive(zipFilename, false); + } else { + throw ioe; + } + } + } + /** Open a new zip file directory, and cache it. */ - protected Archive openArchive(File zipFileName) throws IOException { + private Archive openArchive(File zipFileName, boolean useOptimizedZip) throws IOException { File origZipFileName = zipFileName; if (!ignoreSymbolFile && paths.isDefaultBootClassPathRtJar(zipFileName)) { File file = zipFileName.getParentFile().getParentFile(); // ${java.home} @@ -495,7 +513,7 @@ boolean usePreindexedCache = false; String preindexCacheLocation = null; - if (!useZipFileIndex) { + if (!useOptimizedZip) { zdir = new ZipFile(zipFileName); } else { usePreindexedCache = options.isSet("usezipindex"); @@ -524,23 +542,22 @@ } if (origZipFileName == zipFileName) { - if (!useZipFileIndex) { + if (!useOptimizedZip) { archive = new ZipArchive(this, zdir); } else { archive = new ZipFileIndexArchive(this, - zipFileIndexCache.getZipFileIndex(zipFileName, + zipFileIndexCache.getZipFileIndex(zipFileName, null, usePreindexedCache, preindexCacheLocation, options.isSet("writezipindexfiles"))); } } else { - if (!useZipFileIndex) { + if (!useOptimizedZip) { archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix); - } - else { + } else { archive = new ZipFileIndexArchive(this, - zipFileIndexCache.getZipFileIndex(zipFileName, + zipFileIndexCache.getZipFileIndex(zipFileName, symbolFilePrefix, usePreindexedCache, preindexCacheLocation, @@ -549,6 +566,8 @@ } } catch (FileNotFoundException ex) { archive = new MissingArchive(zipFileName); + } catch (ZipFileIndex.ZipFormatException zfe) { + throw zfe; } catch (IOException ex) { if (zipFileName.exists()) log.error("error.reading.file", zipFileName, getMessage(ex)); diff -r cb9493a80341 -r 6970d9fb8e02 src/share/classes/com/sun/tools/javac/file/ZipFileIndex.java --- a/src/share/classes/com/sun/tools/javac/file/ZipFileIndex.java Mon Mar 07 13:45:06 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/file/ZipFileIndex.java Mon Mar 07 17:39:42 2011 -0800 @@ -492,8 +492,30 @@ public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException { this.zipRandomFile = zipRandomFile; this.zipFileIndex = index; + hasValidHeader(); + findCENRecord(start, end); + } - findCENRecord(start, end); + /* + * the zip entry signature should be at offset 0, otherwise allow the + * calling logic to take evasive action by throwing ZipFormatException. + */ + private boolean hasValidHeader() throws IOException { + final long pos = zipRandomFile.getFilePointer(); + try { + if (zipRandomFile.read() == 'P') { + if (zipRandomFile.read() == 'K') { + if (zipRandomFile.read() == 0x03) { + if (zipRandomFile.read() == 0x04) { + return true; + } + } + } + } + } finally { + zipRandomFile.seek(pos); + } + throw new ZipFormatException("invalid zip magic"); } /* @@ -529,7 +551,13 @@ zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12) + 2]; zipDir[0] = endbuf[i + 10]; zipDir[1] = endbuf[i + 11]; - zipRandomFile.seek(start + get4ByteLittleEndian(endbuf, i + 16)); + int sz = get4ByteLittleEndian(endbuf, i + 16); + // a negative offset or the entries field indicates a + // potential zip64 archive + if (sz < 0 || get2ByteLittleEndian(zipDir, 0) == 0xffff) { + throw new ZipFormatException("detected a zip64 archive"); + } + zipRandomFile.seek(start + sz); zipRandomFile.readFully(zipDir, 2, zipDir.length - 2); return; } else { @@ -1127,4 +1155,18 @@ } } + /* + * Exception primarily used to implement a failover, used exclusively here. + */ + + static final class ZipFormatException extends IOException { + private static final long serialVersionUID = 8000196834066748623L; + protected ZipFormatException(String message) { + super(message); + } + + protected ZipFormatException(String message, Throwable cause) { + super(message, cause); + } + } } diff -r cb9493a80341 -r 6970d9fb8e02 src/share/classes/com/sun/tools/javac/util/Options.java --- a/src/share/classes/com/sun/tools/javac/util/Options.java Mon Mar 07 13:45:06 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/util/Options.java Mon Mar 07 17:39:42 2011 -0800 @@ -76,6 +76,22 @@ } /** + * Get the boolean value for an option, patterned after Boolean.getBoolean, + * essentially will return true, iff the value exists and is set to "true". + */ + public boolean getBoolean(String name) { + return getBoolean(name, false); + } + + /** + * Get the boolean with a default value if the option is not set. + */ + public boolean getBoolean(String name, boolean defaultValue) { + String value = get(name); + return (value == null) ? defaultValue : Boolean.parseBoolean(value); + } + + /** * Check if the value for an undocumented option has been set. */ public boolean isSet(String name) { diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/6508981/TestInferBinaryName.java --- a/test/tools/javac/6508981/TestInferBinaryName.java Mon Mar 07 13:45:06 2011 -0800 +++ b/test/tools/javac/6508981/TestInferBinaryName.java Mon Mar 07 17:39:42 2011 -0800 @@ -139,9 +139,8 @@ throws IOException { Context ctx = new Context(); Options options = Options.instance(ctx); - // uugh, ugly back door, should be cleaned up, someday - if (zipFileIndexKind == USE_ZIP_FILE_INDEX) - options.put("useOptimizedZip", "true"); + options.put("useOptimizedZip", + Boolean.toString(zipFileIndexKind == USE_ZIP_FILE_INDEX)); if (symFileKind == IGNORE_SYMBOL_FILE) options.put("ignore.symbol.file", "true"); diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/api/6411310/Test.java --- a/test/tools/javac/api/6411310/Test.java Mon Mar 07 13:45:06 2011 -0800 +++ b/test/tools/javac/api/6411310/Test.java Mon Mar 07 17:39:42 2011 -0800 @@ -153,14 +153,12 @@ Context c = new Context(); Options options = Options.instance(c); - if (useOptimizedZip) { - options.put("useOptimizedZip", "true"); - } + options.put("useOptimizedZip", Boolean.toString(useOptimizedZip)); - if (!useSymbolFile) { - options.put("ignore.symbol.file", "true"); - } - return new JavacFileManager(c, false, null); + if (!useSymbolFile) { + options.put("ignore.symbol.file", "true"); + } + return new JavacFileManager(c, false, null); } File createDir(String name, String... entries) throws Exception { diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/api/T6838467.java --- a/test/tools/javac/api/T6838467.java Mon Mar 07 13:45:06 2011 -0800 +++ b/test/tools/javac/api/T6838467.java Mon Mar 07 17:39:42 2011 -0800 @@ -178,12 +178,10 @@ return fm; } - JavacFileManager createFileManager(boolean useOptimedZipIndex) { + JavacFileManager createFileManager(boolean useOptimizedZip) { Context ctx = new Context(); - if (useOptimedZipIndex) { - Options options = Options.instance(ctx); - options.put("useOptimizedZip", "true"); - } + Options options = Options.instance(ctx); + options.put("useOptimizedZip", Boolean.toString(useOptimizedZip)); return new JavacFileManager(ctx, false, null); } diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/api/T6877206.java --- a/test/tools/javac/api/T6877206.java Mon Mar 07 13:45:06 2011 -0800 +++ b/test/tools/javac/api/T6877206.java Mon Mar 07 17:39:42 2011 -0800 @@ -168,9 +168,7 @@ JavacFileManager createFileManager(boolean useOptimizedZip, boolean useSymbolFile) { Context ctx = new Context(); Options options = Options.instance(ctx); - if (useOptimizedZip) { - options.put("useOptimizedZip", "true"); - } + options.put("useOptimizedZip", Boolean.toString(useOptimizedZip)); if (!useSymbolFile) { options.put("ignore.symbol.file", "true"); } diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/file/zip/T6836682.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/file/zip/T6836682.java Mon Mar 07 17:39:42 2011 -0800 @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6836682 + * @summary JavacFileManager handles zip64 archives (64K+ entries and large file support) + * @compile -XDignore.symbol.file T6836682.java Utils.java + * @run main T6836682 + */ +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +public class T6836682 { + + private static final long GIGA = 1024 * 1024 * 1024; + + static void createLargeFile(File outFile, long minlength) throws IOException { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + byte[] buffer = new byte[Short.MAX_VALUE * 2]; + try { + fos = new FileOutputStream(outFile); + bos = new BufferedOutputStream(fos); + long count = minlength / ( Short.MAX_VALUE * 2) + 1; + for (long i = 0 ; i < count ; i++) { + bos.write(buffer); + } + } finally { + Utils.close(bos); + Utils.close(fos); + } + if (outFile.length() < minlength) { + throw new RuntimeException("could not create large file " + outFile.getAbsolutePath()); + } + } + + static void createJarWithLargeFile(File jarFile, File javaFile, + long minlength) throws IOException { + Utils.createClassFile(javaFile, null, true); + File largeFile = new File("large.data"); + createLargeFile(largeFile, minlength); + String[] jarArgs = { + "0cvf", + jarFile.getAbsolutePath(), + largeFile.getName(), + Utils.getClassFileName(javaFile) + }; + Utils.jarTool.run(jarArgs); + // deleted to prevent accidental linkage + new File(Utils.getClassFileName(javaFile)).delete(); + } + + static void createLargeJar(File jarFile, File javaFile) throws IOException { + File classFile = new File(Utils.getClassFileName(javaFile)); + Utils.createClassFile(javaFile, null, true); + JarOutputStream jos = null; + FileInputStream fis = null; + try { + jos = new JarOutputStream(new FileOutputStream(jarFile)); + + for (int i = 0; i < Short.MAX_VALUE * 2 + 10; i++) { + jos.putNextEntry(new ZipEntry("X" + i + ".txt")); + } + jos.putNextEntry(new ZipEntry(classFile.getName())); + fis = new FileInputStream(classFile); + Utils.copyStream(fis, jos); + } finally { + Utils.close(jos); + Utils.close(fis); + } + // deleted to prevent accidental linkage + new File(Utils.getClassFileName(javaFile)).delete(); + } + + // a jar with entries exceeding 64k + a class file for the existential test + public static void testLargeJar(String... args) throws IOException { + File largeJar = new File("large.jar"); + File javaFile = new File("Foo.java"); + createLargeJar(largeJar, javaFile); + + File testFile = new File("Bar.java"); + try { + Utils.createJavaFile(testFile, javaFile); + if (!Utils.compile("-doe", "-verbose", "-cp", + largeJar.getAbsolutePath(), testFile.getAbsolutePath())) { + throw new IOException("test failed"); + } + } finally { + Utils.deleteFile(largeJar); + } + } + + // a jar with an enormous file + a class file for the existential test + public static void testHugeJar(String... args) throws IOException { + final File largeJar = new File("huge.jar"); + final File javaFile = new File("Foo.java"); + + final Path path = largeJar.getAbsoluteFile().getParentFile().toPath(); + final long available = Files.getFileStore(path).getUsableSpace(); + final long MAX_VALUE = 0xFFFF_FFFFL; + + final long absolute = MAX_VALUE + 1L; + final long required = (long)(absolute * 1.1); // pad for sundries + System.out.println("\tavailable: " + available / GIGA + " GB"); + System.out.println("\required: " + required / GIGA + " GB"); + + if (available > required) { + createJarWithLargeFile(largeJar, javaFile, absolute); + File testFile = new File("Bar.java"); + Utils.createJavaFile(testFile, javaFile); + try { + if (!Utils.compile("-doe", "-verbose", "-cp", + largeJar.getAbsolutePath(), testFile.getAbsolutePath())) { + throw new IOException("test failed"); + } + } finally { + Utils.deleteFile(largeJar); + } + } else { + System.out.println("Warning: test passes vacuously, requirements exceeds available space"); + } + } + + public static void main(String... args) throws IOException { + testLargeJar(); + testHugeJar(); + } +} diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/file/zip/T6865530.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/file/zip/T6865530.java Mon Mar 07 17:39:42 2011 -0800 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6865530 + * @summary ensure JavacFileManager handles non-standard zipfiles. + * @compile -XDignore.symbol.file T6865530.java + * @run main T6865530 + */ + + +import java.io.File; + + +public class T6865530 { + + public static void main(String... args) throws Exception { + File badFile = new File("bad.exe"); + File testJar = new File("test.jar"); + File fooJava = new File("Foo.java"); + File barJava = new File("Bar.java"); + + // create a jar by compiling a file, and append the jar to some + // arbitrary data to offset the start of the zip/jar archive + Utils.createJavaFile(fooJava); + Utils.compile("-doe", "-verbose", fooJava.getName()); + String[] jarArgs = { + "cvf", testJar.getAbsolutePath(), "Foo.class" + }; + Utils.jarTool.run(jarArgs); + Utils.cat(badFile, fooJava, testJar); + + // create test file and use the above file as a classpath + Utils.createJavaFile(barJava); + try { + if (!Utils.compile("-doe", "-verbose", "-cp", badFile.getAbsolutePath(), "Bar.java")) { + throw new RuntimeException("test fails javac did not compile"); + } + } finally { + Utils.deleteFile(badFile); + Utils.deleteFile(testJar); + } + } +} + diff -r cb9493a80341 -r 6970d9fb8e02 test/tools/javac/file/zip/Utils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/file/zip/Utils.java Mon Mar 07 17:39:42 2011 -0800 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +public class Utils { + + static final sun.tools.jar.Main jarTool = + new sun.tools.jar.Main(System.out, System.err, "jar-tool"); + + static final com.sun.tools.javac.Main javac = + new com.sun.tools.javac.Main(); + + private Utils(){} + + public static boolean compile(String... args) { + return javac.compile(args) == 0; + } + + public static void createClassFile(File javaFile, File superClass, + boolean delete) throws IOException { + createJavaFile(javaFile, superClass); + if (!compile(javaFile.getName())) { + throw new RuntimeException("compile failed unexpectedly"); + } + if (delete) javaFile.delete(); + } + + public static void createJavaFile(File outFile) throws IOException { + createJavaFile(outFile, null); + } + + public static void createJavaFile(File outFile, File superClass) throws IOException { + PrintStream ps = null; + String srcStr = "public class " + getSimpleName(outFile) + " "; + if (superClass != null) { + srcStr = srcStr.concat("extends " + getSimpleName(superClass) + " "); + } + srcStr = srcStr.concat("{}"); + try { + FileOutputStream fos = new FileOutputStream(outFile); + ps = new PrintStream(fos); + ps.println(srcStr); + } finally { + close(ps); + } + } + + static String getClassFileName(File javaFile) { + return javaFile.getName().endsWith(".java") + ? javaFile.getName().replace(".java", ".class") + : null; + } + + static String getSimpleName(File inFile) { + String fname = inFile.getName(); + return fname.substring(0, fname.indexOf(".")); + } + + public static void copyStream(InputStream in, OutputStream out) throws IOException { + byte[] buf = new byte[8192]; + int n = in.read(buf); + while (n > 0) { + out.write(buf, 0, n); + n = in.read(buf); + } + } + + public static void close(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException ignore) {} + } + } + + public static void deleteFile(File f) { + if (!f.delete()) { + throw new RuntimeException("could not delete file: " + f.getAbsolutePath()); + } + } + + public static void cat(File output, File... files) throws IOException { + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + FileOutputStream fos = null; + try { + fos = new FileOutputStream(output); + bos = new BufferedOutputStream(fos); + for (File x : files) { + FileInputStream fis = new FileInputStream(x); + bis = new BufferedInputStream(fis); + copyStream(bis, bos); + Utils.close(bis); + } + } finally { + Utils.close(bis); + Utils.close(bos); + Utils.close(fos); + } + } +}