Clojure

Reflection on internal classes fails under Java 9

Details

  • Patch:
    Code and Test
  • Approval:
    Incomplete

Description

Due to changes in reflective access for the Jigsaw module system in Java 9, the Reflector will now fail on some cases that worked in previous Java versions.

(def fac (javax.xml.stream.XMLInputFactory/newInstance))
(.createXMLStreamReader fac (java.io.StringReader. ""))

Here fac will be an instance of com.sun.xml.internal.stream.XMLInputFactoryImpl, which is an implementation of javax.xml.stream.XMLInputFactory. In the new java.xml module, javax.xml.stream is an exported package, but the XMLInputFactoryImpl is an internal implementation of the public interface in that package. The invocation of createXMLStreamReader will be reflective and the Reflector will attempt to invoke the method based on the implementation class, which is not accessible outside the module, yielding:

IllegalAccessException class clojure.lang.Reflector cannot access class com.sun.xml.internal.stream.XMLInputFactoryImpl (in module java.xml) because module java.xml does not export com.sun.xml.internal.stream to unnamed module @4722ef0c
	jdk.internal.reflect.Reflection.throwIllegalAccessException (Reflection.java:423)
	jdk.internal.reflect.Reflection.throwIllegalAccessException (Reflection.java:414)
	jdk.internal.reflect.Reflection.ensureMemberAccess (Reflection.java:112)
	java.lang.reflect.AccessibleObject.slowCheckMemberAccess (AccessibleObject.java:632)
	java.lang.reflect.AccessibleObject.checkAccess (AccessibleObject.java:624)
	java.lang.reflect.Method.invoke (Method.java:539)
	clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:93)
	clojure.lang.Reflector.invokeInstanceMethod (Reflector.java:28)

One workaround here is to avoid the reflective call by type-hinting to the public exported interface:

(.createXMLStreamReader ^javax.xml.stream.XMLInputFactory fac (java.io.StringReader. ""))

Another (undesirable) workaround is to export the private package from java.xml to the unnamed module (which is the module used when code is loaded from the classpath rather than from a module) when invoking java/javac:

java --add-exports=java.xml/com.sun.xml.internal.stream=ALL-UNNAMED --add-exports=java.xml/com.sun.xml.internal.stream.writers=ALL-UNNAMED --add-exports=java.xml/com.sun.org.apache.xerces.internal.impl=ALL-UNNAMED ...

Proposed: The attached patch will check for and catch IllegalAccessException in the Reflector. When it occurs, the Reflector will attempt to invoke the method on all super-classes and super-interfaces in case one of them can succeed.

Patch: tcrawley.CLJ-2066.2017-02-14.patch

More info:

Activity

Alex Miller made changes -
Field Original Value New Value
Approval Triaged [ 10120 ]
Affects Version/s Release 1.8 [ 10254 ]
Labels interop
Alex Miller made changes -
Labels interop interop reflection
Toby Crawley made changes -
Attachment tcrawley.CLJ-2066.2017-02-14.patch [ 16436 ]
Alex Miller made changes -
Patch Code and Test [ 10002 ]
Approval Triaged [ 10120 ] Incomplete [ 10006 ]
Description With the module system (jigsaw) as it is currently implemented in Java 9 early access builds (9-ea+144), calling a method via reflection is only allowed if the {{Method}} was retrieved for a class/interface in a package that is exported by its containing module. {{Reflector.java}} currently uses only {{target.getClass()}} to locate the {{Method}}, so reflective method invocation on a non-exported class will fail even if the method is provided by an exported parent interface or superclass.

The current workaround is to export the package to the unnamed module (where an application that doesn't explicitly use the module system runs) when invoking java/javac:

{noformat}
java --add-exports=java.xml/com.sun.xml.internal.stream=ALL-UNNAMED --add-exports=java.xml/com.sun.xml.internal.stream.writers=ALL-UNNAMED --add-exports=java.xml/com.sun.org.apache.xerces.internal.impl=ALL-UNNAMED ...
{noformat}

It's possible that this will be addressed in jigsaw before the release of Java 9. If not, {{Reflector.java}} could be modified to walk the ancestor chain if the initial invocation fails. Note that even with that change, accessing methods that are only defined on the non-exported class (i.e. methods that don't override a method from an exported superclass/interface) will require an {{--add-exports}} option.

