Review Request: 8238358: Implementation of JEP 371: Hidden Classes
Mandy Chung
mandy.chung at oracle.com
Fri Apr 3 21:32:48 UTC 2020
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
More information about the valhalla-dev
mailing list