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