1 /*
  2  * Copyright (c) 2022, 2024, 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
 30  * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects
 31  * @library /lib/combo /tools/lib
 32  * @modules
 33  *     jdk.compiler/com.sun.tools.javac.util
 34  *     jdk.compiler/com.sun.tools.javac.api
 35  *     jdk.compiler/com.sun.tools.javac.main
 36  *     jdk.compiler/com.sun.tools.javac.code
 37  *     jdk.jdeps/com.sun.tools.classfile
 38  * @build toolbox.ToolBox toolbox.JavacTask
 39  * @run junit ValueObjectCompilationTests
 40  */
 41 
 42 import java.io.File;
 43 
 44 import java.util.List;
 45 import java.util.Set;
 46 
 47 import com.sun.tools.javac.util.Assert;
 48 
 49 import com.sun.tools.classfile.Attribute;
 50 import com.sun.tools.classfile.Attributes;
 51 import com.sun.tools.classfile.ClassFile;
 52 import com.sun.tools.classfile.Code_attribute;
 53 import com.sun.tools.classfile.ConstantPool;
 54 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info;
 55 import com.sun.tools.classfile.ConstantPool.CONSTANT_Fieldref_info;
 56 import com.sun.tools.classfile.ConstantPool.CONSTANT_Methodref_info;
 57 import com.sun.tools.classfile.Field;
 58 import com.sun.tools.classfile.Instruction;
 59 import com.sun.tools.classfile.Method;
 60 
 61 import com.sun.tools.javac.code.Flags;
 62 
 63 import org.junit.jupiter.api.Test;
 64 import tools.javac.combo.CompilationTestCase;
 65 import toolbox.ToolBox;
 66 
 67 class ValueObjectCompilationTests extends CompilationTestCase {
 68 
 69     private static String[] PREVIEW_OPTIONS = {"--enable-preview", "-source",
 70             Integer.toString(Runtime.version().feature())};
 71 
 72     public ValueObjectCompilationTests() {
 73         setDefaultFilename("ValueObjectsTest.java");
 74         setCompileOptions(PREVIEW_OPTIONS);
 75     }
 76 
 77     @Test
 78     void testAbstractValueClassConstraints() {
 79         assertFail("compiler.err.mod.not.allowed.here",
 80                 """
 81                 abstract value class V {
 82                     synchronized void foo() {
 83                      // Error, abstract value class may not declare a synchronized instance method.
 84                     }
 85                 }
 86                 """);
 87     }
 88 
 89     @Test
 90     void testValueModifierConstraints() {
 91         assertFail("compiler.err.illegal.combination.of.modifiers",
 92                 """
 93                 value @interface IA {}
 94                 """);
 95         assertFail("compiler.err.illegal.combination.of.modifiers",
 96                 """
 97                 value interface I {}
 98                 """);
 99         assertFail("compiler.err.mod.not.allowed.here",
100                 """
101                 class Test {
102                     value int x;
103                 }
104                 """);
105         assertFail("compiler.err.mod.not.allowed.here",
106                 """
107                 class Test {
108                     value int foo();
109                 }
110                 """);
111         assertFail("compiler.err.mod.not.allowed.here",
112                 """
113                 value enum Enum {}
114                 """);
115     }
116 
117     @Test
118     void testSuperClassConstraints() {
119         assertFail("compiler.err.super.class.method.cannot.be.synchronized",
120                 """
121                 abstract class I {
122                     synchronized void foo() {}
123                 }
124                 value class V extends I {}
125                 """);
126         assertFail("compiler.err.concrete.supertype.for.value.class",
127                 """
128                 class ConcreteSuperType {
129                     static abstract value class V extends ConcreteSuperType {}  // Error: concrete super.
130                 }
131                 """);
132         assertOK(
133                 """
134                 value record Point(int x, int y) {}
135                 """);
136     }
137 
138     @Test
139     void testRepeatedModifiers() {
140         assertFail("compiler.err.repeated.modifier", "value value class ValueTest {}");
141     }
142 
143     @Test
144     void testParserTest() {
145         assertOK(
146                 """
147                 value class Substring implements CharSequence {
148                     private String str;
149                     private int start;
150                     private int end;
151 
152                     public Substring(String str, int start, int end) {
153                         checkBounds(start, end, str.length());
154                         this.str = str;
155                         this.start = start;
156                         this.end = end;
157                     }
158 
159                     public int length() {
160                         return end - start;
161                     }
162 
163                     public char charAt(int i) {
164                         checkBounds(0, i, length());
165                         return str.charAt(start + i);
166                     }
167 
168                     public Substring subSequence(int s, int e) {
169                         checkBounds(s, e, length());
170                         return new Substring(str, start + s, start + e);
171                     }
172 
173                     public String toString() {
174                         return str.substring(start, end);
175                     }
176 
177                     private static void checkBounds(int start, int end, int length) {
178                         if (start < 0 || end < start || length < end)
179                             throw new IndexOutOfBoundsException();
180                     }
181                 }
182                 """
183         );
184     }
185 
186     @Test
187     void testSemanticsViolations() {
188         assertFail("compiler.err.cant.inherit.from.final",
189                 """
190                 value class Base {}
191                 class Subclass extends Base {}
192                 """);
193         assertFail("compiler.err.cant.assign.val.to.var",
194                 """
195                 value class Point {
196                     int x = 10;
197                     int y;
198                     Point (int x, int y) {
199                         this.x = x; // Error, final field 'x' is already assigned to.
200                         this.y = y; // OK.
201                     }
202                 }
203                 """);
204         assertFail("compiler.err.cant.assign.val.to.var",
205                 """
206                 value class Point {
207                     int x;
208                     int y;
209                     Point (int x, int y) {
210                         this.x = x;
211                         this.y = y;
212                     }
213 
214                     void foo(Point p) {
215                         this.y = p.y; // Error, y is final and can't be written outside of ctor.
216                     }
217                 }
218                 """);
219         assertFail("compiler.err.var.might.not.have.been.initialized",
220                 """
221                 value class Point {
222                     int x;
223                     int y;
224                     Point (int x, int y) {
225                         this.x = x;
226                         // y hasn't been initialized
227                     }
228                 }
229                 """);
230         assertFail("compiler.err.mod.not.allowed.here",
231                 """
232                 value class V {
233                     synchronized void foo() {}
234                 }
235                 """);
236         assertOK(
237                 """
238                 value class V {
239                     synchronized static void soo() {}
240                 }
241                 """);
242         assertFail("compiler.err.type.found.req",
243                 """
244                 value class V {
245                     { synchronized(this) {} }
246                 }
247                 """);
248         assertFail("compiler.err.mod.not.allowed.here",
249                 """
250                 value record R() {
251                     synchronized void foo() { } // Error;
252                     synchronized static void soo() {} // OK.
253                 }
254                 """);
255         assertFail("compiler.err.cant.ref.before.ctor.called",
256                 """
257                 value class V {
258                     int x;
259                     V() {
260                         foo(this); // Error.
261                         x = 10;
262                     }
263                     void foo(V v) {}
264                 }
265                 """);
266         assertFail("compiler.err.cant.ref.before.ctor.called",
267                 """
268                 value class V {
269                     int x;
270                     V() {
271                         x = 10;
272                         foo(this); // error
273                     }
274                     void foo(V v) {}
275                 }
276                 """);
277         assertFail("compiler.err.type.found.req",
278                 """
279                 interface I {}
280                 interface VI extends I {}
281                 class C {}
282                 value class VC<T extends VC> {
283                     void m(T t) {
284                         synchronized(t) {} // error
285                     }
286                 }
287                 """);
288         assertFail("compiler.err.type.found.req",
289                 """
290                 interface I {}
291                 interface VI extends I {}
292                 class C {}
293                 value class VC<T extends VC> {
294                     void foo(Object o) {
295                         synchronized ((VC & I)o) {} // error
296                     }
297                 }
298                 """);
299         // OK if the value class is abstract
300         assertOK(
301                 """
302                 interface I {}
303                 abstract value class VI implements I {}
304                 class C {}
305                 value class VC<T extends VC> {
306                     void bar(Object o) {
307                         synchronized ((VI & I)o) {} // error
308                     }
309                 }
310                 """);
311     }
312 
313     @Test
314     void testInteractionWithSealedClasses() {
315         assertOK(
316                 """
317                 abstract sealed value class SC {}
318                 value class VC extends SC {}
319                 """
320         );
321         assertOK(
322                 """
323                 abstract sealed interface SI {}
324                 value class VC implements SI {}
325                 """
326         );
327         assertOK(
328                 """
329                 abstract sealed class SC {}
330                 final class IC extends SC {}
331                 non-sealed class IC2 extends SC {}
332                 final class IC3 extends IC2 {}
333                 """
334         );
335         assertOK(
336                 """
337                 abstract sealed interface SI {}
338                 final class IC implements SI {}
339                 non-sealed class IC2 implements SI {}
340                 final class IC3 extends IC2 {}
341                 """
342         );
343         assertFail("compiler.err.illegal.combination.of.modifiers",
344                 """
345                 abstract sealed value class SC {}
346                 non-sealed value class VC extends SC {}
347                 """
348         );
349         assertFail("compiler.err.illegal.combination.of.modifiers",
350                 """
351                 sealed value class SI {}
352                 non-sealed value class VC extends SI {}
353                 """
354         );
355     }
356 
357     @Test
358     void testCheckClassFileFlags() throws Exception {
359         for (String source : List.of(
360                 """
361                 interface I {}
362                 class Test {
363                     I i = new I() {};
364                 }
365                 """,
366                 """
367                 class C {}
368                 class Test {
369                     C c = new C() {};
370                 }
371                 """,
372                 """
373                 class Test {
374                     Object o = new Object() {};
375                 }
376                 """,
377                 """
378                 class Test {
379                     abstract class Inner {}
380                 }
381                 """
382         )) {
383             File dir = assertOK(true, source);
384             for (final File fileEntry : dir.listFiles()) {
385                 if (fileEntry.getName().contains("$")) {
386                     ClassFile classFile = ClassFile.read(fileEntry);
387                     Assert.check((classFile.access_flags.flags & Flags.ACC_IDENTITY) != 0);
388                 }
389             }
390         }
391 
392         for (String source : List.of(
393                 """
394                 class C {}
395                 """,
396                 """
397                 abstract class A {
398                     int i;
399                 }
400                 """,
401                 """
402                 abstract class A {
403                     synchronized void m() {}
404                 }
405                 """,
406                 """
407                 class C {
408                     synchronized void m() {}
409                 }
410                 """,
411                 """
412                 abstract class A {
413                     int i;
414                     { i = 0; }
415                 }
416                 """,
417                 """
418                 abstract class A {
419                     A(int i) {}
420                 }
421                 """,
422                 """
423                     enum E {}
424                 """,
425                 """
426                     record R() {}
427                 """
428         )) {
429             File dir = assertOK(true, source);
430             for (final File fileEntry : dir.listFiles()) {
431                 ClassFile classFile = ClassFile.read(fileEntry);
432                 Assert.check(classFile.access_flags.is(Flags.ACC_IDENTITY));
433             }
434         }
435 
436         {
437             String source =
438                     """
439                     abstract value class A {}
440                     value class Sub extends A {} //implicitly final
441                     """;
442             File dir = assertOK(true, source);
443             for (final File fileEntry : dir.listFiles()) {
444                 ClassFile classFile = ClassFile.read(fileEntry);
445                 switch (classFile.getName()) {
446                     case "Sub":
447                         Assert.check((classFile.access_flags.flags & (Flags.FINAL)) != 0);
448                         break;
449                     case "A":
450                         Assert.check((classFile.access_flags.flags & (Flags.ABSTRACT)) != 0);
451                         break;
452                     default:
453                         throw new AssertionError("you shoulnd't be here");
454                 }
455             }
456         }
457 
458         for (String source : List.of(
459                 """
460                 value class V {
461                     int i = 0;
462                     static int j;
463                 }
464                 """,
465                 """
466                 abstract value class A {
467                     static int j;
468                 }
469 
470                 value class V extends A {
471                     int i = 0;
472                 }
473                 """
474         )) {
475             File dir = assertOK(true, source);
476             for (final File fileEntry : dir.listFiles()) {
477                 ClassFile classFile = ClassFile.read(fileEntry);
478                 for (Field field : classFile.fields) {
479                     if (!field.access_flags.is(Flags.STATIC)) {
480                         Set<String> fieldFlags = field.access_flags.getFieldFlags();
481                         Assert.check(fieldFlags.size() == 2 && fieldFlags.contains("ACC_FINAL") && fieldFlags.contains("ACC_STRICT"));
482                     }
483                 }
484             }
485         }
486     }
487 
488     @Test
489     void testConstruction() throws Exception {
490         record Data(String src, boolean isRecord) {}
491         for (Data data : List.of(
492                 new Data(
493                     """
494                     value class Test {
495                         int i = 100;
496                     }
497                     """, false),
498                 new Data(
499                     """
500                     value class Test {
501                         int i;
502                         Test() {
503                             i = 100;
504                         }
505                     }
506                     """, false),
507                 new Data(
508                     """
509                     value class Test {
510                         int i;
511                         Test() {
512                             i = 100;
513                             super();
514                         }
515                     }
516                     """, false),
517                 new Data(
518                     """
519                     value class Test {
520                         int i;
521                         Test() {
522                             this.i = 100;
523                             super();
524                         }
525                     }
526                     """, false),
527                 new Data(
528                     """
529                     value record Test(int i) {}
530                     """, true)
531         )) {
532             String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return,";
533             String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return,";
534             File dir = assertOK(true, data.src);
535             for (final File fileEntry : dir.listFiles()) {
536                 ClassFile classFile = ClassFile.read(fileEntry);
537                 for (Method method : classFile.methods) {
538                     if (method.getName(classFile.constant_pool).equals("<init>")) {
539                         Code_attribute code = (Code_attribute)method.attributes.get("Code");
540                         String foundCodeSequence = "";
541                         for (Instruction inst: code.getInstructions()) {
542                             foundCodeSequence += inst.getMnemonic() + ",";
543                         }
544                         if (!data.isRecord) {
545                             Assert.check(expectedCodeSequence.equals(foundCodeSequence));
546                         } else {
547                             Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence));
548                         }
549                     }
550                 }
551             }
552         }
553 
554         String source =
555                 """
556                 value class Test {
557                     int i = 100;
558                     int j;
559                     {
560                         j = 200;
561                     }
562                 }
563                 """;
564         String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,aload_0,sipush,putfield,return,";
565         File dir = assertOK(true, source);
566         for (final File fileEntry : dir.listFiles()) {
567             ClassFile classFile = ClassFile.read(fileEntry);
568             for (Method method : classFile.methods) {
569                 if (method.getName(classFile.constant_pool).equals("<init>")) {
570                     Code_attribute code = (Code_attribute)method.attributes.get("Code");
571                     String foundCodeSequence = "";
572                     for (Instruction inst: code.getInstructions()) {
573                         foundCodeSequence += inst.getMnemonic() + ",";
574                     }
575                     Assert.check(expectedCodeSequence.equals(foundCodeSequence));
576                 }
577             }
578         }
579 
580         assertFail("compiler.err.cant.ref.before.ctor.called",
581                 """
582                 value class Test {
583                     Test() {
584                         m();
585                     }
586                     void m() {}
587                 }
588                 """
589         );
590         assertFail("compiler.err.cant.ref.after.ctor.called",
591                 """
592                 value class Test {
593                     int i;
594                     Test() {
595                         super();
596                         this.i = i;
597                     }
598                 }
599                 """
600         );
601         assertOK(
602                 """
603                 class UnrelatedThisLeak {
604                     value class V {
605                         int f;
606                         V() {
607                             UnrelatedThisLeak x = UnrelatedThisLeak.this;
608                             f = 10;
609                             x = UnrelatedThisLeak.this;
610                         }
611                     }
612                 }
613                 """
614         );
615     }
616 
617     @Test
618     void testThisCallingConstructor() throws Exception {
619         // make sure that this() calling constructors doesn't initialize final fields
620         String source =
621                 """
622                 value class Test {
623                     int i;
624                     Test() {
625                         this(0);
626                     }
627 
628                     Test(int i) {
629                         this.i = i;
630                     }
631                 }
632                 """;
633         File dir = assertOK(true, source);
634         File fileEntry = dir.listFiles()[0];
635         ClassFile classFile = ClassFile.read(fileEntry);
636         String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return,";
637         String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return,";
638         for (Method method : classFile.methods) {
639             if (method.getName(classFile.constant_pool).equals("<init>")) {
640                 if (method.descriptor.getParameterCount(classFile.constant_pool) == 0) {
641                     Code_attribute code = (Code_attribute)method.attributes.get("Code");
642                     String foundCodeSequence = "";
643                     for (Instruction inst: code.getInstructions()) {
644                         foundCodeSequence += inst.getMnemonic() + ",";
645                     }
646                     Assert.check(expectedCodeSequenceThisCallingConst.equals(foundCodeSequence));
647                 } else {
648                     Code_attribute code = (Code_attribute)method.attributes.get("Code");
649                     String foundCodeSequence = "";
650                     for (Instruction inst: code.getInstructions()) {
651                         foundCodeSequence += inst.getMnemonic() + ",";
652                     }
653                     Assert.check(expectedCodeSequenceNonThisCallingConst.equals(foundCodeSequence));
654                 }
655             }
656         }
657     }
658 
659     @Test
660     void testSelectors() throws Exception {
661         assertOK(
662                 """
663                 value class V {
664                     void selector() {
665                         Class<?> c = int.class;
666                     }
667                 }
668                 """
669         );
670         assertFail("compiler.err.expected",
671                 """
672                 value class V {
673                     void selector() {
674                         int i = int.some_selector;
675                     }
676                 }
677                 """
678         );
679     }
680 
681     @Test
682     void testAnonymousValue() throws Exception {
683         assertOK(
684                 """
685                 class Test {
686                     void m() {
687                         Object o = new value Comparable<String>() {
688                             @Override
689                             public int compareTo(String o) {
690                                 return 0;
691                             }
692                         };
693                     }
694                 }
695                 """
696         );
697         assertOK(
698                 """
699                 class Test {
700                     void m() {
701                         Object o = new value Comparable<>() {
702                             @Override
703                             public int compareTo(Object o) {
704                                 return 0;
705                             }
706                         };
707                     }
708                 }
709                 """
710         );
711     }
712 
713     @Test
714     void testNullAssigment() throws Exception {
715         assertOK(
716                 """
717                 value final class V {
718                     final int x = 10;
719 
720                     value final class X {
721                         final V v;
722                         final V v2;
723 
724                         X() {
725                             this.v = null;
726                             this.v2 = null;
727                         }
728 
729                         X(V v) {
730                             this.v = v;
731                             this.v2 = v;
732                         }
733 
734                         V foo(X x) {
735                             x = new X(null);  // OK
736                             return x.v;
737                         }
738                     }
739                     V bar(X x) {
740                         x = new X(null); // OK
741                         return x.v;
742                     }
743 
744                     class Y {
745                         V v;
746                         V [] va = { null }; // OK: array initialization
747                         V [] va2 = new V[] { null }; // OK: array initialization
748                         void ooo(X x) {
749                             x = new X(null); // OK
750                             v = null; // legal assignment.
751                             va[0] = null; // legal.
752                             va = new V[] { null }; // legal
753                         }
754                     }
755                 }
756                 """
757         );
758     }
759 
760     private File findClassFileOrFail(File dir, String name) {
761         for (final File fileEntry : dir.listFiles()) {
762             if (fileEntry.getName().equals(name)) {
763                 return fileEntry;
764             }
765         }
766         throw new AssertionError("file not found");
767     }
768 
769     private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass, int numberOfAttributes) {
770         int attrCount = 0;
771         Attribute result = null;
772         for (Attribute attribute : attributes) {
773             if (attribute.getClass() == attrClass) {
774                 attrCount++;
775                 if (result == null) {
776                     result = attribute;
777                 }
778             }
779         }
780         if (attrCount == 0) throw new AssertionError("attribute not found");
781         if (attrCount != numberOfAttributes) throw new AssertionError("incorrect number of attributes found");
782         return result;
783     }
784 
785     private void checkAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) {
786         for (Attribute attribute : attributes) {
787             if (attribute.getClass() == attrClass) {
788                 throw new AssertionError("attribute found");
789             }
790         }
791     }
792 }