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 setCompileOptions(PREVIEW_OPTIONS_PLUS_VM_ANNO); 751 for (String source : List.of( 752 """ 753 import jdk.internal.vm.annotation.Strict; 754 class Test { 755 @Strict int i = 0; 756 } 757 """, 758 """ 759 import jdk.internal.vm.annotation.Strict; 760 class Test { 761 @Strict final int i = 0; 762 } 763 """ 764 )) { 765 File dir = assertOK(true, source); 766 for (final File fileEntry : dir.listFiles()) { 767 var classFile = ClassFile.of().parse(fileEntry.toPath()); 768 Assert.check(classFile.flags().has(AccessFlag.IDENTITY)); 769 for (var field : classFile.fields()) { 770 if (!field.flags().has(AccessFlag.STATIC)) { 771 Set<AccessFlag> fieldFlags = field.flags().flags(); 772 Assert.check(fieldFlags.contains(AccessFlag.STRICT_INIT)); 773 } 774 } 775 } 776 } 777 } finally { 778 setCompileOptions(previousOptions); 779 } 780 } 781 782 @Test 783 void testConstruction() throws Exception { 784 record Data(String src, boolean isRecord) { 785 Data(String src) { 786 this(src, false); 787 } 788 } 789 for (Data data : List.of( 790 new Data( 791 """ 792 value class Test { 793 int i = 100; 794 } 795 """), 796 new Data( 797 """ 798 value class Test { 799 int i; 800 Test() { 801 i = 100; 802 } 803 } 804 """), 805 new Data( 806 """ 807 value class Test { 808 int i; 809 Test() { 810 i = 100; 811 super(); 812 } 813 } 814 """), 815 new Data( 816 """ 817 value class Test { 818 int i; 819 Test() { 820 this.i = 100; 821 super(); 822 } 823 } 824 """), 825 new Data( 826 """ 827 value record Test(int i) {} 828 """, true) 829 )) { 830 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return"; 831 String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return"; 832 File dir = assertOK(true, data.src); 833 for (final File fileEntry : dir.listFiles()) { 834 var classFile = ClassFile.of().parse(fileEntry.toPath()); 835 classFile.methods().stream() 836 .filter(mm -> mm.methodName().equalsString(ConstantDescs.INIT_NAME)) 837 .map(mm -> mm.findAttribute(Attributes.code()).orElseThrow()) 838 .forEach(code -> { 839 List<String> mnemonics = new ArrayList<>(); 840 for (var coe : code) { 841 if (coe instanceof Instruction inst) { 842 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); 843 } 844 } 845 var foundCodeSequence = String.join(",", mnemonics); 846 if (!data.isRecord) { 847 Assert.check(expectedCodeSequence.equals(foundCodeSequence)); 848 } else { 849 Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence)); 850 } 851 }); 852 } 853 } 854 855 String source = 856 """ 857 value class Test { 858 int i = 100; 859 int j = 0; 860 { 861 System.out.println(j); 862 } 863 } 864 """; 865 { 866 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,iconst_0,putfield,aload_0,invokespecial,getstatic,iconst_0,invokevirtual,return"; 867 File dir = assertOK(true, source); 868 for (final File fileEntry : dir.listFiles()) { 869 var classFile = ClassFile.of().parse(fileEntry.toPath()); 870 classFile.methods().stream() 871 .filter(mm -> mm.methodName().equalsString(ConstantDescs.INIT_NAME)) 872 .map(mm -> mm.findAttribute(Attributes.code()).orElseThrow()) 873 .forEach(code -> { 874 List<String> mnemonics = new ArrayList<>(); 875 for (var coe : code) { 876 if (coe instanceof Instruction inst) { 877 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); 878 } 879 } 880 var foundCodeSequence = String.join(",", mnemonics); 881 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 882 }); 883 } 884 } 885 886 assertFail("compiler.err.cant.ref.before.ctor.called", 887 """ 888 value class Test { 889 Test() { 890 m(); 891 } 892 void m() {} 893 } 894 """ 895 ); 896 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 897 """ 898 value class Test { 899 int i; 900 Test() { 901 super(); 902 this.i = i; 903 } 904 } 905 """ 906 ); 907 assertOK( 908 """ 909 class UnrelatedThisLeak { 910 value class V { 911 int f; 912 V() { 913 UnrelatedThisLeak x = UnrelatedThisLeak.this; 914 f = 10; 915 x = UnrelatedThisLeak.this; 916 } 917 } 918 } 919 """ 920 ); 921 assertFail("compiler.err.cant.ref.before.ctor.called", 922 """ 923 value class Test { 924 Test t = null; 925 Runnable r = () -> { System.err.println(t); }; // cant reference `t` from a lambda expression in the prologue 926 } 927 """ 928 ); 929 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 930 """ 931 value class Test { 932 int f; 933 { 934 f = 1; 935 } 936 } 937 """ 938 ); 939 assertOK( 940 """ 941 value class V { 942 int x; 943 int y = x + 1; // allowed 944 V() { 945 x = 12; 946 // super(); 947 } 948 } 949 """ 950 ); 951 assertFail("compiler.err.var.might.already.be.assigned", 952 """ 953 value class V2 { 954 int x; 955 V2() { this(x = 3); } // error 956 V2(int i) { x = 4; } 957 } 958 """ 959 ); 960 assertOK( 961 """ 962 abstract value class AV1 { 963 AV1(int i) {} 964 } 965 value class V3 extends AV1 { 966 int x; 967 V3() { 968 super(x = 3); // ok 969 } 970 } 971 """ 972 ); 973 assertOK( 974 """ 975 value class V4 { 976 int x; 977 int y = x + 1; 978 V4() { 979 x = 12; 980 } 981 V4(int i) { 982 x = i; 983 } 984 } 985 """ 986 ); 987 assertOK( 988 """ 989 value class V { 990 final int x = "abc".length(); 991 { System.out.println(x); } 992 } 993 """ 994 ); 995 assertFail("compiler.err.illegal.forward.ref", 996 """ 997 value class V { 998 { System.out.println(x); } 999 final int x = "abc".length(); 1000 } 1001 """ 1002 ); 1003 assertOK( 1004 """ 1005 value class V { 1006 int x = "abc".length(); 1007 int y = x; 1008 } 1009 """ 1010 ); 1011 assertOK( 1012 """ 1013 value class V { 1014 int x = "abc".length(); 1015 { int y = x; } 1016 } 1017 """ 1018 ); 1019 assertOK( 1020 """ 1021 value class V { 1022 String s1; 1023 { System.out.println(s1); } 1024 String s2 = (s1 = "abc"); 1025 } 1026 """ 1027 ); 1028 1029 String[] previousOptions = getCompileOptions(); 1030 try { 1031 setCompileOptions(PREVIEW_OPTIONS_PLUS_VM_ANNO); 1032 String[] sources = new String[]{ 1033 """ 1034 import jdk.internal.vm.annotation.Strict; 1035 class Test { 1036 static value class IValue { 1037 int i = 0; 1038 } 1039 @Strict 1040 final IValue val = new IValue(); 1041 } 1042 """, 1043 """ 1044 import jdk.internal.vm.annotation.Strict; 1045 class Test { 1046 static value class IValue { 1047 int i = 0; 1048 } 1049 @Strict 1050 final IValue val; 1051 Test() { 1052 val = new IValue(); 1053 } 1054 } 1055 """ 1056 }; 1057 var expectedCodeSequence = "aload_0,new,dup,invokespecial,putfield,aload_0,invokespecial,return"; 1058 for (String src : sources) { 1059 File dir = assertOK(true, src); 1060 for (final File fileEntry : dir.listFiles()) { 1061 var classFile = ClassFile.of().parse(fileEntry.toPath()); 1062 if (classFile.thisClass().name().equalsString("Test")) { 1063 for (var method : classFile.methods()) { 1064 if (method.methodName().equalsString("<init>")) { 1065 var code = method.findAttribute(Attributes.code()).orElseThrow(); 1066 List<String> mnemonics = new ArrayList<>(); 1067 for (var coe : code) { 1068 if (coe instanceof Instruction inst) { 1069 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); 1070 } 1071 } 1072 var foundCodeSequence = String.join(",", mnemonics); 1073 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 1074 } 1075 } 1076 } 1077 } 1078 } 1079 1080 assertFail("compiler.err.cant.ref.before.ctor.called", 1081 """ 1082 import jdk.internal.vm.annotation.NullRestricted; 1083 import jdk.internal.vm.annotation.Strict; 1084 class StrictNR { 1085 static value class IValue { 1086 int i = 0; 1087 } 1088 value class SValue { 1089 short s = 0; 1090 } 1091 @Strict 1092 @NullRestricted 1093 IValue val = new IValue(); 1094 @Strict 1095 @NullRestricted 1096 final IValue val2; 1097 @Strict 1098 @NullRestricted 1099 SValue val3 = new SValue(); 1100 } 1101 """ 1102 ); 1103 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 1104 """ 1105 import jdk.internal.vm.annotation.Strict; 1106 class Test { 1107 @Strict int i; 1108 } 1109 """ 1110 ); 1111 assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", 1112 """ 1113 import jdk.internal.vm.annotation.Strict; 1114 class Test { 1115 @Strict int i; 1116 Test() { 1117 super(); 1118 i = 0; 1119 } 1120 } 1121 """ 1122 ); 1123 assertFail("compiler.err.cant.ref.before.ctor.called", 1124 """ 1125 import jdk.internal.vm.annotation.NullRestricted; 1126 import jdk.internal.vm.annotation.Strict; 1127 class StrictNR { 1128 static value class IValue { 1129 int i = 0; 1130 } 1131 value class SValue { 1132 short s = 0; 1133 } 1134 @Strict 1135 @NullRestricted 1136 IValue val = new IValue(); 1137 @Strict 1138 @NullRestricted 1139 SValue val4; 1140 public StrictNR() { 1141 val4 = new SValue(); 1142 } 1143 } 1144 """ 1145 ); 1146 } finally { 1147 setCompileOptions(previousOptions); 1148 } 1149 1150 source = 1151 """ 1152 value class V { 1153 int i = 1; 1154 int y; 1155 V() { 1156 y = 2; 1157 } 1158 } 1159 """; 1160 { 1161 File dir = assertOK(true, source); 1162 File fileEntry = dir.listFiles()[0]; 1163 var expectedCodeSequence = "putfield i,putfield y"; 1164 var classFile = ClassFile.of().parse(fileEntry.toPath()); 1165 for (var method : classFile.methods()) { 1166 if (method.methodName().equalsString("<init>")) { 1167 var code = method.findAttribute(Attributes.code()).orElseThrow(); 1168 List<String> mnemonics = new ArrayList<>(); 1169 for (var coe : code) { 1170 if (coe instanceof FieldInstruction inst && inst.opcode() == Opcode.PUTFIELD) { 1171 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT) + " " + inst.name()); 1172 } 1173 } 1174 var foundCodeSequence = String.join(",", mnemonics); 1175 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 1176 } 1177 } 1178 } 1179 } 1180 1181 @Test 1182 void testThisCallingConstructor() throws Exception { 1183 // make sure that this() calling constructors doesn't initialize final fields 1184 String source = 1185 """ 1186 value class Test { 1187 int i; 1188 Test() { 1189 this(0); 1190 } 1191 1192 Test(int i) { 1193 this.i = i; 1194 } 1195 } 1196 """; 1197 File dir = assertOK(true, source); 1198 File fileEntry = dir.listFiles()[0]; 1199 String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return"; 1200 String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return"; 1201 var classFile = ClassFile.of().parse(fileEntry.toPath()); 1202 for (var method : classFile.methods()) { 1203 if (method.methodName().equalsString("<init>")) { 1204 var code = method.findAttribute(Attributes.code()).orElseThrow(); 1205 List<String> mnemonics = new ArrayList<>(); 1206 for (var coe : code) { 1207 if (coe instanceof Instruction inst) { 1208 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); 1209 } 1210 } 1211 var foundCodeSequence = String.join(",", mnemonics); 1212 var expected = method.methodTypeSymbol().parameterCount() == 0 ? 1213 expectedCodeSequenceThisCallingConst : expectedCodeSequenceNonThisCallingConst; 1214 Assert.check(expected.equals(foundCodeSequence), "found " + foundCodeSequence); 1215 } 1216 } 1217 } 1218 1219 @Test 1220 void testSelectors() throws Exception { 1221 assertOK( 1222 """ 1223 value class V { 1224 void selector() { 1225 Class<?> c = int.class; 1226 } 1227 } 1228 """ 1229 ); 1230 assertFail("compiler.err.expected", 1231 """ 1232 value class V { 1233 void selector() { 1234 int i = int.some_selector; 1235 } 1236 } 1237 """ 1238 ); 1239 } 1240 1241 @Test 1242 void testNullAssigment() throws Exception { 1243 assertOK( 1244 """ 1245 value final class V { 1246 final int x = 10; 1247 1248 value final class X { 1249 final V v; 1250 final V v2; 1251 1252 X() { 1253 this.v = null; 1254 this.v2 = null; 1255 } 1256 1257 X(V v) { 1258 this.v = v; 1259 this.v2 = v; 1260 } 1261 1262 V foo(X x) { 1263 x = new X(null); // OK 1264 return x.v; 1265 } 1266 } 1267 V bar(X x) { 1268 x = new X(null); // OK 1269 return x.v; 1270 } 1271 1272 class Y { 1273 V v; 1274 V [] va = { null }; // OK: array initialization 1275 V [] va2 = new V[] { null }; // OK: array initialization 1276 void ooo(X x) { 1277 x = new X(null); // OK 1278 v = null; // legal assignment. 1279 va[0] = null; // legal. 1280 va = new V[] { null }; // legal 1281 } 1282 } 1283 } 1284 """ 1285 ); 1286 } 1287 1288 @Test 1289 void testSerializationWarnings() throws Exception { 1290 String[] previousOptions = getCompileOptions(); 1291 try { 1292 setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source", 1293 Integer.toString(Runtime.version().feature())}); 1294 assertOK( 1295 """ 1296 import java.io.*; 1297 abstract value class AVC implements Serializable {} 1298 """); 1299 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", 1300 """ 1301 import java.io.*; 1302 value class VC implements Serializable { 1303 private static final long serialVersionUID = 0; 1304 } 1305 """); 1306 assertOK( 1307 """ 1308 import java.io.*; 1309 class C implements Serializable { 1310 private static final long serialVersionUID = 0; 1311 } 1312 """); 1313 assertOK( 1314 """ 1315 import java.io.*; 1316 abstract value class Super implements Serializable { 1317 private static final long serialVersionUID = 0; 1318 protected Object writeReplace() throws ObjectStreamException { 1319 return null; 1320 } 1321 } 1322 value class ValueSerializable extends Super { 1323 private static final long serialVersionUID = 1; 1324 } 1325 """); 1326 assertOK( 1327 """ 1328 import java.io.*; 1329 abstract value class Super implements Serializable { 1330 private static final long serialVersionUID = 0; 1331 Object writeReplace() throws ObjectStreamException { 1332 return null; 1333 } 1334 } 1335 value class ValueSerializable extends Super { 1336 private static final long serialVersionUID = 1; 1337 } 1338 """); 1339 assertOK( 1340 """ 1341 import java.io.*; 1342 abstract value class Super implements Serializable { 1343 private static final long serialVersionUID = 0; 1344 public Object writeReplace() throws ObjectStreamException { 1345 return null; 1346 } 1347 } 1348 value class ValueSerializable extends Super { 1349 private static final long serialVersionUID = 1; 1350 } 1351 """); 1352 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", 1353 """ 1354 import java.io.*; 1355 abstract value class Super implements Serializable { 1356 private static final long serialVersionUID = 0; 1357 private Object writeReplace() throws ObjectStreamException { 1358 return null; 1359 } 1360 } 1361 value class ValueSerializable extends Super { 1362 private static final long serialVersionUID = 1; 1363 } 1364 """); 1365 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2", 1366 """ 1367 import java.io.*; 1368 abstract value class Super implements Serializable { 1369 private static final long serialVersionUID = 0; 1370 private Object writeReplace() throws ObjectStreamException { 1371 return null; 1372 } 1373 } 1374 class Serializable1 extends Super { 1375 private static final long serialVersionUID = 1; 1376 } 1377 class Serializable2 extends Serializable1 { 1378 private static final long serialVersionUID = 1; 1379 } 1380 """); 1381 assertOK( 1382 """ 1383 import java.io.*; 1384 abstract value class Super implements Serializable { 1385 private static final long serialVersionUID = 0; 1386 Object writeReplace() throws ObjectStreamException { 1387 return null; 1388 } 1389 } 1390 class ValueSerializable extends Super { 1391 private static final long serialVersionUID = 1; 1392 } 1393 """); 1394 assertOK( 1395 """ 1396 import java.io.*; 1397 abstract value class Super implements Serializable { 1398 private static final long serialVersionUID = 0; 1399 public Object writeReplace() throws ObjectStreamException { 1400 return null; 1401 } 1402 } 1403 class ValueSerializable extends Super { 1404 private static final long serialVersionUID = 1; 1405 } 1406 """); 1407 assertOK( 1408 """ 1409 import java.io.*; 1410 abstract value class Super implements Serializable { 1411 private static final long serialVersionUID = 0; 1412 protected Object writeReplace() throws ObjectStreamException { 1413 return null; 1414 } 1415 } 1416 class ValueSerializable extends Super { 1417 private static final long serialVersionUID = 1; 1418 } 1419 """); 1420 assertOK( 1421 """ 1422 import java.io.*; 1423 value record ValueRecord() implements Serializable { 1424 private static final long serialVersionUID = 1; 1425 } 1426 """); 1427 assertOK( 1428 // Number is a special case, no warning for identity classes extending it 1429 """ 1430 class NumberSubClass extends Number { 1431 private static final long serialVersionUID = 0L; 1432 @Override 1433 public double doubleValue() { return 0; } 1434 @Override 1435 public int intValue() { return 0; } 1436 @Override 1437 public long longValue() { return 0; } 1438 @Override 1439 public float floatValue() { return 0; } 1440 } 1441 """ 1442 ); 1443 } finally { 1444 setCompileOptions(previousOptions); 1445 } 1446 } 1447 1448 @Test 1449 void testAssertUnsetFieldsSMEntry() throws Exception { 1450 String[] previousOptions = getCompileOptions(); 1451 try { 1452 String[] testOptions = { 1453 "--enable-preview", 1454 "-source", Integer.toString(Runtime.version().feature()), 1455 "-XDnoLocalProxyVars", 1456 "-XDdebug.stackmap", 1457 "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" 1458 }; 1459 setCompileOptions(testOptions); 1460 1461 record Data(String src, int[] expectedFrameTypes, String[][] expectedUnsetFields) {} 1462 for (Data data : List.of( 1463 new Data( 1464 """ 1465 import jdk.internal.vm.annotation.Strict; 1466 class Test { 1467 @Strict 1468 final int x; 1469 @Strict 1470 final int y; 1471 Test(boolean a, boolean b) { 1472 if (a) { // early_larval {x, y} 1473 x = 1; 1474 if (b) { // early_larval {y} 1475 y = 1; 1476 } else { // early_larval {y} 1477 y = 2; 1478 } 1479 } else { // early_larval {x, y} 1480 x = y = 3; 1481 } 1482 super(); 1483 } 1484 } 1485 """, 1486 // three unset_fields entries, entry type 246, are expected in the stackmap table 1487 new int[] {246, 246, 246}, 1488 // expected fields for each of them: 1489 new String[][] { new String[] { "y:I" }, new String[] { "x:I", "y:I" }, new String[] {} } 1490 ), 1491 new Data( 1492 """ 1493 import jdk.internal.vm.annotation.Strict; 1494 class Test { 1495 @Strict 1496 final int x; 1497 @Strict 1498 final int y; 1499 Test(int n) { 1500 switch(n) { 1501 case 2: 1502 x = y = 2; 1503 break; 1504 default: 1505 x = y = 100; 1506 break; 1507 } 1508 super(); 1509 } 1510 } 1511 """, 1512 // here we expect only one 1513 new int[] {20, 12, 246}, 1514 // stating that no field is unset 1515 new String[][] { new String[] {} } 1516 ), 1517 new Data( 1518 """ 1519 import jdk.internal.vm.annotation.Strict; 1520 class Test { 1521 @Strict 1522 final int x; 1523 @Strict 1524 final int y; 1525 Test(int n) { 1526 if (n % 3 == 0) { 1527 x = n / 3; 1528 } else { // no unset change 1529 x = n + 2; 1530 } // early_larval {y} 1531 y = n >>> 3; 1532 super(); 1533 if ((char) n != n) { 1534 n -= 5; 1535 } // no uninitializedThis - automatically cleared unsets 1536 Math.abs(n); 1537 } 1538 } 1539 """, 1540 // here we expect only one, none for the post-larval frame 1541 new int[] {16, 246, 255}, 1542 // stating that y is unset when if-else finishes 1543 new String[][] { new String[] {"y:I"} } 1544 ) 1545 )) { 1546 File dir = assertOK(true, data.src()); 1547 for (final File fileEntry : dir.listFiles()) { 1548 var classFile = ClassFile.of().parse(fileEntry.toPath()); 1549 for (var method : classFile.methods()) { 1550 if (method.methodName().equalsString(ConstantDescs.INIT_NAME)) { 1551 var code = method.findAttribute(Attributes.code()).orElseThrow(); 1552 var stackMapTable = code.findAttribute(Attributes.stackMapTable()).orElseThrow(); 1553 Assert.check(data.expectedFrameTypes().length == stackMapTable.entries().size(), "unexpected stackmap length"); 1554 int entryIndex = 0; 1555 int expectedUnsetFieldsIndex = 0; 1556 for (var entry : stackMapTable.entries()) { 1557 Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.frameType(), "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.frameType()); 1558 if (entry.frameType() == 246) { 1559 Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex].length == entry.unsetFields().size()); 1560 int index = 0; 1561 for (var nat : entry.unsetFields()) { 1562 String unsetStr = nat.name() + ":" + nat.type(); 1563 Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex][index++].equals(unsetStr)); 1564 } 1565 expectedUnsetFieldsIndex++; 1566 } 1567 } 1568 } 1569 } 1570 } 1571 } 1572 } finally { 1573 setCompileOptions(previousOptions); 1574 } 1575 } 1576 1577 @Test 1578 void testLocalProxyVars() throws Exception { 1579 String[] previousOptions = getCompileOptions(); 1580 try { 1581 String[] testOptions = { 1582 "--enable-preview", 1583 "-source", Integer.toString(Runtime.version().feature()), 1584 "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" 1585 }; 1586 setCompileOptions(testOptions); 1587 String[] sources = new String[] { 1588 """ 1589 value class Test { 1590 int i; 1591 int j; 1592 Test() {// javac should generate a proxy local var for `i` 1593 i = 1; 1594 j = i; // as here `i` is being read during the early construction phase, use the local var instead 1595 super(); 1596 System.err.println(i); 1597 } 1598 } 1599 """, 1600 """ 1601 import jdk.internal.vm.annotation.Strict; 1602 class Test { 1603 @Strict 1604 int i; 1605 @Strict 1606 int j; 1607 Test() { 1608 i = 1; 1609 j = i; 1610 super(); 1611 System.err.println(i); 1612 } 1613 } 1614 """ 1615 }; 1616 for (String source : sources) { 1617 File dir = assertOK(true, source); 1618 File fileEntry = dir.listFiles()[0]; 1619 String expectedCodeSequence = "iconst_1,istore_1,aload_0,iload_1,putfield,aload_0,iload_1,putfield," + 1620 "aload_0,invokespecial,getstatic,aload_0,getfield,invokevirtual,return"; 1621 var classFile = ClassFile.of().parse(fileEntry.toPath()); 1622 for (var method : classFile.methods()) { 1623 if (method.methodName().equalsString("<init>")) { 1624 var code = method.findAttribute(Attributes.code()).orElseThrow(); 1625 List<String> mnemonics = new ArrayList<>(); 1626 for (var coe : code) { 1627 if (coe instanceof Instruction inst) { 1628 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); 1629 } 1630 } 1631 var foundCodeSequence = String.join(",", mnemonics); 1632 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 1633 } 1634 } 1635 } 1636 } finally { 1637 setCompileOptions(previousOptions); 1638 } 1639 } 1640 }