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


Reply via email to