With JDK-8330582, the JVM starts to enforce new field modifiers rules from JEP 401, notably:
"A field that has set ACC_STRICT must also have set ACC_FINAL."
and
"Each field of a value class must have exactly one of its ACC_STATIC or ACC_STRICT flags set.".
Enforcing those rules shows an issue with javac: when generating final synthetic fields in value classes, the ACC_STRICT flag is not set.
Reproducer:
public class NestedTest {
value class MyValue {
int i = 0;
public String toString() {
return "MyValue("+i+","+outerInt+")";
}
}
int outerInt = 42;
MyValue v = new MyValue();
public static void main(String[] args) {
NestedTest nt = new NestedTest();
}
}
Error reported by the JVM (withJDK-8330582 changes in):
Exception in thread "main" java.lang.ClassFormatError: Illegal field modifiers in class NestedTest$MyValue: 0x1010
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1023)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at NestedTest.<init>(NestedTest.java:11)
at NestedTest.main(NestedTest.java:14)
Decompiling the value class shows the illegal field modifiers set for field this$0 :
Classfile [...]/NestedTest$MyValue.class
Last modified Apr 18, 2024; size 965 bytes
SHA-256 checksum e3a71b30c8be9b0529a3297b2bc499abf9a188455a5c4a5463e8217e89520040
Compiled from "NestedTest.java"
final value class NestedTest$MyValue
minor version: 65535
major version: 66
flags: (0x0010) ACC_FINAL
this_class: #2 // NestedTest$MyValue
super_class: #12 // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 4
Constant pool:
#1 = Fieldref #2.#3 // NestedTest$MyValue.this$0:LNestedTest;
#2 = Class #4 // NestedTest$MyValue
#3 = NameAndType #5:#6 // this$0:LNestedTest;
#4 = Utf8 NestedTest$MyValue
#5 = Utf8 this$0
#6 = Utf8 LNestedTest;
#7 = Fieldref #2.#8 // NestedTest$MyValue.i:I
#8 = NameAndType #9:#10 // i:I
#9 = Utf8 i
#10 = Utf8 I
#11 = Methodref #12.#13 // java/lang/Object."<init>":()V
#12 = Class #14 // java/lang/Object
#13 = NameAndType #15:#16 // "<init>":()V
#14 = Utf8 java/lang/Object
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Fieldref #18.#19 // NestedTest.outerInt:I
#18 = Class #20 // NestedTest
#19 = NameAndType #21:#10 // outerInt:I
#20 = Utf8 NestedTest
#21 = Utf8 outerInt
#22 = InvokeDynamic #0:#23 // #0:makeConcatWithConstants:(I)Ljava/lang/String;
#23 = NameAndType #24:#25 // makeConcatWithConstants:(I)Ljava/lang/String;
#24 = Utf8 makeConcatWithConstants
#25 = Utf8 (I)Ljava/lang/String;
#26 = Utf8 ConstantValue
#27 = Integer 0
#28 = Utf8 (LNestedTest;)V
#29 = Utf8 Code
#30 = Utf8 LineNumberTable
#31 = Utf8 MethodParameters
#32 = Utf8 toString
#33 = Utf8 ()Ljava/lang/String;
#34 = Utf8 SourceFile
#35 = Utf8 NestedTest.java
#36 = Utf8 NestHost
#37 = Utf8 BootstrapMethods
#38 = String #39 // MyValue(0,\u0001)
#39 = Utf8 MyValue(0,\u0001)
#40 = MethodHandle 6:#41 // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Looku
p;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#41 = Methodref #42.#43 // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Stri
ng;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#42 = Class #44 // java/lang/invoke/StringConcatFactory
#43 = NameAndType #24:#45 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava
/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#44 = Utf8 java/lang/invoke/StringConcatFactory
#45 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lan
g/invoke/CallSite;
#46 = Utf8 InnerClasses
#47 = Utf8 MyValue
#48 = Class #49 // java/lang/invoke/MethodHandles$Lookup
#49 = Utf8 java/lang/invoke/MethodHandles$Lookup
#50 = Class #51 // java/lang/invoke/MethodHandles
#51 = Utf8 java/lang/invoke/MethodHandles
#52 = Utf8 Lookup
{
final strictfp int i;
descriptor: I
flags: (0x0810) ACC_FINAL, ACC_STRICT
ConstantValue: int 0
final NestedTest this$0;
descriptor: LNestedTest;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
NestedTest$MyValue(NestedTest);
descriptor: (LNestedTest;)V
flags: (0x0000)
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LNestedTest;
5: aload_0
6: iconst_0
7: putfield #7 // Field i:I
10: aload_0
11: invokespecial #11 // Method java/lang/Object."<init>":()V
14: return
LineNumberTable:
line 2: 0
line 3: 5
line 2: 10
MethodParameters:
Name Flags
<no name> final mandated
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field this$0:LNestedTest;
4: getfield #17 // Field NestedTest.outerInt:I
7: invokedynamic #22, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
12: areturn
LineNumberTable:
line 6: 0
}
SourceFile: "NestedTest.java"
NestHost: class NestedTest
BootstrapMethods:
0: #40 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invok
e/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#38 MyValue(0,\u0001)
InnerClasses:
final #47= #2 of #18; // MyValue=class NestedTest$MyValue of class NestedTest
public static final #52= #48 of #50; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
Another minor issue, for field 'i', javap prints " final strictfp int i;" when it should print " final strict int i;".
"A field that has set ACC_STRICT must also have set ACC_FINAL."
and
"Each field of a value class must have exactly one of its ACC_STATIC or ACC_STRICT flags set.".
Enforcing those rules shows an issue with javac: when generating final synthetic fields in value classes, the ACC_STRICT flag is not set.
Reproducer:
public class NestedTest {
value class MyValue {
int i = 0;
public String toString() {
return "MyValue("+i+","+outerInt+")";
}
}
int outerInt = 42;
MyValue v = new MyValue();
public static void main(String[] args) {
NestedTest nt = new NestedTest();
}
}
Error reported by the JVM (with
Exception in thread "main" java.lang.ClassFormatError: Illegal field modifiers in class NestedTest$MyValue: 0x1010
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1023)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at NestedTest.<init>(NestedTest.java:11)
at NestedTest.main(NestedTest.java:14)
Decompiling the value class shows the illegal field modifiers set for field this$0 :
Classfile [...]/NestedTest$MyValue.class
Last modified Apr 18, 2024; size 965 bytes
SHA-256 checksum e3a71b30c8be9b0529a3297b2bc499abf9a188455a5c4a5463e8217e89520040
Compiled from "NestedTest.java"
final value class NestedTest$MyValue
minor version: 65535
major version: 66
flags: (0x0010) ACC_FINAL
this_class: #2 // NestedTest$MyValue
super_class: #12 // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 4
Constant pool:
#1 = Fieldref #2.#3 // NestedTest$MyValue.this$0:LNestedTest;
#2 = Class #4 // NestedTest$MyValue
#3 = NameAndType #5:#6 // this$0:LNestedTest;
#4 = Utf8 NestedTest$MyValue
#5 = Utf8 this$0
#6 = Utf8 LNestedTest;
#7 = Fieldref #2.#8 // NestedTest$MyValue.i:I
#8 = NameAndType #9:#10 // i:I
#9 = Utf8 i
#10 = Utf8 I
#11 = Methodref #12.#13 // java/lang/Object."<init>":()V
#12 = Class #14 // java/lang/Object
#13 = NameAndType #15:#16 // "<init>":()V
#14 = Utf8 java/lang/Object
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Fieldref #18.#19 // NestedTest.outerInt:I
#18 = Class #20 // NestedTest
#19 = NameAndType #21:#10 // outerInt:I
#20 = Utf8 NestedTest
#21 = Utf8 outerInt
#22 = InvokeDynamic #0:#23 // #0:makeConcatWithConstants:(I)Ljava/lang/String;
#23 = NameAndType #24:#25 // makeConcatWithConstants:(I)Ljava/lang/String;
#24 = Utf8 makeConcatWithConstants
#25 = Utf8 (I)Ljava/lang/String;
#26 = Utf8 ConstantValue
#27 = Integer 0
#28 = Utf8 (LNestedTest;)V
#29 = Utf8 Code
#30 = Utf8 LineNumberTable
#31 = Utf8 MethodParameters
#32 = Utf8 toString
#33 = Utf8 ()Ljava/lang/String;
#34 = Utf8 SourceFile
#35 = Utf8 NestedTest.java
#36 = Utf8 NestHost
#37 = Utf8 BootstrapMethods
#38 = String #39 // MyValue(0,\u0001)
#39 = Utf8 MyValue(0,\u0001)
#40 = MethodHandle 6:#41 // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Looku
p;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#41 = Methodref #42.#43 // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Stri
ng;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#42 = Class #44 // java/lang/invoke/StringConcatFactory
#43 = NameAndType #24:#45 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava
/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#44 = Utf8 java/lang/invoke/StringConcatFactory
#45 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lan
g/invoke/CallSite;
#46 = Utf8 InnerClasses
#47 = Utf8 MyValue
#48 = Class #49 // java/lang/invoke/MethodHandles$Lookup
#49 = Utf8 java/lang/invoke/MethodHandles$Lookup
#50 = Class #51 // java/lang/invoke/MethodHandles
#51 = Utf8 java/lang/invoke/MethodHandles
#52 = Utf8 Lookup
{
final strictfp int i;
descriptor: I
flags: (0x0810) ACC_FINAL, ACC_STRICT
ConstantValue: int 0
final NestedTest this$0;
descriptor: LNestedTest;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
NestedTest$MyValue(NestedTest);
descriptor: (LNestedTest;)V
flags: (0x0000)
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LNestedTest;
5: aload_0
6: iconst_0
7: putfield #7 // Field i:I
10: aload_0
11: invokespecial #11 // Method java/lang/Object."<init>":()V
14: return
LineNumberTable:
line 2: 0
line 3: 5
line 2: 10
MethodParameters:
Name Flags
<no name> final mandated
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field this$0:LNestedTest;
4: getfield #17 // Field NestedTest.outerInt:I
7: invokedynamic #22, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
12: areturn
LineNumberTable:
line 6: 0
}
SourceFile: "NestedTest.java"
NestHost: class NestedTest
BootstrapMethods:
0: #40 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invok
e/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#38 MyValue(0,\u0001)
InnerClasses:
final #47= #2 of #18; // MyValue=class NestedTest$MyValue of class NestedTest
public static final #52= #48 of #50; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
Another minor issue, for field 'i', javap prints " final strictfp int i;" when it should print " final strict int i;".