Hi Stephen,
I see your problem...
This is a general problem with utility programs. This isn't, unlike
the sample, something where I can just re-code it to use the public
interface. The utility program isn't checking to see what is public
and what is not. It looks up the class and invokes on it, creating or
opening the object. Then the object is used for further operations.
The class is public. We grep'ed the entire code base looking for an
import or dynamic reference to the internal API but obviously didn't
find it.
This is an interpreted language where obj = clzz.open();
obj.method2() is invoked in a general purpose way.
Class clzz = Class.forName("clzz");
Method m = clzz.getMethod("open");
Object obj = m.invoke(null);
Method m2 = obj.getClass().getMethod("method2");
m.setAccessible(true);
m2.invoke(obj);
If this problem isn't fixed in the JDK, then I might have code that
works in JDK 9 and is broken when someone decides to re-implement
something directly using an internal package in JDK 10.
If you are in a position to change the implementation of the interpreted
language, then there might be a general way to reflectively invoke
methods on objects of unknown types that will always be the right way to
invoke them. When looking up a static method in some class 'clzz' then
the above code is ok: clzz.getMethod(name, parameterTypes...), but when
looking up an instance method to be called upon an instance of some
object, then something like the following could be used:
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class Test {
public static void main(String[] args) throws Exception {
Class clzz = Class.forName("java.nio.channels.SocketChannel");
Method open = clzz.getMethod("open");
Object obj = open.invoke(null);
Method isConn = getAccessibleMethod(Test.class, obj.getClass(),
"isConnected");
// no need to invoke setAccessible
System.out.println(isConn.invoke(obj));
}
/**
* Returns a public method in a public class or interface in a package
* exported to the module of the caller class.
*
* @param caller the caller class that will invoke the method
* @param clazz the runtime class of the object upon which
the method
* will be called
* @param name the name of the method
* @param parameterTypes the types of method parameters
* @return a public method that is accessible to the caller class or
* null if no such method exists
*/
public static Method getAccessibleMethod(Class<?> caller, Class<?>
clazz,
String name, Class<?>...
parameterTypes)
throws NoSuchMethodException {
Method res;
// 1st lookup declared method if the class or interface is public
// and its package is exported to the caller module
if (Modifier.isPublic(clazz.getModifiers()) &&
clazz.getModule().isExported(clazz.getPackageName(),
caller.getModule()) &&
(res = findPublicMethod(clazz.getDeclaredMethods(),
name, parameterTypes)) != null) {
return res;
}
// 2nd search the superclass recursively if there is one
Class<?> superClass = clazz.getSuperclass();
if (superClass != null &&
(res = getAccessibleMethod(caller, superClass,
name, parameterTypes)) != null) {
return res;
}
// finally search the directly implemented interfaces
for (Class<?> intf : clazz.getInterfaces()) {
if ((res = getAccessibleMethod(caller, intf,
name, parameterTypes)) !=
null) {
return res;
}
}
// no luck
return null;
}
private static Method findPublicMethod(Method[] methods, String name,
Class<?>... parameterTypes) {
Method res = null;
for (Method m : methods) {
if (Modifier.isPublic(m.getModifiers()) &&
m.getName().equals(name) &&
Arrays.equals(m.getParameterTypes(), parameterTypes) &&
(res == null ||
res.getReturnType().isAssignableFrom(m.getReturnType()))) {
res = m;
}
}
return res;
}
}
This is similar logic as is used in Class::getMethod() but skips methods
that are not accessible.
Regards, Peter
-----Original Message-----
Sent: Friday, September 09, 2016 11:54 AM
Subject: Re: JDK9 encapsulation problem
Hi Stephen,
Post by Stephen FeltsWe have an application that is running into a problem with a utility
program. Below is a standalone reproducer.
Post by Stephen FeltsThe program does not import the SPI package sun.nio.ch - it isn't
aware of
it, and SocketChannel.isConnected() is a public method of a public
type. In
short, it does not break any law of encapsulation, so call
setAccessible(true) should be OK.
Ok, but...
Post by Stephen Feltsimport java.lang.reflect.Method;
public class JDK9Nio {
public static void main(String args[]) throws Exception {
call();
}
public static void call() throws Exception {
Class clzz = Class.forName("java.nio.channels.SocketChannel");
Method open = clzz.getMethod("open");
Object obj = open.invoke(null);
Method isConn = obj.getClass().getMethod("isConnected");
...This is a classical reflection anti-pattern. What program should be
doing to call the public and exported SocketChannel.isConnected()
Method isConn = SocketChannel.class.getMethod("isConnected");
obj.getClass().getMethod(...) is rarely what is desired. What you get
back is a Method object for method declared in a package-private class.
That's why setAccessible() was needed. And now in JDK 9, this class is
also in a non-exported package, so setAccessible() does not help any
more. But I see your point. It worked before and is not working any more...
Post by Stephen FeltsisConn.setAccessible(true); // OK with JDK8, fail with JDK9
System.out.println(isConn.invoke(obj));
}
}
java JDK9Nio
Exception in thread "main"
java.lang.reflect.InaccessibleObjectException: Unable to make member
of class sun.nio.ch.SocketChannelImpl accessible: module java.base
at
jdk.internal.reflect.Reflection.throwInaccessibleObjectException(java.
at
a/AccessibleObject.java:174)
at
ava:192)
at
at JDK9Nio.call(JDK9Nio.java:14)
at JDK9Nio.main(JDK9Nio.java:6)
It's easy to say that the program should be re-written and the
setAccessible is not necessary but this is a utility program over
which the application has no control (a jython script).
Post by Stephen FeltsWhat's ugly is that the internal implementation is showing through
to the application.
Maybe there could be a solution in supporting such sloppy programming
though. If the reflective invocation is performed to a virtual method,
then the JVM virtual dispatch chooses the method declared in the most
specific class regardless of what the Method object used is telling
about the method's declaring class. So if there exists at least one
matching method declared in the hierarchy of types comprising the
runtime type of the object upon which the method is being invoked, and
such method is accessible to the invoker, such invocation could be
allowed. The rationale is simple: if the invocation dispatches to the
same method for distinct Method objects, it should also succeed or not
succeed consistently regardless of which Method object was used to
perform the invocation.
But such access check would surely be much slower. It's better to just
fix the program (if you can :-( ).
Regards, Peter
Post by Stephen Feltshttps://bugs.eclipse.org/bugs/show_bug.cgi?id=482318
https://netbeans.org/bugzilla/show_bug.cgi?id=258952
https://netbeans.org/bugzilla/show_bug.cgi?id=262765
http://mail.openjdk.java.net/pipermail/quality-discuss/2015-November/0
00468.html
https://community.oracle.com/thread/3937249