Получить объявленные поля java.lang.reflect.Fields в jdk12 - PullRequest
12 голосов
/ 08 мая 2019

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

)

Ответы [ 2 ]

9 голосов
/ 08 мая 2019

Причина, по которой это больше не работает в 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 при необходимости.

7 голосов
/ 08 мая 2019

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

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

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

...