Get declared fields of java.lang.reflect.Fields in jdk12

后端 未结 2 1911
生来不讨喜
生来不讨喜 2020-11-27 07:03

In java8 it was possible to access fields of class java.lang.reflect.Fields using e.g.

Field.class.getDeclaredFields();

In java12 (starting

相关标签:
2条回答
  • 2020-11-27 07:57

    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.

    0 讨论(0)
  • 2020-11-27 08:03

    You can't. This was a change done on purpose.

    For example, you could use PowerMock and it's @PrepareForTest - under the hood it uses javassist (bytecode manipulation) if you want to use that for testing purposes. This is exactly what that bug in the comments suggests to do.

    In other words, since java-12 - there is no way to access that via vanilla java.

    0 讨论(0)
提交回复
热议问题