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