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