1 /*
   2  * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * ValueObjectCompilationTests
  26  *
  27  * @test
  28  * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120
  29  *      8329345 8341061 8340984
  30  * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects
  31  * @library /lib/combo /tools/lib
  32  * @modules
  33  *     jdk.compiler/com.sun.tools.javac.util
  34  *     jdk.compiler/com.sun.tools.javac.api
  35  *     jdk.compiler/com.sun.tools.javac.main
  36  *     jdk.compiler/com.sun.tools.javac.code
  37  *     jdk.jdeps/com.sun.tools.classfile
  38  * @build toolbox.ToolBox toolbox.JavacTask
  39  * @run junit ValueObjectCompilationTests
  40  */
  41 
  42 import java.io.File;
  43 
  44 import java.util.List;
  45 import java.util.Set;
  46 
  47 import com.sun.tools.javac.util.Assert;
  48 
  49 import com.sun.tools.classfile.Attribute;
  50 import com.sun.tools.classfile.Attributes;
  51 import com.sun.tools.classfile.ClassFile;
  52 import com.sun.tools.classfile.Code_attribute;
  53 import com.sun.tools.classfile.ConstantPool;
  54 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info;
  55 import com.sun.tools.classfile.ConstantPool.CONSTANT_Fieldref_info;
  56 import com.sun.tools.classfile.ConstantPool.CONSTANT_Methodref_info;
  57 import com.sun.tools.classfile.Field;
  58 import com.sun.tools.classfile.Instruction;
  59 import com.sun.tools.classfile.Method;
  60 
  61 import com.sun.tools.javac.code.Flags;
  62 
  63 import org.junit.jupiter.api.Test;
  64 import tools.javac.combo.CompilationTestCase;
  65 import toolbox.ToolBox;
  66 
  67 class ValueObjectCompilationTests extends CompilationTestCase {
  68 
  69     private static String[] PREVIEW_OPTIONS = {"--enable-preview", "-source",
  70             Integer.toString(Runtime.version().feature())};
  71 
  72     public ValueObjectCompilationTests() {
  73         setDefaultFilename("ValueObjectsTest.java");
  74         setCompileOptions(PREVIEW_OPTIONS);
  75     }
  76 
  77     @Test
  78     void 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.super.class.method.cannot.be.synchronized",
 172                     """
 173                     abstract class I {
 174                         synchronized void foo() {}
 175                     }
 176                     value class V extends I {}
 177                     """
 178             ),
 179             new TestData(
 180                     "compiler.err.concrete.supertype.for.value.class",
 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.var.might.not.have.been.initialized",
 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                     ClassFile classFile = ClassFile.read(fileEntry);
 642                     Assert.check((classFile.access_flags.flags & Flags.ACC_IDENTITY) != 0);
 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                 ClassFile classFile = ClassFile.read(fileEntry);
 687                 Assert.check(classFile.access_flags.is(Flags.ACC_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                 ClassFile classFile = ClassFile.read(fileEntry);
 700                 switch (classFile.getName()) {
 701                     case "Sub":
 702                         Assert.check((classFile.access_flags.flags & (Flags.FINAL)) != 0);
 703                         break;
 704                     case "A":
 705                         Assert.check((classFile.access_flags.flags & (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                 ClassFile classFile = ClassFile.read(fileEntry);
 733                 for (Field field : classFile.fields) {
 734                     if (!field.access_flags.is(Flags.STATIC)) {
 735                         Set<String> fieldFlags = field.access_flags.getFieldFlags();
 736                         Assert.check(fieldFlags.size() == 2 && fieldFlags.contains("ACC_FINAL") && fieldFlags.contains("ACC_STRICT"));
 737                     }
 738                 }
 739             }
 740         }
 741 
 742         // testing experimental @Strict annotation
 743         String[] previousOptions = getCompileOptions();
 744         try {
 745             String[] testOptions = {"--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"};
 746             setCompileOptions(testOptions);
 747             for (String source : List.of(
 748                     """
 749                     import jdk.internal.vm.annotation.Strict;
 750                     class Test {
 751                         @Strict int i;
 752                     }
 753                     """,
 754                     """
 755                     import jdk.internal.vm.annotation.Strict;
 756                     class Test {
 757                         @Strict final int i = 0;
 758                     }
 759                     """
 760             )) {
 761                 File dir = assertOK(true, source);
 762                 for (final File fileEntry : dir.listFiles()) {
 763                     ClassFile classFile = ClassFile.read(fileEntry);
 764                     for (Field field : classFile.fields) {
 765                         if (!field.access_flags.is(Flags.STATIC)) {
 766                             Set<String> fieldFlags = field.access_flags.getFieldFlags();
 767                             Assert.check(fieldFlags.contains("ACC_STRICT"));
 768                         }
 769                     }
 770                 }
 771             }
 772         } finally {
 773             setCompileOptions(previousOptions);
 774         }
 775     }
 776 
 777     @Test
 778     void testConstruction() throws Exception {
 779         record Data(String src, boolean isRecord) {
 780             Data(String src) {
 781                 this(src, false);
 782             }
 783         }
 784         for (Data data : List.of(
 785                 new Data(
 786                     """
 787                     value class Test {
 788                         int i = 100;
 789                     }
 790                     """),
 791                 new Data(
 792                     """
 793                     value class Test {
 794                         int i;
 795                         Test() {
 796                             i = 100;
 797                         }
 798                     }
 799                     """),
 800                 new Data(
 801                     """
 802                     value class Test {
 803                         int i;
 804                         Test() {
 805                             i = 100;
 806                             super();
 807                         }
 808                     }
 809                     """),
 810                 new Data(
 811                     """
 812                     value class Test {
 813                         int i;
 814                         Test() {
 815                             this.i = 100;
 816                             super();
 817                         }
 818                     }
 819                     """),
 820                 new Data(
 821                     """
 822                     value record Test(int i) {}
 823                     """, true)
 824         )) {
 825             String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return,";
 826             String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return,";
 827             File dir = assertOK(true, data.src);
 828             for (final File fileEntry : dir.listFiles()) {
 829                 ClassFile classFile = ClassFile.read(fileEntry);
 830                 for (Method method : classFile.methods) {
 831                     if (method.getName(classFile.constant_pool).equals("<init>")) {
 832                         Code_attribute code = (Code_attribute)method.attributes.get("Code");
 833                         String foundCodeSequence = "";
 834                         for (Instruction inst: code.getInstructions()) {
 835                             foundCodeSequence += inst.getMnemonic() + ",";
 836                         }
 837                         if (!data.isRecord) {
 838                             Assert.check(expectedCodeSequence.equals(foundCodeSequence));
 839                         } else {
 840                             Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence));
 841                         }
 842                     }
 843                 }
 844             }
 845         }
 846 
 847         String source =
 848                 """
 849                 value class Test {
 850                     int i = 100;
 851                     int j = 0;
 852                     {
 853                         System.out.println(j);
 854                     }
 855                 }
 856                 """;
 857         String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,iconst_0,putfield,aload_0,invokespecial,getstatic,iconst_0,invokevirtual,return,";
 858         File dir = assertOK(true, source);
 859         for (final File fileEntry : dir.listFiles()) {
 860             ClassFile classFile = ClassFile.read(fileEntry);
 861             for (Method method : classFile.methods) {
 862                 if (method.getName(classFile.constant_pool).equals("<init>")) {
 863                     Code_attribute code = (Code_attribute)method.attributes.get("Code");
 864                     String foundCodeSequence = "";
 865                     for (Instruction inst: code.getInstructions()) {
 866                         foundCodeSequence += inst.getMnemonic() + ",";
 867                     }
 868                     Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence);
 869                 }
 870             }
 871         }
 872 
 873         assertFail("compiler.err.cant.ref.before.ctor.called",
 874                 """
 875                 value class Test {
 876                     Test() {
 877                         m();
 878                     }
 879                     void m() {}
 880                 }
 881                 """
 882         );
 883         assertFail("compiler.err.cant.ref.after.ctor.called",
 884                 """
 885                 value class Test {
 886                     int i;
 887                     Test() {
 888                         super();
 889                         this.i = i;
 890                     }
 891                 }
 892                 """
 893         );
 894         assertOK(
 895                 """
 896                 class UnrelatedThisLeak {
 897                     value class V {
 898                         int f;
 899                         V() {
 900                             UnrelatedThisLeak x = UnrelatedThisLeak.this;
 901                             f = 10;
 902                             x = UnrelatedThisLeak.this;
 903                         }
 904                     }
 905                 }
 906                 """
 907         );
 908         assertFail("compiler.err.cant.ref.before.ctor.called",
 909                 """
 910                 value class Test {
 911                     Test t = null;
 912                     Runnable r = () -> { System.err.println(t); };
 913                 }
 914                 """
 915         );
 916         assertFail("compiler.err.cant.ref.after.ctor.called",
 917                 """
 918                 value class Test {
 919                     int f;
 920                     {
 921                         f = 1;
 922                     }
 923                 }
 924                 """
 925         );
 926         assertFail("compiler.err.cant.ref.before.ctor.called",
 927                 """
 928                 value class V {
 929                     int x;
 930                     int y = x + 1; // allowed
 931                     V1() {
 932                         x = 12;
 933                         // super();
 934                     }
 935                 }
 936                 """
 937         );
 938         assertFail("compiler.err.var.might.already.be.assigned",
 939                 """
 940                 value class V2 {
 941                     int x;
 942                     V2() { this(x = 3); } // error
 943                     V2(int i) { x = 4; }
 944                 }
 945                 """
 946         );
 947         assertOK(
 948                 """
 949                 abstract value class AV1 {
 950                     AV1(int i) {}
 951                 }
 952                 value class V3 extends AV1 {
 953                     int x;
 954                     V3() {
 955                         super(x = 3); // ok
 956                     }
 957                 }
 958                 """
 959         );
 960         assertFail("compiler.err.cant.ref.before.ctor.called",
 961                 """
 962                 value class V4 {
 963                     int x;
 964                     int y = x + 1;
 965                     V4() {
 966                         x = 12;
 967                     }
 968                     V4(int i) {
 969                         x = i;
 970                     }
 971                 }
 972                 """
 973         );
 974         assertOK(
 975                 """
 976                 value class V {
 977                     final int x = "abc".length();
 978                     { System.out.println(x); }
 979                 }
 980                 """
 981         );
 982         assertFail("compiler.err.illegal.forward.ref",
 983                 """
 984                 value class V {
 985                     { System.out.println(x); }
 986                     final int x = "abc".length();
 987                 }
 988                 """
 989         );
 990         assertFail("compiler.err.cant.ref.before.ctor.called",
 991                 """
 992                 value class V {
 993                     int x = "abc".length();
 994                     int y = x;
 995                 }
 996                 """
 997         );
 998         assertOK(
 999                 """
1000                 value class V {
1001                     int x = "abc".length();
1002                     { int y = x; }
1003                 }
1004                 """
1005         );
1006     }
1007 
1008     @Test
1009     void testThisCallingConstructor() throws Exception {
1010         // make sure that this() calling constructors doesn't initialize final fields
1011         String source =
1012                 """
1013                 value class Test {
1014                     int i;
1015                     Test() {
1016                         this(0);
1017                     }
1018 
1019                     Test(int i) {
1020                         this.i = i;
1021                     }
1022                 }
1023                 """;
1024         File dir = assertOK(true, source);
1025         File fileEntry = dir.listFiles()[0];
1026         ClassFile classFile = ClassFile.read(fileEntry);
1027         String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return,";
1028         String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return,";
1029         for (Method method : classFile.methods) {
1030             if (method.getName(classFile.constant_pool).equals("<init>")) {
1031                 if (method.descriptor.getParameterCount(classFile.constant_pool) == 0) {
1032                     Code_attribute code = (Code_attribute)method.attributes.get("Code");
1033                     String foundCodeSequence = "";
1034                     for (Instruction inst: code.getInstructions()) {
1035                         foundCodeSequence += inst.getMnemonic() + ",";
1036                     }
1037                     Assert.check(expectedCodeSequenceThisCallingConst.equals(foundCodeSequence));
1038                 } else {
1039                     Code_attribute code = (Code_attribute)method.attributes.get("Code");
1040                     String foundCodeSequence = "";
1041                     for (Instruction inst: code.getInstructions()) {
1042                         foundCodeSequence += inst.getMnemonic() + ",";
1043                     }
1044                     Assert.check(expectedCodeSequenceNonThisCallingConst.equals(foundCodeSequence));
1045                 }
1046             }
1047         }
1048     }
1049 
1050     @Test
1051     void testSelectors() throws Exception {
1052         assertOK(
1053                 """
1054                 value class V {
1055                     void selector() {
1056                         Class<?> c = int.class;
1057                     }
1058                 }
1059                 """
1060         );
1061         assertFail("compiler.err.expected",
1062                 """
1063                 value class V {
1064                     void selector() {
1065                         int i = int.some_selector;
1066                     }
1067                 }
1068                 """
1069         );
1070     }
1071 
1072     @Test
1073     void testNullAssigment() throws Exception {
1074         assertOK(
1075                 """
1076                 value final class V {
1077                     final int x = 10;
1078 
1079                     value final class X {
1080                         final V v;
1081                         final V v2;
1082 
1083                         X() {
1084                             this.v = null;
1085                             this.v2 = null;
1086                         }
1087 
1088                         X(V v) {
1089                             this.v = v;
1090                             this.v2 = v;
1091                         }
1092 
1093                         V foo(X x) {
1094                             x = new X(null);  // OK
1095                             return x.v;
1096                         }
1097                     }
1098                     V bar(X x) {
1099                         x = new X(null); // OK
1100                         return x.v;
1101                     }
1102 
1103                     class Y {
1104                         V v;
1105                         V [] va = { null }; // OK: array initialization
1106                         V [] va2 = new V[] { null }; // OK: array initialization
1107                         void ooo(X x) {
1108                             x = new X(null); // OK
1109                             v = null; // legal assignment.
1110                             va[0] = null; // legal.
1111                             va = new V[] { null }; // legal
1112                         }
1113                     }
1114                 }
1115                 """
1116         );
1117     }
1118 
1119     @Test
1120     void testSerializationWarnings() throws Exception {
1121         String[] previousOptions = getCompileOptions();
1122         try {
1123             setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source",
1124                     Integer.toString(Runtime.version().feature())});
1125             assertOK(
1126                     """
1127                     import java.io.*;
1128                     abstract value class AVC implements Serializable {}
1129                     """);
1130             assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1",
1131                     """
1132                     import java.io.*;
1133                     value class VC implements Serializable {
1134                         private static final long serialVersionUID = 0;
1135                     }
1136                     """);
1137             assertOK(
1138                     """
1139                     import java.io.*;
1140                     class C implements Serializable {
1141                         private static final long serialVersionUID = 0;
1142                     }
1143                     """);
1144             assertOK(
1145                     """
1146                     import java.io.*;
1147                     abstract value class Super implements Serializable {
1148                         private static final long serialVersionUID = 0;
1149                         protected Object writeReplace() throws ObjectStreamException {
1150                             return null;
1151                         }
1152                     }
1153                     value class ValueSerializable extends Super {
1154                         private static final long serialVersionUID = 1;
1155                     }
1156                     """);
1157             assertOK(
1158                     """
1159                     import java.io.*;
1160                     abstract value class Super implements Serializable {
1161                         private static final long serialVersionUID = 0;
1162                         Object writeReplace() throws ObjectStreamException {
1163                             return null;
1164                         }
1165                     }
1166                     value class ValueSerializable extends Super {
1167                         private static final long serialVersionUID = 1;
1168                     }
1169                     """);
1170             assertOK(
1171                     """
1172                     import java.io.*;
1173                     abstract value class Super implements Serializable {
1174                         private static final long serialVersionUID = 0;
1175                         public Object writeReplace() throws ObjectStreamException {
1176                             return null;
1177                         }
1178                     }
1179                     value class ValueSerializable extends Super {
1180                         private static final long serialVersionUID = 1;
1181                     }
1182                     """);
1183             assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1",
1184                     """
1185                     import java.io.*;
1186                     abstract value class Super implements Serializable {
1187                         private static final long serialVersionUID = 0;
1188                         private Object writeReplace() throws ObjectStreamException {
1189                             return null;
1190                         }
1191                     }
1192                     value class ValueSerializable extends Super {
1193                         private static final long serialVersionUID = 1;
1194                     }
1195                     """);
1196             assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2",
1197                     """
1198                     import java.io.*;
1199                     abstract value class Super implements Serializable {
1200                         private static final long serialVersionUID = 0;
1201                         private Object writeReplace() throws ObjectStreamException {
1202                             return null;
1203                         }
1204                     }
1205                     class Serializable1 extends Super {
1206                         private static final long serialVersionUID = 1;
1207                     }
1208                     class Serializable2 extends Serializable1 {
1209                         private static final long serialVersionUID = 1;
1210                     }
1211                     """);
1212             assertOK(
1213                     """
1214                     import java.io.*;
1215                     abstract value class Super implements Serializable {
1216                         private static final long serialVersionUID = 0;
1217                         Object writeReplace() throws ObjectStreamException {
1218                             return null;
1219                         }
1220                     }
1221                     class ValueSerializable extends Super {
1222                         private static final long serialVersionUID = 1;
1223                     }
1224                     """);
1225             assertOK(
1226                     """
1227                     import java.io.*;
1228                     abstract value class Super implements Serializable {
1229                         private static final long serialVersionUID = 0;
1230                         public Object writeReplace() throws ObjectStreamException {
1231                             return null;
1232                         }
1233                     }
1234                     class ValueSerializable extends Super {
1235                         private static final long serialVersionUID = 1;
1236                     }
1237                     """);
1238             assertOK(
1239                     """
1240                     import java.io.*;
1241                     abstract value class Super implements Serializable {
1242                         private static final long serialVersionUID = 0;
1243                         protected Object writeReplace() throws ObjectStreamException {
1244                             return null;
1245                         }
1246                     }
1247                     class ValueSerializable extends Super {
1248                         private static final long serialVersionUID = 1;
1249                     }
1250                     """);
1251             assertOK(
1252                     """
1253                     import java.io.*;
1254                     value record ValueRecord() implements Serializable {
1255                         private static final long serialVersionUID = 1;
1256                     }
1257                     """);
1258             assertOK(
1259                     // Number is a special case, no warning for identity classes extending it
1260                     """
1261                     class NumberSubClass extends Number {
1262                         private static final long serialVersionUID = 0L;
1263                         @Override
1264                         public double doubleValue() { return 0; }
1265                         @Override
1266                         public int intValue() { return 0; }
1267                         @Override
1268                         public long longValue() { return 0; }
1269                         @Override
1270                         public float floatValue() { return 0; }
1271                     }
1272                     """
1273             );
1274         } finally {
1275             setCompileOptions(previousOptions);
1276         }
1277     }
1278 
1279     private File findClassFileOrFail(File dir, String name) {
1280         for (final File fileEntry : dir.listFiles()) {
1281             if (fileEntry.getName().equals(name)) {
1282                 return fileEntry;
1283             }
1284         }
1285         throw new AssertionError("file not found");
1286     }
1287 
1288     private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass, int numberOfAttributes) {
1289         int attrCount = 0;
1290         Attribute result = null;
1291         for (Attribute attribute : attributes) {
1292             if (attribute.getClass() == attrClass) {
1293                 attrCount++;
1294                 if (result == null) {
1295                     result = attribute;
1296                 }
1297             }
1298         }
1299         if (attrCount == 0) throw new AssertionError("attribute not found");
1300         if (attrCount != numberOfAttributes) throw new AssertionError("incorrect number of attributes found");
1301         return result;
1302     }
1303 
1304     private void checkAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) {
1305         for (Attribute attribute : attributes) {
1306             if (attribute.getClass() == attrClass) {
1307                 throw new AssertionError("attribute found");
1308             }
1309         }
1310     }
1311 }