On Thu, 29 Jun 2023 03:16:15 GMT, Chen Liang <li...@openjdk.org> wrote:

>> As John Rose has pointed out in this issue, the current j.l.r.Proxy based 
>> implementation of MethodHandleProxies.asInterface has a few issues:
>> 1. Exposes too much information via Proxy supertype (and WrapperInstance 
>> interface)
>> 2. Does not allow future expansion to support SAM[^1] abstract classes
>> 3. Slow (in fact, very slow)
>> 
>> This patch addresses all 3 problems:
>> 1. It updates the WrapperInstance methods to take an `Empty` to avoid method 
>> clashes
>> 2. This patch obtains already generated classes from a ClassValue by the 
>> requested interface type; the ClassValue can later be updated to compute 
>> implementation generation for abstract classes as well.
>> 3. This patch's faster than old implementation in general.
>> 
>> Benchmark for revision 17:
>> 
>> Benchmark                                                          Mode  Cnt 
>>      Score       Error  Units
>> MethodHandleProxiesAsIFInstance.baselineAllocCompute               avgt   15 
>>      1.503 ±     0.021  ns/op
>> MethodHandleProxiesAsIFInstance.baselineCompute                    avgt   15 
>>      0.269 ±     0.005  ns/op
>> MethodHandleProxiesAsIFInstance.testCall                           avgt   15 
>>      1.806 ±     0.018  ns/op
>> MethodHandleProxiesAsIFInstance.testCreate                         avgt   15 
>>     17.332 ±     0.210  ns/op
>> MethodHandleProxiesAsIFInstance.testCreateCall                     avgt   15 
>>     19.296 ±     1.371  ns/op
>> MethodHandleProxiesAsIFInstanceCall.callDoable                     avgt    5 
>>      0.419 ±     0.004  ns/op
>> MethodHandleProxiesAsIFInstanceCall.callHandle                     avgt    5 
>>      0.421 ±     0.004  ns/op
>> MethodHandleProxiesAsIFInstanceCall.callInterfaceInstance          avgt    5 
>>      1.731 ±     0.018  ns/op
>> MethodHandleProxiesAsIFInstanceCall.callLambda                     avgt    5 
>>      0.418 ±     0.003  ns/op
>> MethodHandleProxiesAsIFInstanceCall.constantDoable                 avgt    5 
>>      0.263 ±     0.003  ns/op
>> MethodHandleProxiesAsIFInstanceCall.constantHandle                 avgt    5 
>>      0.262 ±     0.002  ns/op
>> MethodHandleProxiesAsIFInstanceCall.constantInterfaceInstance      avgt    5 
>>      0.262 ±     0.002  ns/op
>> MethodHandleProxiesAsIFInstanceCall.constantLambda                 avgt    5 
>>      0.267 ±     0.019  ns/op
>> MethodHandleProxiesAsIFInstanceCall.direct                         avgt    5 
>>      0.266 ±     0.013  ns/op
>> MethodHandleProxiesAsIFInstanceCreate.createCallInterfaceInstance  avgt    5 
>>     18.057 ±     0.182 ...
>
> Chen Liang has updated the pull request with a new target base due to a merge 
> or a rebase. The pull request now contains 41 commits:
> 
>  - Code cleanup, thanks mandy!
>  - Merge branch 'master' of https://github.com/openjdk/jdk into 
> explore/mhp-iface
>  - 1. Change WRAPPER_TYPES to WeakHashMap to accurately determine if the 
> given class is
>       the proxy class
>    
>    Discussion:
>    2. I dropped ProxyClassInfo and use Lookup just to see the simplication.
>       If wrapperInstanceTarget and wrapperInstanceType are frequently called, 
> it makes
>       sense to cache the method handles.
>    
>    3. Should it use SoftReference or WeakReference?  It depends if 
> asInterfaceInstance
>       will be used heavily.
>    
>    3. I also dropped SamInfo and getStats as it can be inlined in the caller, 
> which
>       I think it's clearer to see what it does in place.
>  - SecurityManager fixed, minimize changes
>  - Merge branch 'master' into explore/mhp-iface
>  - Some changes per Mandy's review. SecurityManager test fails in this patch
>  - Merge branch 'master' into explore/mhp-iface
>  - Merge branch 'master' into explore/mhp-iface
>  - Merge branch 'master' into explore/mhp-iface
>  - stage, needs to fix is mh proxy instance check
>  - ... and 31 more: https://git.openjdk.org/jdk/compare/6f58ab2b...340b0751