For more details, see https://groups.google.com/d/msg/clojure-dev/Tp_WuEEcdWI/LMMQVAUYBwAJ
Due to changes in reflective access for the Jigsaw module system in Java 9, the Reflector will now fail on some cases that worked in previous Java versions.

{code}
(def fac (javax.xml.stream.XMLInputFactory/newInstance))
(.createXMLStreamReader fac (java.io.StringReader. ""))
{code}

Here {{fac}} will be an instance of {{com.sun.xml.internal.stream.XMLInputFactoryImpl}}, which is an implementation of {{javax.xml.stream.XMLInputFactory}}. In the new {{java.xml}} module, {{javax.xml.stream}} is an exported package, but the {{XMLInputFactoryImpl}} is an internal implementation of the public interface in that package. The invocation of {{createXMLStreamReader}} will be reflective and the {{Reflector}} will attempt to invoke the method based on the implementation class, which is not accessible outside the module, yielding:

{code}
IllegalAccessException class clojure.lang.Reflector cannot access class com.sun.xml.internal.stream.XMLInputFactoryImpl (in module java.xml) because module java.xml does not export com.sun.xml.internal.stream to unnamed module @4722ef0c
jdk.internal.reflect.Reflection.throwIllegalAccessException (Reflection.java:423)
jdk.internal.reflect.Reflection.throwIllegalAccessException (Reflection.java:414)
jdk.internal.reflect.Reflection.ensureMemberAccess (Reflection.java:112)
java.lang.reflect.AccessibleObject.slowCheckMemberAccess (AccessibleObject.java:632)
java.lang.reflect.AccessibleObject.checkAccess (AccessibleObject.java:624)
java.lang.reflect.Method.invoke (Method.java:539)
clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:93)
clojure.lang.Reflector.invokeInstanceMethod (Reflector.java:28)
{code}

One workaround here is to avoid the reflective call by type-hinting to the public exported interface:

{code}
(.createXMLStreamReader ^javax.xml.stream.XMLInputFactory fac (java.io.StringReader. ""))
{code}

Another (undesirable) workaround is to export the private package from java.xml to the unnamed module (which is the module used when code is loaded from the classpath rather than from a module) when invoking java/javac:

{noformat}
java --add-exports=java.xml/com.sun.xml.internal.stream=ALL-UNNAMED --add-exports=java.xml/com.sun.xml.internal.stream.writers=ALL-UNNAMED --add-exports=java.xml/com.sun.org.apache.xerces.internal.impl=ALL-UNNAMED ...
{noformat}

*Proposed:* The attached patch will check for and catch IllegalAccessException in the Reflector. When it occurs, the Reflector will attempt to invoke the method on all super-classes and super-interfaces in case one of them can succeed.

*Patch:* tcrawley.CLJ-2066.2017-02-14.patch

*More info:*

- For more details, see https://groups.google.com/d/msg/clojure-dev/Tp_WuEEcdWI/LMMQVAUYBwAJ
- For reference info, see http://openjdk.java.net/projects/jigsaw/spec/sotms/#reflective-readability
Fix Version/s Release 1.9 [ 10750 ]
Labels interop reflection interop reflection regression
Toby Crawley made changes -
Attachment tcrawley.CLJ-2066.2017-03-13.patch [ 16545 ]
Toby Crawley made changes -
Attachment tcrawley.CLJ-2066.2017-02-14.patch [ 16436 ]
Toby Crawley made changes -
Attachment tcrawley.CLJ-2066.2017-03-16.patch [ 16551 ]
Toby Crawley made changes -
Attachment tcrawley.CLJ-2066.2017-03-13.patch [ 16545 ]
Alex Miller made changes -
Comment [ My understanding (which may be faulty) is that the new module system is independent from the classloader setup (although the base classloader hierarchy has changed a bit) and that reflection is still largely the same with respect to examining classes and methods. But you may run into issues with *invoking* constructors or methods that are not allowed according to access restrictions. I'm not sure if the ability to load classes is affected (but I didn't think so). ]

People

Vote (1)
Watch (7)

Dates

  • Created:
    Updated: