Reflection: how does one access a protected member in a superclass reflectively?

Peter Levart peter.levart at gmail.com
Mon Jan 22 13:04:31 UTC 2018


Hi Rony,

On 01/22/2018 10:58 AM, Peter Levart wrote:
> The 2nd problem is not trivial as you want to access a protected 
> member on behalf of some other sub-class of the member's declaring 
> class which is not cooperating (voluntarily handing you an instance of 
> its Lookup object). This currently requires the package containing the 
> member's declaring class to be opened at least to you (the Rexx 
> interpreter) and using the member.setAccessible(true) trick or 
> MethodHandles.privateLookupIn(declaringClass) equivalent for method 
> handles. Which is awkward because libraries packed as modules would 
> normally not specify that in their module descriptors and system 
> modules don't either. So you are left with either --add-opens command 
> line switches or deploying a javaagent to the JVM and using it's API 
> point java.lang.instrument.Instrumentation#redefineModule to add opens 
> to modules that way. Both approaches are not elegant, but that's what 
> is currently available, I think. 

Just one more thing... While solutions for tackling the 2nd problem 
might seem attractive to use for solving the 1st problem too, I would 
recommend not doing that. Opening all the packages of public API(s) 
might inhibit possible optimizations John Rose has been talking about. 
For reflective access to public API(s) you don't need to open the 
packages because public API(s) are in exported packages and all the 
"static" types that are needed to access them (field types, method 
return an parameter types) are also guaranteed to be part of public 
API(s) (at least good modules guarantee that). Public API(s) are 
transitively public. For public API(s) it is just a matter of finding 
the accessible member in the hierarchy where there will always be at 
least one.

For the 2nd problem, the main difficulty seems to be how to open just 
the packages that are involved in accessing the protected members on 
behalf of subclasses hoping that those packages are in minority. Here's 
one trick by using javaagent. Suppose your Rexx runtime had the 
following nonpublic class in its heart:

package runtime;

import java.util.function.BiConsumer;

class Opener {
     private static class Holder {
         static BiConsumer<Class<?>, Module> opener;
     }

     static void openPackageOfTo(Class<?> clazz, Module module) {
         Holder.opener.accept(clazz, module);
     }
}


Now if you start the JVM by supplying the -javaagent:agent.jar command 
line in addition to everything else and pack the following compiled code 
into agent.jar with the following MANIFEST:

Manifest-Version: 1.0
Premain-Class: agent.Agent

---
package agent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;

public class Agent {
     private static final String OPENER_BINARY_CLASS_NAME = 
"runtime/Opener";
     private static final String HOLDER_CLASS_NAME = 
"runtime.Opener$Holder";
     private static final String OPENER_FIELD_NAME = "opener";

     private static Instrumentation instrumentation;

     public static void premain(String agentArgs, Instrumentation inst) {
         instrumentation = inst;
         inst.addTransformer(new ClassFileTransformer() {
             @Override
             public byte[] transform(Module module,
                                     ClassLoader loader,
                                     String className,
                                     Class<?> classBeingRedefined,
                                     ProtectionDomain protectionDomain,
                                     byte[] classfileBuffer) throws 
IllegalClassFormatException {

                 // when runtime.Opener starts loading...
                 if (className.equals(OPENER_BINARY_CLASS_NAME) && 
classBeingRedefined == null) {
                     try {
                         // ...load runtime.Opener$Holder upfront using 
the same classloader
                         Class<?> holderClass = 
Class.forName(HOLDER_CLASS_NAME, true, loader);
                         // find the runtime.Opener$Holder#opener field
                         Field openerField = 
holderClass.getDeclaredField(OPENER_FIELD_NAME);
                         // and make it accessible
                         openerField.setAccessible(true);
                         // inject the BiConsumer
                         openerField.set(null, (BiConsumer<Class<?>, 
Module>) Agent::openPackageOfTo);
                     } catch (ReflectiveOperationException e) {
                         throw new InternalError(e);
                     }
                 }

                 // perform no actual transformation
                 return null;
             }
         }, false);
     }

     static void openPackageOfTo(Class<?> clazz, Module module) {
         String pn = clazz.getPackageName();
         System.out.println("Opening package " + pn + " to " + module);
         instrumentation.redefineModule(
             clazz.getModule(),
             Set.of(),
             Map.of(),
             Map.of(pn, Set.of(module)), // extra opens
             Set.of(),
             Map.of()
         );
     }
}


With such Rexx runtime specific helper agent jar you can extend the 
controlled power of java agent to your Rexx interpreter so you now have 
the power to dynamically add just those opens that are absolutely 
necessary for performing the accesses to protected members.


Regards, Peter



More information about the jigsaw-dev mailing list