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) ?

Marcin Wisnicki
  • 4,511
  • 4
  • 35
  • 57
  • I'm facing the similar problem (for getters/setters), it will be great if you can share how you managed to call private methods? – bachr Feb 17 '14 at 16:35
  • See examples in answers below ? – Marcin Wisnicki Feb 18 '14 at 16:37
  • 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](http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.html#lookup%28%29). – bachr Feb 19 '14 at 09:27
  • 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. – Marcin Wisnicki Feb 19 '14 at 15:51

3 Answers3

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();
    }

}
Marcin Wisnicki
  • 4,511
  • 4
  • 35
  • 57
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).

Community
  • 1
  • 1
Thorben
  • 953
  • 13
  • 28
  • I know how to do that, but I have asked for **a public way to obtain it**. – Marcin Wisnicki Oct 03 '13 at 13:16
  • And by "public way" I mean using public API. – Marcin Wisnicki Oct 03 '13 at 13:17
  • 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. – Thorben Oct 03 '13 at 14:50
  • 1
    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.*`. – Marcin Wisnicki Oct 03 '13 at 19:00
  • 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. – Thorben Oct 05 '13 at 15:13
  • 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. – Marcin Wisnicki Oct 07 '13 at 14:32
  • `IMPL_LOOKUP` is a static field. No need to pass parameter to `Field.get`. – stepancheg Nov 13 '15 at 19:17
-3

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);
user2810910
  • 279
  • 1
  • 2