The reason this no longer works in Java 12 is due to JDK-8210522. This CSR says:
Summary
Core reflection has a filtering mechanism to hide security and integrity sensitive fields and methods from Class getXXXField(s) and getXXXMethod(s). The filtering mechanism has been used for several releases to hide security sensitive fields such as System.security and Class.classLoader.
This CSR proposes to extend the filters to hide fields from a number of highly security sensitive classes in java.lang.reflect and java.lang.invoke.
Problem
Many of classes in java.lang.reflect and java.lang.invoke packages have private fields that, if accessed directly, will compromise the runtime or crash the VM. Ideally all non-public/non-protected fields of classes in java.base would be filtered by core reflection and not be readable/writable via the Unsafe API but we are no where near this at this time. In the mean-time the filtering mechanism is used as a band aid.
Solution
Extend the filter to all fields in the following classes:
java.lang.ClassLoader
java.lang.reflect.AccessibleObject
java.lang.reflect.Constructor
java.lang.reflect.Field
java.lang.reflect.Method
and the private fields in java.lang.invoke.MethodHandles.Lookup that are used for the lookup class and access mode.
Specification
There are no specification changes, this is filtering of non-public/non-protected fields that nothing outside of java.base should rely on. None of the classes are serializable.
Basically, they filter out the fields of java.lang.reflect.Field
so you can't abuse them—as you're currently trying to do. You should find another way to do what you need; the answer by Eugene appears to provide at least one option.
Note: The above CSR indicates the ultimate goal is to prevent all reflective access to internal code within the java.base
module. This filtering mechanism seems to only affect the Core Reflection API, however, and can be worked around by using the Invoke API. I'm not exactly sure how the two APIs are related, so if this isn't desired behavior—beyond the dubiousness of changing a static final field—someone should submit a bug report (check for an existing one first). In other words, use the below hack at your own risk; try to find another way to do what you need first.
That said, it looks like you can still hack into the modifiers
field, at least in OpenJDK 12.0.1, using java.lang.invoke.VarHandle
.
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public final class FieldHelper {
private static final VarHandle MODIFIERS;
static {
try {
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
public static void makeNonFinal(Field field) {
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
}
}
}
The following uses the above to change the static final EMPTY_ELEMENTDATA
field inside ArrayList
. This field is used when an ArrayList
is initialized with a capacity of 0
. The end result is the created ArrayList
contains elements without having actually added any elements.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
var newEmptyElementData = new Object[]{"Hello", "World!"};
updateEmptyElementDataField(newEmptyElementData);
var list = new ArrayList<>(0);
// toString() relies on iterator() which relies on size
var sizeField = list.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(list, newEmptyElementData.length);
System.out.println(list);
}
private static void updateEmptyElementDataField(Object[] array) throws Exception {
var field = ArrayList.class.getDeclaredField("EMPTY_ELEMENTDATA");
FieldHelper.makeNonFinal(field);
field.setAccessible(true);
field.set(null, array);
}
}
Output:
[Hello, World!]
Use --add-opens
as necessary.