JDK9 encapsulation problem
Peter Levart
peter.levart at gmail.com
Fri Sep 9 20:00:23 UTC 2016
Hi Stephen,
I see your problem...
On 09/09/2016 07:32 PM, Stephen Felts wrote:
>
> 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-----
> From: Peter Levart [mailto:peter.levart at gmail.com]
> Sent: Friday, September 09, 2016 11:54 AM
> To: Stephen Felts; jigsaw-dev at openjdk.java.net
> Subject: Re: JDK9 encapsulation problem
>
> Hi Stephen,
>
> On 09/09/2016 04:30 PM, Stephen Felts wrote:
>
> > We have an application that is running into a problem with a utility
> program. Below is a standalone reproducer.
>
> >
>
> >
>
> >
>
> > The 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...
>
> >
>
> >
>
> >
>
> > import 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 is the following:
>
> 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...
>
> >
>
> > isConn.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
>
> > does not export sun.nio.ch to unnamed module @3857f613
>
> >
>
> > at
>
> > jdk.internal.reflect.Reflection.throwInaccessibleObjectException(java.
>
> > base at 9-ea/Reflection.java:414 <mailto:base at 9-ea/Reflection.java:414>)
>
> >
>
> > at
>
> >
> java.lang.reflect.AccessibleObject.checkCanSetAccessible(java.base at 9-e
> <mailto:java.base at 9-e>
>
> > a/AccessibleObject.java:174)
>
> >
>
> > at
>
> >
> java.lang.reflect.Method.checkCanSetAccessible(java.base at 9-ea/Method.j
> <mailto:java.base at 9-ea/Method.j>
>
> > ava:192)
>
> >
>
> > at
>
> >
> java.lang.reflect.Method.setAccessible(java.base at 9-ea/Method.java:186
> <mailto:java.base at 9-ea/Method.java:186>)
>
> >
>
> > 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).
>
> >
>
> > What'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
>
> >
>
> >
>
> >
>
> > Many people especially tool makers have this problem:
>
> >
>
> > https://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
>
> >
>
> >
>
> >
>
> >
>
More information about the jigsaw-dev
mailing list