JDK-8229959: Current status and thoughts about JDK-8242888
Johannes Kuhn
info at j-kuhn.de
Wed Jun 3 13:01:51 UTC 2020
Hi,
half a year ago, I proposed a patch that implements JDK-8229959: Convert
proxy class to use constant dynamic [2].
**Background**: java.lang.reflect.Proxy creates a class file at runtime
that extends j.l.r.Proxy and implements the passed interfaces.
Methods invoked on that proxy are passed to a
j.l.r.InvocationHandler.invoke. One of it's argument is a j.l.r.Method.
This Method is currently cached in a private static final field in the
generated proxy class.
Remi Forax suggested to use a constant dynamic instead.
Last time I was not sure about the scope of my proposal, and when
discussion drifted to related topics, I did not know how to handle that.
After some thought, the scope of this is only changing the shape of the
generated class file. Those are NOT in the scope:
* Changes to a public API.
* Changes on how the generated class file is loaded.
Especially JDK-8242888: Convert dynamic proxy to hidden classes [3] is
not in scope. But see below for some thoughts on it.
---------------------
To avoid changing the specification, I decided to put the bootstrap
method for the constant dynamic into the generated class file.
My first approach was a bootstrap method that is equivalent to this:
private static Method $proxy$bootstrap(Lookup l, String ignored,
Class jlrMethod, MethodHandle mh) {
return (Method) lookup.revealDirect(mh).reflectAs(jlrMethod);
}
One of the responses was to measure the impact, and I wrote some JMH
tests - which did run that code JITed.
It is much more likely that this code is executed in the interpreter
through.
Brian Goetz then suggested to write a bootstrap method per proxy mehod,
and calling Class::getMethod in them.
After some tries, I found a way to parameterize it, using MethodType as
carrier.
This avoids encoding the primitive types as yet an other constant dynamic.
Example bootstrap method:
private static Method $proxy$bootstrap(Lookup l, String name, Class
jlrMethod, Class owner, MethodType mt) {
return owner.getMethod(name, mt.parameterArray());
}
This approach seemed to work - until I did look into the interaction
with an installed SecurityManager.
Class::getMethod can throw a SecurityException if the method belongs to
an interface that is in a package not exported by the JDK.
In particular, it breaks j.l.i.MethodHandleProxies.wrapperInstanceTarget
when a SecurityManager is installed. [4]
(A separate change could be made to fix that particular case, but I
consider it just to be a symptom.)
This breaks because Class::getMethod is called at a later point - when
untrusted code is on the stack.
----------------------
Where can this go from here?
* Wrap the call to Class::getMethod in an AccessController.doPrivileged
block.
While possible, this would add even more overhead.
* Go back to revealDirect/reflectAs.
This does not work because Lookup::revealDirect will check the
package access.
* Use hidden classes, as suggested by JDK-8242888.
A promising approach, but has a few unsolved problems.
What about hidden classes?
It's possible to pass some ClassData to a hidden class when defining it.
Which can be used to pass the Methods.
But Hidden Classes need some more requirements, as outlined by Mandy in
JDK-8242888:
* Serialization: No change necessary.
Proxy classes (as determined by Proxy.isProxyClass) are written as a
proxy descriptor, containing a list of all implemented interfaces.
Deserialization is fine if Proxy.h can be updated even if the
instance is implemented by a hidden class.
* A lookup in the same package as the proxy is needed.
This is a problem for not-yet-existing packages. It's possible to
inject a "package-witness" or shim.
* Protection domain: Proxy is defined to have a null ProtectionDomain.
This is currently necessary for Class.getMethod.
If the methods are passed using ClassData, then that requirement is
no longer needed.
An other option is to grant the package witness a null ProtectionDomain.
As conclusion, JDK-8229959 can only be implemented if it either uses
AccessController.doPrivileged or use hidden classes with ClassData.
- Johannes
[1]: https://bugs.openjdk.java.net/browse/JDK-8229959
[2]:
https://mail.openjdk.java.net/pipermail/core-libs-dev/2019-November/063595.html
[3]: https://bugs.openjdk.java.net/browse/JDK-8242888
[4]: https://gist.github.com/DasBrain/37855876743fed7828c7690603aa3039
More information about the core-libs-dev
mailing list