Получить объявленные поля java.lang.reflect.Fields в jdk12

В java8 было возможно получить доступ к полям класса java.lang.reflect.Fields, используя например

Field.class.getDeclaredFields();

В java12 (начиная с java9?) Это возвращает только пустой массив. Это не меняется даже с

--add-opens java.base/java.lang.reflect=ALL-UNNAMED

задавать.

Есть идеи как этого добиться? (Исходя из того, что это может быть плохой идеей, я хочу иметь возможность изменять поле "static final" в моем коде во время тестирования junit с помощью отражения. Это стало возможным с помощью java8 путем изменения "модификаторов").

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(myfield, myfield.getModifiers() & ~Modifier.FINAL);

)

4 ответа

Причина, по которой это больше не работает в Java 12, связана с JDK-8210522. Этот CSR говорит:

Резюме

Базовое отражение имеет механизм фильтрации, позволяющий скрыть чувствительные к безопасности и целостности поля и методы от классов getXXXField(s) и getXXXMethod(s). Механизм фильтрации был использован для нескольких выпусков, чтобы скрыть чувствительные к безопасности поля, такие как System.security и Class.classLoader.

Этот CSR предлагает расширить фильтры, чтобы скрыть поля от ряда классов с высокой степенью безопасности в java.lang.reflect и java.lang.invoke.

проблема

Многие из классов в пакетах java.lang.reflect и java.lang.invoke имеют закрытые поля, которые при прямом доступе могут поставить под угрозу среду выполнения или привести к сбою виртуальной машины. В идеале все непубличные / незащищенные поля классов в java.base должны быть отфильтрованы по отражению ядра и недоступны для чтения / записи через небезопасный API, но в данный момент мы не находимся поблизости от этого. В то же время механизм фильтрации используется в качестве помощи полосы.

Решение

Расширить фильтр на все поля в следующих классах:

java.lang.ClassLoader
java.lang.reflect.AccessibleObject
java.lang.reflect.Constructor
java.lang.reflect.Field
java.lang.reflect.Method

и частные поля в java.lang.invoke.MethodHandles.Lookup, которые используются для класса поиска и режима доступа.

Спецификация

Изменений в спецификации нет, это фильтрация непубличных / незащищенных полей, на которые не следует полагаться ничего за пределами java.base. Ни один из классов не является сериализуемым.

В основном они отфильтровывают поля java.lang.reflect.Field так что вы не можете злоупотреблять ими - как вы сейчас пытаетесь. Вы должны найти другой способ сделать то, что вам нужно; Похоже, что ответ Юджина дает хотя бы один вариант.


Примечание. Приведенный выше CSR указывает на то, что конечной целью является предотвращение любого отражающего доступа к внутреннему коду внутри java.base модуль. Этот механизм фильтрации, по-видимому, влияет только на Core Reflection API, и его можно обойти, используя Invoke API. Я не совсем уверен, как связаны эти два API, поэтому, если это нежелательное поведение - за исключением сомнительности изменения статического конечного поля - кто-то должен отправить отчет об ошибке (сначала проверьте существующий). Другими словами, используйте приведенный ниже взлом на свой страх и риск; попробуйте найти другой способ сделать то, что вам нужно в первую очередь.


Тем не менее, похоже, что вы все еще можете взломать modifiers поле, по крайней мере, в OpenJDK 12.0.1, используя 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);
        }
    }

}

Следующее использует вышеизложенное, чтобы изменить статический финал EMPTY_ELEMENTDATA поле внутри ArrayList, Это поле используется, когда ArrayList инициализируется с емкостью 0, Конечным результатом является созданный ArrayList содержит элементы без добавления каких-либо элементов.

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);
    }

}

Выход:

[Hello, World!]

использование --add-opens как необходимо.

Я нашел способ, и он работал на JDK 8, 11, 17.

      Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
Field modifiers = null;
for (Field each : fields) {
    if ("modifiers".equals(each.getName())) {
        modifiers = each;
        break;
    }
}
assertNotNull(modifiers);

Не забудьте установить следующие аргументы при использовании JDK 11 или выше:

      --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED

Ты не можешь Это было изменение сделано специально.

Например, вы можете использовать PowerMock И его @PrepareForTest - под капотом он использует javassist (манипулирование байт-кодом), если вы хотите использовать это для целей тестирования. Это именно то, что предлагает сделать эта ошибка в комментариях.

Другими словами, так как java-12 - нет никакого способа получить доступ к этому через ванильную Java.

Это работает в JDK 17.

      import java.lang.reflect.Field;
import sun.misc.Unsafe;

/**
 * @author Arnah
 * @since Feb 21, 2021
 **/
public class FieldUtil{

    private static Unsafe unsafe;

    static{
        try{
            final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            unsafe = (Unsafe) unsafeField.get(null);
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }

    public static void setFinalStatic(Field field, Object value) throws Exception{
        Object fieldBase = unsafe.staticFieldBase(field);
        long fieldOffset = unsafe.staticFieldOffset(field);

        unsafe.putObject(fieldBase, fieldOffset, value);
    }
}
      public class YourClass{
    public static final int MAX_ITEM_ROWS = 35_000;
}

FieldUtil.setFinalStatic(YourClass.class.getDeclaredField("MAX_ITEM_ROWS"), 1);
Другие вопросы по тегам