JDK-8229959/JDK-8242888 Prototype for j.l.r.Proxy with hidden classes & class data
Johannes Kuhn
info at j-kuhn.de
Fri Dec 18 00:59:54 UTC 2020
Thanks Mandy,
Appreciate your feedback, as always.
On 18-Dec-20 0:01, Mandy Chung wrote:
> Hi Johannes,
>
> I'm glad to see this prototype. Converting dynamic proxy to hidden
> classes has a few challenges as JDK-8242888 describes.
>
> 1) the serialization specification w.r.t. dynamic proxies
Serialization is a must have, because annotation are implemented using
dynamic proxies.
But I made it work in my prototype.
The serialization format uses a special PROXYCLASSDESC - basically an
array of CLASSDESC of all implemented interfaces of the dynamic proxy.
It then asks j.l.r.Proxy for the proxy class that implements those
interfaces.
The issue I run in was the way the proxy class is instantiated.
ReflectionFactory::newConstructorForSerialization did spin a class that
referenced the expected type by it's binary name.
To avoid that, I created a MethodHandleConstructorAccessor, that just
delegates call to a MethodHandle. j.l.invoke internals are very powerful
and can do such shenanigans.
In the end, I think no specification change is necessary, at least for
that part. (Other parts may need one - the jshell test shows that such a
change is visible in the stack trace)
> We need to look into the implication to the specification and whether
> the default serialization mechanism should (or should not) support
> dynamic proxies if it's defined as hidden classes.
>
> 2) how to get a Lookup on a package for the dynamic proxy class to be
> injected in without injecting a shim class (i.e. your anchor class)?
>
> Frameworks don't always have the access to inject a class in a package
> defined to a class loader. Dynamic proxies are a use case to determine
> what functionality is needed to avoid spinning a shim class.
That topic certainly needs a lot more thought.
What are the requirements, what kind of frameworks exist, what do they
need...
For the dynamic proxy case: it just needs two unique packages per
ClassLoader. Names don't matter, as long as they don't clash with
anything else.
Currently they don't clash, because the specification says: don't name
your packages this way.
But a hidden class (and Lookup.defineClass) will always have package
private access to your package. This may or may not be intended by a
framework - especially if it compiles user code, like xerces or clojure.
>
> 3) protection domain
>
> The current spec of Proxy class is defined with null protection domain
> (same protection domain as the bootstrap class loader).
> Lookup::defineHiddenClass defines the hidden class in the same
> protection domain as the defining class loader. This requires to
> understand deeper the compatibility risks and what and how
> applications/libraries depend on this behavior.
My anchor has a null PD, hidden classes share the that, so it should be
fine (for now). (Class::getProtectionDomain returns an all permission PD
if the class has a null PD - interesting. Looks like THAT is copied to
the hidden class??)
I think the original security reasoning for why proxies have a null PD
is fine goes something like this:
1. The code in the proxy is trusted, aside from <clinit> it will only
ever calls InvocationHandler.invoke.
2. The proxy itself should not contribute to the protection domains on
the stack - as it may sit between two privileged PDs.
The only sensible solution to keep 2. without a null PD is to use the PD
of the invocation handler - even if that PD is null.
I'm not sure I like the idea of having a class with null PD in a package
full of untrusted code, but that is already the case with a non-public
interface.
An other option is to use the PD of the caller - which gets *really*
interesting with serialization (Who is the caller now?).
>
> That's all the feedbacks I can share so far. We need to have a clear
> idea w.r.t. serialization spec and compatibility risk to existing code
> w.r.t. protection domain and explore other options.
Compatibility risk is always hard to assess. But I don't think that
there is any change necessary for the serialization spec.
Otherwise - as only one new tests did fail - I would consider the risk
low. And the failing test was in jshell, which expected a stack frame
element from the proxy class. So, somewhat expected to break that.
>
> W.r.t. AOT tests, AOT has been disabled in openjdk build [1] and that
> may be the reason why these tests fail.
Thanks for the info. For now, I just ignore the failures.
>
> Mandy
> [1] https://github.com/openjdk/jdk/pull/960
>
> On 12/17/20 2:07 PM, Johannes Kuhn wrote:
>> Now that class data support for hidden classes in master, I decided to
>> tackle JDK-8229959 again.
>>
>> JDK-8229959: Convert proxy class to use constant dynamic [1]
>> JDK-8242888: Convert dynamic proxy to hidden classes [2]
>>
>> The idea is simple: Define proxies as hidden classes, pass the methods
>> as class data, and access it from the Proxy with the
>> MethodHandles.classDataAt() BSM.
>>
>> The current prototype[3] works - and aside from one test for jshell
>> that expects a stack trace element containing "jdk.proxy" as a stack
>> trace element (hidden classes, duh), no new tests did fail with "make
>> test-tier1".
>> Also, the aot tests fail ("link.exe not found"), if someone has some
>> instructions how to get them properly run on windows, I'm very
>> interested. But they are not new failures.
>>
>> Problems I did run into:
>>
>> I need a lookup for a class in a package to define a hidden class.
>> ---------------------------
>> Solution: I inject an anchor class, obtain a lookup on it, and use
>> that. The anchor is reused for the same package, and named pkg +
>> ".$ProxyAnchor".
>>
>> I need to return both the class data & created bytecode back to Proxy
>> from ProxyGenerator
>> ---------------------------
>> Solution: Added a record-like class that contains both bytecode as
>> well as the class data.
>>
>> Serialization of proxies did break.
>> ---------------------------
>> Serializing the proxy descriptor was not a problem - the problem is
>> how the constructor for serialization works.
>> It spins a MagicConstructorAccessor subclass - which requires for the
>> created class to have a binary name. Hidden classes don't have one.
>> Solution: MethodHandles don't have this limitation, so I hacked a
>> shared secret into JavaLangInvokeAccess to create such a MethodHandle,
>> and replaced the Reflection.generateConstructor() implementation to
>> use a ConstructorAccessor that delegates to that MethodHandle.
>>
>> ---------------------------
>>
>> In all, this is a prototype - for now, I want some feedback on the
>> approach. Also a broader view - making it work is one thing.
>> Checking all the right boxes in all the different areas an other.
>> Some parts might still be a bit sloppy, but I want to know if there is
>> merit if I follow the current path and polish it up.
>>
>> - Johannes
>>
>> PS.: I decided to use a draft PR - as with my last approaches I had
>> problems when I did commit more stuff.
>>
>> [1]: https://bugs.openjdk.java.net/browse/JDK-8229959
>> [2]: https://bugs.openjdk.java.net/browse/JDK-8242888
>> [3]: https://github.com/openjdk/jdk/pull/1830/files
>>
>
More information about the core-libs-dev
mailing list