# HG changeset patch # User alundblad # Date 1399476582 -7200 # Node ID cc4f0f71a5051f23754584d97a7cc69031c2e5ac # Parent 84baad0ac4d7e5a9c06154e2e18e80a006bdf62c 8028196: Javac allows timestamps inside rt.jar to affect compilation when using -sourcepath. Summary: Added -XXuserPathsFirst to allow user classes to take precedence over boot classes Reviewed-by: jjg diff -r 84baad0ac4d7 -r cc4f0f71a505 langtools/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java --- a/langtools/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java Wed May 07 15:54:16 2014 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java Wed May 07 17:29:42 2014 +0200 @@ -131,6 +131,12 @@ public boolean preferSource; /** + * Switch: Search classpath and sourcepath for classes before the + * bootclasspath + */ + public boolean userPathsFirst; + + /** * The currently selected profile. */ public final Profile profile; @@ -270,6 +276,7 @@ saveParameterNames = options.isSet("save-parameter-names"); cacheCompletionFailure = options.isUnset("dev"); preferSource = "source".equals(options.get("-Xprefer")); + userPathsFirst = options.isSet(XXUSERPATHSFIRST); profile = Profile.instance(context); @@ -2649,7 +2656,7 @@ if (c.owner == p) // it might be an inner class p.members_field.enter(c); } - } else if (c.classfile != null && (c.flags_field & seen) == 0) { + } else if (!preferCurrent && c.classfile != null && (c.flags_field & seen) == 0) { // if c.classfile == null, we are currently compiling this class // and no further action is necessary. // if (c.flags_field & seen) != 0, we have already encountered @@ -2695,19 +2702,32 @@ private boolean verbosePath = true; + // Set to true when the currently selected file should be kept + private boolean preferCurrent; + /** Load directory of package into members scope. */ private void fillIn(PackageSymbol p) throws IOException { - if (p.members_field == null) p.members_field = new Scope(p); - String packageName = p.fullname.toString(); - - Set kinds = getPackageFileKinds(); + if (p.members_field == null) + p.members_field = new Scope(p); - fillIn(p, PLATFORM_CLASS_PATH, - fileManager.list(PLATFORM_CLASS_PATH, - packageName, - EnumSet.of(JavaFileObject.Kind.CLASS), - false)); + preferCurrent = false; + if (userPathsFirst) { + scanUserPaths(p); + preferCurrent = true; + scanPlatformPath(p); + } else { + scanPlatformPath(p); + scanUserPaths(p); + } + verbosePath = false; + } + + /** + * Scans class path and source path for files in given package. + */ + private void scanUserPaths(PackageSymbol p) throws IOException { + Set kinds = getPackageFileKinds(); Set classKinds = EnumSet.copyOf(kinds); classKinds.remove(JavaFileObject.Kind.SOURCE); @@ -2748,6 +2768,7 @@ } } + String packageName = p.fullname.toString(); if (wantSourceFiles && !haveSourcePath) { fillIn(p, CLASS_PATH, fileManager.list(CLASS_PATH, @@ -2768,7 +2789,17 @@ sourceKinds, false)); } - verbosePath = false; + } + + /** + * Scans platform class path for files in given package. + */ + private void scanPlatformPath(PackageSymbol p) throws IOException { + fillIn(p, PLATFORM_CLASS_PATH, + fileManager.list(PLATFORM_CLASS_PATH, + p.fullname.toString(), + EnumSet.of(JavaFileObject.Kind.CLASS), + false)); } // where private void fillIn(PackageSymbol p, diff -r 84baad0ac4d7 -r cc4f0f71a505 langtools/src/share/classes/com/sun/tools/javac/main/Option.java --- a/langtools/src/share/classes/com/sun/tools/javac/main/Option.java Wed May 07 15:54:16 2014 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/main/Option.java Wed May 07 17:29:42 2014 +0200 @@ -416,6 +416,8 @@ XPREFER("-Xprefer:", "opt.prefer", EXTENDED, BASIC, ONEOF, "source", "newer"), + XXUSERPATHSFIRST("-XXuserPathsFirst", "opt.userpathsfirst", HIDDEN, BASIC), + // see enum PkgInfo XPKGINFO("-Xpkginfo:", "opt.pkginfo", EXTENDED, BASIC, ONEOF, "always", "legacy", "nonempty"), diff -r 84baad0ac4d7 -r cc4f0f71a505 langtools/src/share/classes/com/sun/tools/javac/resources/javac.properties --- a/langtools/src/share/classes/com/sun/tools/javac/resources/javac.properties Wed May 07 15:54:16 2014 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/resources/javac.properties Wed May 07 17:29:42 2014 +0200 @@ -240,6 +240,8 @@ Print information about rounds of annotation processing javac.opt.printProcessorInfo=\ Print information about which annotations a processor is asked to process +javac.opt.userpathsfirst=\ + Search classpath and sourcepath for classes before the bootclasspath instead of after javac.opt.prefer=\ Specify which file to read when both a source file and class file are found for an implicitly compiled class javac.opt.AT=\ diff -r 84baad0ac4d7 -r cc4f0f71a505 langtools/test/tools/javac/options/xprefer/XPreferTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/options/xprefer/XPreferTest.java Wed May 07 17:29:42 2014 +0200 @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2014, 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 + * @summary Tests which path is used to represent an implicit type given + * various xprefer arguments and multiple .class / .java files involved. + * @bug 8028196 + */ + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.ToolProvider; + + +public class XPreferTest { + + enum Dir { + SOURCE_PATH("src"), + CLASS_PATH("cp"), + BOOT_PATH("boot"); + + File file; + Dir(String dir) { + this.file = new File(dir); + } + } + + enum ImplicitOption { + XPREFER_SOURCE("-Xprefer:source"), + XPREFER_NEWER("-Xprefer:newer"), + XXUSERPATHSFIRST("-XXuserPathsFirst"); + + final String optionString; + private ImplicitOption(String optionString) { + this.optionString = optionString; + } + } + + final static JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); + final static File OUTPUT_DIR = new File("out"); + + public static void main(String... args) throws Exception { + + // Initialize test-directories + OUTPUT_DIR.mkdir(); + for (Dir dir : Dir.values()) + dir.file.mkdir(); + + int testCaseCounter = 0; + + for (List dirSubset : SubseqIter.subseqsOf(Dir.values())) { + + if (dirSubset.isEmpty()) + continue; + + for (ImplicitOption policy : ImplicitOption.values()) { + for (List dirOrder : PermutationIterator.permutationsOf(dirSubset)) { + new TestCase(dirOrder, policy, testCaseCounter++).run(); + } + } + } + } + + static class TestCase { + + String classId; + List dirs; + ImplicitOption option; + + public TestCase(List dirs, ImplicitOption option, int testCaseNum) { + this.dirs = dirs; + this.option = option; + this.classId = "XPreferTestImplicit" + testCaseNum; + } + + void run() throws Exception { + + System.out.println("Test:"); + System.out.println(" Class id: " + classId); + System.out.println(" Dirs: " + dirs); + System.out.println(" Option: " + option); + + createTestFiles(); + String compileOutput = compile(); + Dir actual = getChosenOrigin(compileOutput); + Dir expected = getExpectedOrigin(); + + System.out.println(" Expected: " + expected); + System.out.println(" Actual: " + actual); + + if (actual != expected) { + throw new RuntimeException(String.format( + "Expected javac to choose %s but %s was chosen", + expected == null ? "" : expected.name(), + actual == null ? "" : actual.name())); + } + } + + Dir getExpectedOrigin() { + + Dir newest = dirs.get(0); + + switch (option) { + + case XPREFER_NEWER: + + Dir cls = dirs.contains(Dir.BOOT_PATH) ? Dir.BOOT_PATH + : dirs.contains(Dir.CLASS_PATH) ? Dir.CLASS_PATH + : null; + + Dir src = dirs.contains(Dir.SOURCE_PATH) ? Dir.SOURCE_PATH + : null; + + for (Dir dir : dirs) + if (dir == cls || dir == src) + return dir; + + return null; + + case XPREFER_SOURCE: + return dirs.contains(Dir.SOURCE_PATH) ? Dir.SOURCE_PATH + : dirs.contains(Dir.BOOT_PATH) ? Dir.BOOT_PATH + : dirs.contains(Dir.CLASS_PATH) ? Dir.CLASS_PATH + : null; + + case XXUSERPATHSFIRST: + + for (Dir dir : dirs) + if (dir == Dir.SOURCE_PATH || dir == Dir.CLASS_PATH) + return dir; + + // Neither SOURCE_PATH nor CLASS_PATH among dirs. Safty check: + if (newest != Dir.BOOT_PATH) + throw new AssertionError("Expected to find BOOT_PATH"); + + return Dir.BOOT_PATH; + + default: + throw new RuntimeException("Unhandled policy case."); + } + } + + Dir getChosenOrigin(String compilerOutput) { + Scanner s = new Scanner(compilerOutput); + while (s.hasNextLine()) { + String line = s.nextLine(); + if (line.matches("\\[loading .*\\]")) + for (Dir dir : Dir.values()) + if (line.contains(dir.file.getName() + "/" + classId)) + return dir; + } + return null; + } + + String compile() throws IOException { + + // Create a class that references classId + File explicit = new File("ExplicitClass.java"); + FileWriter filewriter = new FileWriter(explicit); + filewriter.append("class ExplicitClass { " + classId + " implicit; }"); + filewriter.close(); + + StringWriter sw = new StringWriter(); + + com.sun.tools.javac.Main.compile(new String[] { + "-verbose", + option.optionString, + "-sourcepath", Dir.SOURCE_PATH.file.getPath(), + "-classpath", Dir.CLASS_PATH.file.getPath(), + "-Xbootclasspath/p:" + Dir.BOOT_PATH.file.getPath(), + "-d", XPreferTest.OUTPUT_DIR.getPath(), + explicit.getPath() + }, new PrintWriter(sw)); + + return sw.toString(); + } + + void createTestFiles() throws IOException { + long t = 1390927988755L; // Tue Jan 28 17:53:08 CET 2014 + for (Dir dir : dirs) { + createFile(dir).setLastModified(t); + t -= 10000; + } + } + + File createFile(Dir dir) throws IOException { + File src = new File(dir.file, classId + ".java"); + try (FileWriter w = new FileWriter(src)) { + w.append("public class " + classId + " {}"); + } + // If we're after the ".java" representation, we're done... + if(dir == Dir.SOURCE_PATH) + return src; + // ...otherwise compile into a ".class". + CompilationTask task = comp.getTask(null, null, null, null, null, + comp.getStandardFileManager(null, null, null).getJavaFileObjects(src)); + File dest = new File(dir.file, classId + ".class"); + if(!task.call() || !dest.exists()) + throw new RuntimeException("Compilation failure."); + src.delete(); + return dest; + } + } +} + +// Iterator for iteration over all subsequences of a given list. +class SubseqIter implements Iterator> { + + List elements; + boolean[] states; + + public SubseqIter(Collection c) { + states = new boolean[c.size()]; + elements = new ArrayList(c); + } + + public static Iterable> subseqsOf(final T[] t) { + return new Iterable>() { + @Override + public Iterator> iterator() { + return new SubseqIter(Arrays.asList(t)); + } + }; + } + + // Roll values in 'states' from index i and forward. + // Return true if we wrapped back to zero. + private boolean roll(int i) { + if (i == states.length) + return true; + if (!roll(i + 1)) + return false; + states[i] = !states[i]; + return !states[i]; + } + + @Override + public List next() { + if (!hasNext()) + throw new NoSuchElementException(); + // Include element i if states[i] is true + List next = new ArrayList(); + for (int i = 0; i < states.length; i++) + if (states[i]) + next.add(elements.get(i)); + if (roll(0)) + states = null; // hasNext() == false from now on. + return next; + } + + @Override + public boolean hasNext() { + return states != null; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} + +class PermutationIterator implements Iterator> { + + DirInt head; + boolean hasNext = true; + + public PermutationIterator(List toPermute) { + ListIterator iter = toPermute.listIterator(); + if (iter.hasNext()) + head = new DirInt(iter.nextIndex(), iter.next()); + DirInt prev = head; + while (iter.hasNext()) { + DirInt di = new DirInt(iter.nextIndex(), iter.next()); + di.left = prev; + prev.right = di; + prev = di; + } + } + + public static Iterable> permutationsOf(final List list) { + return new Iterable>() { + public Iterator> iterator() { + return new PermutationIterator<>(list); + } + }; + } + + @Override + public boolean hasNext() { + return hasNext; + } + + @Override + public List next() { + // Prep return value based on current state + List result = new ArrayList<>(); + for (DirInt di = head; di != null; di = di.right) + result.add(di.object); + + // Step state forward + DirInt maxMob = null; + for (DirInt di = head; di != null; di = di.right) + if (di.isMobile() && (maxMob == null || di.val > maxMob.val)) + maxMob = di; + + if (maxMob == null) { + hasNext = false; + } else { + maxMob.swapWithAdjacent(); + for (DirInt di = head; di != null; di = di.right) + if (di.val > maxMob.val) + di.facingLeft = !di.facingLeft; + } + return result; + } + + private final class DirInt { + int val; + T object; + DirInt left, right; + boolean facingLeft = true; + + public DirInt(int val, T object) { + this.val = val; + this.object = object; + } + + boolean isMobile() { + DirInt adjacent = facingLeft ? left : right; + return adjacent != null && val > adjacent.val; + } + + public void swapWithAdjacent() { + DirInt l = facingLeft ? left : this; + DirInt r = facingLeft ? this : right; + if (head == l) head = r; + if (l.left != null) l.left.right = r; + if (r.right != null) r.right.left = l; + l.right = r.right; + r.left = l.left; + r.right = l; + l.left = r; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +}