1 /* 2 * Copyright (c) 2022, 2025, 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 * ValueObjectCompilationTests 26 * 27 * @test 28 * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120 29 * 8329345 8341061 8340984 8334484 30 * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects 31 * @library /lib/combo /tools/lib 32 * @modules 33 * jdk.compiler/com.sun.tools.javac.util 34 * jdk.compiler/com.sun.tools.javac.api 35 * jdk.compiler/com.sun.tools.javac.main 36 * jdk.compiler/com.sun.tools.javac.code 37 * jdk.jdeps/com.sun.tools.classfile 38 * @build toolbox.ToolBox toolbox.JavacTask 39 * @run junit ValueObjectCompilationTests 40 */ 41 42 import java.io.File; 43 44 import java.util.List; 45 import java.util.Set; 46 47 import com.sun.tools.javac.util.Assert; 48 49 import com.sun.tools.classfile.Attribute; 50 import com.sun.tools.classfile.Attributes; 51 import com.sun.tools.classfile.ClassFile; 52 import com.sun.tools.classfile.Code_attribute; 53 import com.sun.tools.classfile.ConstantPool; 54 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info; 55 import com.sun.tools.classfile.ConstantPool.CONSTANT_Fieldref_info; 56 import com.sun.tools.classfile.ConstantPool.CONSTANT_Methodref_info; 57 import com.sun.tools.classfile.ConstantPool.CONSTANT_NameAndType_info; 58 import com.sun.tools.classfile.ConstantPool.CPRefInfo; 59 import com.sun.tools.classfile.Field; 60 import com.sun.tools.classfile.Instruction; 61 import com.sun.tools.classfile.Method; 62 import com.sun.tools.classfile.StackMapTable_attribute; 63 64 import com.sun.tools.javac.code.Flags; 65 66 import org.junit.jupiter.api.Test; 67 import tools.javac.combo.CompilationTestCase; 68 import toolbox.ToolBox; 69 70 class ValueObjectCompilationTests extends CompilationTestCase { 71 72 private static String[] PREVIEW_OPTIONS = { 73 "--enable-preview", 74 "-source", Integer.toString(Runtime.version().feature()) 75 }; 76 77 private static String[] PREVIEW_OPTIONS_PLUS_VM_ANNO = { 78 "--enable-preview", 79 "-source", Integer.toString(Runtime.version().feature()), 80 "--add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED" 81 }; 82 83 public ValueObjectCompilationTests() { 84 setDefaultFilename("ValueObjectsTest.java"); 85 setCompileOptions(PREVIEW_OPTIONS); 86 } 87 88 @Test 89 void testValueModifierConstraints() { 90 assertFail("compiler.err.illegal.combination.of.modifiers", 91 """ 92 value @interface IA {} 93 """); 94 assertFail("compiler.err.illegal.combination.of.modifiers", 95 """ 96 value interface I {} 97 """); 98 assertFail("compiler.err.mod.not.allowed.here", 99 """ 100 class Test { 101 value int x; 102 } 103 """); 104 assertFail("compiler.err.mod.not.allowed.here", 105 """ 106 class Test { 107 value int foo(); 108 } 109 """); 110 assertFail("compiler.err.mod.not.allowed.here", 111 """ 112 value enum Enum {} 113 """); 114 } 115 116 record TestData(String message, String snippet, String[] compilerOptions, boolean testLocalToo) { 117 TestData(String snippet) { 118 this("", snippet, null, true); 119 } 120 121 TestData(String snippet, boolean testLocalToo) { 122 this("", snippet, null, testLocalToo); 123 } 124 125 TestData(String message, String snippet) { 126 this(message, snippet, null, true); 127 } 128 129 TestData(String snippet, String[] compilerOptions) { 130 this("", snippet, compilerOptions, true); 131 } 132 133 TestData(String message, String snippet, String[] compilerOptions) { 134 this(message, snippet, compilerOptions, true); 135 } 136 137 TestData(String message, String snippet, boolean testLocalToo) { 138 this(message, snippet, null, testLocalToo); 139 } 140 } 141 142 private void testHelper(List<TestData> testDataList) { 143 String ttt = 144 """ 145 class TTT { 146 void m() { 147 #LOCAL 148 } 149 } 150 """; 151 for (TestData td : testDataList) { 152 String localSnippet = ttt.replace("#LOCAL", td.snippet); 153 String[] previousOptions = getCompileOptions(); 154 try { 155 if (td.compilerOptions != null) { 156 setCompileOptions(td.compilerOptions); 157 } 158 if (td.message == "") { 159 assertOK(td.snippet); 160 if (td.testLocalToo) { 161 assertOK(localSnippet); 162 } 163 } else if (td.message.startsWith("compiler.err")) { 164 assertFail(td.message, td.snippet); 165 if (td.testLocalToo) { 166 assertFail(td.message, localSnippet); 167 } 168 } else { 169 assertOKWithWarning(td.message, td.snippet); 170 if (td.testLocalToo) { 171 assertOKWithWarning(td.message, localSnippet); 172 } 173 } 174 } finally { 175 setCompileOptions(previousOptions); 176 } 177 } 178 } 179 180 private static final List<TestData> superClassConstraints = List.of( 181 new TestData( 182 "compiler.err.super.class.method.cannot.be.synchronized", 183 """ 184 abstract class I { 185 synchronized void foo() {} 186 } 187 value class V extends I {} 188 """ 189 ), 190 new TestData( 191 "compiler.err.concrete.supertype.for.value.class", 192 """ 193 class ConcreteSuperType { 194 static abstract value class V extends ConcreteSuperType {} // Error: concrete super. 195 } 196 """ 197 ), 198 new TestData( 199 """ 200 value record Point(int x, int y) {} 201 """ 202 ), 203 new TestData( 204 """ 205 value class One extends Number { 206 public int intValue() { return 0; } 207 public long longValue() { return 0; } 208 public float floatValue() { return 0; } 209 public double doubleValue() { return 0; } 210 } 211 """ 212 ), 213 new TestData( 214 """ 215 value class V extends Object {} 216 """ 217 ), 218 new TestData( 219 "compiler.err.value.type.has.identity.super.type", 220 """ 221 abstract class A {} 222 value class V extends A {} 223 """ 224 ) 225 ); 226 227 @Test 228 void testSuperClassConstraints() { 229 testHelper(superClassConstraints); 230 } 231 232 @Test 233 void testRepeatedModifiers() { 234 assertFail("compiler.err.repeated.modifier", "value value class ValueTest {}"); 235 } 236 237 @Test 238 void testParserTest() { 239 assertOK( 240 """ 241 value class Substring implements CharSequence { 242 private String str; 243 private int start; 244 private int end; 245 246 public Substring(String str, int start, int end) { 247 checkBounds(start, end, str.length()); 248 this.str = str; 249 this.start = start; 250 this.end = end; 251 } 252 253 public int length() { 254 return end - start; 255 } 256 257 public char charAt(int i) { 258 checkBounds(0, i, length()); 259 return str.charAt(start + i); 260 } 261 262 public Substring subSequence(int s, int e) { 263 checkBounds(s, e, length()); 264 return new Substring(str, start + s, start + e); 265 } 266 267 public String toString() { 268 return str.substring(start, end); 269 } 270 271 private static void checkBounds(int start, int end, int length) { 272 if (start < 0 || end < start || length < end) 273 throw new IndexOutOfBoundsException(); 274 } 275 } 276 """ 277 ); 278 } 279 280 private static final List<TestData> semanticsViolations = List.of( 281 new TestData( 282 "compiler.err.cant.inherit.from.final", 283 """ 284 value class Base {} 285 class Subclass extends Base {} 286 """ 287 ), 288 new TestData( 289 "compiler.err.cant.assign.val.to.var", 290 """ 291 value class Point { 292 int x = 10; 293 int y; 294 Point (int x, int y) { 295 this.x = x; // Error, final field 'x' is already assigned to. 296 this.y = y; // OK. 297 } 298 } 299 """ 300 ), 301 new TestData( 302 "compiler.err.cant.assign.val.to.var", 303 """ 304 value class Point { 305 int x; 306 int y; 307 Point (int x, int y) { 308 this.x = x; 309 this.y = y; 310 } 311 void foo(Point p) { 312 this.y = p.y; // Error, y is final and can't be written outside of ctor. 313 } 314 } 315 """ 316 ), 317 new TestData( 318 "compiler.err.cant.assign.val.to.var", 319 """ 320 abstract value class Point { 321 int x; 322 int y; 323 Point (int x, int y) { 324 this.x = x; 325 this.y = y; 326 } 327 void foo(Point p) { 328 this.y = p.y; // Error, y is final and can't be written outside of ctor. 329 } 330 } 331 """ 332 ), 333 new TestData( 334 "compiler.err.strict.field.not.have.been.initialized.before.super", 335 """ 336 value class Point { 337 int x; 338 int y; 339 Point (int x, int y) { 340 this.x = x; 341 // y hasn't been initialized 342 } 343 } 344 """ 345 ), 346 new TestData( 347 "compiler.err.mod.not.allowed.here", 348 """ 349 abstract value class V { 350 synchronized void foo() { 351 // Error, abstract value class may not declare a synchronized instance method. 352 } 353 } 354 """ 355 ), 356 new TestData( 357 """ 358 abstract value class V { 359 static synchronized void foo() {} // OK static 360 } 361 """ 362 ), 363 new TestData( 364 "compiler.err.mod.not.allowed.here", 365 """ 366 value class V { 367 synchronized void foo() {} 368 } 369 """ 370 ), 371 new TestData( 372 """ 373 value class V { 374 synchronized static void soo() {} // OK static 375 } 376 """ 377 ), 378 new TestData( 379 "compiler.err.type.found.req", 380 """ 381 value class V { 382 { synchronized(this) {} } 383 } 384 """ 385 ), 386 new TestData( 387 "compiler.err.mod.not.allowed.here", 388 """ 389 value record R() { 390 synchronized void foo() { } // Error; 391 synchronized static void soo() {} // OK. 392 } 393 """ 394 ), 395 new TestData( 396 "compiler.err.cant.ref.before.ctor.called", 397 """ 398 value class V { 399 int x; 400 V() { 401 foo(this); // Error. 402 x = 10; 403 } 404 void foo(V v) {} 405 } 406 """ 407 ), 408 new TestData( 409 "compiler.err.cant.ref.before.ctor.called", 410 """ 411 value class V { 412 int x; 413 V() { 414 x = 10; 415 foo(this); // error 416 } 417 void foo(V v) {} 418 } 419 """ 420 ), 421 new TestData( 422 "compiler.err.type.found.req", 423 """ 424 interface I {} 425 interface VI extends I {} 426 class C {} 427 value class VC<T extends VC> { 428 void m(T t) { 429 synchronized(t) {} // error 430 } 431 } 432 """ 433 ), 434 new TestData( 435 "compiler.err.type.found.req", 436 """ 437 interface I {} 438 interface VI extends I {} 439 class C {} 440 value class VC<T extends VC> { 441 void foo(Object o) { 442 synchronized ((VC & I)o) {} // error 443 } 444 } 445 """ 446 ), 447 new TestData( 448 // OK if the value class is abstract 449 """ 450 interface I {} 451 abstract value class VI implements I {} 452 class C {} 453 value class VC<T extends VC> { 454 void bar(Object o) { 455 synchronized ((VI & I)o) {} // error 456 } 457 } 458 """ 459 ), 460 new TestData( 461 "compiler.err.type.found.req", // --enable-preview -source" 462 """ 463 class V { 464 final Integer val = Integer.valueOf(42); 465 void test() { 466 synchronized (val) { // error 467 } 468 } 469 } 470 """ 471 ), 472 new TestData( 473 "compiler.err.type.found.req", // --enable-preview -source" 474 """ 475 import java.time.*; 476 class V { 477 final Duration val = Duration.ZERO; 478 void test() { 479 synchronized (val) { // warn 480 } 481 } 482 } 483 """, 484 false // cant do local as there is an import statement 485 ), 486 new TestData( 487 "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // empty options 488 """ 489 class V { 490 final Integer val = Integer.valueOf(42); 491 void test() { 492 synchronized (val) { // warn 493 } 494 } 495 } 496 """, 497 new String[] {} 498 ), 499 new TestData( 500 "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source 501 """ 502 class V { 503 final Integer val = Integer.valueOf(42); 504 void test() { 505 synchronized (val) { // warn 506 } 507 } 508 } 509 """, 510 new String[] {"--source", Integer.toString(Runtime.version().feature())} 511 ), 512 new TestData( 513 "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source 514 """ 515 class V { 516 final Integer val = Integer.valueOf(42); 517 void test() { 518 synchronized (val) { // warn 519 } 520 } 521 } 522 """, 523 new String[] {"--source", Integer.toString(Runtime.version().feature())} 524 ), 525 new TestData( 526 "compiler.err.illegal.combination.of.modifiers", // --enable-preview -source" 527 """ 528 value class V { 529 volatile int f = 1; 530 } 531 """ 532 ) 533 ); 534 535 @Test 536 void testSemanticsViolations() { 537 testHelper(semanticsViolations); 538 } 539 540 private static final List<TestData> sealedClassesData = List.of( 541 new TestData( 542 """ 543 abstract sealed value class SC {} 544 value class VC extends SC {} 545 """, 546 false // local sealed classes are not allowed 547 ), 548 new TestData( 549 """ 550 abstract sealed interface SI {} 551 value class VC implements SI {} 552 """, 553 false // local sealed classes are not allowed 554 ), 555 new TestData( 556 """ 557 abstract sealed class SC {} 558 final class IC extends SC {} 559 non-sealed class IC2 extends SC {} 560 final class IC3 extends IC2 {} 561 """, 562 false 563 ), 564 new TestData( 565 """ 566 abstract sealed interface SI {} 567 final class IC implements SI {} 568 non-sealed class IC2 implements SI {} 569 final class IC3 extends IC2 {} 570 """, 571 false // local sealed classes are not allowed 572 ), 573 new TestData( 574 "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed", 575 """ 576 abstract sealed value class SC {} 577 non-sealed value class VC extends SC {} 578 """, 579 false 580 ), 581 new TestData( 582 "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed", 583 """ 584 sealed value class SI {} 585 """, 586 false 587 ), 588 new TestData( 589 """ 590 sealed abstract value class SI {} 591 value class V extends SI {} 592 """, 593 false 594 ), 595 new TestData( 596 """ 597 sealed abstract value class SI permits V {} 598 value class V extends SI {} 599 """, 600 false 601 ), 602 new TestData( 603 """ 604 sealed interface I {} 605 non-sealed abstract value class V implements I {} 606 """, 607 false 608 ), 609 new TestData( 610 """ 611 sealed interface I permits V {} 612 non-sealed abstract value class V implements I {} 613 """, 614 false 615 ) 616 ); 617 618 @Test 619 void testInteractionWithSealedClasses() { 620 testHelper(sealedClassesData); 621 } 622 623 @Test 624 void testCheckClassFileFlags() throws Exception { 625 for (String source : List.of( 626 """ 627 interface I {} 628 class Test { 629 I i = new I() {}; 630 } 631 """, 632 """ 633 class C {} 634 class Test { 635 C c = new C() {}; 636 } 637 """, 638 """ 639 class Test { 640 Object o = new Object() {}; 641 } 642 """, 643 """ 644 class Test { 645 abstract class Inner {} 646 } 647 """ 648 )) { 649 File dir = assertOK(true, source); 650 for (final File fileEntry : dir.listFiles()) { 651 if (fileEntry.getName().contains("$")) { 652 ClassFile classFile = ClassFile.read(fileEntry); 653 Assert.check((classFile.access_flags.flags & Flags.ACC_IDENTITY) != 0); 654 } 655 } 656 } 657 658 for (String source : List.of( 659 """ 660 class C {} 661 """, 662 """ 663 abstract class A { 664 int i; 665 } 666 """, 667 """ 668 abstract class A { 669 synchronized void m() {} 670 } 671 """, 672 """ 673 class C { 674 synchronized void m() {} 675 } 676 """, 677 """ 678 abstract class A { 679 int i; 680 { i = 0; } 681 } 682 """, 683 """ 684 abstract class A { 685 A(int i) {} 686 } 687 """, 688 """ 689 enum E {} 690 """, 691 """ 692 record R() {} 693 """ 694 )) { 695 File dir = assertOK(true, source); 696 for (final File fileEntry : dir.listFiles()) { 697 ClassFile classFile = ClassFile.read(fileEntry); 698 Assert.check(classFile.access_flags.is(Flags.ACC_IDENTITY)); 699 } 700 } 701 702 { 703 String source = 704 """ 705 abstract value class A {} 706 value class Sub extends A {} //implicitly final 707 """; 708 File dir = assertOK(true, source); 709 for (final File fileEntry : dir.listFiles()) { 710 ClassFile classFile = ClassFile.read(fileEntry); 711 switch (classFile.getName()) { 712 case "Sub": 713 Assert.check((classFile.access_flags.flags & (Flags.FINAL)) != 0); 714 break; 715 case "A": 716 Assert.check((classFile.access_flags.flags & (Flags.ABSTRACT)) != 0); 717 break; 718 default: 719 throw new AssertionError("you shoulnd't be here"); 720 } 721 } 722 } 723 724 for (String source : List.of( 725 """ 726 value class V { 727 int i = 0; 728 static int j; 729 } 730 """, 731 """ 732 abstract value class A { 733 static int j; 734 } 735 736 value class V extends A { 737 int i = 0; 738 } 739 """ 740 )) { 741 File dir = assertOK(true, source); 742 for (final File fileEntry : dir.listFiles()) { 743 ClassFile classFile = ClassFile.read(fileEntry); 744 for (Field field : classFile.fields) { 745 if (!field.access_flags.is(Flags.STATIC)) { 746 Set<String> fieldFlags = field.access_flags.getFieldFlags(); 747 Assert.check(fieldFlags.size() == 2 && fieldFlags.contains("ACC_FINAL") && fieldFlags.contains("ACC_STRICT")); 748 } 749 } 750 } 751 } 752 753 // testing experimental @Strict annotation 754 String[] previousOptions = getCompileOptions(); 755 try { 756 String[] testOptions = {"--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"}; 757 setCompileOptions(testOptions); 758 for (String source : List.of( 759 """ 760 import jdk.internal.vm.annotation.Strict; 761 class Test { 762 @Strict int i = 0; 763 } 764 """, 765 """ 766 import jdk.internal.vm.annotation.Strict; 767 class Test { 768 @Strict final int i = 0; 769 } 770 """ 771 )) { 772 File dir = assertOK(true, source); 773 for (final File fileEntry : dir.listFiles()) { 774 ClassFile classFile = ClassFile.read(fileEntry); 775 for (Field field : classFile.fields) { 776 if (!field.access_flags.is(Flags.STATIC)) { 777 Set<String> fieldFlags = field.access_flags.getFieldFlags(); 778 Assert.check(fieldFlags.contains("ACC_STRICT")); 779 } 780 } 781 } 782 } 783 } finally { 784 setCompileOptions(previousOptions); 785 } 786 } 787 788 @Test 789 void testConstruction() throws Exception { 790 record Data(String src, boolean isRecord) { 791 Data(String src) { 792 this(src, false); 793 } 794 } 795 for (Data data : List.of( 796 new Data( 797 """ 798 value class Test { 799 int i = 100; 800 } 801 """), 802 new Data( 803 """ 804 value class Test { 805 int i; 806 Test() { 807 i = 100; 808 } 809 } 810 """), 811 new Data( 812 """ 813 value class Test { 814 int i; 815 Test() { 816 i = 100; 817 super(); 818 } 819 } 820 """), 821 new Data( 822 """ 823 value class Test { 824 int i; 825 Test() { 826 this.i = 100; 827 super(); 828 } 829 } 830 """), 831 new Data( 832 """ 833 value record Test(int i) {} 834 """, true) 835 )) { 836 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return,"; 837 String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return,"; 838 File dir = assertOK(true, data.src); 839 for (final File fileEntry : dir.listFiles()) { 840 ClassFile classFile = ClassFile.read(fileEntry); 841 for (Method method : classFile.methods) { 842 if (method.getName(classFile.constant_pool).equals("<init>")) { 843 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 844 String foundCodeSequence = ""; 845 for (Instruction inst: code.getInstructions()) { 846 foundCodeSequence += inst.getMnemonic() + ","; 847 } 848 if (!data.isRecord) { 849 Assert.check(expectedCodeSequence.equals(foundCodeSequence)); 850 } else { 851 Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence)); 852 } 853 } 854 } 855 } 856 } 857 858 String source = 859 """ 860 value class Test { 861 int i = 100; 862 int j = 0; 863 { 864 System.out.println(j); 865 } 866 } 867 """; 868 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,iconst_0,putfield,aload_0,invokespecial,getstatic,iconst_0,invokevirtual,return,"; 869 File dir = assertOK(true, source); 870 for (final File fileEntry : dir.listFiles()) { 871 ClassFile classFile = ClassFile.read(fileEntry); 872 for (Method method : classFile.methods) { 873 if (method.getName(classFile.constant_pool).equals("<init>")) { 874 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 875 String foundCodeSequence = ""; 876 for (Instruction inst: code.getInstructions()) { 877 foundCodeSequence += inst.getMnemonic() + ","; 878 } 879 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 880 } 881 } 882 } 883 884 assertFail("compiler.err.cant.ref.before.ctor.called", 885 """ 886 value class Test { 887 Test() { 888 m(); 889 } 890 void m() {} 891 } 892 """ 893 ); 894 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 895 """ 896 value class Test { 897 int i; 898 Test() { 899 super(); 900 this.i = i; 901 } 902 } 903 """ 904 ); 905 assertOK( 906 """ 907 class UnrelatedThisLeak { 908 value class V { 909 int f; 910 V() { 911 UnrelatedThisLeak x = UnrelatedThisLeak.this; 912 f = 10; 913 x = UnrelatedThisLeak.this; 914 } 915 } 916 } 917 """ 918 ); 919 assertOK( 920 """ 921 value class Test { 922 Test t = null; 923 Runnable r = () -> { System.err.println(t); }; // compiler will generate a local proxy for `t` 924 } 925 """ 926 ); 927 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 928 """ 929 value class Test { 930 int f; 931 { 932 f = 1; 933 } 934 } 935 """ 936 ); 937 assertOK( 938 """ 939 value class V { 940 int x; 941 int y = x + 1; // allowed 942 V() { 943 x = 12; 944 // super(); 945 } 946 } 947 """ 948 ); 949 assertFail("compiler.err.var.might.already.be.assigned", 950 """ 951 value class V2 { 952 int x; 953 V2() { this(x = 3); } // error 954 V2(int i) { x = 4; } 955 } 956 """ 957 ); 958 assertOK( 959 """ 960 abstract value class AV1 { 961 AV1(int i) {} 962 } 963 value class V3 extends AV1 { 964 int x; 965 V3() { 966 super(x = 3); // ok 967 } 968 } 969 """ 970 ); 971 assertOK( 972 """ 973 value class V4 { 974 int x; 975 int y = x + 1; 976 V4() { 977 x = 12; 978 } 979 V4(int i) { 980 x = i; 981 } 982 } 983 """ 984 ); 985 assertOK( 986 """ 987 value class V { 988 final int x = "abc".length(); 989 { System.out.println(x); } 990 } 991 """ 992 ); 993 assertFail("compiler.err.illegal.forward.ref", 994 """ 995 value class V { 996 { System.out.println(x); } 997 final int x = "abc".length(); 998 } 999 """ 1000 ); 1001 assertOK( 1002 """ 1003 value class V { 1004 int x = "abc".length(); 1005 int y = x; 1006 } 1007 """ 1008 ); 1009 assertOK( 1010 """ 1011 value class V { 1012 int x = "abc".length(); 1013 { int y = x; } 1014 } 1015 """ 1016 ); 1017 assertOK( 1018 """ 1019 value class V { 1020 String s1; 1021 { System.out.println(s1); } 1022 String s2 = (s1 = "abc"); 1023 } 1024 """ 1025 ); 1026 1027 String[] previousOptions = getCompileOptions(); 1028 try { 1029 setCompileOptions(PREVIEW_OPTIONS_PLUS_VM_ANNO); 1030 String[] sources = new String[]{ 1031 """ 1032 import jdk.internal.vm.annotation.Strict; 1033 class Test { 1034 static value class IValue { 1035 int i = 0; 1036 } 1037 @Strict 1038 final IValue val = new IValue(); 1039 } 1040 """, 1041 """ 1042 import jdk.internal.vm.annotation.Strict; 1043 class Test { 1044 static value class IValue { 1045 int i = 0; 1046 } 1047 @Strict 1048 final IValue val; 1049 Test() { 1050 val = new IValue(); 1051 } 1052 } 1053 """ 1054 }; 1055 expectedCodeSequence = "aload_0,new,dup,invokespecial,putfield,aload_0,invokespecial,return,"; 1056 for (String src : sources) { 1057 dir = assertOK(true, src); 1058 for (final File fileEntry : dir.listFiles()) { 1059 ClassFile classFile = ClassFile.read(fileEntry); 1060 if (classFile.getName().equals("Test")) { 1061 for (Method method : classFile.methods) { 1062 if (method.getName(classFile.constant_pool).equals("<init>")) { 1063 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1064 String foundCodeSequence = ""; 1065 for (Instruction inst: code.getInstructions()) { 1066 foundCodeSequence += inst.getMnemonic() + ","; 1067 } 1068 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 1069 } 1070 } 1071 } 1072 } 1073 } 1074 1075 assertFail("compiler.err.cant.ref.before.ctor.called", 1076 """ 1077 import jdk.internal.vm.annotation.NullRestricted; 1078 import jdk.internal.vm.annotation.Strict; 1079 class StrictNR { 1080 static value class IValue { 1081 int i = 0; 1082 } 1083 value class SValue { 1084 short s = 0; 1085 } 1086 @Strict 1087 @NullRestricted 1088 IValue val = new IValue(); 1089 @Strict 1090 @NullRestricted 1091 final IValue val2; 1092 @Strict 1093 @NullRestricted 1094 SValue val3 = new SValue(); 1095 } 1096 """ 1097 ); 1098 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 1099 """ 1100 import jdk.internal.vm.annotation.Strict; 1101 class Test { 1102 @Strict int i; 1103 } 1104 """ 1105 ); 1106 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 1107 """ 1108 import jdk.internal.vm.annotation.Strict; 1109 class Test { 1110 @Strict int i; 1111 Test() { 1112 super(); 1113 i = 0; 1114 } 1115 } 1116 """ 1117 ); 1118 assertFail("compiler.err.cant.ref.before.ctor.called", 1119 """ 1120 import jdk.internal.vm.annotation.NullRestricted; 1121 import jdk.internal.vm.annotation.Strict; 1122 class StrictNR { 1123 static value class IValue { 1124 int i = 0; 1125 } 1126 value class SValue { 1127 short s = 0; 1128 } 1129 @Strict 1130 @NullRestricted 1131 IValue val = new IValue(); 1132 @Strict 1133 @NullRestricted 1134 SValue val4; 1135 public StrictNR() { 1136 val4 = new SValue(); 1137 } 1138 } 1139 """ 1140 ); 1141 } finally { 1142 setCompileOptions(previousOptions); 1143 } 1144 1145 source = 1146 """ 1147 value class V { 1148 int i = 1; 1149 int y; 1150 V() { 1151 y = 2; 1152 } 1153 } 1154 """; 1155 dir = assertOK(true, source); 1156 File fileEntry = dir.listFiles()[0]; 1157 ClassFile classFile = ClassFile.read(fileEntry); 1158 expectedCodeSequence = "putfield i,putfield y,"; 1159 for (Method method : classFile.methods) { 1160 if (method.getName(classFile.constant_pool).equals("<init>")) { 1161 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1162 String foundCodeSequence = ""; 1163 for (Instruction inst: code.getInstructions()) { 1164 if (inst.getMnemonic().equals("putfield")) { 1165 CPRefInfo refInfo = (CPRefInfo)classFile.constant_pool.get(inst.getShort(1)); 1166 CONSTANT_NameAndType_info nameAndType = refInfo.getNameAndTypeInfo(); 1167 foundCodeSequence += inst.getMnemonic() + " " + nameAndType.getName() + ","; 1168 } 1169 } 1170 Assert.check(foundCodeSequence.equals(expectedCodeSequence), foundCodeSequence); 1171 } 1172 } 1173 } 1174 1175 @Test 1176 void testThisCallingConstructor() throws Exception { 1177 // make sure that this() calling constructors doesn't initialize final fields 1178 String source = 1179 """ 1180 value class Test { 1181 int i; 1182 Test() { 1183 this(0); 1184 } 1185 1186 Test(int i) { 1187 this.i = i; 1188 } 1189 } 1190 """; 1191 File dir = assertOK(true, source); 1192 File fileEntry = dir.listFiles()[0]; 1193 ClassFile classFile = ClassFile.read(fileEntry); 1194 String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return,"; 1195 String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return,"; 1196 for (Method method : classFile.methods) { 1197 if (method.getName(classFile.constant_pool).equals("<init>")) { 1198 if (method.descriptor.getParameterCount(classFile.constant_pool) == 0) { 1199 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1200 String foundCodeSequence = ""; 1201 for (Instruction inst: code.getInstructions()) { 1202 foundCodeSequence += inst.getMnemonic() + ","; 1203 } 1204 Assert.check(expectedCodeSequenceThisCallingConst.equals(foundCodeSequence)); 1205 } else { 1206 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1207 String foundCodeSequence = ""; 1208 for (Instruction inst: code.getInstructions()) { 1209 foundCodeSequence += inst.getMnemonic() + ","; 1210 } 1211 Assert.check(expectedCodeSequenceNonThisCallingConst.equals(foundCodeSequence)); 1212 } 1213 } 1214 } 1215 } 1216 1217 @Test 1218 void testSelectors() throws Exception { 1219 assertOK( 1220 """ 1221 value class V { 1222 void selector() { 1223 Class<?> c = int.class; 1224 } 1225 } 1226 """ 1227 ); 1228 assertFail("compiler.err.expected", 1229 """ 1230 value class V { 1231 void selector() { 1232 int i = int.some_selector; 1233 } 1234 } 1235 """ 1236 ); 1237 } 1238 1239 @Test 1240 void testNullAssigment() throws Exception { 1241 assertOK( 1242 """ 1243 value final class V { 1244 final int x = 10; 1245 1246 value final class X { 1247 final V v; 1248 final V v2; 1249 1250 X() { 1251 this.v = null; 1252 this.v2 = null; 1253 } 1254 1255 X(V v) { 1256 this.v = v; 1257 this.v2 = v; 1258 } 1259 1260 V foo(X x) { 1261 x = new X(null); // OK 1262 return x.v; 1263 } 1264 } 1265 V bar(X x) { 1266 x = new X(null); // OK 1267 return x.v; 1268 } 1269 1270 class Y { 1271 V v; 1272 V [] va = { null }; // OK: array initialization 1273 V [] va2 = new V[] { null }; // OK: array initialization 1274 void ooo(X x) { 1275 x = new X(null); // OK 1276 v = null; // legal assignment. 1277 va[0] = null; // legal. 1278 va = new V[] { null }; // legal 1279 } 1280 } 1281 } 1282 """ 1283 ); 1284 } 1285 1286 @Test 1287 void testSerializationWarnings() throws Exception { 1288 String[] previousOptions = getCompileOptions(); 1289 try { 1290 setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source", 1291 Integer.toString(Runtime.version().feature())}); 1292 assertOK( 1293 """ 1294 import java.io.*; 1295 abstract value class AVC implements Serializable {} 1296 """); 1297 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", 1298 """ 1299 import java.io.*; 1300 value class VC implements Serializable { 1301 private static final long serialVersionUID = 0; 1302 } 1303 """); 1304 assertOK( 1305 """ 1306 import java.io.*; 1307 class C implements Serializable { 1308 private static final long serialVersionUID = 0; 1309 } 1310 """); 1311 assertOK( 1312 """ 1313 import java.io.*; 1314 abstract value class Super implements Serializable { 1315 private static final long serialVersionUID = 0; 1316 protected Object writeReplace() throws ObjectStreamException { 1317 return null; 1318 } 1319 } 1320 value class ValueSerializable extends Super { 1321 private static final long serialVersionUID = 1; 1322 } 1323 """); 1324 assertOK( 1325 """ 1326 import java.io.*; 1327 abstract value class Super implements Serializable { 1328 private static final long serialVersionUID = 0; 1329 Object writeReplace() throws ObjectStreamException { 1330 return null; 1331 } 1332 } 1333 value class ValueSerializable extends Super { 1334 private static final long serialVersionUID = 1; 1335 } 1336 """); 1337 assertOK( 1338 """ 1339 import java.io.*; 1340 abstract value class Super implements Serializable { 1341 private static final long serialVersionUID = 0; 1342 public Object writeReplace() throws ObjectStreamException { 1343 return null; 1344 } 1345 } 1346 value class ValueSerializable extends Super { 1347 private static final long serialVersionUID = 1; 1348 } 1349 """); 1350 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", 1351 """ 1352 import java.io.*; 1353 abstract value class Super implements Serializable { 1354 private static final long serialVersionUID = 0; 1355 private Object writeReplace() throws ObjectStreamException { 1356 return null; 1357 } 1358 } 1359 value class ValueSerializable extends Super { 1360 private static final long serialVersionUID = 1; 1361 } 1362 """); 1363 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2", 1364 """ 1365 import java.io.*; 1366 abstract value class Super implements Serializable { 1367 private static final long serialVersionUID = 0; 1368 private Object writeReplace() throws ObjectStreamException { 1369 return null; 1370 } 1371 } 1372 class Serializable1 extends Super { 1373 private static final long serialVersionUID = 1; 1374 } 1375 class Serializable2 extends Serializable1 { 1376 private static final long serialVersionUID = 1; 1377 } 1378 """); 1379 assertOK( 1380 """ 1381 import java.io.*; 1382 abstract value class Super implements Serializable { 1383 private static final long serialVersionUID = 0; 1384 Object writeReplace() throws ObjectStreamException { 1385 return null; 1386 } 1387 } 1388 class ValueSerializable extends Super { 1389 private static final long serialVersionUID = 1; 1390 } 1391 """); 1392 assertOK( 1393 """ 1394 import java.io.*; 1395 abstract value class Super implements Serializable { 1396 private static final long serialVersionUID = 0; 1397 public Object writeReplace() throws ObjectStreamException { 1398 return null; 1399 } 1400 } 1401 class ValueSerializable extends Super { 1402 private static final long serialVersionUID = 1; 1403 } 1404 """); 1405 assertOK( 1406 """ 1407 import java.io.*; 1408 abstract value class Super implements Serializable { 1409 private static final long serialVersionUID = 0; 1410 protected Object writeReplace() throws ObjectStreamException { 1411 return null; 1412 } 1413 } 1414 class ValueSerializable extends Super { 1415 private static final long serialVersionUID = 1; 1416 } 1417 """); 1418 assertOK( 1419 """ 1420 import java.io.*; 1421 value record ValueRecord() implements Serializable { 1422 private static final long serialVersionUID = 1; 1423 } 1424 """); 1425 assertOK( 1426 // Number is a special case, no warning for identity classes extending it 1427 """ 1428 class NumberSubClass extends Number { 1429 private static final long serialVersionUID = 0L; 1430 @Override 1431 public double doubleValue() { return 0; } 1432 @Override 1433 public int intValue() { return 0; } 1434 @Override 1435 public long longValue() { return 0; } 1436 @Override 1437 public float floatValue() { return 0; } 1438 } 1439 """ 1440 ); 1441 } finally { 1442 setCompileOptions(previousOptions); 1443 } 1444 } 1445 1446 @Test 1447 void testAssertUnsetFieldsSMEntry() throws Exception { 1448 String[] previousOptions = getCompileOptions(); 1449 try { 1450 String[] testOptions = { 1451 "--enable-preview", 1452 "-source", Integer.toString(Runtime.version().feature()), 1453 "-XDgenerateAssertUnsetFieldsFrame", 1454 "-XDnoLocalProxyVars", 1455 "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" 1456 }; 1457 setCompileOptions(testOptions); 1458 1459 record Data(String src, int[] expectedFrameTypes, String[][] expectedUnsetFields) {} 1460 for (Data data : List.of( 1461 new Data( 1462 """ 1463 import jdk.internal.vm.annotation.Strict; 1464 class Test { 1465 @Strict 1466 final int x; 1467 @Strict 1468 final int y; 1469 Test(boolean a, boolean b) { 1470 if (a) { // assert_unset_fields {x, y} 1471 x = 1; 1472 if (b) { // assert_unset_fields {y} 1473 y = 1; 1474 } else { // assert_unset_fields {y} 1475 y = 2; 1476 } 1477 } else { // assert_unset_fields {x, y} 1478 x = y = 3; 1479 } 1480 super(); 1481 } 1482 } 1483 """, 1484 // three unset_fields entries, entry type 246, are expected in the stackmap table 1485 new int[] {246, 21, 246, 7, 246, 9}, 1486 // expected fields for each of them: 1487 new String[][] { new String[] { "y:I" }, new String[] { "x:I", "y:I" }, new String[] {} } 1488 ), 1489 new Data( 1490 """ 1491 import jdk.internal.vm.annotation.Strict; 1492 class Test { 1493 @Strict 1494 final int x; 1495 @Strict 1496 final int y; 1497 Test(int n) { 1498 switch(n) { 1499 case 2: 1500 x = y = 2; 1501 break; 1502 default: 1503 x = y = 100; 1504 break; 1505 } 1506 super(); 1507 } 1508 } 1509 """, 1510 // here we expect only one 1511 new int[] {20, 12, 246, 10}, 1512 // stating that no field is unset 1513 new String[][] { new String[] {} } 1514 ) 1515 )) { 1516 File dir = assertOK(true, data.src()); 1517 for (final File fileEntry : dir.listFiles()) { 1518 ClassFile classFile = ClassFile.read(fileEntry); 1519 for (Method method : classFile.methods) { 1520 if (method.getName(classFile.constant_pool).equals("<init>")) { 1521 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1522 StackMapTable_attribute stackMapTable = (StackMapTable_attribute)code.attributes.get("StackMapTable"); 1523 int entryIndex = 0; 1524 Assert.check(data.expectedFrameTypes().length == stackMapTable.entries.length, "unexpected stackmap length"); 1525 int expectedUnsetFieldsIndex = 0; 1526 for (StackMapTable_attribute.stack_map_entry entry : stackMapTable.entries) { 1527 Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.entry_type, "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.entry_type); 1528 if (entry.entry_type == 246) { 1529 StackMapTable_attribute.assert_unset_fields auf = (StackMapTable_attribute.assert_unset_fields)entry; 1530 Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex].length == auf.number_of_unset_fields); 1531 int index = 0; 1532 for (int i : auf.unset_fields) { 1533 String unsetStr = classFile.constant_pool.getNameAndTypeInfo(i).getName() + ":" + 1534 classFile.constant_pool.getNameAndTypeInfo(i).getType(); 1535 Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex][index++].equals(unsetStr)); 1536 } 1537 expectedUnsetFieldsIndex++; 1538 } 1539 } 1540 } 1541 } 1542 } 1543 } 1544 } finally { 1545 setCompileOptions(previousOptions); 1546 } 1547 } 1548 1549 private File findClassFileOrFail(File dir, String name) { 1550 for (final File fileEntry : dir.listFiles()) { 1551 if (fileEntry.getName().equals(name)) { 1552 return fileEntry; 1553 } 1554 } 1555 throw new AssertionError("file not found"); 1556 } 1557 1558 private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass, int numberOfAttributes) { 1559 int attrCount = 0; 1560 Attribute result = null; 1561 for (Attribute attribute : attributes) { 1562 if (attribute.getClass() == attrClass) { 1563 attrCount++; 1564 if (result == null) { 1565 result = attribute; 1566 } 1567 } 1568 } 1569 if (attrCount == 0) throw new AssertionError("attribute not found"); 1570 if (attrCount != numberOfAttributes) throw new AssertionError("incorrect number of attributes found"); 1571 return result; 1572 } 1573 1574 private void checkAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) { 1575 for (Attribute attribute : attributes) { 1576 if (attribute.getClass() == attrClass) { 1577 throw new AssertionError("attribute found"); 1578 } 1579 } 1580 } 1581 1582 @Test 1583 void testLocalProxyVars() throws Exception { 1584 String[] previousOptions = getCompileOptions(); 1585 try { 1586 String[] testOptions = { 1587 "--enable-preview", 1588 "-source", Integer.toString(Runtime.version().feature()), 1589 "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" 1590 }; 1591 setCompileOptions(testOptions); 1592 String[] sources = new String[] { 1593 """ 1594 value class Test { 1595 int i; 1596 int j; 1597 Test() {// javac should generate a proxy local var for `i` 1598 i = 1; 1599 j = i; // as here `i` is being read during the early construction phase, use the local var instead 1600 super(); 1601 System.err.println(i); 1602 } 1603 } 1604 """, 1605 """ 1606 import jdk.internal.vm.annotation.Strict; 1607 class Test { 1608 @Strict 1609 int i; 1610 @Strict 1611 int j; 1612 Test() { 1613 i = 1; 1614 j = i; 1615 super(); 1616 System.err.println(i); 1617 } 1618 } 1619 """ 1620 }; 1621 for (String source : sources) { 1622 File dir = assertOK(true, source); 1623 File fileEntry = dir.listFiles()[0]; 1624 ClassFile classFile = ClassFile.read(fileEntry); 1625 String expectedCodeSequence = "iconst_1,istore_1,aload_0,iload_1,putfield,aload_0,iload_1,putfield," + 1626 "aload_0,invokespecial,getstatic,aload_0,getfield,invokevirtual,return,"; 1627 for (Method method : classFile.methods) { 1628 if (method.getName(classFile.constant_pool).equals("<init>")) { 1629 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1630 String foundCodeSequence = ""; 1631 for (Instruction inst: code.getInstructions()) { 1632 foundCodeSequence += inst.getMnemonic() + ","; 1633 } 1634 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 1635 } 1636 } 1637 } 1638 } finally { 1639 setCompileOptions(previousOptions); 1640 } 1641 } 1642 }