Review Request: 8238358: Implementation of JEP 371: Hidden Classes

Peter Levart peter.levart at gmail.com
Fri Apr 3 11:40:54 UTC 2020


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();
     }
}

...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.

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?

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. 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. 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?
>
> Regards, Peter
>
>
> On 3/27/20 12:57 AM, Mandy Chung wrote:
>> Please review the implementation of JEP 371: Hidden Classes. The main 
>> changes are in core-libs and hotspot runtime area.  Small changes are 
>> made in javac, VM compiler (intrinsification of 
>> Class::isHiddenClass), JFR, JDI, and jcmd.  CSR [1]has been reviewed 
>> and is in the finalized state (see specdiff and javadoc below for 
>> reference).
>>
>> Webrev:
>> http://cr.openjdk.java.net/~mchung/valhalla/webrevs/hidden-classes/webrev.03 
>>
>>
>> Hidden class is created via `Lookup::defineHiddenClass`. From JVM's 
>> point
>> of view, a hidden class is a normal class except the following:
>>
>> - A hidden class has no initiating class loader and is not registered 
>> in any dictionary.
>> - A hidden class has a name containing an illegal character 
>> `Class::getName` returns `p.Foo/0x1234` whereas `GetClassSignature` 
>> returns "Lp/Foo.0x1234;".
>> - A hidden class is not modifiable, i.e. cannot be redefined or 
>> retransformed. JVM TI IsModifableClass returns false on a hidden.
>> - Final fields in a hidden class is "final".  The value of final 
>> fields cannot be overriden via reflection.  setAccessible(true) can 
>> still be called on reflected objects representing final fields in a 
>> hidden class and its access check will be suppressed but only have 
>> read-access (i.e. can do Field::getXXX but not setXXX).
>>
>> Brief summary of this patch:
>>
>> 1. A new Lookup::defineHiddenClass method is the API to create a 
>> hidden class.
>> 2. A new Lookup.ClassOption enum class defines NESTMATE and STRONG 
>> option that
>>    can be specified when creating a hidden class.
>> 3. A new Class::isHiddenClass method tests if a class is a hidden class.
>> 4. Field::setXXX method will throw IAE on a final field of a hidden 
>> class
>>    regardless of the value of the accessible flag.
>> 5. JVM_LookupDefineClass is the new JVM entry point for 
>> Lookup::defineClass
>>    and defineHiddenClass to create a class from the given bytes.
>> 6. ClassLoaderData implementation is not changed.  There is one 
>> primary CLD
>>    that holds the classes strongly referenced by its defining 
>> loader.  There
>>    can be zero or more additional CLDs - one per weak class.
>> 7. Nest host determination is updated per revised JVMS 5.4.4. Access 
>> control
>>    check no longer throws LinkageError but instead it will throw IAE 
>> with
>>    a clear message if a class fails to resolve/validate the nest host 
>> declared
>>    in NestHost/NestMembers attribute.
>> 8. JFR, jcmd, JDI are updated to support hidden classes.
>> 9. update javac LambdaToMethod as lambda proxy starts using nestmates
>>    and generate a bridge method to desuger a method reference to a 
>> protected
>>    method in its supertype in a different package
>>
>> This patch also updates StringConcatFactory, LambdaMetaFactory, and 
>> LambdaForms
>> to use hidden classes.  The webrev includes changes in nashorn to 
>> hidden class
>> and I will update the webrev if JEP 372 removes it any time soon.
>>
>> We uncovered a bug in Lookup::defineClass spec throws LinkageError 
>> and intends
>> to have the newly created class linked.  However, the implementation 
>> in 14
>> does not link the class.  A separate CSR [2] proposes to update the
>> implementation to match the spec.  This patch fixes the implementation.
>>
>> The spec update on JVM TI, JDI and Instrumentation will be done as
>> a separate RFE [3].  This patch includes new tests for JVM TI and
>> java.instrument that validates how the existing APIs work for hidden 
>> classes.
>>
>> javadoc/specdiff
>> http://cr.openjdk.java.net/~mchung/valhalla/webrevs/hidden-classes/api/
>> http://cr.openjdk.java.net/~mchung/valhalla/webrevs/hidden-classes/specdiff/ 
>>
>>
>> JVMS 5.4.4 change:
>> http://cr.openjdk.java.net/~mchung/valhalla/webrevs/hidden-classes/Draft-JVMS-HiddenClasses.pdf 
>>
>>
>> CSR:
>> https://bugs.openjdk.java.net/browse/JDK-8238359
>>
>> Thanks
>> Mandy
>> [1] https://bugs.openjdk.java.net/browse/JDK-8238359
>> [2] https://bugs.openjdk.java.net/browse/JDK-8240338
>> [3] https://bugs.openjdk.java.net/browse/JDK-8230502
>



More information about the valhalla-dev mailing list