-
CSR
-
Resolution: Unresolved
-
P4
-
None
-
behavioral
-
minimal
-
-
Java API, add/remove/modify command line option
-
SE
Summary
By default, emit a warning when mutating a final
field using reflection unless the mutating module has been granted the capability to do so via a command-line option. The behavior of emitting a warning can be strengthened to throw an exception instead (which will become the default in a future JDK release). Alternatively, the warning can be avoided (an option that will be removed in a future release).
Problem
java.lang.reflect.Field::setAccessible
makes it possible to mutate final
instance fields, provided that the package containing the field's class is open to the caller. Because every module is open to itself, the JVM cannot trust that any final
instance field is immutable, making it hard for it to apply optimizations such as constant folding.
Mutation of final
instance fields has been allowed since JDK 5, see JDK-5044412.
Solution
Mutating a final
instance field with Field::set
is legal, and succeeds without warning, if and only if:
setAccessible(true)
has succeeded for the Field object,- the field's class is in a package that is open to the module performing the mutation, and
- the module performing the mutation has been granted the capability to mutate final fields reflectively via
--enable-final-field-mutation=...
.
All other attempts to mutate a final
field via Field::set
are deemed illegal. The behavior of the JDK for illegal final
field mutation (assuming setAccessible(true)
has succeeded) is determined by the flag --illegal-final-field-mutation
. The default value of the flag is warn
, which allows the field mutation to succeed but emits a warning (at most one per caller's module). Future JDK releases will change the default of this flag and/or restrict its allowed values, or even remove it altogether.
Conditions 2 and 3 above are new. They introduce three incompatibilities, all of which are expected to be rare:
- If
setAccessible(true)
is called from module X, and theField
object is then passed to module Y, then Y callingField::set
will fail for afinal
field if the package is not open to Y. This is necessary to preserve strong encapsulation. It is not sufficient merely to enablefinal
field mutation for Y in this case. - If a package is opened reflectively to Z via
Module::addOpens
, then Z can callsetAccessible(true)
for afinal
field in the package but callingField::set
will fail even iffinal
field mutation is enabled for Z. This is a design decision to preserve the traceability of code which can mutatefinal
fields. - If native code in a JNI-attached thread with no Java frames on the stack obtains a
Field
object for whichsetAccessible(true)
succeeded, then callingField::set
will fail if the package is not exported to all modules. This is a design decision to prevent native code from undermining strong encapsulation.
There are no changes to the specification of the Java Native Interface except for a technical clarification to mention final
instance fields for completeness. Native code, if allowed to run by --enable-native-access
, can mutate final
instance fields via the JNI as in prior JDK releases, except for the corner-case incompatibility noted above.
There are no changes to the specification of the Instrumentation API, which means that agents, if allowed to run by -javaagent
or -XX:+EnableDynamicAgentLoading
, can perform exactly the same actions w.r.t. final
fields as in prior JDK releases.
Specification
The Java Platform Specification gates the ability of Field::set
to mutate a final
field on whether the end user chose to enable "final field mutation" at startup. This is specified in a new new section in "5. Features", after the existing "Restricted Methods" section:
* Integrity of final fields
Various methods in the Java SE API provide _write access_ to final
fields at run time. This means that Java code can mutate the value of a
final field after the field has been initialized in a constructor,
an instance initializer, or the field's declaration. Only the values of
final instance fields, not final static fields, can be mutated in this way.
The methods that provide write access, known as _mutation
methods_, are:
<ul>
<li>java.lang.reflect.Field::set(Object, Object)</li>
<li>java.lang.reflect.Field::setBoolean(Object, boolean)</li>
<li>java.lang.reflect.Field::setByte(Object, byte)</li>
<li>java.lang.reflect.Field::setChar(Object, char)</li>
<li>java.lang.reflect.Field::setShort(Object, short)</li>
<li>java.lang.reflect.Field::setInt(Object, int)</li>
<li>java.lang.reflect.Field::setLong(Object, long)</li>
<li>java.lang.reflect.Field::setFloat(Object, float)</li>
<li>java.lang.reflect.Field::setDouble(Object, double)</li>
<li>java.lang.invoke.MethodHandles.Lookup::unreflectSetter(Field)</li>
</ul>
The use of mutation methods to alter the values of final fields is a
legacy practice. It is strongly inadvisable because it undermines the
correctness of programs written in expectation of final fields being
immutable.
In Java SE NN and later, when a mutation method is invoked, one of the
preconditions for providing its caller with write access is that
_final field mutation_ is enabled for the caller. If final field
mutation is disabled for the caller, then write access is not provided
and the mutation method throws IllegalAccessException.
An Implementation of this Specification must:
<ul>
<li><p>disable final field mutation for all code by default.</p></li>
<li><p>provide a means to invoke its run-time system with final field
mutation enabled for code identified to the run-time system, and
disabled for all other code.</p></li>
</ul>
(The Reference Implementation provides this capability via the
command-line option --enable-final-field-mutation=M, where M is a
comma-separated list of modules. The special operand ALL-UNNAMED
indicates every unnamed module, which includes code on the class
path.)
As an aid to migration, an Implementation may allow code to alter the
values of final fields by invoking mutation methods, even if the code
is not identified to the run-time system as being enabled for final
field mutation. In particular:
<ul>
<li>An Implementation may provide a means to invoke its run-time system
with final field mutation enabled for all code. If it does so then
it must, further, provide a means to invoke its run-time system with
final field mutation dissabled for all code.</li>
</ul>
(The Reference Implementation provides the first capability via the
command-line option --illegal-final-field-mutation=allow, and the
second capability via the command-line option
--illegal-final-field-mutation=deny.)
<ul>
<li>An Implementation may provide a means to invoke its run-time system
such that all code is provided with write access to final fields. If
the run-time system is invoked in this way, and if by doing so some
invocations of mutation methods succeed where otherwise they would
have failed (because final field mutation was disabled for the
caller or because some other precondition for write accesss did not
hold), then the first such invocation must cause a warning to be
issued on the standard error stream.</li>
</ul>
(The Reference Implementation provides this capability via the
command-line option --illegal-final-field-mutation=warn. At most one
warning is issued for each module whose code invokes mutation
methods.)
Future revisions of this Specification are expected to remove these
migration aids.
Field.setAccessible(boolean)
is updated to expand the text on when write access is enabled:
+ * <p>If this reflected object represents a non-final field, and this method is
+ * used to enable access, then both <em>{@linkplain #get(Object) read}</em>
+ * and <em>{@linkplain #set(Object, Object) write}</em> access to the field
+ * are enabled.
+ *
+ * <p>If this reflected object represents a <em>non-modifiable</em> final field
+ * then enabling access only enables read access. Any attempt to {@linkplain
+ * #set(Object, Object) set} the field value throws an {@code
+ * IllegalAccessException}. The following fields are non-modifiable:
+ * <ul>
+ * <li>static final fields declared in any class or interface</li>
+ * <li>final fields declared in a {@linkplain Class#isRecord() record}</li>
+ * <li>final fields declared in a {@linkplain Class#isHidden() hidden class}</li>
+ * </ul>
+ * <p>If this reflected object represents a non-static final field in a normal
+ * class (not a record class or hidden class) then enabling access will enable read
+ * access. Whether write access is allowed or not is checked when attempting to
+ * {@linkplain #set(Object, Object) set} the field value.
The set of conditions specified by Field.set(Object, Object)
for write access is updated..
- * <li>{@link #setAccessible(boolean) setAccessible(true)} has succeeded for
- * this {@code Field} object;</li>
- * <li>the field is non-static; and</li>
- * <li>the field's declaring class is not a {@linkplain Class#isHidden()
- * hidden class}; and</li>
+ * <li>{@link #setAccessible(boolean) setAccessible(true)} has succeeded for this
+ * {@code Field} object; and</li>
+ * <li><a href="doc-files/MutationMethods.html">final field mutation is enabled</a>
+ * for the caller's module; and</li>
+ * <li>the field's {@link #getDeclaringClass() declaring class} {@code D} is in
+ * a package that is {@linkplain Module#isOpen(String, Module) open} to the
+ * caller's module. This condition is not met if the module containing
+ * {@code D} has been {@linkplain Module#addOpens(String, Module) updated}
+ * to open the package to the caller module; and </li>
* <li>the field's declaring class is not a {@linkplain Class#isRecord()
- * record class}.</li>
+ * record class}; and </li>
+ * <li>the field's declaring class is not a {@linkplain Class#isHidden()
+ * hidden class}; and </li>
+ * <li>the field is non-static. </li>
+ * </ul>
+ *
+ * <p> This method may be called by <a href="{@docRoot}/../specs/jni/index.html">
+ * JNI code</a> with no caller class on the stack. In that case, and when the
+ * underlying field is final, this {@code Field} object has <em>write</em> access
+ * if and only if the following conditions are met:
+ * <ul>
+ * <li>{@code setAccessible(true)} has succeeded for this {@code Field} object; and</li>
+ * <li>final field mutation is enabled for the unnamed module; and</li>
+ * <li>the field is declared {@code public} and its declaring class is {@code
+ * public} in a package that is {@linkplain Module#isExported(String) exported}
+ * to all modules; and</li>
+ * <li>the field's declaring class is not a record class; and </li>
+ * <li>the field's declaring class is not a hidden class; and </li>
+ * <li>the field is non-static. </li>
java.lang.Module.addOpens(String, Module)
is updated to add:
+ * <p> Opening a package with this method does not allow the given module
+ * to {@linkplain Field#set(Object, Object) reflectively set} a final field
+ * in the package, or {@linkplain java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field)
+ * obtain a method handle with write access} to a final field in the package.
java/lang/reflect/doc-files/MutationMethods.html is added (attached as the html doesn't inline).
java.lang.invoke.MethodHandle.Lookup.unreflectSetter(Field)
delegates the conditions for when write access is allowed to Field.set
. Consequently, no changes are proposed to API docs for unreflectSetter
.
The command line options --enable-final-field-mutation=module[,module..]
and --illegal-reflective-final-mutation=(allow,warn,deny)
are added. The default value for the latter will be "warn". The man page for the "java" launcher will be updated to document these command line options, the following is the current draft of the changes to man page:
+`--enable-final-field-mutation` *module*\[,*module*...\]
+: This option allows code in the specified modules to mutate final instance
+ fields when the field's declaring class is in a package that is open to the
+ module mutating the field.
+
+ *module* can the name of a module on the module path, or `ALL-UNNAMED` to indicate
+ code on the class path.
+
+-`--illegal-final-field-mutation=`*parameter*
+: This option specifies the mode for how illegal final field mutation is handled:
+
+ > **Note:** This option will be removed in a future release.
+
+ - `allow`: This mode allows illegal final field mutation in all modules,
+ without any warings.
+
+ - `warn`: This mode is identical to `allow` except that a warning message is
+ issued for the first illegal final field mutation performaed in a module.
+ This mode is the default for the current JDK but will change in a future
+ release.
+
+ - `debug`: This mode is identical to `allow` except that a warning message
+ and stack trace are printed for every illegal final field mutation.
+
+ - `deny`: This mode disables final field mutation. That is, any illegal final
+ field mutation access causes an `IllegalAccessException`. This mode will
+ become the default in a future release.
+
+ To verify that your application is ready for a future version of the JDK,
+ run it with `--illegal-final-field-mutation=deny` along with any necessary
+ `--enable-final-field-mutation` options.
The "Main Manifest" section of the JAR File Specification will be updated to document the JDK-specific JAR file attribute "Enable-Reflective-Final-Mutation" allowed in executable JAR files:
+- Enable-Final-Field-Mutation: Enables final mutation for all code on the
+ class path (including the code in the executable JAR itself). The only
+ supported value is `ALL-UNNAMED`; no other module name can be given. It
+ is equivalent to running with `--enable-final-field-mutation ALL-UNNAMED`.
The JNI spec will be updated so that JNI Set<type>Field
or SetStatic<type>Field
functions specify that their behavior is undefined when setting the value of final fields. In the current draft, Set<type>Field
's description is added to add
+calling `GetFieldID()`. Specifying the field ID of a final instance field
+leads to undefined behavior.
and SetStatic<type>Field
is similarly updated to add:
+`GetStaticFieldID()`. Specifying the field ID of a final static field leads to
+undefined behavior.
The JDK-specific command line option -Xcheck:jni is updated to report a fatal error when attempting to mutate a final field with Set<type>Field
or SetStatic<type>Field
.
Additionally, if the application is run with -Xlog:jni=debug
, then mutation of final fields (both instance and static) are logged.
At most one warning is issued for any particular module, and only if a warning has not yet been issued for that module. The warning is written to the standard error stream. The following is an example warning when code using Field.set
on the class path mutates a final field of another class on the class path.
WARNING: Final field value in class CommandLineTestHelper$1C has been mutated reflectively by class CommandLineTestHelper in unnamed module @14dad5dc (file:/dir/CommandLineTest.d/)
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
- csr of
-
JDK-8353835 Implement JEP xxx: Prepare to Make Final Mean Final
-
- Open
-