This looks quite good.  I have some comments below.  Also pushed some of my 
suggestion to my [mh-proxies](https://github.com/mlchung/jdk/tree/mh-proxies) 
branch.

I'd like to have a test to verify that  `isWrapperInstance` and other 
`wrapperXXX` methods determine a random object whose class happens to have the 
same fields as the wrapper class expects but not a wrapper instance.

I will review BasicTest in details next.

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 226:

> 224:          * The hidden classes defined for I is defined in a dynamic 
> module M
> 225:          * which has access to the types referenced by the members of I 
> including
> 226:          * the parameter types, return type and exception types.

This comment can be moved to where it creates the dynamic module.

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 241:

> 239:     }
> 240: 
> 241:     private record LocalMethodInfo(MethodTypeDesc desc, List<ClassDesc> 
> thrown, String fieldName) {}

Perhaps a simpler name `MethodInfo`

Suggestion:

    private record MethodInfo(MethodTypeDesc desc, List<ClassDesc> thrown, 
String fieldName) {}

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 247:

> 245: 
> 246:     private static final Set<Class<?>> WRAPPER_TYPES = 
> Collections.newSetFromMap(new WeakHashMap<>());
> 247:     private static final ClassValue<SoftReference<Lookup>> PROXY_LOOKUPS 
> = new ClassValue<>() {

Let's use weak references for this implementation so that the hidden classes 
can be unloaded when it becomes weakly reachable.  We can reevaluate this when 
we get more feedback on this usage.

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 265:

> 263:                     continue;
> 264: 
> 265:                 String mname = m.getName();

Better documentation, more comments, would be helpful.   Some suggestions below.

Suggestion:

                // ensure it's SAM interface
                String mname = m.getName();

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 284:

> 282:                             Stream.concat(DEFAULT_RETHROWS.stream(),
> 283:                                     
> Arrays.stream(thrown).map(MethodHandleProxies::desc)).distinct().toList();
> 284:                     methods.add(new LocalMethodInfo(mtDesc, 
> exceptionTypeDescs, fieldName));

Suggestion:

                var exceptionTypeDescs =
                     thrown.length == 0 ? DEFAULT_RETHROWS
                                        : 
Stream.concat(DEFAULT_RETHROWS.stream(),
                                                        
Arrays.stream(thrown).map(MethodHandleProxies::desc))
                                                .distinct().toList();
                methods.add(new MethodInfo(desc(mt), exceptionTypeDescs, 
fieldName));

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 297:

> 295: 
> 296:             var loader = intfc.getClassLoader();
> 297:             Module targetModule = newDynamicModule(loader, 
> referencedTypes);

Suggestion:

            // create a dynamic module for each proxy class, which needs access
            // to the types referenced by the members of the interface including
            // the parameter types, return type and exception types
            var loader = intfc.getClassLoader();
            Module targetModule = newDynamicModule(loader, referencedTypes);

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 306:

> 304:             ClassDesc proxyDesc = ClassDesc.of(cn);
> 305:             byte[] template = createTemplate(loader, proxyDesc, 
> desc(intfc), uniqueName, methods);
> 306:             var definer = new Lookup(intfc).makeHiddenClassDefiner(cn, 
> template, Set.of(), DUMPER);

Suggestion:

            // define the dynamic module to the class loader of the interface
            var definer = new Lookup(intfc).makeHiddenClassDefiner(cn, 
template, Set.of(), DUMPER);

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 336:

> 334:     private static final ClassDesc CD_IllegalAccessException = 
> desc(IllegalAccessException.class);
> 335:     private static final MethodTypeDesc MTD_void_Throwable = 
> MethodTypeDesc.of(CD_void, CD_Throwable);
> 336:     private static final MethodType 
> MT_void_Lookup_MethodHandle_MethodHandle = methodType(void.class, 
> Lookup.class, MethodHandle.class, MethodHandle.class);

Several of these MethodType declarations are long lines that can be wrapped.

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 347:

> 345:     private static final MethodTypeDesc MTD_void_String = 
> MethodTypeDesc.of(CD_void, CD_String);
> 346:     private static final String ORIGINAL_TARGET_NAME = "originalTarget";
> 347:     private static final String ORIGINAL_TYPE_NAME = "originalType";

Can drop "original" prefix?

Suggestion:

    private static final String TARGET_NAME = "target";
    private static final String TYPE_NAME = "interfaceType";

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 367:

> 365:             clb.withInterfaceSymbols(ifaceDesc);
> 366: 
> 367:             // individual handle fields

Suggestion:

            // static and instance fields

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 381:

> 379:             });
> 380: 
> 381:             // <init>(Lookup, MethodHandle originalHandle, MethodHandle 
> implHandle)

