1 /* 2 * Copyright (c) 2018, 2020, 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 8205418 8207229 8207230 8230847 8245786 8247334 27 * @summary Test the outcomes from Trees.getScope 28 * @modules jdk.compiler/com.sun.tools.javac.api 29 * jdk.compiler/com.sun.tools.javac.comp 30 * jdk.compiler/com.sun.tools.javac.tree 31 * jdk.compiler/com.sun.tools.javac.util 32 */ 33 34 import java.io.IOException; 35 import java.net.URI; 36 import java.util.ArrayList; 37 import java.util.List; 38 39 import javax.lang.model.element.Element; 40 import javax.tools.JavaCompiler; 41 import javax.tools.SimpleJavaFileObject; 42 import javax.tools.StandardJavaFileManager; 43 import javax.tools.ToolProvider; 44 45 import com.sun.source.tree.AnnotationTree; 46 import com.sun.source.tree.BlockTree; 47 import com.sun.source.tree.ClassTree; 48 import com.sun.source.tree.CompilationUnitTree; 49 import com.sun.source.tree.ConditionalExpressionTree; 50 import com.sun.source.tree.IdentifierTree; 51 import com.sun.source.tree.LambdaExpressionTree; 52 import com.sun.source.tree.MethodInvocationTree; 53 import com.sun.source.tree.MethodTree; 54 import com.sun.source.tree.Scope; 55 import com.sun.source.tree.Tree; 56 import com.sun.source.tree.VariableTree; 57 import com.sun.source.util.JavacTask; 58 import com.sun.source.util.TaskEvent; 59 import com.sun.source.util.TaskListener; 60 import com.sun.source.util.TreePath; 61 import com.sun.source.util.TreePathScanner; 62 import com.sun.source.util.Trees; 63 64 import com.sun.tools.javac.api.JavacTool; 65 import com.sun.tools.javac.comp.Analyzer; 66 import com.sun.tools.javac.comp.AttrContext; 67 import com.sun.tools.javac.comp.Env; 68 import com.sun.tools.javac.tree.JCTree.JCStatement; 69 import com.sun.tools.javac.util.Context; 70 import com.sun.tools.javac.util.Context.Factory; 71 72 import static javax.tools.JavaFileObject.Kind.SOURCE; 73 74 public class TestGetScopeResult { 75 public static void main(String... args) throws IOException { 76 new TestGetScopeResult().run(); 77 new TestGetScopeResult().testAnalyzerDisabled(); 78 new TestGetScopeResult().testVariablesInSwitch(); 79 new TestGetScopeResult().testMemberRefs(); 80 new TestGetScopeResult().testAnnotations(); 81 new TestGetScopeResult().testAnnotationsLazy(); 82 new TestGetScopeResult().testCircular(); 83 new TestGetScopeResult().testRecord(); 84 new TestGetScopeResult().testLocalRecordAnnotation(); 85 } 86 87 public void run() throws IOException { 88 String[] simpleLambda = { 89 "s:java.lang.String", 90 "i:Test.I", 91 "super:java.lang.Object", 92 "this:Test" 93 }; 94 doTest("class Test { void test() { I i = s -> { }; } interface I { public void test(String s); } }", 95 simpleLambda); 96 doTest("class Test { void test() { I i = s -> { }; } interface I { public int test(String s); } }", 97 simpleLambda); 98 doTest("class Test { void test() { I i = s -> { }; } interface I { public String test(String s); } }", 99 simpleLambda); 100 doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public void test(String s); } }", 101 simpleLambda); 102 doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public int test(String s); } }", 103 simpleLambda); 104 doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public String test(String s); } }", 105 simpleLambda); 106 String[] dualLambda = { 107 "s:java.lang.String", 108 "i:Test.I1", 109 "super:java.lang.Object", 110 "this:Test", 111 "s:java.lang.CharSequence", 112 "i:Test.I1", 113 "super:java.lang.Object", 114 "this:Test" 115 }; 116 doTest("class Test { void test() { I1 i; inv(s -> { }, s -> { }); } void inv(I1 i, I2 i) { } interface I1 { public String test(String s); } interface I2 { public void test(CharSequence s); } }", 117 dualLambda); 118 doTest("class Test { void test() { I1 i; inv(s -> { }, s -> { }); } void inv(I1 i, I2 i) { } interface I1 { public String test(String s); } interface I2 { public int test(CharSequence s); } }", 119 dualLambda); 120 String[] brokenType = { 121 "s:<any>", 122 "u:Undefined", 123 "super:java.lang.Object", 124 "this:Test" 125 }; 126 doTest("class Test { void test() { Undefined u = s -> { }; } }", 127 brokenType); 128 String[] multipleCandidates1 = { 129 "s:<any>", 130 "super:java.lang.Object", 131 "this:Test" 132 }; 133 doTest("class Test { void test() { cand1(s -> { }); } void cand1(I1 i) { } void cand1(I2 i) { } interface I1 { public String test(String s); } interface I2 { public int test(CharSequence s); } }", 134 multipleCandidates1); 135 String[] multipleCandidates2 = { 136 "s:java.lang.String", 137 "super:java.lang.Object", 138 "this:Test" 139 }; 140 doTest("class Test { void test() { cand1(s -> { }); } void cand1(I1 i) { } void cand1(I2 i, int i) { } interface I1 { public String test(String s); } interface I2 { public int test(CharSequence s); } }", 141 multipleCandidates2); 142 143 String[] implicitExplicitConflict1 = { 144 ":t", 145 "s:java.lang.String", 146 "super:java.lang.Object", 147 "this:Test" 148 }; 149 150 doTest("class Test { void test() { cand((var s, t) -> \"\"); } void cand(I i) { } interface I { public String test(String s); } }", 151 implicitExplicitConflict1); 152 153 String[] implicitExplicitConflict2 = { 154 "s:none", 155 ":t", 156 "super:java.lang.Object", 157 "this:Test" 158 }; 159 160 doTest("class Test { void test() { cand((t, var s) -> \"\"); } void cand(I i) { } interface I { public String test(String s); } }", 161 implicitExplicitConflict2); 162 163 String[] noFunctionInterface = { 164 "s:none", 165 ":t", 166 "super:java.lang.Object", 167 "this:Test" 168 }; 169 170 doTest("class Test { void test() { cand((t, var s) -> \"\"); } void cand(String s) { } }", 171 noFunctionInterface); 172 173 String[] invocationInMethodInvocation = { 174 "d2:java.lang.Double", 175 "d1:java.lang.Double", 176 "super:java.lang.Object", 177 "this:Test" 178 }; 179 180 doTest(""" 181 class Test { 182 void test() { test(reduce(0.0, (d1, d2) -> 0)); } 183 void test(int i) {} 184 <T> T reduce(T t, BiFunction<T, T, T> f1) {} 185 static interface BiFunction<R, P, Q> { 186 R apply(P p, Q q); 187 } 188 }""", 189 invocationInMethodInvocation); 190 } 191 192 public void doTest(String code, String... expected) throws IOException { 193 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 194 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 195 class MyFileObject extends SimpleJavaFileObject { 196 MyFileObject() { 197 super(URI.create("myfo:///Test.java"), SOURCE); 198 } 199 @Override 200 public String getCharContent(boolean ignoreEncodingErrors) { 201 return code; 202 } 203 } 204 JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, List.of(new MyFileObject())); 205 CompilationUnitTree cut = t.parse().iterator().next(); 206 t.analyze(); 207 208 List<String> actual = new ArrayList<>(); 209 210 new TreePathScanner<Void, Void>() { 211 @Override 212 public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { 213 Scope scope = Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody())); 214 actual.addAll(dumpScope(scope)); 215 return super.visitLambdaExpression(node, p); 216 } 217 }.scan(cut, null); 218 219 List<String> expectedList = List.of(expected); 220 221 if (!expectedList.equals(actual)) { 222 throw new IllegalStateException("Unexpected scope content: " + actual + "\n" + 223 "expected: " + expectedList); 224 } 225 } 226 } 227 228 void testAnalyzerDisabled() throws IOException { 229 JavacTool c = JavacTool.create(); 230 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 231 class MyFileObject extends SimpleJavaFileObject { 232 MyFileObject() { 233 super(URI.create("myfo:///Test.java"), SOURCE); 234 } 235 @Override 236 public String getCharContent(boolean ignoreEncodingErrors) { 237 return "class Test {" + 238 " void test() { cand(() -> { System.err.println(); }); }" + 239 " Runnable r = new Runnable() { public void test() { System.err.println(); } };" + 240 " void cand(Runnable r) { }" + 241 "}"; 242 } 243 } 244 Context ctx = new Context(); 245 TestAnalyzer.preRegister(ctx); 246 JavacTask t = (JavacTask) c.getTask(null, fm, null, List.of("-XDfind=lambda"), null, 247 List.of(new MyFileObject()), ctx); 248 CompilationUnitTree cut = t.parse().iterator().next(); 249 t.analyze(); 250 251 TestAnalyzer analyzer = (TestAnalyzer) TestAnalyzer.instance(ctx); 252 253 if (!analyzer.analyzeCalled) { 254 throw new IllegalStateException("Analyzer didn't run!"); 255 } 256 257 new TreePathScanner<Void, Void>() { 258 @Override 259 public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { 260 analyzer.analyzeCalled = false; 261 Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody())); 262 if (analyzer.analyzeCalled) { 263 throw new IllegalStateException("Analyzer was run during getScope!"); 264 } 265 return super.visitLambdaExpression(node, p); 266 } 267 268 @Override 269 public Void visitVariable(VariableTree node, Void p) { 270 if (node.getInitializer() != null) { 271 analyzer.analyzeCalled = false; 272 TreePath tp = new TreePath(getCurrentPath(), node.getInitializer()); 273 Trees.instance(t).getScope(tp); 274 if (analyzer.analyzeCalled) { 275 throw new IllegalStateException("Analyzer was run during getScope!"); 276 } 277 } 278 return super.visitVariable(node, p); 279 } 280 }.scan(cut, null); 281 } 282 } 283 284 private static final class TestAnalyzer extends Analyzer { 285 286 public static void preRegister(Context context) { 287 context.put(analyzerKey, (Factory<Analyzer>) ctx -> new TestAnalyzer(ctx)); 288 } 289 290 private boolean analyzeCalled; 291 292 public TestAnalyzer(Context context) { 293 super(context); 294 } 295 296 @Override 297 protected void analyze(JCStatement statement, Env<AttrContext> env) { 298 analyzeCalled = true; 299 super.analyze(statement, env); 300 } 301 } 302 303 void testVariablesInSwitch() throws IOException { 304 JavacTool c = JavacTool.create(); 305 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 306 class MyFileObject extends SimpleJavaFileObject { 307 MyFileObject() { 308 super(URI.create("myfo:///Test.java"), SOURCE); 309 } 310 @Override 311 public String getCharContent(boolean ignoreEncodingErrors) { 312 return "class Test {" + 313 " void test() {\n" + 314 " E e = E.A;\n" + 315 " Object o = E.A;\n" + 316 " switch (e) {\n" + 317 " case A:\n" + 318 " return;\n" + 319 " case B:\n" + 320 " test();\n" + 321 " E ee = null;\n" + 322 " break;\n" + 323 " }\n" + 324 " }\n" + 325 " enum E {A, B}\n" + 326 "}"; 327 } 328 } 329 Context ctx = new Context(); 330 TestAnalyzer.preRegister(ctx); 331 JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, 332 List.of(new MyFileObject()), ctx); 333 CompilationUnitTree cut = t.parse().iterator().next(); 334 t.analyze(); 335 336 new TreePathScanner<Void, Void>() { 337 @Override 338 public Void visitMethodInvocation(MethodInvocationTree node, Void p) { 339 Trees.instance(t).getScope(getCurrentPath()); 340 return super.visitMethodInvocation(node, p); 341 } 342 }.scan(cut, null); 343 } 344 } 345 346 void testMemberRefs() throws IOException { 347 JavacTool c = JavacTool.create(); 348 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 349 class MyFileObject extends SimpleJavaFileObject { 350 MyFileObject() { 351 super(URI.create("myfo:///Test.java"), SOURCE); 352 } 353 @Override 354 public String getCharContent(boolean ignoreEncodingErrors) { 355 return "class Test {" + 356 " void test() {\n" + 357 " Test t = this;\n" + 358 " Runnable r1 = t::test;\n" + 359 " Runnable r2 = true ? t::test : t::test;\n" + 360 " c(t::test);\n" + 361 " c(true ? t::test : t::test);\n" + 362 " }\n" + 363 " void c(Runnable r) {}\n" + 364 "}"; 365 } 366 } 367 Context ctx = new Context(); 368 TestAnalyzer.preRegister(ctx); 369 JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, 370 List.of(new MyFileObject()), ctx); 371 CompilationUnitTree cut = t.parse().iterator().next(); 372 t.analyze(); 373 374 new TreePathScanner<Void, Void>() { 375 @Override 376 public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) { 377 Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getCondition())); 378 return super.visitConditionalExpression(node, p); 379 } 380 381 @Override 382 public Void visitBlock(BlockTree node, Void p) { 383 Trees.instance(t).getScope(getCurrentPath()); 384 return super.visitBlock(node, p); 385 } 386 }.scan(cut, null); 387 } 388 } 389 390 void testAnnotations() throws IOException { 391 JavacTool c = JavacTool.create(); 392 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 393 class MyFileObject extends SimpleJavaFileObject { 394 MyFileObject() { 395 super(URI.create("myfo:///Test.java"), SOURCE); 396 } 397 @Override 398 public String getCharContent(boolean ignoreEncodingErrors) { 399 return "class Test {" + 400 " void test() {\n" + 401 " new Object() {\n" + 402 " @A\n" + 403 " public String t() { return null; }\n" + 404 " };\n" + 405 " }\n" + 406 " @interface A {}\n" + 407 "}"; 408 } 409 } 410 Context ctx = new Context(); 411 TestAnalyzer.preRegister(ctx); 412 JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, 413 List.of(new MyFileObject()), ctx); 414 CompilationUnitTree cut = t.parse().iterator().next(); 415 t.analyze(); 416 417 new TreePathScanner<Void, Void>() { 418 @Override 419 public Void visitIdentifier(IdentifierTree node, Void p) { 420 if (node.getName().contentEquals("A")) { 421 Trees.instance(t).getScope(getCurrentPath()); 422 } 423 return super.visitIdentifier(node, p); 424 } 425 426 @Override 427 public Void visitMethod(MethodTree node, Void p) { 428 super.visitMethod(node, p); 429 if (node.getReturnType() != null) { 430 Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getReturnType())); 431 } 432 return null; 433 } 434 }.scan(cut, null); 435 } 436 } 437 438 void testAnnotationsLazy() throws IOException { 439 JavacTool c = JavacTool.create(); 440 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 441 class MyFileObject extends SimpleJavaFileObject { 442 MyFileObject() { 443 super(URI.create("myfo:///Test.java"), SOURCE); 444 } 445 @Override 446 public String getCharContent(boolean ignoreEncodingErrors) { 447 return "import java.lang.annotation.*;\n" + 448 "\n" + 449 "class ClassA {\n" + 450 " Object o = ClassB.lcv;\n" + 451 "}\n" + 452 "\n" + 453 "class ClassB {\n" + 454 " static final String[] lcv = new @TA String[0];\n" + 455 "}\n" + 456 "\n" + 457 "class ClassC {\n" + 458 " static final Object o = (@TA Object) null;\n" + 459 "}\n" + 460 "\n" + 461 "@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})\n" + 462 "@interface TA {}\n"; 463 } 464 } 465 Context ctx = new Context(); 466 TestAnalyzer.preRegister(ctx); 467 JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, 468 List.of(new MyFileObject()), ctx); 469 t.addTaskListener(new TaskListener() { 470 @Override 471 public void finished(TaskEvent e) { 472 if (e.getKind() == TaskEvent.Kind.ANALYZE) { 473 new TreePathScanner<Void, Void>() { 474 @Override 475 public Void scan(Tree tree, Void p) { 476 if (tree != null) { 477 Trees.instance(t).getScope(new TreePath(getCurrentPath(), tree)); 478 } 479 return super.scan(tree, p); 480 } 481 }.scan(Trees.instance(t).getPath(e.getTypeElement()), null); 482 } 483 } 484 }); 485 486 t.call(); 487 } 488 } 489 490 void testCircular() throws IOException { 491 JavacTool c = JavacTool.create(); 492 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 493 class MyFileObject extends SimpleJavaFileObject { 494 MyFileObject() { 495 super(URI.create("myfo:///Test.java"), SOURCE); 496 } 497 @Override 498 public String getCharContent(boolean ignoreEncodingErrors) { 499 return "class Test extends Test {" + 500 " {\n" + 501 " int i;\n" + 502 " }\n" + 503 "}"; 504 } 505 } 506 Context ctx = new Context(); 507 TestAnalyzer.preRegister(ctx); 508 JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, 509 List.of(new MyFileObject()), ctx); 510 CompilationUnitTree cut = t.parse().iterator().next(); 511 t.analyze(); 512 513 new TreePathScanner<Void, Void>() { 514 @Override 515 public Void visitBlock(BlockTree node, Void p) { 516 Trees.instance(t).getScope(getCurrentPath()); 517 return super.visitBlock(node, p); 518 } 519 }.scan(cut, null); 520 } 521 } 522 523 void testRecord() throws IOException { 524 JavacTool c = JavacTool.create(); 525 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 526 class MyFileObject extends SimpleJavaFileObject { 527 MyFileObject() { 528 super(URI.create("myfo:///Test.java"), SOURCE); 529 } 530 @Override 531 public String getCharContent(boolean ignoreEncodingErrors) { 532 return "record Test<T>(int mark) {}"; 533 } 534 } 535 Context ctx = new Context(); 536 TestAnalyzer.preRegister(ctx); 537 List<String> options = List.of("--enable-preview", 538 "-source", System.getProperty("java.specification.version")); 539 JavacTask t = (JavacTask) c.getTask(null, fm, null, options, null, 540 List.of(new MyFileObject()), ctx); 541 CompilationUnitTree cut = t.parse().iterator().next(); 542 t.analyze(); 543 544 List<String> actual = new ArrayList<>(); 545 546 new TreePathScanner<Void, Void>() { 547 @Override 548 public Void visitClass(ClassTree node, Void p) { 549 Scope scope = Trees.instance(t).getScope(getCurrentPath()); 550 actual.addAll(dumpScope(scope)); 551 return super.visitClass(node, p); 552 } 553 }.scan(cut, null); 554 555 List<String> expected = List.of( 556 "super:java.lang.Record", 557 "this:Test<T>", 558 "T:T" 559 ); 560 561 if (!expected.equals(actual)) { 562 throw new AssertionError("Unexpected Scope content: " + actual); 563 } 564 } 565 } 566 567 void testLocalRecordAnnotation() throws IOException { 568 JavacTool c = JavacTool.create(); 569 try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { 570 class Variant { 571 final String code; 572 final List<List<String>> expectedScopeContent; 573 public Variant(String code, List<List<String>> expectedScopeContent) { 574 this.code = code; 575 this.expectedScopeContent = expectedScopeContent; 576 } 577 } 578 Variant[] variants = new Variant[] { 579 new Variant(""" 580 class Test { 581 void t() { 582 record R(@Annotation int i) { 583 void stop () {} 584 } 585 } 586 } 587 @interface Annotation {} 588 """, 589 List.of( 590 List.of("super:java.lang.Object", "this:Test"), 591 List.of("super:java.lang.Object", "this:Test") 592 )), 593 new Variant(""" 594 record Test(@Annotation int i) {} 595 @interface Annotation {} 596 """, 597 List.of( 598 List.of("i:int", "super:java.lang.Record", "this:Test"), 599 List.of("super:java.lang.Record", "this:Test") 600 )) 601 }; 602 for (Variant currentVariant : variants) { 603 class MyFileObject extends SimpleJavaFileObject { 604 MyFileObject() { 605 super(URI.create("myfo:///Test.java"), SOURCE); 606 } 607 @Override 608 public String getCharContent(boolean ignoreEncodingErrors) { 609 return currentVariant.code; 610 } 611 } 612 Context ctx = new Context(); 613 TestAnalyzer.preRegister(ctx); 614 List<String> options = List.of("--enable-preview", 615 "-source", System.getProperty("java.specification.version")); 616 JavacTask t = (JavacTask) c.getTask(null, fm, null, options, null, 617 List.of(new MyFileObject()), ctx); 618 CompilationUnitTree cut = t.parse().iterator().next(); 619 t.analyze(); 620 621 List<List<String>> actual = new ArrayList<>(); 622 623 new TreePathScanner<Void, Void>() { 624 @Override 625 public Void visitAnnotation(AnnotationTree node, Void p) { 626 Scope scope = Trees.instance(t).getScope(getCurrentPath()); 627 actual.add(dumpScope(scope)); 628 return super.visitAnnotation(node, p); 629 } 630 }.scan(cut, null); 631 632 if (!currentVariant.expectedScopeContent.equals(actual)) { 633 throw new AssertionError("Unexpected Scope content: " + actual); 634 } 635 } 636 } 637 } 638 639 private List<String> dumpScope(Scope scope) { 640 List<String> content = new ArrayList<>(); 641 while (scope.getEnclosingClass() != null) { 642 for (Element el : scope.getLocalElements()) { 643 content.add(el.getSimpleName() + ":" +el.asType().toString()); 644 } 645 scope = scope.getEnclosingScope(); 646 } 647 return content; 648 } 649 }