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


Reply via email to