web-dev-qa-db-fra.com

Obtenez les champs déclarés de Java.lang.reflect.Fields dans jdk12

En Java8, il était possible d'accéder aux champs de la classe Java.lang.reflect.Fields en utilisant par ex.

Field.class.getDeclaredFields();

En Java12 (en commençant par Java9?), Cela ne renvoie qu'un tableau vide. Cela ne change pas même avec

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

ensemble.

Des idées pour y parvenir? (Mis à part le fait que cela pourrait être une mauvaise idée, je veux pouvoir changer un champ "final statique" dans mon code pendant les tests junit via la réflexion. Cela a été possible avec Java8 en changeant les "modificateurs"

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

)

15
Henning

La raison pour laquelle cela ne fonctionne plus dans Java 12 est due à JDK-8210522 . Ce CSR dit:

Résumé

La réflexion centrale a un mécanisme de filtrage pour masquer les champs et méthodes sensibles à la sécurité et à l'intégrité des classes getXXXField (s) et getXXXMethod (s). Le mécanisme de filtrage a été utilisé pour plusieurs versions pour masquer les champs sensibles à la sécurité tels que System.security et Class.classLoader.

Ce CSR propose d'étendre les filtres pour masquer les champs d'un certain nombre de classes hautement sensibles à la sécurité dans Java.lang.reflect et Java.lang.invoke.

Problème

De nombreuses classes dans les packages Java.lang.reflect et Java.lang.invoke ont des champs privés qui, s'ils sont accessibles directement, compromettront l'exécution ou planteront la machine virtuelle. Idéalement, tous les champs de classes non publics/non protégés dans Java.base seraient filtrés par réflexion de base et ne seraient pas lisibles/inscriptibles via l'API Unsafe, mais nous n'en sommes pas du tout proches pour le moment. En attendant, le mécanisme de filtrage est utilisé comme pansement.

Solution

Étendez le filtre à tous les champs des classes suivantes:

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

et les champs privés dans Java.lang.invoke.MethodHandles.Lookup qui sont utilisés pour la classe de recherche et le mode d'accès.

Spécification

Il n'y a pas de changement de spécification, c'est le filtrage des champs non publics/non protégés sur lesquels rien en dehors de Java.base ne devrait s'appuyer. Aucune des classes n'est sérialisable.

Fondamentalement, ils filtrent les champs de Java.lang.reflect.Field afin que vous ne puissiez pas en abuser - comme vous essayez actuellement de le faire. Vous devriez trouver une autre façon de faire ce dont vous avez besoin; le réponse d'Eugene semble fournir au moins une option.


Remarque : Le CSR ci-dessus indique que le but ultime est d'empêcher tout accès réfléchi au code interne dans le Java.base module. Cependant, ce mécanisme de filtrage semble affecter uniquement l'API Core Reflection et peut être contourné en utilisant l'API Invoke. Je ne sais pas exactement comment les deux API sont liées, donc si ce n'est pas le comportement souhaité - au-delà du doute de changer un champ final statique - quelqu'un devrait soumettre un rapport de bogue (vérifier s'il existe un Un premier). En d'autres termes, utilisez le hack ci-dessous à vos risques et périls ; essayez de trouver une autre façon de faire ce dont vous avez besoin en premier.


Cela dit, il semble que vous pouvez toujours pirater le champ modifiers, au moins dans OpenJDK 12.0.1, en utilisant 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);
        }
    }

}

Ce qui suit utilise ce qui précède pour modifier la finale statique EMPTY_ELEMENTDATA champ à l'intérieur ArrayList. Ce champ est utilisé lorsqu'un ArrayList est initialisé avec une capacité de 0. Le résultat final est que le ArrayList créé contient des éléments sans avoir réellement ajouté d'éléments.

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

}

Production:

[Hello, World!]

Utilisation --add-opens le cas échéant.

17
Slaw

Tu ne peux pas. C'était un changement fait exprès.

Par exemple, vous pouvez utiliser PowerMock et c'est @PrepareForTest - sous le capot, il utilise javassist (manipulation de bytecode) si vous souhaitez l'utiliser à des fins de test. C'est exactement ce que ce bug dans les commentaires suggère de faire.

En d'autres termes, puisque Java-12 - il n'y a aucun moyen d'y accéder via Vanilla Java.

7
Eugene