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
1155 void checkMnemonicsFor(String source, String expectedMnemonics) throws Exception {
1156 File dir = assertOK(true, source);
1157 for (final File fileEntry : dir.listFiles()) {
1158 var classFile = ClassFile.of().parse(fileEntry.toPath());
1159 if (classFile.thisClass().name().equalsString("Test")) {
1160 for (var method : classFile.methods()) {
1161 if (method.methodName().equalsString("<init>")) {
1162 var code = method.findAttribute(Attributes.code()).orElseThrow();
1163 List<String> mnemonics = new ArrayList<>();
1164 for (var coe : code) {
1165 if (coe instanceof Instruction inst) {
1166 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT));
1167 }
1168 }
1169 var foundCodeSequence = String.join(",", mnemonics);
1170 Assert.check(expectedMnemonics.equals(foundCodeSequence), "found " + foundCodeSequence);
1171 }
1172 }
1173 }
1174 }
1175 }
1176
1177 @Test
1178 void testThisCallingConstructor() throws Exception {
1179 // make sure that this() calling constructors doesn't initialize final fields
1180 String source =
1181 """
1182 value class Test {
1183 int i;
1184 Test() {
1185 this(0);
1186 }
1187
1188 Test(int i) {
1189 this.i = i;
1190 }
1191 }
1192 """;
1193 File dir = assertOK(true, source);
1194 File fileEntry = dir.listFiles()[0];
1195 String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return";
1196 String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return";
1197 var classFile = ClassFile.of().parse(fileEntry.toPath());
1198 for (var method : classFile.methods()) {
1199 if (method.methodName().equalsString("<init>")) {
1200 var code = method.findAttribute(Attributes.code()).orElseThrow();
1201 List<String> mnemonics = new ArrayList<>();
1202 for (var coe : code) {
1203 if (coe instanceof Instruction inst) {
1204 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT));
1205 }
1206 }
1207 var foundCodeSequence = String.join(",", mnemonics);
1208 var expected = method.methodTypeSymbol().parameterCount() == 0 ?
1209 expectedCodeSequenceThisCallingConst : expectedCodeSequenceNonThisCallingConst;
1210 Assert.check(expected.equals(foundCodeSequence), "found " + foundCodeSequence);
1211 }
1212 }
1213 }
1214
1215 @Test
1216 void testSelectors() throws Exception {
1217 assertOK(
1218 """
1219 value class V {
1220 void selector() {
1221 Class<?> c = int.class;
1222 }
1223 }
1224 """
1225 );
1226 assertFail("compiler.err.expected",
1227 """
1228 value class V {
1229 void selector() {
1230 int i = int.some_selector;
1231 }
1232 }
1233 """
1234 );
1235 }
1236
1237 @Test
1238 void testNullAssigment() throws Exception {
1239 assertOK(
1240 """
1241 value final class V {
1242 final int x = 10;
1243
1244 value final class X {
1245 final V v;
1246 final V v2;
1247
1248 X() {
1249 this.v = null;
1250 this.v2 = null;
1251 }
1252
1253 X(V v) {
1254 this.v = v;
1255 this.v2 = v;
1256 }
1257
1258 V foo(X x) {
1259 x = new X(null); // OK
1260 return x.v;
1261 }
1262 }
1263 V bar(X x) {
1264 x = new X(null); // OK
1265 return x.v;
1266 }
1267
1268 class Y {
1269 V v;
1270 V [] va = { null }; // OK: array initialization
1271 V [] va2 = new V[] { null }; // OK: array initialization
1272 void ooo(X x) {
1273 x = new X(null); // OK
1274 v = null; // legal assignment.
1275 va[0] = null; // legal.
1276 va = new V[] { null }; // legal
1277 }
1278 }
1279 }
1280 """
1281 );
1282 }
1283
1284 @Test
1285 void testSerializationWarnings() throws Exception {
1286 String[] previousOptions = getCompileOptions();
1287 try {
1288 setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source",
1289 Integer.toString(Runtime.version().feature())});
1290 assertOK(
1291 """
1292 import java.io.*;
1293 abstract value class AVC implements Serializable {}
1294 """);
1295 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1",
1296 """
1297 import java.io.*;
1298 value class VC implements Serializable {
1299 private static final long serialVersionUID = 0;
1300 }
1301 """);
1302 assertOK(
1303 """
1304 import java.io.*;
1305 class C implements Serializable {
1306 private static final long serialVersionUID = 0;
1307 }
1308 """);
1309 assertOK(
1310 """
1311 import java.io.*;
1312 abstract value class Super implements Serializable {
1313 private static final long serialVersionUID = 0;
1314 protected Object writeReplace() throws ObjectStreamException {
1315 return null;
1316 }
1317 }
1318 value class ValueSerializable extends Super {
1319 private static final long serialVersionUID = 1;
1320 }
1321 """);
1322 assertOK(
1323 """
1324 import java.io.*;
1325 abstract value class Super implements Serializable {
1326 private static final long serialVersionUID = 0;
1327 Object writeReplace() throws ObjectStreamException {
1328 return null;
1329 }
1330 }
1331 value class ValueSerializable extends Super {
1332 private static final long serialVersionUID = 1;
1333 }
1334 """);
1335 assertOK(
1336 """
1337 import java.io.*;
1338 abstract value class Super implements Serializable {
1339 private static final long serialVersionUID = 0;
1340 public Object writeReplace() throws ObjectStreamException {
1341 return null;
1342 }
1343 }
1344 value class ValueSerializable extends Super {
1345 private static final long serialVersionUID = 1;
1346 }
1347 """);
1348 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1",
1349 """
1350 import java.io.*;
1351 abstract value class Super implements Serializable {
1352 private static final long serialVersionUID = 0;
1353 private Object writeReplace() throws ObjectStreamException {
1354 return null;
1355 }
1356 }
1357 value class ValueSerializable extends Super {
1358 private static final long serialVersionUID = 1;
1359 }
1360 """);
1361 assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2",
1362 """
1363 import java.io.*;
1364 abstract value class Super implements Serializable {
1365 private static final long serialVersionUID = 0;
1366 private Object writeReplace() throws ObjectStreamException {
1367 return null;
1368 }
1369 }
1370 class Serializable1 extends Super {
1371 private static final long serialVersionUID = 1;
1372 }
1373 class Serializable2 extends Serializable1 {
1374 private static final long serialVersionUID = 1;
1375 }
1376 """);
1377 assertOK(
1378 """
1379 import java.io.*;
1380 abstract value class Super implements Serializable {
1381 private static final long serialVersionUID = 0;
1382 Object writeReplace() throws ObjectStreamException {
1383 return null;
1384 }
1385 }
1386 class ValueSerializable extends Super {
1387 private static final long serialVersionUID = 1;
1388 }
1389 """);
1390 assertOK(
1391 """
1392 import java.io.*;
1393 abstract value class Super implements Serializable {
1394 private static final long serialVersionUID = 0;
1395 public Object writeReplace() throws ObjectStreamException {
1396 return null;
1397 }
1398 }
1399 class ValueSerializable extends Super {
1400 private static final long serialVersionUID = 1;
1401 }
1402 """);
1403 assertOK(
1404 """
1405 import java.io.*;
1406 abstract value class Super implements Serializable {
1407 private static final long serialVersionUID = 0;
1408 protected Object writeReplace() throws ObjectStreamException {
1409 return null;
1410 }
1411 }
1412 class ValueSerializable extends Super {
1413 private static final long serialVersionUID = 1;
1414 }
1415 """);
1416 assertOK(
1417 """
1418 import java.io.*;
1419 value record ValueRecord() implements Serializable {
1420 private static final long serialVersionUID = 1;
1421 }
1422 """);
1423 assertOK(
1424 // Number is a special case, no warning for identity classes extending it
1425 """
1426 class NumberSubClass extends Number {
1427 private static final long serialVersionUID = 0L;
1428 @Override
1429 public double doubleValue() { return 0; }
1430 @Override
1431 public int intValue() { return 0; }
1432 @Override
1433 public long longValue() { return 0; }
1434 @Override
1435 public float floatValue() { return 0; }
1436 }
1437 """
1438 );
1439 } finally {
1440 setCompileOptions(previousOptions);
1441 }
1442 }
1443
1444 @Test
1445 void testAssertUnsetFieldsSMEntry() throws Exception {
1446 String[] previousOptions = getCompileOptions();
1447 try {
1448 String[] testOptions = {
1449 "--enable-preview",
1450 "-source", Integer.toString(Runtime.version().feature()),
1451 "-XDnoLocalProxyVars",
1452 "-XDdebug.stackmap",
1453 "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"
1454 };
1455 setCompileOptions(testOptions);
1456
1457 record Data(String src, int[] expectedFrameTypes, String[][] expectedUnsetFields) {}
1458 for (Data data : List.of(
1459 new Data(
1460 """
1461 import jdk.internal.vm.annotation.Strict;
1462 class Test {
1463 @Strict
1464 final int x;
1465 @Strict
1466 final int y;
1467 Test(boolean a, boolean b) {
1468 if (a) { // early_larval {x, y}
1469 x = 1;
1470 if (b) { // early_larval {y}
1471 y = 1;
1472 } else { // early_larval {y}
1473 y = 2;
1474 }
1475 } else { // early_larval {x, y}
1476 x = y = 3;
1477 }
1478 super();
1479 }
1480 }
1481 """,
1482 // three unset_fields entries, entry type 246, are expected in the stackmap table
1483 new int[] {246, 246, 246},
1484 // expected fields for each of them:
1485 new String[][] { new String[] { "y:I" }, new String[] { "x:I", "y:I" }, new String[] {} }
1486 ),
1487 new Data(
1488 """
1489 import jdk.internal.vm.annotation.Strict;
1490 class Test {
1491 @Strict
1492 final int x;
1493 @Strict
1494 final int y;
1495 Test(int n) {
1496 switch(n) {
1497 case 2:
1498 x = y = 2;
1499 break;
1500 default:
1501 x = y = 100;
1502 break;
1503 }
1504 super();
1505 }
1506 }
1507 """,
1508 // here we expect only one
1509 new int[] {20, 12, 246},
1510 // stating that no field is unset
1511 new String[][] { new String[] {} }
1512 ),
1513 new Data(
1514 """
1515 import jdk.internal.vm.annotation.Strict;
1516 class Test {
1517 @Strict
1518 final int x;
1519 @Strict
1520 final int y;
1521 Test(int n) {
1522 if (n % 3 == 0) {
1523 x = n / 3;
1524 } else { // no unset change
1525 x = n + 2;
1526 } // early_larval {y}
1527 y = n >>> 3;
1528 super();
1529 if ((char) n != n) {
1530 n -= 5;
1531 } // no uninitializedThis - automatically cleared unsets
1532 Math.abs(n);
1533 }
1534 }
1535 """,
1536 // here we expect only one, none for the post-larval frame
1537 new int[] {16, 246, 255},
1538 // stating that y is unset when if-else finishes
1539 new String[][] { new String[] {"y:I"} }
1540 )
1541 )) {
1542 File dir = assertOK(true, data.src());
1543 for (final File fileEntry : dir.listFiles()) {
1544 var classFile = ClassFile.of().parse(fileEntry.toPath());
1545 for (var method : classFile.methods()) {
1546 if (method.methodName().equalsString(ConstantDescs.INIT_NAME)) {
1547 var code = method.findAttribute(Attributes.code()).orElseThrow();
1548 var stackMapTable = code.findAttribute(Attributes.stackMapTable()).orElseThrow();
1549 Assert.check(data.expectedFrameTypes().length == stackMapTable.entries().size(), "unexpected stackmap length");
1550 int entryIndex = 0;
1551 int expectedUnsetFieldsIndex = 0;
1552 for (var entry : stackMapTable.entries()) {
1553 Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.frameType(), "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.frameType());
1554 if (entry.frameType() == 246) {
1555 Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex].length == entry.unsetFields().size());
1556 int index = 0;
1557 for (var nat : entry.unsetFields()) {
1558 String unsetStr = nat.name() + ":" + nat.type();
1559 Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex][index++].equals(unsetStr));
1560 }
1561 expectedUnsetFieldsIndex++;
1562 }
1563 }
1564 }
1565 }
1566 }
1567 }
1568 } finally {
1569 setCompileOptions(previousOptions);
1570 }
1571 }
1572
1573 @Test
1574 void testLocalProxyVars() throws Exception {
1575 String[] previousOptions = getCompileOptions();
1576 try {
1577 String[] testOptions = {
1578 "--enable-preview",
1579 "-source", Integer.toString(Runtime.version().feature()),
1580 "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"
1581 };
1582 setCompileOptions(testOptions);
1583 String[] sources = new String[] {
1584 """
1585 value class Test {
1586 int i;
1587 int j;
1588 Test() {// javac should generate a proxy local var for `i`
1589 i = 1;
1590 j = i; // as here `i` is being read during the early construction phase, use the local var instead
1591 super();
1592 System.err.println(i);
1593 }
1594 }
1595 """,
1596 """
1597 import jdk.internal.vm.annotation.Strict;
1598 class Test {
1599 @Strict
1600 int i;
1601 @Strict
1602 int j;
1603 Test() {
1604 i = 1;
1605 j = i;
1606 super();
1607 System.err.println(i);
1608 }
1609 }
1610 """
1611 };
1612 for (String source : sources) {
1613 checkMnemonicsFor(source, "iconst_1,istore_1,aload_0,iload_1,putfield,aload_0,iload_1,putfield," +
1614 "aload_0,invokespecial,getstatic,aload_0,getfield,invokevirtual,return");
1615 }
1616 } finally {
1617 setCompileOptions(previousOptions);
1618 }
1619 }
1620 }