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