A lot of questions around the configuration of a reflective style library
Peter Levart
peter.levart at gmail.com
Mon Jul 11 09:43:13 UTC 2016
Hi Jochen,
I will not interfere with the discussion you have with Alan about how to
arrange the access among modules. I'll just try to touch the last part
(Part 3) where you are asking whether bringing MethodHandles into the
picture changes anything about isolating M1 and M2 form each other...
On 07/10/2016 12:04 PM, Jochen Theodorou wrote:
> Hi,
>
> since Alan suggested to make a new thread for this (coming from
> "Feedback on proposal for #ReflectiveAccessToNonExportedTypes"), I
> will do just this.
>
> Let me rephrase the scenario from there a bit:
> I am trying to figure out the configuration and implications of the
> configuration and what is different to from before using modules. Let
> us for simplicity assume we talk only about classes with public
> modifier and all modules are loaded with the same class loader.
>
> We have a reflective library as module MyReflection, which uses
> classes generated at runtime to realize access similar to reflection.
> Let us also ignore where the information for classes and signatures
> come from. Then we have a Module M1, which has hidden API and wants to
> use MyReflection to access its own hidden API. The question here is
> about how to make the configuration and maybe what has to be done with
> regards to layers to enable this or if that is not possible.
> MethodHandles and service loader based solutions are to be ignored for
> this.
>
> Part 2 would then be to have M2 working in the same way and the
> question would be if M1 and M2 suddenly gain access to each others
> hidden APIs. Of course this makes only sense if part 1 is possible.
> but my thought is based on the following:
> To access the hidden API in M1 through M1, I have something like this:
> M1 calls into MyReflection, MyReflection calls into generated class
> (Layer in M1 I guess), generated class calls into M1. In short:
> M1->MyReflection->M1Accessor->M1. And for M2 I would of course have
> M2->MyReflection->M2Accessor->M2. Since MyReflection has access to
> M1Accessor and M2Accessor I see the following possible:
> M1->MyReflection->M2Accessor->M2
>
>
> And I have a new Part 3, as another followup. Would a service loader
> or MethodHandles based logic change anything here? And while
> M1->MyReflection->M2Accessor->M2 itself would change I do not think
> that M1 accessing hidden API from M2 would change.
>
>
> bye Jochen
In short, it does.
Suppose you design MyReflection API in a way that takes a
java.lang.invoke.MethodHandles.Lookup object. A class in M1 could then
pass an instance of Lookup obtained by invoking MethodHandles.lookup()
to MyReflection API. MyReflection would use such Lookup object to "find"
the MethodHandle pertaining to a particular "hidden" method in M1 or
simply pass the Lookup to the generated M1Accessor and delegate the
"finding of the method" to it. It doesn't matter where MyReflection or
M1Accessor live (in which modules) and what access those modules have to
the target method. The powers of the Lookup object are the same as the
powers of the class that invoked the Methodhandles.lookup() to obtain it
(a class in M1). Such Lookup object has the powers to find the
unexported methods in M1 or package-private methods in the package of
the class that invoked Methodhandles.lookup() or private methods in the
class that invoked Methodhandles.lookup(). Such Lookup object does *not*
have the powers for finding "hidden" methods in M2, for example. A
Lookup object obtained by a class in M2 would have to be passed to
MyReflection API to find a MethodHandle of a "hidden" method in M2.
Note: access checks for invocation through MethodHandle are performed
when a Methodhandle is looked-up (when find* method is invoked on the
Lookup object) and not when the MethodHandle is invoked. The access
checks are performed on behalf of the class that obtained the Lookup
object by invoking Methodhandles.lookup().
Hope this helps to understand the Lookup/MethodHandle access checking. I
think this approach is right for the task at hand as it doesn't require
to change the exports or readability graph of any module. It simply
delegates the permissions. The task at hand is specific, because the
call chains presented above originate in a class which already has
access to all the possible targets that the invocation is finally
dispatched to.
But this is not the only approach possible. You will find out, with
Alan's help, how to arrange access of MyReflection to M1Accessor /
M2Accessor and access of M1Accessor to M1 hidden methods and access of
M2Accessor to M2 hidden methods, but any such arrangement alone will not
prevent code in M1 from calling hidden methods in M2 or vice-versa. You
will have to add a "security token" into the picture with additional
access checks performed by MyReflection and/or M1|M2Accessor(s) based on
such security token.
Here's a simplified idea. Suppose MyReflection uses java reflection to
invoke target methods directly and it is already arranged so that
MyReflection has access to hidden methods in both M1 and M2 (let's take
the generated M1|M2Accessor(s) out of the picture for now - the idea can
be extended to include them later). Suppose that simplified MyReflection
is used to invoke just static methods in the module of the original caller:
public class MyReflection {
public interface AccessToken {}
public static Object invoke(AccessToken accessToken,
Class<?> target, String methodName,
Class<?>[] parameterTypes, Object ... args)
throws IllegalAccessException,
NoSuchMethodException,
InvocationTargetException
{
if (accessToken.getClass().getModule() != target.getModule()) {
throw new IllegalAccessException("Cross module call");
}
return target.getDeclaredMethod(methodName, parameterTypes)
// this is a reflective call, but it could as well
be a call made by generated accessor
.invoke(null, args);
}
}
The original invoker and the hidden target method in M1 could then look
like:
public class CM1 {
public static int hiddenAdd(int a, int b) {
return a + b;
}
// keep the instance of AccessToken private and only pass it to
MyReflection
private static final MyReflection.AccessToken ACCESS =
new MyReflection.AccessToken() {};
public static void main(String[] args) throws Exception {
int sum = (Integer)
MyReflection.invoke(ACCESS,
CM1.class, "hiddenAdd", new
Class<?>[]{int.class, int.class},
10, 20);
}
}
As long as instance(s) of AccessToken implemented by classes in M1 are
kept private and only passed to MyReflection, MyReflection can verify
that the one that has access to such instance is an authorized caller so
it grants it access to hidden methods in M1.
Regards, Peter
More information about the jigsaw-dev
mailing list