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