Invoke private method with java.lang.invoke.MethodHandle
Asked Answered
E

3

14

How can I invoke private method using method handles ?

As far as I can see there are only two kinds of publicly accessible Lookup instances:

  • MethodHandles.lookup()
  • MethodHandles.publicLookup()

and neither allows unrestricted private access.

There is the non-public Lookup.IMPL_LOOKUP that does what I want. Is there some public way to obtain it (assuming that SecurityManager allows it) ?

Emmetropia answered 2/10, 2013 at 10:49 Comment(4)
I'm facing the similar problem (for getters/setters), it will be great if you can share how you managed to call private methods?Curium
See examples in answers below ?Emmetropia
It uses reflection to set the accessibility before calling invoke, I was wondering why should do that as lookup() is supposed to give the caller access to direct/private fields and methods as specified in the documentation.Curium
It's only using setAccessible to obtain special privileged implementation of Lookup that can invoke private members regardless of whether they are accessible to caller.Emmetropia
E
17

Turns out it's possible with Lookup#unreflect(Method) and temporarily making method accessible (potentially introducing small security issue unless done during program initialization).

Here is modified main method from Thorben's answer:

public static void main(String[] args) {

    Lookup lookup = MethodHandles.lookup();
    NestedTestClass ntc = new Program().new NestedTestClass();

    try {
        // Grab method using normal reflection and make it accessible
        Method pm = NestedTestClass.class.getDeclaredMethod("gimmeTheAnswer");
        pm.setAccessible(true);

        // Now convert reflected method into method handle
        MethodHandle pmh = lookup.unreflect(pm);
        System.out.println("reflection:" + pm.invoke(ntc));

        // We can now revoke access to original method
        pm.setAccessible(false);

        // And yet the method handle still works!
        System.out.println("handle:" + pmh.invoke(ntc));

        // While reflection is now denied again (throws exception)
        System.out.println("reflection:" + pm.invoke(ntc));

    } catch (Throwable e) {
        e.printStackTrace();
    }

}
Emmetropia answered 17/6, 2014 at 17:17 Comment(1)
Better still is to use MethodHandles.privateLookupIn, added in Java SE 9.Teasley
P
3

I don't know, if this is what you really want. Perhaps you could give some more information about what you want to achieve with it. But if you want to access Lookup.IMPL_LOOKUP, you can do it like in this code sample:

public class Main {

public static void main(String[] args) {

    Lookup myLookup = MethodHandles.lookup(); // the Lookup which should be trusted
    NestedTestClass ntc = new Main().new NestedTestClass(); // test class instance

    try {
        Field impl_lookup = Lookup.class.getDeclaredField("IMPL_LOOKUP"); // get the required field via reflections
        impl_lookup.setAccessible(true); // set it accessible
        Lookup lutrusted = (Lookup) impl_lookup.get(myLookup); // get the value of IMPL_LOOKUP from the Lookup instance and save it in a new Lookup object

        // test the trusted Lookup
        MethodHandle pmh = lutrusted.findVirtual(NestedTestClass.class, "gimmeTheAnswer", MethodType.methodType(int.class));
        System.out.println(pmh.invoke(ntc));

    } catch (Throwable e) {
        e.printStackTrace();
    }

}

// nested class with private method for testing
class NestedTestClass{

    @SuppressWarnings("unused")
    private int gimmeTheAnswer(){

        return 42;
    }
}

}

It works with JDK 7, but could break in JDK 8. And be cautious! My antivirus gave an alarm when I executed it. I think there isn't a public or clean way to do it.

I had a similar issue and finally found a solution: Access non-public (java-native) classes from JDK (7).

Pedicular answered 3/10, 2013 at 11:31 Comment(7)
I know how to do that, but I have asked for a public way to obtain it.Emmetropia
And by "public way" I mean using public API.Emmetropia
I don't think that this is possible with a public API. Accessing private fields or methods directly by public methods would break Java's security system.Pedicular
No, it wouldn't. You can attempt to access private members USING public API already via java.lang.reflect.*. I'm asking about doing the same but with java.lang.invoke.*.Emmetropia
Ah okay, now I think I understand what you want. Yes, Reflections can do this, but with the drawback of enormous security checks. Thus they are a kind of special case. Invokedynamic is mainly built for speed and as far as I know there aren't these security checks like with Reflections. Correct me, if this is wrong. Nevertheless I think the neccessary security checks would destroy invokedynamic's performance goals.Pedicular
Avoiding per-call security checks is one of the reasons why I want to use invokedynamic. If there was a public API that can return IMPL_LOOKUP it would only need to perform access check once.Emmetropia
IMPL_LOOKUP is a static field. No need to pass parameter to Field.get.Ductile
G
-4

Here's a similiar solution which includes arguments in a private function (I just happened to have the code lying around from a previous project):

class name: InspectionTree.java

function signature: private String getSamePackagePathAndName(String className, String classPath)

String firstName = "John";
String lastName = "Smith";

//call the class's constructor to set up the instance, before calling the private function
InspectionTree inspectionTree = new InspectionTree(firstName, lastName);

String privateMethodName ="getSamePackagePathAndName";        
Class[] privateMethodArgClasses = new Class[] { String.class, String.class };

Method method = 
         inspectionTree.getClass().getDeclaredMethod(privateMethodName, privateArgClasses);

method.setAccessible(true);

String className = "Person";
String classPath = "C:\\workspace";

Object[] params = new Object[]{className, classPath};        

//note the return type of function 'getSamePackagePathAndName' is a String, so we cast
//the return type here as a string
String answer=  (String)method.invoke(inspectionTree, params);

method.setAccessible(false);
Grunter answered 2/10, 2013 at 12:30 Comment(1)
And where should this misterious InspectionTree come from?Pedalfer

© 2022 - 2024 — McMap. All rights reserved.