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