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