aoqi@0: /* aoqi@0: * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: /* aoqi@0: * @test aoqi@0: * @bug 8013789 aoqi@0: * @summary Compiler should emit bridges in interfaces aoqi@0: */ aoqi@0: aoqi@0: import com.sun.source.util.JavacTask; aoqi@0: import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper; aoqi@0: import com.sun.tools.javac.code.Symbol; aoqi@0: import com.sun.tools.javac.util.JCDiagnostic; aoqi@0: aoqi@0: import java.io.File; aoqi@0: import java.io.PrintWriter; aoqi@0: import java.net.URI; aoqi@0: import java.util.ArrayList; aoqi@0: import java.util.Arrays; aoqi@0: import java.util.EnumSet; aoqi@0: import java.util.List; aoqi@0: import java.util.Set; aoqi@0: aoqi@0: import javax.tools.Diagnostic; aoqi@0: import javax.tools.Diagnostic.Kind; aoqi@0: import javax.tools.JavaCompiler; aoqi@0: import javax.tools.JavaFileObject; aoqi@0: import javax.tools.SimpleJavaFileObject; aoqi@0: import javax.tools.ToolProvider; aoqi@0: aoqi@0: public class TestMetafactoryBridges { aoqi@0: aoqi@0: static int checkCount = 0; aoqi@0: aoqi@0: enum ClasspathKind { aoqi@0: NONE(), aoqi@0: B7(7, ClassKind.B), aoqi@0: A7(7, ClassKind.A), aoqi@0: B8(8, ClassKind.B), aoqi@0: A8(8, ClassKind.A); aoqi@0: aoqi@0: int version; aoqi@0: ClassKind ck; aoqi@0: aoqi@0: ClasspathKind() { aoqi@0: this(-1, null); aoqi@0: } aoqi@0: aoqi@0: ClasspathKind(int version, ClassKind ck) { aoqi@0: this.version = version; aoqi@0: this.ck = ck; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: enum PreferPolicy { aoqi@0: SOURCE("-Xprefer:source"), aoqi@0: NEWER("-Xprefer:newer"); aoqi@0: aoqi@0: String preferOpt; aoqi@0: aoqi@0: PreferPolicy(String preferOpt) { aoqi@0: this.preferOpt = preferOpt; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: enum SourcepathKind { aoqi@0: NONE, aoqi@0: A(ClassKind.A), aoqi@0: B(ClassKind.B), aoqi@0: C(ClassKind.C), aoqi@0: AB(ClassKind.A, ClassKind.B), aoqi@0: BC(ClassKind.B, ClassKind.C), aoqi@0: AC(ClassKind.A, ClassKind.C), aoqi@0: ABC(ClassKind.A, ClassKind.B, ClassKind.C); aoqi@0: aoqi@0: List sources; aoqi@0: aoqi@0: SourcepathKind(ClassKind... sources) { aoqi@0: this.sources = Arrays.asList(sources); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: enum SourceSet { aoqi@0: ALL() { aoqi@0: @Override aoqi@0: List> permutations() { aoqi@0: return Arrays.asList( aoqi@0: Arrays.asList(ClassKind.A, ClassKind.B, ClassKind.C), aoqi@0: Arrays.asList(ClassKind.A, ClassKind.B, ClassKind.C), aoqi@0: Arrays.asList(ClassKind.B, ClassKind.A, ClassKind.C), aoqi@0: Arrays.asList(ClassKind.B, ClassKind.C, ClassKind.A), aoqi@0: Arrays.asList(ClassKind.C, ClassKind.A, ClassKind.B), aoqi@0: Arrays.asList(ClassKind.C, ClassKind.B, ClassKind.A) aoqi@0: ); aoqi@0: } aoqi@0: }, aoqi@0: AC() { aoqi@0: @Override aoqi@0: List> permutations() { aoqi@0: return Arrays.asList( aoqi@0: Arrays.asList(ClassKind.A, ClassKind.C), aoqi@0: Arrays.asList(ClassKind.C, ClassKind.A) aoqi@0: ); aoqi@0: } aoqi@0: }, aoqi@0: C() { aoqi@0: @Override aoqi@0: List> permutations() { aoqi@0: return Arrays.asList(Arrays.asList(ClassKind.C)); aoqi@0: } aoqi@0: }; aoqi@0: aoqi@0: abstract List> permutations(); aoqi@0: } aoqi@0: aoqi@0: enum ClassKind { aoqi@0: A("A", "interface A { Object m(); }"), aoqi@0: B("B", "interface B extends A { Integer m(); }", A), aoqi@0: C("C", "class C { B b = ()->42; }", A, B); aoqi@0: aoqi@0: String name; aoqi@0: String source; aoqi@0: ClassKind[] deps; aoqi@0: aoqi@0: ClassKind(String name, String source, ClassKind... deps) { aoqi@0: this.name = name; aoqi@0: this.source = source; aoqi@0: this.deps = deps; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public static void main(String... args) throws Exception { aoqi@0: String SCRATCH_DIR = System.getProperty("user.dir"); aoqi@0: //create default shared JavaCompiler - reused across multiple compilations aoqi@0: JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); aoqi@0: aoqi@0: int n = 0; aoqi@0: for (SourceSet ss : SourceSet.values()) { aoqi@0: for (List sources : ss.permutations()) { aoqi@0: for (SourcepathKind spKind : SourcepathKind.values()) { aoqi@0: for (ClasspathKind cpKind : ClasspathKind.values()) { aoqi@0: for (PreferPolicy pp : PreferPolicy.values()) { aoqi@0: Set deps = EnumSet.noneOf(ClassKind.class); aoqi@0: if (cpKind.ck != null) { aoqi@0: deps.add(cpKind.ck); aoqi@0: } aoqi@0: deps.addAll(sources); aoqi@0: if (deps.size() < 3) continue; aoqi@0: File testDir = new File(SCRATCH_DIR, "test" + n); aoqi@0: testDir.mkdir(); aoqi@0: try (PrintWriter debugWriter = new PrintWriter(new File(testDir, "debug.txt"))) { aoqi@0: new TestMetafactoryBridges(testDir, sources, spKind, cpKind, pp, debugWriter).run(comp); aoqi@0: n++; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: System.out.println("Total check executed: " + checkCount); aoqi@0: } aoqi@0: aoqi@0: File testDir; aoqi@0: List sources; aoqi@0: SourcepathKind spKind; aoqi@0: ClasspathKind cpKind; aoqi@0: PreferPolicy pp; aoqi@0: PrintWriter debugWriter; aoqi@0: DiagnosticChecker diagChecker; aoqi@0: aoqi@0: TestMetafactoryBridges(File testDir, Listsources, SourcepathKind spKind, aoqi@0: ClasspathKind cpKind, PreferPolicy pp, PrintWriter debugWriter) { aoqi@0: this.testDir = testDir; aoqi@0: this.sources = sources; aoqi@0: this.spKind = spKind; aoqi@0: this.cpKind = cpKind; aoqi@0: this.pp = pp; aoqi@0: this.debugWriter = debugWriter; aoqi@0: this.diagChecker = new DiagnosticChecker(); aoqi@0: } aoqi@0: aoqi@0: class JavaSource extends SimpleJavaFileObject { aoqi@0: aoqi@0: final String source; aoqi@0: aoqi@0: public JavaSource(ClassKind ck) { aoqi@0: super(URI.create(String.format("myfo:/%s.java", ck.name)), JavaFileObject.Kind.SOURCE); aoqi@0: this.source = ck.source; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public CharSequence getCharContent(boolean ignoreEncodingErrors) { aoqi@0: return source; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void run(JavaCompiler tool) throws Exception { aoqi@0: File classesDir = new File(testDir, "classes"); aoqi@0: File outDir = new File(testDir, "out"); aoqi@0: File srcDir = new File(testDir, "src"); aoqi@0: classesDir.mkdir(); aoqi@0: outDir.mkdir(); aoqi@0: srcDir.mkdir(); aoqi@0: aoqi@0: debugWriter.append(testDir.getName() + "\n"); aoqi@0: debugWriter.append("sources = " + sources + "\n"); aoqi@0: debugWriter.append("spKind = " + spKind + "\n"); aoqi@0: debugWriter.append("cpKind = " + cpKind + "\n"); aoqi@0: debugWriter.append("preferPolicy = " + pp.preferOpt + "\n"); aoqi@0: aoqi@0: //step 1 - prepare sources (older!!) aoqi@0: debugWriter.append("Preparing sources\n"); aoqi@0: for (ClassKind ck : spKind.sources) { aoqi@0: //skip sources explicitly provided on command line aoqi@0: if (!sources.contains(ck)) { aoqi@0: debugWriter.append("Copy " + ck.name + ".java to" + srcDir.getAbsolutePath() + "\n"); aoqi@0: File dest = new File(srcDir, ck.name + ".java"); aoqi@0: PrintWriter pw = new PrintWriter(dest); aoqi@0: pw.append(ck.source); aoqi@0: pw.close(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: //step 2 - prepare classes aoqi@0: debugWriter.append("Preparing classes\n"); aoqi@0: if (cpKind != ClasspathKind.NONE) { aoqi@0: List sources = new ArrayList<>(); aoqi@0: ClassKind toRemove = null; aoqi@0: sources.add(new JavaSource(cpKind.ck)); aoqi@0: if (cpKind.ck.deps.length != 0) { aoqi@0: //at most only one dependency aoqi@0: toRemove = cpKind.ck.deps[0]; aoqi@0: sources.add(new JavaSource(toRemove)); aoqi@0: } aoqi@0: JavacTask ct = (JavacTask)tool.getTask(debugWriter, null, null, aoqi@0: Arrays.asList("-d", classesDir.getAbsolutePath(), "-source", String.valueOf(cpKind.version)), null, sources); aoqi@0: try { aoqi@0: ct.generate(); aoqi@0: if (toRemove != null) { aoqi@0: debugWriter.append("Remove " + toRemove.name + ".class from" + classesDir.getAbsolutePath() + "\n"); aoqi@0: File fileToRemove = new File(classesDir, toRemove.name + ".class"); aoqi@0: fileToRemove.delete(); aoqi@0: } aoqi@0: } catch (Throwable ex) { aoqi@0: throw new AssertionError("Error thrown when generating side-classes"); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: //step 3 - compile aoqi@0: debugWriter.append("Compiling test\n"); aoqi@0: List sourcefiles = new ArrayList<>(); aoqi@0: for (ClassKind ck : sources) { aoqi@0: sourcefiles.add(new JavaSource(ck)); aoqi@0: } aoqi@0: JavacTask ct = (JavacTask)tool.getTask(debugWriter, null, diagChecker, aoqi@0: Arrays.asList("-XDdumpLambdaToMethodStats", "-d", outDir.getAbsolutePath(), aoqi@0: "-sourcepath", srcDir.getAbsolutePath(), aoqi@0: "-classpath", classesDir.getAbsolutePath(), aoqi@0: pp.preferOpt), null, sourcefiles); aoqi@0: try { aoqi@0: ct.generate(); aoqi@0: } catch (Throwable ex) { aoqi@0: throw new AssertionError("Error thrown when compiling test case"); aoqi@0: } aoqi@0: check(); aoqi@0: } aoqi@0: aoqi@0: void check() { aoqi@0: checkCount++; aoqi@0: if (diagChecker.errorFound) { aoqi@0: throw new AssertionError("Unexpected compilation failure"); aoqi@0: } aoqi@0: aoqi@0: boolean altMetafactory = aoqi@0: cpKind == ClasspathKind.B7 && aoqi@0: !sources.contains(ClassKind.B) && aoqi@0: (pp == PreferPolicy.NEWER || !spKind.sources.contains(ClassKind.B)); aoqi@0: aoqi@0: if (altMetafactory != diagChecker.altMetafactory) { aoqi@0: throw new AssertionError("Bad metafactory detected - expected altMetafactory: " + altMetafactory + aoqi@0: "\ntest: " + testDir); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: static class DiagnosticChecker implements javax.tools.DiagnosticListener { aoqi@0: aoqi@0: boolean altMetafactory = false; aoqi@0: boolean errorFound = false; aoqi@0: aoqi@0: public void report(Diagnostic diagnostic) { aoqi@0: if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { aoqi@0: errorFound = true; aoqi@0: } else if (statProcessor.matches(diagnostic)) { aoqi@0: statProcessor.process(diagnostic); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: abstract class DiagnosticProcessor { aoqi@0: aoqi@0: List codes; aoqi@0: Diagnostic.Kind kind; aoqi@0: aoqi@0: public DiagnosticProcessor(Kind kind, String... codes) { aoqi@0: this.codes = Arrays.asList(codes); aoqi@0: this.kind = kind; aoqi@0: } aoqi@0: aoqi@0: abstract void process(Diagnostic diagnostic); aoqi@0: aoqi@0: boolean matches(Diagnostic diagnostic) { aoqi@0: return (codes.isEmpty() || codes.contains(diagnostic.getCode())) && aoqi@0: diagnostic.getKind() == kind; aoqi@0: } aoqi@0: aoqi@0: JCDiagnostic asJCDiagnostic(Diagnostic diagnostic) { aoqi@0: if (diagnostic instanceof JCDiagnostic) { aoqi@0: return (JCDiagnostic)diagnostic; aoqi@0: } else if (diagnostic instanceof DiagnosticSourceUnwrapper) { aoqi@0: return ((DiagnosticSourceUnwrapper)diagnostic).d; aoqi@0: } else { aoqi@0: throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName()); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: DiagnosticProcessor statProcessor = new DiagnosticProcessor(Kind.NOTE, aoqi@0: "compiler.note.lambda.stat", aoqi@0: "compiler.note.mref.stat", aoqi@0: "compiler.note.mref.stat.1") { aoqi@0: @Override aoqi@0: void process(Diagnostic diagnostic) { aoqi@0: JCDiagnostic diag = asJCDiagnostic(diagnostic); aoqi@0: if ((Boolean)diag.getArgs()[0]) { aoqi@0: altMetafactory = true; aoqi@0: } aoqi@0: } aoqi@0: }; aoqi@0: } aoqi@0: }