In method InlineKlass::InlineKlass::copy_payload_to_addr(), used to copy the payload of a value, there's this shortcut when writing null to a nullable flat value:
if (is_payload_marked_as_null((address)src)) {
if (!contains_oops()) {
mark_payload_as_null((address)dst);
return;
}
The idea of this shortcut is that if there's no oop in the flat value, it is sufficient to update the null marker to 'is_null' without changing the rest of the flat value.
But not resetting the rest of the flat value is not without consequence. If the flat value is stored in another value object, the old value of the field could change the result of a substitutability test.
This could only happen if the field is initialized multiple times, like in the code below:
static value record MyInt(int i) { }
static value class MyValue {
byte b;
MyInt mi;
MyValue(byte b, boolean z) {
this.b = b;
if (z) {
mi = new MyInt(2);
}
mi = null;
}
}
This code cannot be compiled by javac, because javac enforces a single initialization rule for these strict final fields. However, the JVM is more lenient and the verification only guarantee is that the field will be initialized before a call to super(), but there's no limit on the
number of putfield that can be applied to this field. So class files generators other than javac can produce a scenario has described above.
This left over of the previous content of the field can then interfere in the substitutability test, because the algorithm compares chunks of memory, and is agnostic to the presence and the semantic of null markers.
The shortcut in InlineKlass::InlineKlass::copy_payload_to_addr() must be removed.
if (is_payload_marked_as_null((address)src)) {
if (!contains_oops()) {
mark_payload_as_null((address)dst);
return;
}
The idea of this shortcut is that if there's no oop in the flat value, it is sufficient to update the null marker to 'is_null' without changing the rest of the flat value.
But not resetting the rest of the flat value is not without consequence. If the flat value is stored in another value object, the old value of the field could change the result of a substitutability test.
This could only happen if the field is initialized multiple times, like in the code below:
static value record MyInt(int i) { }
static value class MyValue {
byte b;
MyInt mi;
MyValue(byte b, boolean z) {
this.b = b;
if (z) {
mi = new MyInt(2);
}
mi = null;
}
}
This code cannot be compiled by javac, because javac enforces a single initialization rule for these strict final fields. However, the JVM is more lenient and the verification only guarantee is that the field will be initialized before a call to super(), but there's no limit on the
number of putfield that can be applied to this field. So class files generators other than javac can produce a scenario has described above.
This left over of the previous content of the field can then interfere in the substitutability test, because the algorithm compares chunks of memory, and is agnostic to the presence and the semantic of null markers.
The shortcut in InlineKlass::InlineKlass::copy_payload_to_addr() must be removed.