Suggestion:

            // <init>(Lookup, MethodHandle target, MethodHandle 
callerBoundTarget)

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 386:

> 384:                 cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
> 385: 
> 386:                 // WrapperInstance.ensureOriginalLookup

Suggestion:

                // call ensureOriginalLookup to verify the given Lookup has 
access

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 391:

> 389: 
> 390:                 // keep original target
> 391:                 // this.originalTarget = originalHandle;

Suggestion:

                // this.target = target;

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 396:

> 394:                 cob.putfield(proxyDesc, ORIGINAL_TARGET_NAME, 
> CD_MethodHandle);
> 395: 
> 396:                 // convert individual handles

Suggestion:

                // adjust the callerBoundTarget to the method type for each 
method

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 398:

> 396:                 // convert individual handles
> 397:                 for (var mi : methods) {
> 398:                     // this.handleField = implHandle.asType(xxType);

Suggestion:

                    // this.m<i> = callerBoundTarget.asType(xxType);

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 410:

> 408:             });
> 409: 
> 410:             clb.withMethodBody(ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup, 
> ACC_PRIVATE | ACC_STATIC, cob -> {

Suggestion:

+            // void ensureOriginalLookup(Lookup) checks if the given Lookup 
has ORIGINAL
+            // access to this class, i.e. the lookup class is this class; 
otherwise,
+            // IllegalAccessException is thrown
            clb.withMethodBody(ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup, 
ACC_PRIVATE | ACC_STATIC, cob -> {

test/jdk/java/lang/invoke/MethodHandleProxies/ProxyForMethodHandleTest.java 
line 34:

> 32: import org.junit.jupiter.api.Test;
> 33: 
> 34: /*

Add `@bug` tag

test/jdk/java/lang/invoke/MethodHandleProxies/WithSecurityManagerTest.java line 
37:

> 35:     public static void main(String... args) {
> 36:         var originalMh = MethodHandles.zero(void.class);
> 37:         Object o = 
> MethodHandleProxies.asInterfaceInstance(Untrusted.class, originalMh);

Is it better to define an interface in this test?   It seems that this test has 
nothing to do with Untrusted.  It may confuse readers.

test/jdk/java/lang/invoke/MethodHandleProxies/WithSecurityManagerTest.java line 
42:

> 40:         assert originalMh == untrustedTarget : "Got " + untrustedTarget;
> 41: 
> 42:         var runnableTarget = 
> MethodHandleProxies.wrapperInstanceTarget(MethodHandleProxies.asInterfaceInstance(Runnable.class,
>  originalMh));

please break this long line.  Perhaps adding a local variable may make it easy 
to read too.

test/jdk/java/lang/invoke/MethodHandleProxies/WithSecurityManagerTest.java line 
42:

> 40:         assert originalMh == untrustedTarget : "Got " + untrustedTarget;
> 41: 
> 42:         var runnableTarget = 
> MethodHandleProxies.wrapperInstanceTarget(MethodHandleProxies.asInterfaceInstance(Runnable.class,
>  originalMh));

Just to be completed, also add test cases for `isWrapperInstance` and 
`wrapperInstanceType`.

-------------

PR Review: https://git.openjdk.org/jdk/pull/13197#pullrequestreview-1507889870
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248283212
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248281976
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248281584
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248285455
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248284374
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248285933
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248286077
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248287026
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248287738
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248289389
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248289527
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248289943
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248290172
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248290731
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248290809
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248289730
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248292045
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248294487
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248292958
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1248295035

Reply via email to