Is it possible to obtain java.lang.reflection.Method directly from java class file's Constant_Method_REF?

StackOverflow https://stackoverflow.com/questions/19548573

Question

I am using BCEL to transform method byte code to achieve method interceptor with anonymous inner class style, while intercepting the method, I need to process some annotations on the intercepted method. I use BCEL to intercept method access other than java reflection.

Right now my code can work well with method that do not have primitive types. Since I don't know how to use Class.getDeclaredMethod with primitive argument types list, due to getDeclaredMethod accept methodName and Class[] array as arguments.

So the 1st question is how to do this.

And then I found in JDK7, I can directly obtain a MethodHandle reference through CONSTANT_MethodHandle with ldc_w byte code in java class file. Just like using ldc to reference a Java Class, if I can reference java.lang.reflection.Method with ldc_w directly , then I will save my time to do reflection and won't be bothered by the primitive types mentioned above in the 1st question. I tried but I failed to do this.

So the 2nd question is can I use ldc_w to reference a java.lang.reflection.Method?

And the 3rd question is can I covert MethodHandle to java.lang.reflection.Method or the Annotations on the corresponding method?

Thank you Holger, I am almost totally clear by your answer, but the following problem. I might misunderstanding your answer, right now I got exception during runtime:

Exception in thread "main" java.lang.NoClassDefFoundError: long
at net.madz.lifecycle.demo.standalone.ServiceOrder.allocateResources(ServiceOrder.java)
at net.madz.lifecycle.demo.standalone.Main2.main(Main2.java:18)
Caused by: java.lang.ClassNotFoundException: long
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 2 more

Code is as following //2.5 assign value for each element in array

    // Step 2. final InterceptContext<Void> context = new
    // InterceptContext<Void>(getClass(), this, "allocateResources",
    // new Class[] { Long.class, Long.class, Long.class });
    // 2.1 getClass()
    ilist.append(ifact.createNew(InterceptContext.class.getName()));
    ilist.append(InstructionFactory.DUP);
    ilist.append(new LDC(cgen.getConstantPool().lookupClass(interceptingClass)));
    // 2.2 load this
    ilist.append(InstructionFactory.createLoad(new ObjectType(interceptingClass), 0));// this
    // 2.3 load intercepting method
    int methodNameIndex = cgen.getConstantPool().lookupString(interceptingMethod);
    if ( -1 >= methodNameIndex ) {
        methodNameIndex = cgen.getConstantPool().addString(interceptingMethod);
    }
    ilist.append(new LDC(methodNameIndex));// methodName
    // 2.4 calculate argument size and allocate an array with same size
    ilist.append(new ICONST(types.length));
    ilist.append(ifact.createNewArray(new ObjectType("java.lang.Class"), (short) 1));
    // 2.5 assign value for each element in array
    for ( int i = 0; i < types.length; i++ ) {
        ilist.append(InstructionFactory.DUP);
        ilist.append(new ICONST(i));
        String className = convertType2ClassName(types[i]);
        int argumentClassIndex = cgen.getConstantPool().lookupClass(className); // ?
        if ( -1 >= argumentClassIndex ) {
            argumentClassIndex = cgen.getConstantPool().addClass(className);
        }
        if ( types[i].getSize() > 4 ) {
            ilist.append(new LDC_W(argumentClassIndex));
        } else {
            ilist.append(new LDC(argumentClassIndex));
        }
        ilist.append(InstructionConstants.AASTORE);
    }
    // 2.6 new InterceptContext<Void>(...
    final Type[] interceptor_method_arg_types = new Type[4];
    interceptor_method_arg_types[0] = new ObjectType("java.lang.Class");
    interceptor_method_arg_types[1] = new ObjectType("java.lang.Object");
    interceptor_method_arg_types[2] = new ObjectType("java.lang.String");
    interceptor_method_arg_types[3] = new ArrayType("java.lang.Class", 1);
    ilist.append(ifact.createInvoke(InterceptContext.class.getName(), "<init>", Type.VOID,
            interceptor_method_arg_types, Constants.INVOKESPECIAL));

