-
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 (i) setAccessible(true)
has succeeded for that field, (ii) the field's class is in a package that is open to the module performing the mutation, and (iii, a new condition) the module performing the mutation has been granted the capability to mutate final fields reflectively via the --enable-final-field-mutation
flag.
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.
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. 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.
The set of conditions in Field.set(Object, Object)
for write access is updated..
- * <li>the field's declaring class is not a {@linkplain Class#isHidden()
- * hidden class}; and</li>
- * <li>the field's declaring class is not a {@linkplain Class#isRecord()
- * record class}.</li>
+ * <li>the field's declaring class is a normal class (not a {@linkplain Class#isRecord()
+ * record class} or {@linkplain Class#isHidden() hidden class}), and;</li>
+ * <li>the field's declaring class is in an unnamed module, an automatic module,
+ * or a package of a named module with an explicit declaration that opens
+ * the package to the caller's module; and</li>
+ * <li>the caller's module is allowed to <a href="doc-files/MutationMethods.html">
+ * reflectively mutate final fields</a> of normal classes, or the implementation
+ * allows the illegal mutation of final fields of normal classes. </li>
AccessibleObject.set(boolean)
is also updated to expand the text on when write access is enabled:
- * <p> This method cannot be used to enable {@linkplain Field#set <em>write</em>}
- * access to a <em>non-modifiable</em> final field. The following fields
- * are non-modifiable:
+ * <p> If this reflected object represents a non-final field, and this method is
+ * used to enable access, then both <em>{@linkplain Field#get(Object) read}</em>
+ * and <em>{@linkplain Field#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
+ * Field#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#isHidden() hidden class}</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> The {@code accessible} flag when {@code true} suppresses Java language access
- * control checks to only enable {@linkplain Field#get <em>read</em>} access to
- * these non-modifiable final fields.
+ * <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 Field#set(Object, Object) set} the field value.
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-reflective-final-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 name, or `ALL-UNNAMED` to indicate
+ code on the class path.
+
+-`--illegal-reflective-final-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 warnings.
+
+ - `warn`: This mode is identical to `allow` except that a warning message is
+ issued for the first illegal final field mutation performed in a module.
+ This mode is the default for the current JDK but will change in a future
+ release.
+
+ - `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-reflective-final-mutation=deny` along with any necessary
+ `--enable-reflective-final-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-Reflective-Final-Mutation: Enables reflective 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-reflective-final-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-reflective-final-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
-