RFR: 8159746: (proxy) Support for default methods

Mandy Chung mchung at openjdk.java.net
Thu Oct 15 18:33:13 UTC 2020


On Thu, 15 Oct 2020 09:04:09 GMT, Peter Levart <plevart at openjdk.org> wrote:

>> `invokeDefaultMethod` is moved to a static method in `InvocationHandler`.
>>  I also add `@throws IllegalAccessException` if the specified default method is
>> inaccessible to the caller (access check was missing in the previous version).
>> The declaring interface of the default method must be exported to the caller to
>> use.  If it's a non-public interface, the caller class must be in the same runtime
>> package as the interface.  The example in javadoc includes the case when
>> the proxy interface is modified.
>
> Hi Mandy,
> 
> I'm beginning to think that we somewhat wondered away from the original intent which was to mimic the bytecode
> behaviour as much as possible. Bytecode behaviour for super calls (invokespecial) is very special and what you do in
> the last patch is guarding invocation to a super default method via @CallerSensitive method which checks the access of
> the caller as though this was a normal call. Similar access check when a default method might reside in a
> package-private class or when this class might not be exported is already performed when the code creates a Proxy
> class/instance (Proxy::getProxyClass/newProxyInstance). This code also designates an InvocationHandler for the proxy
> instance. From that point on, the designated invocation handler should have full access to invoking super default
> methods on the proxy instance regardless of where it resides because it has been chosen as the delegate of the Proxy
> class with full powers.  Since we are exposing the invokeDefaultMethod functionality in a public static method, it is
> hard (or not quite possible) to check that the code doing the invocation is in fact the designated invocation handler
> code. One approximation might be the following check:  `Proxy.getInvocationHandler(proxy).getClass() == callerClass`
> ...but that would not work for code residing in the invocation handler superclass even though such code might be
> eligible to call the super default method. OTOH such check is over permissive as it allows calling the super method
> from static context too.  So I was thinking about an alternative approach. To expose the method as a protected final
> instance method in an abstract class implementing InvocationHandler:  public interface InvocationHandler {
>   ...
>   abstract class Support implements InvocationHandler {
>     protected final Object invokeDefaultMethod(Object proxy, Method method, Object... args) throws
>     InvocationTargetException {
>       if (this != Proxy.getInvocationHandler(proxy)) throw ...
>       ...
>     }
>   }
> }
> 
> Users would have to extend `InvocationHandler.Support` to access this feature. This might be OK for most users, but
> those wanting to extend some other class will not be able to do that.
> So WDYT? In case you still prefer your approach of re-checking the permissions in invokeDefaultMethod, then I might
> have some ideas of how to speed-up theses checks. As currently stands, invoking public exported interface default
> methods does exhibit a little more penalty, but for non-public or non-exported interfaces, the penalty is bigger:
> Mandy's original:
> Benchmark             Mode  Cnt    Score    Error  Units
> ProxyBench.implClass  avgt    5    3.709 ±  0.026  ns/op
> ProxyBench.implProxy  avgt    5  926.215 ± 11.835  ns/op
> 
> Peter's performance patch:
> 
> Benchmark             Mode  Cnt   Score   Error  Units
> ProxyBench.implClass  avgt    5   3.777 ± 0.005  ns/op
> ProxyBench.implProxy  avgt    5  27.579 ± 0.250  ns/op
> 
> Moved to InvocationHandler + added access check
> 
> Benchmark               Mode  Cnt    Score   Error  Units
> ProxyBench.implClass    avgt    5    3.740 ± 0.004  ns/op
> ProxyBench.implProxy    avgt    5   34.226 ± 0.125  ns/op
> ProxyBench.ppImplClass  avgt    5    3.780 ± 0.004  ns/op
> ProxyBench.ppImplProxy  avgt    5  147.318 ± 1.422  ns/op
> 
> The updated benchmark code is here: https://gist.github.com/plevart/f6e0a5224c3ffbf14b28b47755599226
> (the ppImpl... benchmarks are against package-private interface)

Hi Peter,

The current `newProxyInstance` API does not perform access check against the proxy interfaces.    I want to avoid
adding new caller-sensitive method where possible.   I did consider any alternative to make it non-static method which
only allows a proxy's invocation handler to call `invokeDefaultMethod` if `proxy.getInvocationHandler() == this`.  If
access check done at invocation time, `invokeDefaultMethod` still needs to be caller-sensitive which must be a final
method (can't be on a default method)  In addition, an invocation handler to call `invokeDefaultMethod` can't be
implemented in a lambda because it can't self-reference itself.  So the invocation handler that wants to invoke default
methods must be a concrete or anonymous class which is a limitation.

Alan and I also discussed other alternative earlier.   I agree to prototype a slightly different alternative that will
perform access check at proxy instance creation time: 1. define an abstract `DelegatingInvocationHandler` class that
defines a protected final `invokeDefault` method (shortened method name). 2. `Proxy::newProxyClass` will perform access
check if the given `InvocationHandler` is an instance of `DelegatingInvocationHandler` 3. `Proxy(InvocationHandler h)`
constructor should throw an exception if the given `h` is an instance of `DelegatingInvocationHandler`.
`Proxy::getProxyClass` is deprecated and it's recommended to call `newProxyInstance` instead.  It's strongly
discouraged to invoke the Proxy constructor and so I think not supporting `DelegatingInvocationHandler` may be
reasonable. 4. `Proxy::getInvocationHandler` needs to check if the caller class has access to the proxy interfaces if
it's a `DelegatingInvocationHandler`.

So one can't get a proxy instance whose invocation handler can invoke default methods if the caller has no access to
the proxy interfaces; similarly one can't get a `DelegatingInvocationHandler` from a proxy instance if the caller has
no access.

This alternative avoids `invokeDefault` being caller-sensitive but the limitation of `DelegatingInvocationHandler`
implementation in a concrete/anonymous class.

Thought/feedback?

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

PR: https://git.openjdk.java.net/jdk/pull/313


More information about the core-libs-dev mailing list