And the convertType2ClassName is as following:

    private static String convertType2ClassName(Type type) {
    if ( Type.BOOLEAN.equals(type) ) {
        return boolean.class.getName();
    } else if ( Type.BYTE.equals(type) ) {
        return byte.class.getName();
    } else if ( Type.CHAR.equals(type) ) {
        return char.class.getName();
    } else if ( Type.DOUBLE.equals(type) ) {
        return double.class.getName();
    } else if ( Type.FLOAT.equals(type) ) {
        return float.class.getName();
    } else if ( Type.INT.equals(type) ) {
        return int.class.getName();
    } else if ( Type.LONG.equals(type) ) {
        return long.class.getName();
    } else if ( Type.SHORT.equals(type) ) {
        return short.class.getName();
    } else if ( type instanceof ObjectType ) {
        String signature = type.getSignature();
        if ( signature.startsWith("L") ) {
            signature = signature.substring(1);
        }
        int leftArrow = signature.indexOf("<");
        if ( -1 < leftArrow ) {
            signature = signature.substring(0, leftArrow);
        }
        if ( signature.endsWith(";") ) {
            signature = signature.substring(0, signature.length() - 1);
        }
        return signature;
    } else if ( type instanceof ArrayType ) {
        //unsupport for now
    }
    //wrong return
    return type.getSignature();
}
Was it helpful?

Solution 2

You can’t get a java.lang.reflection.Method with ldc. Doing an ldc to a CONSTANT_MethodHandle produces a MethodHandle.

These method handles can be used to execute the associated code (despite its name, it is not restricted to methods) and you can query its parameter types. But you cannot convert it into a Reflection Method. Though you can do the inverse, convert a java.lang.reflection.Method into a java.lang.invoke.MethodHandle.

Since java.lang.invoke.MethodHandle can invoke methods far faster and without the overhead of autoboxing and without putting all arguments into an array, you might wan’t to work with it anyway.


By the way, using reflection with primitive types is quite easy, e.g.

obj.getClass()
   .getDeclareMethodMethod("foo", int.class, String.class)
   .invoke(obj, 42, "blah");

It will wrap the int into an Integer and create a temporary Object[] array holding both argument Objects. But for the lookup of the method you have to specify the right primitive type.


Starting with Java 8, there is a way to convert a direct MethodHandle to a Method, so you could use an ldc instruction followed by a conversion, but you need to acquire a MethodHandles.Lookup instance and use it two times, so the byte code would look like this:

ldc             class java/lang/reflect/Method
ldc             method handle <your desired method>
invokestatic    java/lang/invoke/MethodHandles.reflectAs:(Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;)Ljava/lang/reflect/Member;
checkcast       java/lang/reflect/Method

which is slightly shorter than, e.g.

ldc             class <declaring class of your desired method>
ldc             string <name of your desired method>
ldc             method type <type signature of your desired method>
invokevirtual   java/lang/invoke/MethodType.parameterArray:()[Ljava/lang/Class;
invokevirtual   java/lang/Class.getDeclaredMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;

keep in mind that on the bytecode level, there is no enforcement of exception handling

OTHER TIPS

Holger's answer covered your first question (getDeclaredMethod with primitive arguments). The answer to your second question (use ldc to get a java.lang.reflect.Method) is still no. But the answer to your third question (convert a method handle to java.lang.reflect.Method) changed with Java 8's release -- the answer is now sometimes, including in the case you care about (converting a handle you just ldc'd).


As of Java 8, it is possible to "crack" direct method handles to get a Method, Constructor or Field object. Direct method handles are defined as the handles produced by ldc of a CONSTANT_MethodHandle constant or the find* and unreflect* methods in MethodHandles.Lookup, with no further transformations (no bound arguments etc.). There are two pathways to crack a direct method handle:

  • Call MethodHandles.reflectAs, passing the expected class (Method, Constructor or Field) and the handle to crack. This method requires ReflectPermission("suppressAccessChecks"), so may be inappropriate if you need to run under a security manager.
  • Obtain a MethodHandles.Lookup "equivalent to that which created the target method handle, or which has enough access permissions to recreate an equivalent method handle" (per the MethodHandleInfo docs), then call Lookup.revealDirect passing the handle to crack to get a MethodHandleInfo, then call MethodHandleInfo.reflectAs passing the expected class (Method, Constructor or Field) and the Lookup. To maintain security, this pathway treats caller-sensitive methods specially by checking against the lookup class.

The first pathway is more convenient, but if you need to run under a security manager, you'll need to create a Lookup in each class you need to crack method handles in (stored in a new static field initialized in <clinit>) and use that Lookup to crach the handle.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top