Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep reflection fails in xxSkin classes

Since update 9-u175, java permits illegal access by default thus allowing all the old reflection tricks. Works fine, except when it comes to classes in control.skin (maybe others as well, didn't check) - to reproduce, run the example below, click the button and see how access succeeds until the line that tries to access a private field in ButtonSkin. The stacktrace:

Exception in thread "JavaFX Application Thread" java.lang.reflect.InaccessibleObjectException: 
Unable to make field private final com.sun.javafx.scene.control.behavior.BehaviorBase javafx.scene.control.skin.ButtonSkin.behavior accessible: 
module javafx.controls does not "opens javafx.scene.control.skin" to unnamed module @537fb2
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)

My context: jdk9-u175, eclipse-oxygen-R with patch for java9, access rules in the project are set to allow javafx/**

The question is: who's the culprit? FX, Eclipse, the ea or ..?

The example:

import java.lang.reflect.Field;
import java.util.logging.Logger;

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SkinBase;
import javafx.scene.control.skin.ButtonSkin;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;

public class AccessFieldFX extends Application {

    private Parent getContent() {
        Button button = new Button("something to click on");
        // okay
        Object def = invokeGetFieldValue(Button.class, button, "defaultButton");

        button.setOnAction(e -> {
            ButtonSkin skin = (ButtonSkin) button.getSkin();
            // okay
            LambdaMultiplePropertyChangeListenerHandler cl =
                    (LambdaMultiplePropertyChangeListenerHandler) invokeGetFieldValue(SkinBase.class, skin, "lambdaChangeListenerHandler");
            // okay
            Object clField = invokeGetFieldValue(LambdaMultiplePropertyChangeListenerHandler.class, cl, "EMPTY_CONSUMER");
            // failure
            Object beh = invokeGetFieldValue(ButtonSkin.class, skin, "behavior");
        });
        BorderPane pane = new BorderPane(button);
        return pane;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent(), 600, 400));
//        primaryStage.setTitle(FXUtils.version());
        primaryStage.show();
    }

    public static Object invokeGetFieldValue(Class declaringClass, Object target, String name) {
        try {
            Field field = declaringClass.getDeclaredField(name);
            field.setAccessible(true);
            return field.get(target);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        launch(args);
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(AccessFieldFX.class.getName());
}
like image 351
kleopatra Avatar asked Jul 28 '17 09:07

kleopatra


1 Answers

To prevent accidental dependencies on new APIs, illegal access is only granted to packages that existed before Java 9 - I hence assume com.sun.javafx.scene.control.behavior is new.

In his mail with the revised proposal for --illegal-access Mark Reinhold writes (emphasis mine):

--illegal-access=permit

This mode opens each package in each module in the run-time image to code in all unnamed modules, i.e., code on the class path, if that package existed in JDK 8. This enables both static access, i.e., by compiled bytecode, and deep reflective access, via the platform's various reflection APIs.

The first reflective-access operation to any such package causes a warning to be issued, but no warnings are issued after that point. This single warning describes how to enable further warnings.

This mode will be the default for JDK 9. It will be removed in a future release.

like image 178
Nicolai Parlog Avatar answered Oct 23 '22 09:10

Nicolai Parlog