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