Hi Peter,
I added a JBS comment [1] to describe this special case trying to put
the story together (let me know if it needs more explanation). More
comment inline below.
On 4/3/20 4:40 AM, Peter Levart wrote:
Ok, I think I found one such use-case. In the following example:
package test;
public class LambdaTest {
protected void m() {
}
}
package test.sub;
public class LambdaTestSub extends test.LambdaTest {
public void test() {
Runnable r = this::m;
r.run();
}
}
Yes.
This is specific for binary compatibility. the invocation of a
protected method inherited from its supertype in a different package.
The lambda proxy is in the same package as the target class (`test.sub`
in the example above) but it has no access to `test.LambdaTest::m`.
...when compiled with JDK 14 javac. In this case the implClass is
test.sub.LambdaTestSub while implInfo is "invokeVirtual
test.LambdaTest.m:()void" and the method is not public.
In JDK 14, a lambda proxy `test.sub.LambdaTestSub$Lambda$$1234` is VM
anonymous class which has a special powerful access as if the host
class. This proxy class, even though it's not an instance of
`test.LambdaTest`, can invoke the protected`test.LambdaTest.m:()void`
member.
Anyway, the name of the proxy class is derived from the targetClass
(and therefore shares the same package with targetClass) which is
caller's lookup class. Is targetClass always the same as implClass in
the invokeVirtual/invokeInterface case?
implMethod is the direct method handle describing the implementation
method resolved by the VM. So it depends.
In the above example, it's `test.LambdaTest.m:()void` and implClass is
test.LambdaTest. The targetClass is test.sub.LambdaTestSub which is
*NOT* the same as implClass. That's why we change if they are in the
same package.
It can be invoking a method in targetClass or a method in another class
in the same package with package access, then implClass may or may not
be the same as targetClass.
I also noticed that JDK 15 patched javac transforms method reference
in above code into a lambda method. But looking at the javac changes
in the patch, I don't quite see where this distinction between JDK 14
and patched JDK 15 javac comes from.
javac has been changed in JDK 14 to synthesize a bridge method if it's a
method reference to access a protected member in a remote supertype
(JDK-8234729).
BTW, the new tests relevant to this scenario are under
test/jdk/java/lang/invoke/lambda/superProtectedMethod.
From the changes to method
com.sun.tools.javac.comp.LambdaToMethod.LambdaAnalyzerPreprocessor.ReferenceTranslationContext#needsConversionToLambda:
final boolean needsConversionToLambda() {
return interfaceParameterIsIntersectionOrUnionType() ||
isSuper ||
needsVarArgsConversion() ||
isArrayOp() ||
# isPrivateInOtherClass() ||
isProtectedInSuperClassOfEnclosingClassInOtherPackage() ||
!receiverAccessible() ||
(tree.getMode() == ReferenceMode.NEW &&
tree.kind != ReferenceKind.ARRAY_CTOR &&
(tree.sym.owner.isLocal() ||
tree.sym.owner.isInner()));
}
...I would draw the conclusion that conversion to lambda is performed
in less cases not in more.
Jan and Srikanath may be able to explain this further.
Hm.
Regards, Peter
On 4/3/20 11:11 AM, Peter Levart wrote:
Hi Mandy,
Good work.
I'm trying to find out which language use-case is covered by the
InnerClassLambdaMetafactory needing to inject method handle into the
generated proxy class to be invoked instead of proxy class directly
invoking the method:
useImplMethodHandle =
!implClass.getPackageName().equals(implInfo.getDeclaringClass().getPackageName())
&&
!Modifier.isPublic(implInfo.getModifiers());
If I check what implClass and implInfo get resolved to in
AbstractValidatingLambdaMetafactory, there are several cases:
this.implInfo = caller.revealDirect(implMethod);
switch (implInfo.getReferenceKind()) {
case REF_invokeVirtual:
case REF_invokeInterface:
this.implClass = implMethodType.parameterType(0);
// reference kind reported by implInfo may not match
implMethodType's first param
// Example: implMethodType is (Cloneable)String,
implInfo is for Object.toString
this.implKind = implClass.isInterface() ?
REF_invokeInterface : REF_invokeVirtual;
this.implIsInstanceMethod = true;
break;
case REF_invokeSpecial:
// JDK-8172817: should use referenced class here, but
we don't know what it was
this.implClass = implInfo.getDeclaringClass();
this.implIsInstanceMethod = true;
// Classes compiled prior to dynamic nestmate support
invokes a private instance
// method with REF_invokeSpecial.
//
// invokespecial should only be used to invoke
private nestmate constructors.
// The lambda proxy class will be defined as a
nestmate of targetClass.
// If the method to be invoked is an instance method
of targetClass, then
// convert to use invokevirtual or invokeinterface.
if (targetClass == implClass &&
!implInfo.getName().equals("<init>")) {
this.implKind = implClass.isInterface() ?
REF_invokeInterface : REF_invokeVirtual;
} else {
this.implKind = REF_invokeSpecial;
}
break;
case REF_invokeStatic:
case REF_newInvokeSpecial:
// JDK-8172817: should use referenced class here for
invokestatic, but we don't know what it was
this.implClass = implInfo.getDeclaringClass();
this.implKind = implInfo.getReferenceKind();
this.implIsInstanceMethod = false;
break;
default:
throw new
LambdaConversionException(String.format("Unsupported MethodHandle
kind: %s", implInfo));
}
For majority of cases (REF_invokeSpecial, REF_invokeStatic,
REF_newInvokeSpecial) the this.implClass = implInfo.getDeclaringClass();
Only REF_invokeVirtual and REF_invokeInterface are possible
kandidates, right?
So when does the implMethod type of parameter 0 differ from the
declaring class of the MethodHandleInfo cracked from that same
implMethod and in addition those two types leave in different packages?
This is concerning the instance method and so parameter 0 is what it
wants to look at.
Regards, Peter
Hope this helps.
Mandy
[1]
https://bugs.openjdk.java.net/browse/JDK-8239384?focusedCommentId=14328369&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14328369