TestMetafactoryBridges.java revision 3643:589ff4d43428
1/*
2 * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8013789
27 * @summary Compiler should emit bridges in interfaces
28 * @modules jdk.compiler/com.sun.tools.javac.api
29 *          jdk.compiler/com.sun.tools.javac.util
30 */
31
32import com.sun.source.util.JavacTask;
33import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
34import com.sun.tools.javac.util.JCDiagnostic;
35
36import java.io.File;
37import java.io.PrintWriter;
38import java.net.URI;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.EnumSet;
42import java.util.List;
43import java.util.Set;
44
45import javax.tools.Diagnostic;
46import javax.tools.Diagnostic.Kind;
47import javax.tools.JavaCompiler;
48import javax.tools.JavaFileObject;
49import javax.tools.SimpleJavaFileObject;
50import javax.tools.ToolProvider;
51
52public class TestMetafactoryBridges {
53
54    static int checkCount = 0;
55
56    enum ClasspathKind {
57        NONE(),
58        B7(7, ClassKind.B),
59        A7(7, ClassKind.A),
60        B8(8, ClassKind.B),
61        A8(8, ClassKind.A);
62
63        int version;
64        ClassKind ck;
65
66        ClasspathKind() {
67            this(-1, null);
68        }
69
70        ClasspathKind(int version, ClassKind ck) {
71            this.version = version;
72            this.ck = ck;
73        }
74    }
75
76    enum PreferPolicy {
77        SOURCE("-Xprefer:source"),
78        NEWER("-Xprefer:newer");
79
80        String preferOpt;
81
82        PreferPolicy(String preferOpt) {
83            this.preferOpt = preferOpt;
84        }
85    }
86
87    enum SourcepathKind {
88        NONE,
89        A(ClassKind.A),
90        B(ClassKind.B),
91        C(ClassKind.C),
92        AB(ClassKind.A, ClassKind.B),
93        BC(ClassKind.B, ClassKind.C),
94        AC(ClassKind.A, ClassKind.C),
95        ABC(ClassKind.A, ClassKind.B, ClassKind.C);
96
97        List<ClassKind> sources;
98
99        SourcepathKind(ClassKind... sources) {
100            this.sources = Arrays.asList(sources);
101        }
102    }
103
104    enum SourceSet {
105        ALL() {
106            @Override
107            List<List<ClassKind>> permutations() {
108                return Arrays.asList(
109                    Arrays.asList(ClassKind.A, ClassKind.B, ClassKind.C),
110                    Arrays.asList(ClassKind.A, ClassKind.B, ClassKind.C),
111                    Arrays.asList(ClassKind.B, ClassKind.A, ClassKind.C),
112                    Arrays.asList(ClassKind.B, ClassKind.C, ClassKind.A),
113                    Arrays.asList(ClassKind.C, ClassKind.A, ClassKind.B),
114                    Arrays.asList(ClassKind.C, ClassKind.B, ClassKind.A)
115                );
116            }
117        },
118        AC() {
119            @Override
120            List<List<ClassKind>> permutations() {
121                return Arrays.asList(
122                    Arrays.asList(ClassKind.A, ClassKind.C),
123                    Arrays.asList(ClassKind.C, ClassKind.A)
124                );
125            }
126        },
127        C() {
128            @Override
129            List<List<ClassKind>> permutations() {
130                return Arrays.asList(Arrays.asList(ClassKind.C));
131            }
132        };
133
134        abstract List<List<ClassKind>> permutations();
135    }
136
137    enum ClassKind {
138        A("A", "interface A { Object m(); }"),
139        B("B", "interface B extends A { Integer m(); }", A),
140        C("C", "class C { B b = ()->42; }", A, B);
141
142        String name;
143        String source;
144        ClassKind[] deps;
145
146        ClassKind(String name, String source, ClassKind... deps) {
147            this.name = name;
148            this.source = source;
149            this.deps = deps;
150        }
151    }
152
153    public static void main(String... args) throws Exception {
154        String SCRATCH_DIR = System.getProperty("user.dir");
155        //create default shared JavaCompiler - reused across multiple compilations
156        JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
157
158        int n = 0;
159        for (SourceSet ss : SourceSet.values()) {
160            for (List<ClassKind> sources : ss.permutations()) {
161                for (SourcepathKind spKind : SourcepathKind.values()) {
162                    for (ClasspathKind cpKind : ClasspathKind.values()) {
163                        for (PreferPolicy pp : PreferPolicy.values()) {
164                            Set<ClassKind> deps = EnumSet.noneOf(ClassKind.class);
165                            if (cpKind.ck != null) {
166                                deps.add(cpKind.ck);
167                            }
168                            deps.addAll(sources);
169                            if (deps.size() < 3) continue;
170                            File testDir = new File(SCRATCH_DIR, "test" + n);
171                            testDir.mkdir();
172                            try (PrintWriter debugWriter = new PrintWriter(new File(testDir, "debug.txt"))) {
173                                new TestMetafactoryBridges(testDir, sources, spKind, cpKind, pp, debugWriter).run(comp);
174                                n++;
175                            }
176                        }
177                    }
178                }
179            }
180        }
181        System.out.println("Total check executed: " + checkCount);
182    }
183
184    File testDir;
185    List<ClassKind> sources;
186    SourcepathKind spKind;
187    ClasspathKind cpKind;
188    PreferPolicy pp;
189    PrintWriter debugWriter;
190    DiagnosticChecker diagChecker;
191
192    TestMetafactoryBridges(File testDir, List<ClassKind>sources, SourcepathKind spKind,
193            ClasspathKind cpKind, PreferPolicy pp, PrintWriter debugWriter) {
194        this.testDir = testDir;
195        this.sources = sources;
196        this.spKind = spKind;
197        this.cpKind = cpKind;
198        this.pp = pp;
199        this.debugWriter = debugWriter;
200        this.diagChecker = new DiagnosticChecker();
201    }
202
203    class JavaSource extends SimpleJavaFileObject {
204
205        final String source;
206
207        public JavaSource(ClassKind ck) {
208            super(URI.create(String.format("myfo:/%s.java", ck.name)), JavaFileObject.Kind.SOURCE);
209            this.source = ck.source;
210        }
211
212        @Override
213        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
214            return source;
215        }
216    }
217
218    void run(JavaCompiler tool) throws Exception {
219        File classesDir = new File(testDir, "classes");
220        File outDir = new File(testDir, "out");
221        File srcDir = new File(testDir, "src");
222        classesDir.mkdir();
223        outDir.mkdir();
224        srcDir.mkdir();
225
226        debugWriter.append(testDir.getName() + "\n");
227        debugWriter.append("sources = " + sources + "\n");
228        debugWriter.append("spKind = " + spKind  + "\n");
229        debugWriter.append("cpKind = " + cpKind + "\n");
230        debugWriter.append("preferPolicy = " + pp.preferOpt + "\n");
231
232        //step 1 - prepare sources (older!!)
233        debugWriter.append("Preparing sources\n");
234        for (ClassKind ck : spKind.sources) {
235            //skip sources explicitly provided on command line
236            if (!sources.contains(ck)) {
237                debugWriter.append("Copy " + ck.name + ".java to" + srcDir.getAbsolutePath() + "\n");
238                File dest = new File(srcDir, ck.name + ".java");
239                PrintWriter pw = new PrintWriter(dest);
240                pw.append(ck.source);
241                pw.close();
242            }
243        }
244
245        //step 2 - prepare classes
246        debugWriter.append("Preparing classes\n");
247        if (cpKind != ClasspathKind.NONE) {
248            List<JavaSource> sources = new ArrayList<>();
249            ClassKind toRemove = null;
250            sources.add(new JavaSource(cpKind.ck));
251            if (cpKind.ck.deps.length != 0) {
252                //at most only one dependency
253                toRemove = cpKind.ck.deps[0];
254                sources.add(new JavaSource(toRemove));
255            }
256            JavacTask ct = (JavacTask)tool.getTask(debugWriter, null, null,
257                    Arrays.asList("-d", classesDir.getAbsolutePath(), "-source", String.valueOf(cpKind.version)), null, sources);
258            try {
259                ct.generate();
260                if (toRemove != null) {
261                    debugWriter.append("Remove " + toRemove.name + ".class from" + classesDir.getAbsolutePath() + "\n");
262                    File fileToRemove = new File(classesDir, toRemove.name + ".class");
263                    fileToRemove.delete();
264                }
265            } catch (Throwable ex) {
266                throw new AssertionError("Error thrown when generating side-classes");
267            }
268        }
269
270        //step 3 - compile
271        debugWriter.append("Compiling test\n");
272        List<JavaSource> sourcefiles = new ArrayList<>();
273        for (ClassKind ck : sources) {
274            sourcefiles.add(new JavaSource(ck));
275        }
276        JavacTask ct = (JavacTask)tool.getTask(debugWriter, null, diagChecker,
277                    Arrays.asList("--debug:dumpLambdaToMethodStats", "-d", outDir.getAbsolutePath(),
278                                  "-sourcepath", srcDir.getAbsolutePath(),
279                                  "-classpath", classesDir.getAbsolutePath(),
280                                  pp.preferOpt), null, sourcefiles);
281        try {
282            ct.generate();
283        } catch (Throwable ex) {
284            throw new AssertionError("Error thrown when compiling test case");
285        }
286        check();
287    }
288
289    void check() {
290        checkCount++;
291        if (diagChecker.errorFound) {
292            throw new AssertionError("Unexpected compilation failure");
293        }
294
295        boolean altMetafactory =
296                cpKind == ClasspathKind.B7 &&
297                !sources.contains(ClassKind.B) &&
298                (pp == PreferPolicy.NEWER || !spKind.sources.contains(ClassKind.B));
299
300        if (altMetafactory != diagChecker.altMetafactory) {
301            throw new AssertionError("Bad metafactory detected - expected altMetafactory: " + altMetafactory +
302                    "\ntest: " + testDir);
303        }
304    }
305
306    static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
307
308        boolean altMetafactory = false;
309        boolean errorFound = false;
310
311        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
312            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
313                errorFound = true;
314            } else if (statProcessor.matches(diagnostic)) {
315                statProcessor.process(diagnostic);
316            }
317        }
318
319        abstract class DiagnosticProcessor {
320
321            List<String> codes;
322            Diagnostic.Kind kind;
323
324            public DiagnosticProcessor(Kind kind, String... codes) {
325                this.codes = Arrays.asList(codes);
326                this.kind = kind;
327            }
328
329            abstract void process(Diagnostic<? extends JavaFileObject> diagnostic);
330
331            boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) {
332                return (codes.isEmpty() || codes.contains(diagnostic.getCode())) &&
333                        diagnostic.getKind() == kind;
334            }
335
336            JCDiagnostic asJCDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
337                if (diagnostic instanceof JCDiagnostic) {
338                    return (JCDiagnostic)diagnostic;
339                } else if (diagnostic instanceof DiagnosticSourceUnwrapper) {
340                    return ((DiagnosticSourceUnwrapper)diagnostic).d;
341                } else {
342                    throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName());
343                }
344            }
345        }
346
347        DiagnosticProcessor statProcessor = new DiagnosticProcessor(Kind.NOTE,
348                "compiler.note.lambda.stat",
349                "compiler.note.mref.stat",
350                "compiler.note.mref.stat.1") {
351            @Override
352            void process(Diagnostic<? extends JavaFileObject> diagnostic) {
353                JCDiagnostic diag = asJCDiagnostic(diagnostic);
354                if ((Boolean)diag.getArgs()[0]) {
355                    altMetafactory = true;
356                }
357            }
358        };
359    }
360}
361