JDK-8068253 : MethodHandleInfo does not provide the referenced class
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2014-12-24
  • Updated: 2018-09-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Related Reports
Blocks :  
Relates :  
Relates :  
Relates :  
MethodHandle lookups mimic the JVM's method resolution behavior (JVMS  This involves taking a *referenced* class and a method name/descriptor, and returning a *declared* class and method.  Often, the declaring class is a supertype of the referenced class, rather than being the same as the referenced class.

MethodHandleInfo provides getDeclaringClass, but does not have any way to get the referenced class.

import java.lang.invoke.*;

public class MethodHandleInfoTest {

    public static void main(String... args) throws Exception {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodHandle mh = l.findVirtual(MethodHandleInfoTest.class, "toString", MethodType.methodType(String.class));
        MethodHandleInfo info = l.revealDirect(mh);
        System.out.println(info.getDeclaringClass()); // declaring class
        System.out.println(info.???); // referenced class


The referenced class is important, because i) the mapping from a referenced class to a declaring class is nondeterministic if there are multiple superinterfaces that declare equivalent methods (the VM may pick any one superinterface); ii) access restrictions on the declaring class may be different than access restrictions on the referenced class.

Concretely, for example, an implementation of LambdaMetafactory may wish to serialize a MethodHandle in bytecode.  In order to avoid access problems, this serialized form needs to name the referenced class, not the declaring class.
'getReferenceKind' is also relevant here. In the above example of 'I.toString()String', an original 'invokeinterface' is reported by MethodHandleInfo as an 'invokevirtual' of 'Object.toString()String'. (To see this more clearly, try resolving and then revealing an invokeinterface MethodHandle in bytecode.)

Recategorizing as a bug, because this blocks bug JDK-8172817: LambdaMetafactory can't spin the correct bytecode to invoke a MethodHandle unless the referenced class is made available to it. (Feel free to change back to "Enhancement" if necessary. I'm not sure what the conventions are for these terms.)

As an alternative to changing the API, we could just ensure that 'getDeclaringClass()' always returns the referenced class. It's a bit of a misnomer���the "declaring class" often wouldn't actually be the class that declares the method���but that's true already, because for various reasons the API already changes the "declaring class" to a subtype. Example: import java.lang.invoke.*; public class MethodHandleInfoTest2 { interface I { default void m() {} } class C implements I {} public static void main(String... args) throws Exception { MethodHandles.Lookup l = MethodHandles.lookup(); MethodHandle mh = l.findVirtual(C.class, "m", MethodType.methodType(void.class)); MethodHandleInfo info = l.revealDirect(mh); System.out.println(info.getDeclaringClass()); // prints "class MethodHandleInfoTest2$C" MethodHandle mh2 = l.findVirtual(I.class, "toString", MethodType.methodType(String.class)); MethodHandleInfo info2 = l.revealDirect(mh2); System.out.println(info2.getDeclaringClass()); // prints "class java.lang.Object" } }

In principle, a MethodHandle encodes a call site -- this is in contrast to a java.lang.reflect.Method, which encodes a declaration. So it seems to me that the class used at the call site could be encoded in the object. There may very well be implementation details that make this unattractive, but I don't see a fundamental impediment as far as the model (java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandleInfo, JVMS 4.4.8) is concerned. On the proposed alternatives: - Inferring a subclass that will "work", without knowing the original referenced subclass name, is a hard task, and its nondeterministic contract makes future binary incompatibilities unpredictable. (E.g., if I resolve from 'Foo', but the method returns to me 'random.package.Bar', then there's an unwanted dependency on that class. I might deserialize without 'random.package' on my class path, and then my program breaks.) - Using 'l.revealDirectSymbolReference(mh).getDeclaringClass()' rather than 'l.revealDirect(mh).getReferencedClass()': in both cases, 'mh' is responsible for encoding the wanted information somehow, so I'm not sure how the first buys us anything. In the minus column, using two different MethodHandleInfo objects to encode the relevant information about mh is bound to be confusing, as is the abuse of 'getDeclaringClass' to provide a class that is not, in fact, the declaring class.

Suggested fix: Add method to Lookup which provides a suitable symbolic reference: MethodHandleInfo refInfo = l.findSymbolicReference(info); Class<?> refClass = refInfo.getDeclaringClass(); System.out.println(refClass); // referenced class At the core of this method would be an "oracle" which, given a lookup class, finds a symbolic reference class RefC that will resolve back to the given info. As the reporter says, RefC is not uniquely determined. But the required behavior is uniquely determined, since whatever RefC is returned must be a component of a valid symbolic reference (or Lookup.find* call) which will resolve to the desired actual method handle. Note that this RefC might differ slightly from whatever RefC0 was originally used to materialize the method handle. If there is no such RefC (for whatever reason), then an IllegalArgumentException should be thrown, as with Lookup.revealDirect. Alternatively, starting from the mh itself, a method on Lookup can work like this: MethodHandleInfo refInfo = l.revealDirectSymbolicReference(mh); ... It does not make sense to add a new method to MethodHandleInfo, because the method handle mh by itself cannot recover the desired symbolic reference. The mh only "knows" the identity of the implementation method which was resolved; it doesn't "know" how it was resolved or from which symbolic reference. A method on Lookup could be designed which would return the RefC value noted above, but it is more robust (and no less useful?) to require the whole MethodHandleInfo (or MethodHandle) as an operand, since the details of the referenceKind might conceivably affect whether the resulting symbolic reference is valid or not.