Hi Peter,
On 4/4/20 3:58 AM, Peter Levart wrote:
Here I think, you are not quite right. First I need to clarify that we
are talking about the case where the method reference in above example
is not converted to lambda by javac, so the proxy class needs to
invoke the superclass method directly (without the help of lambda
bridge). I did an experiment and compiled the example with JDK 13
javac, where the patch for (JDK-8234729) is not applied yet. What I
get from this compilation is the following metafactory bootstrap
method invocation:
0: #35 REF_invokeStatic
java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#42 ()V
#43 REF_invokeVirtual test/LambdaTest.m:()V
#42 ()V
The #43 is the implMethod method handle and it is the following:
#43 = MethodHandle 5:#44 // REF_invokeVirtual
test/LambdaTest.m:()V
#44 = Methodref #2.#45 // test/LambdaTest.m:()V
#45 = NameAndType #46:#6 // m:()V
#46 = Utf8 m
#2 = Class #4 // test/LambdaTest
#4 = Utf8 test/LambdaTest
*BUT* the class that looks up this MH from the constant pool is the
subclass (test.sub.LambdaTestSub) which is equivalent to the following
programmatic lookup:
var mh = MethodHandles.lookup().findVirtual(LambdaTest.class,
"m", MethodType.methodType(void.class));
System.out.println(mh.type());
and this results in a method handle of the following type:
(LambdaTestSub)void
which is correct since the method handle *MUST* check that the passed
in instance (this) is of type LambdaTestSub or subtype or else the
"protected" access would be violated.
And since the ref type is REF_invokeVirtual, the
AbstractValidatingLambdaMetafactory assigns the following to the
implClass:
this.implMethodType = implMethod.type();
this.implInfo = caller.revealDirect(implMethod);
switch (implInfo.getReferenceKind()) {
case REF_invokeVirtual:
case REF_invokeInterface:
this.implClass = implMethodType.parameterType(0);
...which makes the implClass be LambdaTestSub in this case. Which is
correct again since we want InnerClassLambdaMetafactory to decide that
this is the special case for proxy to invoke the method via method
handle:
useImplMethodHandle =
!implClass.getPackageName().equals(implInfo.getDeclaringClass().getPackageName())
&&
!Modifier.isPublic(implInfo.getModifiers());
If implClass was test.LambdaTest as you said above, this condition
would evaluate to false, since implInfo is "invokeVirtual
test.LambdaTest.m:()void" in above case.
My bad. I mixed up with implClass and implInfo cracked from implMethod
in your question.
implInfo::getDeclaringClass() returns the declaring class of the
resolved method, which is the superclass (which is what I have been
thinking for test.LambdaTest).
implClass is the target type of the method reference.
AbstractValidatingLambdaMetafactory has clear comment:
final MethodType implMethodType; // Type of the implMethod
MethodHandle "(CC,int)String"
final Class<?> implClass; // Class for referencing
the implementation method "class CC"
So everything is OK, but my original question was the following: The
name of the generated proxy class is derived from the targetClass
which is the caller's lookup class. In this example the caller is
LambdaTestSub and this is the same as implClass in this case.
Yes.
Would those two classes always be the same in the case where the
evaluation of the above `useImplMethodHandle` boolean matters? I mean,
the decision about whether to base the proxy invocation strategy on
method handle or direct bytecode invocation is based on one class
(implClass), but the actual package of the proxy class which
effectively influences the bytecode invocation rights is taken from
another class (targetClass).
On one hand the package of the proxy class has to be the same as
targetClass if the proxy wants to be the nestmate of the targetClass
(for example to have private access to the members of the nest). But
OTOH it needs to be the same package also with implClass so that the
above decision of the proxy invocation strategy is correct. I have a
feeling that for protected virtual methods, this is true, because the
type of the 0-argument of such method handle is always the lookup
class. But am I right?
What do you think of another alternative to invoking the super
protected method in other package: What if the LMF would decide to
base the name of the proxy class on the implInfo.getDeclaringClass()
in such case? It would not have to be a nestmate of any class, just in
the same package with the method's declaring class. Consequently it
would be in the same module as the declaring class and loaded by the
same class loader. Therefore it would have to be WEAKLY referenced
from the class loader. And the Lookup instance passed to bootstrap LMF
method would not be enough for defining such class. But LMF could
obtain special powers since it is JDK internal class...
The implementation of the method reference invocation is logical part of
the caller class. I don't think spinning the proxy class in a remote
package is the desirable thing to do (injecting a class from package A
to package B).
Well, I don't know for myself. Is this situation rare enough so that
invoking via method handle is not a drawback? It only happens when
running JDK 13- compiled classes with JDK 15+ and in addition the
method reference must point to protected method in a distant class.
There are other alternatives we considered. This implementation is a
just short term solution. We plan to follow up some future enhancements
that are the possible longer-term solutions for this issue.
1. JDK-8239580 evaluate the performance of direct method handle
invocation rather than bytecode invocation for ALL cases
Direct invocation of the `implMethod` method handle by the lambda proxy
was explored in JDK 8 lambda development time. It is time to remeasure
the performance of direct method handle invocation and re-evaluate that
approach.
2. JDK-8230501 class data support. The live MethodHandle can be passed
to the hidden class to replace the current implementation to set the
implMethod in a static field of proxy class after it's defined.
3. JDK-8199386 enhance Lookup::in to support nestmates
Hope this helps.
Mandy