1 /* 2 * Copyright (c) 2022, 2024, 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 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.Field; 58 import com.sun.tools.classfile.Instruction; 59 import com.sun.tools.classfile.Method; 60 61 import com.sun.tools.javac.code.Flags; 62 63 import org.junit.jupiter.api.Test; 64 import tools.javac.combo.CompilationTestCase; 65 import toolbox.ToolBox; 66 67 class ValueObjectCompilationTests extends CompilationTestCase { 68 69 private static String[] PREVIEW_OPTIONS = {"--enable-preview", "-source", 70 Integer.toString(Runtime.version().feature())}; 71 72 public ValueObjectCompilationTests() { 73 setDefaultFilename("ValueObjectsTest.java"); 74 setCompileOptions(PREVIEW_OPTIONS); 75 } 76 77 @Test 78 void testValueModifierConstraints() { 79 assertFail("compiler.err.illegal.combination.of.modifiers", 80 """ 81 value @interface IA {} 82 """); 83 assertFail("compiler.err.illegal.combination.of.modifiers", 84 """ 85 value interface I {} 86 """); 87 assertFail("compiler.err.mod.not.allowed.here", 88 """ 89 class Test { 90 value int x; 91 } 92 """); 93 assertFail("compiler.err.mod.not.allowed.here", 94 """ 95 class Test { 96 value int foo(); 97 } 98 """); 99 assertFail("compiler.err.mod.not.allowed.here", 100 """ 101 value enum Enum {} 102 """); 103 } 104 105 record TestData(String message, String snippet, String[] compilerOptions, boolean testLocalToo) { 106 TestData(String snippet) { 107 this("", snippet, null, true); 108 } 109 110 TestData(String snippet, boolean testLocalToo) { 111 this("", snippet, null, testLocalToo); 112 } 113 114 TestData(String message, String snippet) { 115 this(message, snippet, null, true); 116 } 117 118 TestData(String snippet, String[] compilerOptions) { 119 this("", snippet, compilerOptions, true); 120 } 121 122 TestData(String message, String snippet, String[] compilerOptions) { 123 this(message, snippet, compilerOptions, true); 124 } 125 126 TestData(String message, String snippet, boolean testLocalToo) { 127 this(message, snippet, null, testLocalToo); 128 } 129 } 130 131 private void testHelper(List<TestData> testDataList) { 132 String ttt = 133 """ 134 class TTT { 135 void m() { 136 #LOCAL 137 } 138 } 139 """; 140 for (TestData td : testDataList) { 141 String localSnippet = ttt.replace("#LOCAL", td.snippet); 142 String[] previousOptions = getCompileOptions(); 143 try { 144 if (td.compilerOptions != null) { 145 setCompileOptions(td.compilerOptions); 146 } 147 if (td.message == "") { 148 assertOK(td.snippet); 149 if (td.testLocalToo) { 150 assertOK(localSnippet); 151 } 152 } else if (td.message.startsWith("compiler.err")) { 153 assertFail(td.message, td.snippet); 154 if (td.testLocalToo) { 155 assertFail(td.message, localSnippet); 156 } 157 } else { 158 assertOKWithWarning(td.message, td.snippet); 159 if (td.testLocalToo) { 160 assertOKWithWarning(td.message, localSnippet); 161 } 162 } 163 } finally { 164 setCompileOptions(previousOptions); 165 } 166 } 167 } 168 169 private static final List<TestData> superClassConstraints = List.of( 170 new TestData( 171 "compiler.err.super.class.method.cannot.be.synchronized", 172 """ 173 abstract class I { 174 synchronized void foo() {} 175 } 176 value class V extends I {} 177 """ 178 ), 179 new TestData( 180 "compiler.err.concrete.supertype.for.value.class", 181 """ 182 class ConcreteSuperType { 183 static abstract value class V extends ConcreteSuperType {} // Error: concrete super. 184 } 185 """ 186 ), 187 new TestData( 188 """ 189 value record Point(int x, int y) {} 190 """ 191 ), 192 new TestData( 193 """ 194 value class One extends Number { 195 public int intValue() { return 0; } 196 public long longValue() { return 0; } 197 public float floatValue() { return 0; } 198 public double doubleValue() { return 0; } 199 } 200 """ 201 ), 202 new TestData( 203 """ 204 value class V extends Object {} 205 """ 206 ), 207 new TestData( 208 "compiler.err.value.type.has.identity.super.type", 209 """ 210 abstract class A {} 211 value class V extends A {} 212 """ 213 ) 214 ); 215 216 @Test 217 void testSuperClassConstraints() { 218 testHelper(superClassConstraints); 219 } 220 221 @Test 222 void testRepeatedModifiers() { 223 assertFail("compiler.err.repeated.modifier", "value value class ValueTest {}"); 224 } 225 226 @Test 227 void testParserTest() { 228 assertOK( 229 """ 230 value class Substring implements CharSequence { 231 private String str; 232 private int start; 233 private int end; 234 235 public Substring(String str, int start, int end) { 236 checkBounds(start, end, str.length()); 237 this.str = str; 238 this.start = start; 239 this.end = end; 240 } 241 242 public int length() { 243 return end - start; 244 } 245 246 public char charAt(int i) { 247 checkBounds(0, i, length()); 248 return str.charAt(start + i); 249 } 250 251 public Substring subSequence(int s, int e) { 252 checkBounds(s, e, length()); 253 return new Substring(str, start + s, start + e); 254 } 255 256 public String toString() { 257 return str.substring(start, end); 258 } 259 260 private static void checkBounds(int start, int end, int length) { 261 if (start < 0 || end < start || length < end) 262 throw new IndexOutOfBoundsException(); 263 } 264 } 265 """ 266 ); 267 } 268 269 private static final List<TestData> semanticsViolations = List.of( 270 new TestData( 271 "compiler.err.cant.inherit.from.final", 272 """ 273 value class Base {} 274 class Subclass extends Base {} 275 """ 276 ), 277 new TestData( 278 "compiler.err.cant.assign.val.to.var", 279 """ 280 value class Point { 281 int x = 10; 282 int y; 283 Point (int x, int y) { 284 this.x = x; // Error, final field 'x' is already assigned to. 285 this.y = y; // OK. 286 } 287 } 288 """ 289 ), 290 new TestData( 291 "compiler.err.cant.assign.val.to.var", 292 """ 293 value class Point { 294 int x; 295 int y; 296 Point (int x, int y) { 297 this.x = x; 298 this.y = y; 299 } 300 void foo(Point p) { 301 this.y = p.y; // Error, y is final and can't be written outside of ctor. 302 } 303 } 304 """ 305 ), 306 new TestData( 307 "compiler.err.cant.assign.val.to.var", 308 """ 309 abstract value class Point { 310 int x; 311 int y; 312 Point (int x, int y) { 313 this.x = x; 314 this.y = y; 315 } 316 void foo(Point p) { 317 this.y = p.y; // Error, y is final and can't be written outside of ctor. 318 } 319 } 320 """ 321 ), 322 new TestData( 323 "compiler.err.var.might.not.have.been.initialized", 324 """ 325 value class Point { 326 int x; 327 int y; 328 Point (int x, int y) { 329 this.x = x; 330 // y hasn't been initialized 331 } 332 } 333 """ 334 ), 335 new TestData( 336 "compiler.err.mod.not.allowed.here", 337 """ 338 abstract value class V { 339 synchronized void foo() { 340 // Error, abstract value class may not declare a synchronized instance method. 341 } 342 } 343 """ 344 ), 345 new TestData( 346 """ 347 abstract value class V { 348 static synchronized void foo() {} // OK static 349 } 350 """ 351 ), 352 new TestData( 353 "compiler.err.mod.not.allowed.here", 354 """ 355 value class V { 356 synchronized void foo() {} 357 } 358 """ 359 ), 360 new TestData( 361 """ 362 value class V { 363 synchronized static void soo() {} // OK static 364 } 365 """ 366 ), 367 new TestData( 368 "compiler.err.type.found.req", 369 """ 370 value class V { 371 { synchronized(this) {} } 372 } 373 """ 374 ), 375 new TestData( 376 "compiler.err.mod.not.allowed.here", 377 """ 378 value record R() { 379 synchronized void foo() { } // Error; 380 synchronized static void soo() {} // OK. 381 } 382 """ 383 ), 384 new TestData( 385 "compiler.err.cant.ref.before.ctor.called", 386 """ 387 value class V { 388 int x; 389 V() { 390 foo(this); // Error. 391 x = 10; 392 } 393 void foo(V v) {} 394 } 395 """ 396 ), 397 new TestData( 398 "compiler.err.cant.ref.before.ctor.called", 399 """ 400 value class V { 401 int x; 402 V() { 403 x = 10; 404 foo(this); // error 405 } 406 void foo(V v) {} 407 } 408 """ 409 ), 410 new TestData( 411 "compiler.err.type.found.req", 412 """ 413 interface I {} 414 interface VI extends I {} 415 class C {} 416 value class VC<T extends VC> { 417 void m(T t) { 418 synchronized(t) {} // error 419 } 420 } 421 """ 422 ), 423 new TestData( 424 "compiler.err.type.found.req", 425 """ 426 interface I {} 427 interface VI extends I {} 428 class C {} 429 value class VC<T extends VC> { 430 void foo(Object o) { 431 synchronized ((VC & I)o) {} // error 432 } 433 } 434 """ 435 ), 436 new TestData( 437 // OK if the value class is abstract 438 """ 439 interface I {} 440 abstract value class VI implements I {} 441 class C {} 442 value class VC<T extends VC> { 443 void bar(Object o) { 444 synchronized ((VI & I)o) {} // error 445 } 446 } 447 """ 448 ), 449 new TestData( 450 "compiler.err.type.found.req", // --enable-preview -source" 451 """ 452 class V { 453 final Integer val = Integer.valueOf(42); 454 void test() { 455 synchronized (val) { // error 456 } 457 } 458 } 459 """ 460 ), 461 new TestData( 462 "compiler.err.type.found.req", // --enable-preview -source" 463 """ 464 import java.time.*; 465 class V { 466 final Duration val = Duration.ZERO; 467 void test() { 468 synchronized (val) { // warn 469 } 470 } 471 } 472 """, 473 false // cant do local as there is an import statement 474 ), 475 new TestData( 476 "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // empty options 477 """ 478 class V { 479 final Integer val = Integer.valueOf(42); 480 void test() { 481 synchronized (val) { // warn 482 } 483 } 484 } 485 """, 486 new String[] {} 487 ), 488 new TestData( 489 "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source 490 """ 491 class V { 492 final Integer val = Integer.valueOf(42); 493 void test() { 494 synchronized (val) { // warn 495 } 496 } 497 } 498 """, 499 new String[] {"--source", Integer.toString(Runtime.version().feature())} 500 ), 501 new TestData( 502 "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source 503 """ 504 class V { 505 final Integer val = Integer.valueOf(42); 506 void test() { 507 synchronized (val) { // warn 508 } 509 } 510 } 511 """, 512 new String[] {"--source", Integer.toString(Runtime.version().feature())} 513 ), 514 new TestData( 515 "compiler.err.illegal.combination.of.modifiers", // --enable-preview -source" 516 """ 517 value class V { 518 volatile int f = 1; 519 } 520 """ 521 ) 522 ); 523 524 @Test 525 void testSemanticsViolations() { 526 testHelper(semanticsViolations); 527 } 528 529 private static final List<TestData> sealedClassesData = List.of( 530 new TestData( 531 """ 532 abstract sealed value class SC {} 533 value class VC extends SC {} 534 """, 535 false // local sealed classes are not allowed 536 ), 537 new TestData( 538 """ 539 abstract sealed interface SI {} 540 value class VC implements SI {} 541 """, 542 false // local sealed classes are not allowed 543 ), 544 new TestData( 545 """ 546 abstract sealed class SC {} 547 final class IC extends SC {} 548 non-sealed class IC2 extends SC {} 549 final class IC3 extends IC2 {} 550 """, 551 false 552 ), 553 new TestData( 554 """ 555 abstract sealed interface SI {} 556 final class IC implements SI {} 557 non-sealed class IC2 implements SI {} 558 final class IC3 extends IC2 {} 559 """, 560 false // local sealed classes are not allowed 561 ), 562 new TestData( 563 "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed", 564 """ 565 abstract sealed value class SC {} 566 non-sealed value class VC extends SC {} 567 """, 568 false 569 ), 570 new TestData( 571 "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed", 572 """ 573 sealed value class SI {} 574 """, 575 false 576 ), 577 new TestData( 578 """ 579 sealed abstract value class SI {} 580 value class V extends SI {} 581 """, 582 false 583 ), 584 new TestData( 585 """ 586 sealed abstract value class SI permits V {} 587 value class V extends SI {} 588 """, 589 false 590 ), 591 new TestData( 592 """ 593 sealed interface I {} 594 non-sealed abstract value class V implements I {} 595 """, 596 false 597 ), 598 new TestData( 599 """ 600 sealed interface I permits V {} 601 non-sealed abstract value class V implements I {} 602 """, 603 false 604 ) 605 ); 606 607 @Test 608 void testInteractionWithSealedClasses() { 609 testHelper(sealedClassesData); 610 } 611 612 @Test 613 void testCheckClassFileFlags() throws Exception { 614 for (String source : List.of( 615 """ 616 interface I {} 617 class Test { 618 I i = new I() {}; 619 } 620 """, 621 """ 622 class C {} 623 class Test { 624 C c = new C() {}; 625 } 626 """, 627 """ 628 class Test { 629 Object o = new Object() {}; 630 } 631 """, 632 """ 633 class Test { 634 abstract class Inner {} 635 } 636 """ 637 )) { 638 File dir = assertOK(true, source); 639 for (final File fileEntry : dir.listFiles()) { 640 if (fileEntry.getName().contains("$")) { 641 ClassFile classFile = ClassFile.read(fileEntry); 642 Assert.check((classFile.access_flags.flags & Flags.ACC_IDENTITY) != 0); 643 } 644 } 645 } 646 647 for (String source : List.of( 648 """ 649 class C {} 650 """, 651 """ 652 abstract class A { 653 int i; 654 } 655 """, 656 """ 657 abstract class A { 658 synchronized void m() {} 659 } 660 """, 661 """ 662 class C { 663 synchronized void m() {} 664 } 665 """, 666 """ 667 abstract class A { 668 int i; 669 { i = 0; } 670 } 671 """, 672 """ 673 abstract class A { 674 A(int i) {} 675 } 676 """, 677 """ 678 enum E {} 679 """, 680 """ 681 record R() {} 682 """ 683 )) { 684 File dir = assertOK(true, source); 685 for (final File fileEntry : dir.listFiles()) { 686 ClassFile classFile = ClassFile.read(fileEntry); 687 Assert.check(classFile.access_flags.is(Flags.ACC_IDENTITY)); 688 } 689 } 690 691 { 692 String source = 693 """ 694 abstract value class A {} 695 value class Sub extends A {} //implicitly final 696 """; 697 File dir = assertOK(true, source); 698 for (final File fileEntry : dir.listFiles()) { 699 ClassFile classFile = ClassFile.read(fileEntry); 700 switch (classFile.getName()) { 701 case "Sub": 702 Assert.check((classFile.access_flags.flags & (Flags.FINAL)) != 0); 703 break; 704 case "A": 705 Assert.check((classFile.access_flags.flags & (Flags.ABSTRACT)) != 0); 706 break; 707 default: 708 throw new AssertionError("you shoulnd't be here"); 709 } 710 } 711 } 712 713 for (String source : List.of( 714 """ 715 value class V { 716 int i = 0; 717 static int j; 718 } 719 """, 720 """ 721 abstract value class A { 722 static int j; 723 } 724 725 value class V extends A { 726 int i = 0; 727 } 728 """ 729 )) { 730 File dir = assertOK(true, source); 731 for (final File fileEntry : dir.listFiles()) { 732 ClassFile classFile = ClassFile.read(fileEntry); 733 for (Field field : classFile.fields) { 734 if (!field.access_flags.is(Flags.STATIC)) { 735 Set<String> fieldFlags = field.access_flags.getFieldFlags(); 736 Assert.check(fieldFlags.size() == 2 && fieldFlags.contains("ACC_FINAL") && fieldFlags.contains("ACC_STRICT")); 737 } 738 } 739 } 740 } 741 742 // testing experimental @Strict annotation 743 String[] previousOptions = getCompileOptions(); 744 try { 745 String[] testOptions = {"--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"}; 746 setCompileOptions(testOptions); 747 for (String source : List.of( 748 """ 749 import jdk.internal.vm.annotation.Strict; 750 class Test { 751 @Strict int i; 752 } 753 """, 754 """ 755 import jdk.internal.vm.annotation.Strict; 756 class Test { 757 @Strict final int i = 0; 758 } 759 """ 760 )) { 761 File dir = assertOK(true, source); 762 for (final File fileEntry : dir.listFiles()) { 763 ClassFile classFile = ClassFile.read(fileEntry); 764 for (Field field : classFile.fields) { 765 if (!field.access_flags.is(Flags.STATIC)) { 766 Set<String> fieldFlags = field.access_flags.getFieldFlags(); 767 Assert.check(fieldFlags.contains("ACC_STRICT")); 768 } 769 } 770 } 771 } 772 } finally { 773 setCompileOptions(previousOptions); 774 } 775 } 776 777 @Test 778 void testConstruction() throws Exception { 779 record Data(String src, boolean isRecord) { 780 Data(String src) { 781 this(src, false); 782 } 783 } 784 for (Data data : List.of( 785 new Data( 786 """ 787 value class Test { 788 int i = 100; 789 } 790 """), 791 new Data( 792 """ 793 value class Test { 794 int i; 795 Test() { 796 i = 100; 797 } 798 } 799 """), 800 new Data( 801 """ 802 value class Test { 803 int i; 804 Test() { 805 i = 100; 806 super(); 807 } 808 } 809 """), 810 new Data( 811 """ 812 value class Test { 813 int i; 814 Test() { 815 this.i = 100; 816 super(); 817 } 818 } 819 """), 820 new Data( 821 """ 822 value record Test(int i) {} 823 """, true) 824 )) { 825 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return,"; 826 String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return,"; 827 File dir = assertOK(true, data.src); 828 for (final File fileEntry : dir.listFiles()) { 829 ClassFile classFile = ClassFile.read(fileEntry); 830 for (Method method : classFile.methods) { 831 if (method.getName(classFile.constant_pool).equals("<init>")) { 832 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 833 String foundCodeSequence = ""; 834 for (Instruction inst: code.getInstructions()) { 835 foundCodeSequence += inst.getMnemonic() + ","; 836 } 837 if (!data.isRecord) { 838 Assert.check(expectedCodeSequence.equals(foundCodeSequence)); 839 } else { 840 Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence)); 841 } 842 } 843 } 844 } 845 } 846 847 String source = 848 """ 849 value class Test { 850 int i = 100; 851 int j = 0; 852 { 853 System.out.println(j); 854 } 855 } 856 """; 857 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,iconst_0,putfield,aload_0,invokespecial,getstatic,iconst_0,invokevirtual,return,"; 858 File dir = assertOK(true, source); 859 for (final File fileEntry : dir.listFiles()) { 860 ClassFile classFile = ClassFile.read(fileEntry); 861 for (Method method : classFile.methods) { 862 if (method.getName(classFile.constant_pool).equals("<init>")) { 863 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 864 String foundCodeSequence = ""; 865 for (Instruction inst: code.getInstructions()) { 866 foundCodeSequence += inst.getMnemonic() + ","; 867 } 868 Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); 869 } 870 } 871 } 872 873 assertFail("compiler.err.cant.ref.before.ctor.called", 874 """ 875 value class Test { 876 Test() { 877 m(); 878 } 879 void m() {} 880 } 881 """ 882 ); 883 assertFail("compiler.err.cant.ref.after.ctor.called", 884 """ 885 value class Test { 886 int i; 887 Test() { 888 super(); 889 this.i = i; 890 } 891 } 892 """ 893 ); 894 assertOK( 895 """ 896 class UnrelatedThisLeak { 897 value class V { 898 int f; 899 V() { 900 UnrelatedThisLeak x = UnrelatedThisLeak.this; 901 f = 10; 902 x = UnrelatedThisLeak.this; 903 } 904 } 905 } 906 """ 907 ); 908 assertFail("compiler.err.cant.ref.before.ctor.called", 909 """ 910 value class Test { 911 Test t = null; 912 Runnable r = () -> { System.err.println(t); }; 913 } 914 """ 915 ); 916 assertFail("compiler.err.cant.ref.after.ctor.called", 917 """ 918 value class Test { 919 int f; 920 { 921 f = 1; 922 } 923 } 924 """ 925 ); 926 assertFail("compiler.err.cant.ref.before.ctor.called", 927 """ 928 value class V { 929 int x; 930 int y = x + 1; // allowed 931 V1() { 932 x = 12; 933 // super(); 934 } 935 } 936 """ 937 ); 938 assertFail("compiler.err.var.might.already.be.assigned", 939 """ 940 value class V2 { 941 int x; 942 V2() { this(x = 3); } // error 943 V2(int i) { x = 4; } 944 } 945 """ 946 ); 947 assertOK( 948 """ 949 abstract value class AV1 { 950 AV1(int i) {} 951 } 952 value class V3 extends AV1 { 953 int x; 954 V3() { 955 super(x = 3); // ok 956 } 957 } 958 """ 959 ); 960 assertFail("compiler.err.cant.ref.before.ctor.called", 961 """ 962 value class V4 { 963 int x; 964 int y = x + 1; 965 V4() { 966 x = 12; 967 } 968 V4(int i) { 969 x = i; 970 } 971 } 972 """ 973 ); 974 assertOK( 975 """ 976 value class V { 977 final int x = "abc".length(); 978 { System.out.println(x); } 979 } 980 """ 981 ); 982 assertFail("compiler.err.illegal.forward.ref", 983 """ 984 value class V { 985 { System.out.println(x); } 986 final int x = "abc".length(); 987 } 988 """ 989 ); 990 assertFail("compiler.err.cant.ref.before.ctor.called", 991 """ 992 value class V { 993 int x = "abc".length(); 994 int y = x; 995 } 996 """ 997 ); 998 assertOK( 999 """ 1000 value class V { 1001 int x = "abc".length(); 1002 { int y = x; } 1003 } 1004 """ 1005 ); 1006 } 1007 1008 @Test 1009 void testThisCallingConstructor() throws Exception { 1010 // make sure that this() calling constructors doesn't initialize final fields 1011 String source = 1012 """ 1013 value class Test { 1014 int i; 1015 Test() { 1016 this(0); 1017 } 1018 1019 Test(int i) { 1020 this.i = i; 1021 } 1022 } 1023 """; 1024 File dir = assertOK(true, source); 1025 File fileEntry = dir.listFiles()[0]; 1026 ClassFile classFile = ClassFile.read(fileEntry); 1027 String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return,"; 1028 String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return,"; 1029 for (Method method : classFile.methods) { 1030 if (method.getName(classFile.constant_pool).equals("<init>")) { 1031 if (method.descriptor.getParameterCount(classFile.constant_pool) == 0) { 1032 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1033 String foundCodeSequence = ""; 1034 for (Instruction inst: code.getInstructions()) { 1035 foundCodeSequence += inst.getMnemonic() + ","; 1036 } 1037 Assert.check(expectedCodeSequenceThisCallingConst.equals(foundCodeSequence)); 1038 } else { 1039 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 1040 String foundCodeSequence = ""; 1041 for (Instruction inst: code.getInstructions()) { 1042 foundCodeSequence += inst.getMnemonic() + ","; 1043 } 1044 Assert.check(expectedCodeSequenceNonThisCallingConst.equals(foundCodeSequence)); 1045 } 1046 } 1047 } 1048 } 1049 1050 @Test 1051 void testSelectors() throws Exception { 1052 assertOK( 1053 """ 1054 value class V { 1055 void selector() { 1056 Class<?> c = int.class; 1057 } 1058 } 1059 """ 1060 ); 1061 assertFail("compiler.err.expected", 1062 """ 1063 value class V { 1064 void selector() { 1065 int i = int.some_selector; 1066 } 1067 } 1068 """ 1069 ); 1070 } 1071 1072 @Test 1073 void testNullAssigment() throws Exception { 1074 assertOK( 1075 """ 1076 value final class V { 1077 final int x = 10; 1078 1079 value final class X { 1080 final V v; 1081 final V v2; 1082 1083 X() { 1084 this.v = null; 1085 this.v2 = null; 1086 } 1087 1088 X(V v) { 1089 this.v = v; 1090 this.v2 = v; 1091 } 1092 1093 V foo(X x) { 1094 x = new X(null); // OK 1095 return x.v; 1096 } 1097 } 1098 V bar(X x) { 1099 x = new X(null); // OK 1100 return x.v; 1101 } 1102 1103 class Y { 1104 V v; 1105 V [] va = { null }; // OK: array initialization 1106 V [] va2 = new V[] { null }; // OK: array initialization 1107 void ooo(X x) { 1108 x = new X(null); // OK 1109 v = null; // legal assignment. 1110 va[0] = null; // legal. 1111 va = new V[] { null }; // legal 1112 } 1113 } 1114 } 1115 """ 1116 ); 1117 } 1118 1119 @Test 1120 void testSerializationWarnings() throws Exception { 1121 String[] previousOptions = getCompileOptions(); 1122 try { 1123 setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source", 1124 Integer.toString(Runtime.version().feature())}); 1125 assertOK( 1126 """ 1127 import java.io.*; 1128 abstract value class AVC implements Serializable {} 1129 """); 1130 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", 1131 """ 1132 import java.io.*; 1133 value class VC implements Serializable { 1134 private static final long serialVersionUID = 0; 1135 } 1136 """); 1137 assertOK( 1138 """ 1139 import java.io.*; 1140 class C implements Serializable { 1141 private static final long serialVersionUID = 0; 1142 } 1143 """); 1144 assertOK( 1145 """ 1146 import java.io.*; 1147 abstract value class Super implements Serializable { 1148 private static final long serialVersionUID = 0; 1149 protected Object writeReplace() throws ObjectStreamException { 1150 return null; 1151 } 1152 } 1153 value class ValueSerializable extends Super { 1154 private static final long serialVersionUID = 1; 1155 } 1156 """); 1157 assertOK( 1158 """ 1159 import java.io.*; 1160 abstract value class Super implements Serializable { 1161 private static final long serialVersionUID = 0; 1162 Object writeReplace() throws ObjectStreamException { 1163 return null; 1164 } 1165 } 1166 value class ValueSerializable extends Super { 1167 private static final long serialVersionUID = 1; 1168 } 1169 """); 1170 assertOK( 1171 """ 1172 import java.io.*; 1173 abstract value class Super implements Serializable { 1174 private static final long serialVersionUID = 0; 1175 public Object writeReplace() throws ObjectStreamException { 1176 return null; 1177 } 1178 } 1179 value class ValueSerializable extends Super { 1180 private static final long serialVersionUID = 1; 1181 } 1182 """); 1183 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", 1184 """ 1185 import java.io.*; 1186 abstract value class Super implements Serializable { 1187 private static final long serialVersionUID = 0; 1188 private Object writeReplace() throws ObjectStreamException { 1189 return null; 1190 } 1191 } 1192 value class ValueSerializable extends Super { 1193 private static final long serialVersionUID = 1; 1194 } 1195 """); 1196 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2", 1197 """ 1198 import java.io.*; 1199 abstract value class Super implements Serializable { 1200 private static final long serialVersionUID = 0; 1201 private Object writeReplace() throws ObjectStreamException { 1202 return null; 1203 } 1204 } 1205 class Serializable1 extends Super { 1206 private static final long serialVersionUID = 1; 1207 } 1208 class Serializable2 extends Serializable1 { 1209 private static final long serialVersionUID = 1; 1210 } 1211 """); 1212 assertOK( 1213 """ 1214 import java.io.*; 1215 abstract value class Super implements Serializable { 1216 private static final long serialVersionUID = 0; 1217 Object writeReplace() throws ObjectStreamException { 1218 return null; 1219 } 1220 } 1221 class ValueSerializable extends Super { 1222 private static final long serialVersionUID = 1; 1223 } 1224 """); 1225 assertOK( 1226 """ 1227 import java.io.*; 1228 abstract value class Super implements Serializable { 1229 private static final long serialVersionUID = 0; 1230 public Object writeReplace() throws ObjectStreamException { 1231 return null; 1232 } 1233 } 1234 class ValueSerializable extends Super { 1235 private static final long serialVersionUID = 1; 1236 } 1237 """); 1238 assertOK( 1239 """ 1240 import java.io.*; 1241 abstract value class Super implements Serializable { 1242 private static final long serialVersionUID = 0; 1243 protected Object writeReplace() throws ObjectStreamException { 1244 return null; 1245 } 1246 } 1247 class ValueSerializable extends Super { 1248 private static final long serialVersionUID = 1; 1249 } 1250 """); 1251 assertOK( 1252 """ 1253 import java.io.*; 1254 value record ValueRecord() implements Serializable { 1255 private static final long serialVersionUID = 1; 1256 } 1257 """); 1258 assertOK( 1259 // Number is a special case, no warning for identity classes extending it 1260 """ 1261 class NumberSubClass extends Number { 1262 private static final long serialVersionUID = 0L; 1263 @Override 1264 public double doubleValue() { return 0; } 1265 @Override 1266 public int intValue() { return 0; } 1267 @Override 1268 public long longValue() { return 0; } 1269 @Override 1270 public float floatValue() { return 0; } 1271 } 1272 """ 1273 ); 1274 } finally { 1275 setCompileOptions(previousOptions); 1276 } 1277 } 1278 1279 private File findClassFileOrFail(File dir, String name) { 1280 for (final File fileEntry : dir.listFiles()) { 1281 if (fileEntry.getName().equals(name)) { 1282 return fileEntry; 1283 } 1284 } 1285 throw new AssertionError("file not found"); 1286 } 1287 1288 private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass, int numberOfAttributes) { 1289 int attrCount = 0; 1290 Attribute result = null; 1291 for (Attribute attribute : attributes) { 1292 if (attribute.getClass() == attrClass) { 1293 attrCount++; 1294 if (result == null) { 1295 result = attribute; 1296 } 1297 } 1298 } 1299 if (attrCount == 0) throw new AssertionError("attribute not found"); 1300 if (attrCount != numberOfAttributes) throw new AssertionError("incorrect number of attributes found"); 1301 return result; 1302 } 1303 1304 private void checkAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) { 1305 for (Attribute attribute : attributes) { 1306 if (attribute.getClass() == attrClass) { 1307 throw new AssertionError("attribute found"); 1308 } 1309 } 1310 } 1311 }