setAccessible is broken?
If you’ve ever written code using Core Reflection API, you wrote something like this:
Field field=...
field.setAccessible(true);
or
Method method=...
method.setAccessible(true);
This code works fine till JDK 8 . In JDK 9 incompatible change was made. Quote from JavaDoc of setAccessible()
method. This quote is done from recent JDK 14, but it is (almost) the same was in JDK 9:
public void setAccessible(boolean flag)
Set the
accessible
flag for this reflected object to the indicated boolean value. A value oftrue
indicates that the reflected object should suppress checks for Java language access control when it is used. A value offalse
indicates that the reflected object should enforce checks for Java language access control when it is used, with the variation noted in the class description.This method may be used by a caller in class
C
to enable access to amember
ofdeclaring class
D
if any of the following hold:
C
andD
are in the same module.The member is
public
andD
ispublic
in a package that the module containingD
exports
to at least the module containingC
.The member is
protected
static
,D
ispublic
in a package that the module containingD
exports to at least the module containingC
, andC
is a subclass ofD
.
D
is in a package that the module containingD
opens
to at least the module containingC
. All packages in unnamed and open modules are open to all modules and so this method always succeeds whenD
is in an unnamed or open module.This method cannot be used to enable access to private members, members with default (package) access, protected instance members, or protected constructors when the declaring class is in a different module to the caller and the package containing the declaring class is not open to the caller’s module.
If there is a security manager, its
checkPermission
method is first called with aReflectPermission("suppressAccessChecks")
permission.
Even more, new method was added to Field
/Method
:
public final boolean trySetAccessible()
Set the
accessible
flag for this reflected object totrue
if possible. This method sets theaccessible
flag, as if by invokingsetAccessible(true)
, and returns the possibly-updated value for theaccessible
flag. If access cannot be enabled, i.e. the checks or Java language access control cannot be suppressed, this method returnsfalse
(as opposed tosetAccessible(true)
throwingInaccessibleObjectException
when it fails).This method is a no-op if the
accessible
flag for this reflected object istrue
.For example, a caller can invoke
trySetAccessible
on aMethod
object for a private instance methodp.T::privateMethod
to suppress the checks for Java language access control when theMethod
is invoked. Ifp.T
class is in a different module to the caller and packagep
is open to at least the caller's module, the code below successfully sets theaccessible
flag totrue
.
p.T obj = ....; // instance of p.T
:
Method m = p.T.class.getDeclaredMethod("privateMethod");
if (m.trySetAccessible()) {
m.invoke(obj);
} else {
// package p is not opened to the caller to access private member of T
...
}
In the last minute of JDK 9 release the “big kill switch” was added as temporary solution for 1 release:
Mark Reinhold has a new proposal for JDK 9: to allow illegal reflective access from code on the class path by default in JDK 9 and to disallow it in a future release. He wrote in a message to the OpenJDK mailing list that “the strong encapsulation of JDK-internal APIs has triggered many worried expressions of concern that code that works on JDK 8 today will not work on JDK 9 tomorrow, yet no advance warning of this change was given in JDK 8.”
To help the ecosystem “migrate to the modular Java platform at a more relaxed pace,” Reinhold proposed to allow illegal reflective access from code on the class path by default in JDK 9 and to disallow it in a future release…
The Chief Architect of the Java Platform Group at Oracle explained that the existing “big kill switch” of the ‘
–permit-illegal-access
‘ option will become the default behavior of the JDK 9 run-time system but emphasized that there won’t be as many warnings. He also added that the current behavior of JDK 9 [where illegal reflective-access operations from code on the class path are not permitted] will become the default in a future release and nothing will change at compile time.Reinhold wrote that the recently-introduced `
–permit-illegal-access
` option will be replaced by a more general option, `–illegal-access
`.
…
If adopted [today we know, it was, indeed, adopted], the proposal will require some changes to JEP 260, “Encapsulate Most Internal APIs”. Although APIs that are internal to the JDK will still be strongly encapsulated from the standpoint of code in modules [whether those modules are automatic or explicit] they won’t appear to be encapsulated at run time from the standpoint of code on the class path.
Furthermore, when `deny` becomes the default mode, Reinhold expects `permit` to remain supported for at least one release, so that developers can continue to migrate their code.
https://jaxenter.com/jdk-9-replace-permit-illegal-access-134180.html
Indeed, –permit-illegal-access
was changed to –illegal-access.
As for now, the last released JDK is 14. And the “big kill switch” is still in place.
Quote from documentation of --illegal-access
for JDK-14:
-illegal-access=
parameterWhen present at run time,
--illegal-access=
takes a keyword parameter to specify a mode of operation:Note: This option will be removed in a future release.
*
permit
: This mode opens each package in each module in the run-time image to code in all unnamed modules ( such as code on the class path), if that package existed in JDK 8. This enables both static access, (for example, by compiled bytecode, and deep reflective access) through the platform's various reflection APIs. The first reflective-access operation to any such package causes a warning to be issued. However, no warnings are issued after the first occurrence. This single warning describes how to enable further warnings. This mode is the default for the current JDK but will change in a future release.*
warn
: This mode is identical topermit
except that a warning message is issued for each illegal reflective-access operation.*
debug
: This mode is identical towarn
except that both a warning message and a stack trace are issued for each illegal reflective-access operation.*
deny
: This mode disables all illegal-access operations except for those enabled by other command-line options, such as--add-opens
. This mode will become the default in a future release.The default mode,
--illegal-access=permit
, is intended to make you aware of code on the class path that reflectively accesses any JDK-internal APIs at least once. To learn about all such accesses, you can use thewarn
or thedebug
modes. For each library or framework on the class path that requires illegal access, you have two options:If the component’s maintainers have already released a fixed version that no longer uses JDK-internal APIs then you can consider upgrading to that version.
If the component still needs to be fixed, then you can contact its maintainers and ask them to replace their use of JDK-internal APIs with the proper exported APIs.
If you must continue to use a component that requires illegal access, then you can eliminate the warning messages by using one or more
--add-opens
options to open only those internal packages to which access is required.To verify that your application is ready for a future version of the JDK, run it with
--illegal-access=deny
along with any necessary--add-opens
options. Any remaining illegal-access errors will most likely be due to static references from compiled code to JDK-internal APIs. You can identify those by running the jdeps tool with the--jdk-internals
option. For performance reasons, the current JDK does not issue warnings for illegal static-access operations.
https://docs.oracle.com/en/java/javase/14/docs/specs/man/java.html
Personally, I think weeking Core Reflection API was big design mistake. On Java conferences many people, including myself, still says, that they are using JDK 8. And when people that uses JDK 9 and later are asked whether they used modules, the majority of them still uses classpath
.With this “big kill switch” in place user code that Java developer is writting can access JDK internals. So, effectively, modules “strong encapsulation” have failed big time.
P.S. Many popular libraries in maven central not works on JDK 9 and later with modules
.