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