1 /*
2  * Copyright (c) 2013, 2018, 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 
32 import com.sun.source.util.JavacTask;
33 import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
34 import com.sun.tools.javac.util.JCDiagnostic;
35 
36 import java.io.File;
37 import java.io.PrintWriter;
38 import java.net.URI;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.EnumSet;
42 import java.util.List;
43 import java.util.Set;
44 
45 import javax.tools.Diagnostic;
46 import javax.tools.Diagnostic.Kind;
47 import javax.tools.JavaCompiler;
48 import javax.tools.JavaFileObject;
49 import javax.tools.SimpleJavaFileObject;
50 import javax.tools.ToolProvider;
51 
52 public 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 
ClasspathKind()66         ClasspathKind() {
67             this(-1, null);
68         }
69 
ClasspathKind(int version, ClassKind ck)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 
PreferPolicy(String preferOpt)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 
SourcepathKind(ClassKind... sources)99         SourcepathKind(ClassKind... sources) {
100             this.sources = Arrays.asList(sources);
101         }
102     }
103 
104     enum SourceSet {
ALL()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         },
AC()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         },
C()127         C() {
128             @Override
129             List<List<ClassKind>> permutations() {
130                 return Arrays.asList(Arrays.asList(ClassKind.C));
131             }
132         };
133 
permutations()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 
ClassKind(String name, String source, ClassKind... deps)146         ClassKind(String name, String source, ClassKind... deps) {
147             this.name = name;
148             this.source = source;
149             this.deps = deps;
150         }
151     }
152 
main(String... args)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 
TestMetafactoryBridges(File testDir, List<ClassKind>sources, SourcepathKind spKind, ClasspathKind cpKind, PreferPolicy pp, PrintWriter debugWriter)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 
JavaSource(ClassKind ck)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
getCharContent(boolean ignoreEncodingErrors)213         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
214             return source;
215         }
216     }
217 
run(JavaCompiler tool)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 
check()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 
report(Diagnostic<? extends JavaFileObject> diagnostic)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 
DiagnosticProcessor(Kind kind, String... codes)324             public DiagnosticProcessor(Kind kind, String... codes) {
325                 this.codes = Arrays.asList(codes);
326                 this.kind = kind;
327             }
328 
process(Diagnostic<? extends JavaFileObject> diagnostic)329             abstract void process(Diagnostic<? extends JavaFileObject> diagnostic);
330 
matches(Diagnostic<? extends JavaFileObject> diagnostic)331             boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) {
332                 return (codes.isEmpty() || codes.contains(diagnostic.getCode())) &&
333                         diagnostic.getKind() == kind;
334             }
335 
asJCDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic)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