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