Me trying to explain the problem behind #ReflectiveAccessToNonExportedTypes
Remi Forax
forax at univ-mlv.fr
Fri Jul 15 08:18:42 UTC 2016
----- Mail original -----
> De: "David M. Lloyd" <david.lloyd at redhat.com>
> À: jpms-spec-experts at openjdk.java.net
> Envoyé: Jeudi 14 Juillet 2016 17:31:38
> Objet: Re: Me trying to explain the problem behind #ReflectiveAccessToNonExportedTypes
>
> On 07/14/2016 10:06 AM, Remi Forax wrote:
> > Let me try to explain again the problem and the possible solution.
> >
> > Let say i have two modules, module1 and module2.
> > In module1, I have two packages module1.exported and module1.nonexported,
> > so module1 has this module-info.java
> >
> > module module1 {
> > exports module1.exported;
> > }
> >
> > in module1.nonexported, I have a class A that's implement an interface I
> > defined like this:
> > package module1.nonexported;
> >
> > import module1.exported.I;
> >
> > public class A implements I {
> > @Override
> > public void foo() {
> > System.out.println("foo");
> > }
> > }
> >
> > I is defined in module1.exported like this:
> > package module1.exported;
> >
> > import module1.nonexported.A;
> >
> > public interface I {
> > public void foo();
> >
> > static I instance() {
> > return new A();
> > }
> > }
> >
> > Now, in module2, i want to use I so module2 requires module1, like this:
> > module module2 {
> > requires module1;
> > }
> >
> > and I have a main defined in the package modul2.main, like that:
> > package module2.main;
> >
> > import java.lang.reflect.Method;
> >
> > import module1.exported.I;
> >
> > public class Main {
> > public static void main(String[] args) throws
> > ReflectiveOperationException {
> > System.out.println(Class.forName("module1.nonexported.A"));
> >
> > I i = I.instance();
> > Method m = i.getClass().getMethod("foo");
> > m.invoke(i);
> > }
> > }
> >
> > If i run this code in with the two modules in the classpath,
> > everything work and the code prints
> > class module1.nonexported.A
> > foo
> >
> > Now, if if the two modules are in the modulepath,
> > Class.forName works but m.invoke() throws an IllegalAccessException because
> > A is in a non exported package
> > java.lang.IllegalAccessException: class module2.main.Main (in module
> > module2) cannot access class module1.nonexported.A (in module module1)
> > because module module1 does not export module1.nonexported to module
> > module2
> >
> > If i try with m.setAccessible(true), before m.invoke(), setAccessible
> > throws an exception InaccessibleObjectException
> > java.lang.reflect.InaccessibleObjectException: Unable to make member of
> > class module1.nonexported.A accessible: module module1 does not export
> > module1.nonexported to module module2
> >
> > A way to solve this issue, is to use the interface I to call foo instead of
> > using the class A,
> > Method m = I.class.getMethod("foo");
> > m.invoke(i);
> >
> > in that case, everything is Ok, because i don't try to use the method foo
> > of A anymore.
> >
> > What Mark as proposed is to introduce a new "export dynamic" semantics
> > which doesn't allow the package to be visible at compile time but to be
> > visible at runtime.
> > This solve the issue by supposing that everyone will write export dynamic
> > on every non exported package to be backward compatible with the Java 8
> > behavior.
> >
> > While i agree that "export dynamic" is a semantics that jigsaw should
> > provide, i disagree with the syntax "export dynamic" because for me, the a
> > non exported package should have this semantics in order to be backward
> > compatible with existing code.
> > So we should have 3 way to export or not a package,
> > 1. don't export at compile time, don't export at runtime
> > 2. don't export at compile time, export at runtime
> > 3. export at compile time, export at runtime.
> >
> > 2 should be the default, 1 should be use by the package of the JDK (or any
> > other libs) that want strong security, 3 is for the package that defines
> > API (that will be maintained forever).
>
> This is a good summary and I think matches my understanding. But the
> basic problem is that if everyone is doing this "export dynamic" then
> the safety proposed by restricting "public" is a false promise: we are
> providing the feature only to take it away again, resulting in the exact
> same situation we have today, but with no alternatives. If we have to
> do this then I think the feature itself needs to be re-examined.
>
> What I was suggesting was that we re-separate the notion of exports from
> accessibility altogether, so you again have three different situations
> rather than two:
>
> A. Compile time
> B. Class link at run time
> C. Reflection at run time
B and C may be indistinguishable. For the OpenJdk + Hotspot, you can distinguish between access control done by the VM in C++ and access control done by the reflection API done at runtime. For OpenJDK + IBM J9, there is only one entry point for the the access checks. When specifying the JSR292 (java.lang.invoke + invokedynamic), we spend a lot of time to be sure that reflection behavior and bytecode behavior were aligned. I don't think that trying to separate B and C is possible that late in the game.
>
> This changes the basic "export" back to mean "make available for
> linkage", with no bearing on accessibility. Then you have (at minimum)
> the following ways to export (or not) a package:
>
> 1. No export at compile time, no export but reflection-only at run time
> (but only for public types & members)
> 2. Yes export at compile time, yes export at run time (but only for
> public types & members)
> 3. Yes export at compile time and run time, including specific grants to
> access package-private members from targeted modules and/or packages
>
> Note: We could add a 1½ which is an export at run time but not compile
> time if the EG agrees; I don't think that is particularly important
> though as build tools can accomplish this easily enough. I don't want
> to derail the core point of the discussion on this topic, so I'm leaving
> it aside for now.
friend accesssibility doesn't solve the issue here, when you want 'strong encapsulation' i.e. no way to use reflection to access members of a not-exported class, you should not have to provide the list of all possible existing module. I think we want 'export private' (strong encapsulation) and 'export dynamic' to be available without having a friend list.
And again, changing the meaning of package private is a no go for me, by example for a package like java.lang.invoke, i want the package to be exported, i want strong encapsulation and i want some classes inside the package by example the class that implements the whole method handle hierarchy to share secret (using the package private access).
>
> This way the Java 8 behavior is retained with no "export dynamic"
> anywhere in sight. Rather you'd have something like "export private",
> which you'd only grant for those special cases where a package must be
> shared to a specific friend. Things not intended to be shared can be
> safely migrated to the new friend package feature.
I agree that we should have a way to have Java 8 behavior without having to declare every packages with export dynamic,
see my discussion with mark about specifying a default export behavior.
> No new access limits
> are introduced - instead old ones are enhanced in what I think is a
> simple and intuitive way. All the compatibility concerns go away, but
> we can still use the mechanism to seal up the problems in the JDK with
> shared code and things which should not be public.
>
> We should retain setAccessible() for now for compatibility. We could
> also introduce a "--safe-mode" flag which disables setAccessible()
> *across the board* (that is, not just for modular code) without
> controversy, because all those public members will remain accessible
> without "help", plus a way is provided to allow access to be volunteered
> from one package to another (along much the same philosophical lines as
> MethodHandles.Lookup IMO).
Deprecating setAccessible is another story for me.
>
> The platform EG can then later decide if this safer mode is good for the
> platform, and if so, could move to making this the default and possibly
> adding an "--unsafe-mode" for a limited time.
>
> --
> - DML
>
regards,
Rémi
More information about the jpms-spec-experts
mailing list