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