I am using JDK 15. (I am using ByteBuddy 1.10.16 to generate some classes but it's mostly irrelevant here, I think, except as background information.)
In one of these generated classes, I am calling invokeExact()
on a MethodHandle
constant I've managed to store in the generated class. It is a "field setter" acquired via MethodHandles.Lookup#findSetter
.
(In what follows I am aware of the MethodHandles.privateLookupIn()
method.)
I've noticed that the "field setter" MethodHandle
in question fails when it represents a private
field. At most levels this does not surprise me: a direct MethodHandle
is, well, direct: while I don't pretend to know much about the innards of all this stuff, it seems to me that surely it must just wrap some low-level bytecode devoid of access checks.
But given the existence of privateLookupIn()
which shows that bypassing access checks is possible in certain situations, is there a path where I can "harvest" a "field setter" MethodHandle
from class A that can read a private
field, and then store it as a constant in another class B such that invokeExact()
on it will succeed?
I believe I have done something similar in the past (have to check) involving private
methods, but in those cases I was not using MethodHandle
constants, i.e. I was acquiring the MethodHandle
at class initialization time during <clinit>
time using privateLookupIn()
and storing the resulting MethodHandle
in a private static final
field, and then calling invokeExact()
on the contents of that field. If I have to continue to go this route, I will, but MethodHandle
constants seem appealing here and it would be nice to use them if I can.
So another way of phrasing my question is: is the constant form in which a MethodHandle
is represented capable of storing its privileges? Or is there some one-time way of "upping" the privileges given a MethodHandle
stored as a constant? Or does the fact that a given MethodHandle
is stored as a constant prevent it for all time from accessing anything other than conventionally accessible Java constructs? I didn't see anything super obvious in the JVM specification in the relevant section.
The specification you’ve linked states:
The linked chapters, i.e. §5.4.3.2 for fields, describe the ordinary resolution process, including access control. Even without that explicit statement, you could derive the existence of access control from the preceding description, that states that these symbolic method handle references are supposed to be equivalent to specific listed bytecode behavior.
So a direct method handle acquired via a
CONSTANT_MethodHandle_info
entry of the class file’s constant pool can not access classes or members that wouldn’t be also accessible directly by bytecode instructions.But since JDK 11, you can use Dynamic Constants to load constants of arbitrary type defined by an arbitrary bootstrapping process. So when you can express how to get the constant in terms of Java code, like the use of
privateLookupIn
, you can also define it as bootstrapping of a dynamic constant and load that constant at places where you would otherwise load the direct method handle.Consider the following starting point:
It tries to define a new runtime class that attempts to load a
MethodHandle
constant pointing toinacessibleMethod()
via aCONSTANT_MethodHandle_info
. The program printsNow, let’s change the constant to a dynamic constant that will perform the equivalent to
when the constant is resolved the first time. The definition of the constant is “a bit” more involved. Since the code contains three method invocations, the definition requires three method handles, further, another handle to the already existing bootstrap method
ConstantBootstraps.invoke(…)
that allows to use arbitrary method invocations for the bootstrapping. These handles can be used to define dynamic constants, whereas dynamic constants are allowed as constant input to another dynamic constant.So we replace the definition after the
// express the constant
comment with:To avoid repeating the very long constant method descriptor strings, I use ASM’s
Type
abstraction. In principle, we could use constant strings for all type names and signatures.This program prints:
The complexity of a dynamic constant composed of three constants created by method invocations will result in quite a big constant pool. We may generate a custom bootstrap method instead and get a significantly smaller class file, despite we have an additional method:
The bootstrap method has been designed to be reusable. It receives all necessary information as constant arguments, so different
ldc
instructions can use it to get handles to different members. The JVM does already pass the caller’s lookup context as first argument, so we can use this and don’t need to callMethodHandles.lookup()
. The class to search for the member is the first additional argument, which is used as first argument to both,privateLookupIn
andfindStatic
. Since every dynamic constant has a standard name argument, we can use it to denote the member’s name. The last argument denotes theMethodType
for the method to look up. When we retrofit this for field lookups, we could remove that parameter, as the third standard argument, the expected constant type could be matched with the expected field’s type.Basically, the custom bootstrap method does the
privateLookupIn
based lookup you described in your question, but using it withldc
allows to have lazy initialization (rather than the class initialization time ofstatic final
fields) while still getting optimized likestatic final
fields once the instruction has been linked. Also, these dynamic constants are permitted as constant input to other bootstrap methods for other dynamic constants orinvokedynamic
instructions (though, you can also adapt an existingstatic final
field to a dynamic constant using this bootstrap method).