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