8353835: Implement JEP 500: Prepare to Make Final Mean Final

Reviewed-by: liach, vlivanov, dholmes, vyazici
This commit is contained in:
Alan Bateman 2025-11-18 08:06:18 +00:00
parent 8cdfec8d1c
commit 26460b6f12
76 changed files with 5311 additions and 196 deletions

View File

@ -80,6 +80,7 @@ else
BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libExplicitAttach := -pthread
BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libImplicitAttach := -pthread
BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libJNIAttachMutator := -pthread
BUILD_JDK_JTREG_EXCLUDE += exerevokeall.c
ifeq ($(call isTargetOs, linux), true)
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exelauncher := -ldl

View File

@ -258,17 +258,7 @@ void ciField::initialize_from(fieldDescriptor* fd) {
// not be constant is when the field is a *special* static & final field
// whose value may change. The three examples are java.lang.System.in,
// java.lang.System.out, and java.lang.System.err.
assert(vmClasses::System_klass() != nullptr, "Check once per vm");
if (k == vmClasses::System_klass()) {
// Check offsets for case 2: System.in, System.out, or System.err
if (_offset == java_lang_System::in_offset() ||
_offset == java_lang_System::out_offset() ||
_offset == java_lang_System::err_offset()) {
_is_constant = false;
return;
}
}
_is_constant = true;
_is_constant = !fd->is_mutable_static_final();
} else {
// An instance field can be constant if it's a final static field or if
// it's a final non-static field of a trusted class (classes in

View File

@ -1867,6 +1867,32 @@ address jni_GetDoubleField_addr() {
return (address)jni_GetDoubleField;
}
static void log_debug_if_final_static_field(JavaThread* current, const char* func_name, InstanceKlass* ik, int offset) {
if (log_is_enabled(Debug, jni)) {
fieldDescriptor fd;
bool found = ik->find_field_from_offset(offset, true, &fd);
assert(found, "bad field offset");
assert(fd.is_static(), "static/instance mismatch");
if (fd.is_final() && !fd.is_mutable_static_final()) {
ResourceMark rm(current);
log_debug(jni)("%s mutated final static field %s.%s", func_name, ik->external_name(), fd.name()->as_C_string());
}
}
}
static void log_debug_if_final_instance_field(JavaThread* current, const char* func_name, InstanceKlass* ik, int offset) {
if (log_is_enabled(Debug, jni)) {
fieldDescriptor fd;
bool found = ik->find_field_from_offset(offset, false, &fd);
assert(found, "bad field offset");
assert(!fd.is_static(), "static/instance mismatch");
if (fd.is_final()) {
ResourceMark rm(current);
log_debug(jni)("%s mutated final instance field %s.%s", func_name, ik->external_name(), fd.name()->as_C_string());
}
}
}
JNI_ENTRY_NO_PRESERVE(void, jni_SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value))
HOTSPOT_JNI_SETOBJECTFIELD_ENTRY(env, obj, (uintptr_t) fieldID, value);
oop o = JNIHandles::resolve_non_null(obj);
@ -1879,6 +1905,7 @@ JNI_ENTRY_NO_PRESERVE(void, jni_SetObjectField(JNIEnv *env, jobject obj, jfieldI
o = JvmtiExport::jni_SetField_probe(thread, obj, o, k, fieldID, false, JVM_SIGNATURE_CLASS, (jvalue *)&field_value);
}
HeapAccess<ON_UNKNOWN_OOP_REF>::oop_store_at(o, offset, JNIHandles::resolve(value));
log_debug_if_final_instance_field(thread, "SetObjectField", InstanceKlass::cast(k), offset);
HOTSPOT_JNI_SETOBJECTFIELD_RETURN();
JNI_END
@ -1901,6 +1928,7 @@ JNI_ENTRY_NO_PRESERVE(void, jni_Set##Result##Field(JNIEnv *env, jobject obj, jfi
o = JvmtiExport::jni_SetField_probe(thread, obj, o, k, fieldID, false, SigType, (jvalue *)&field_value); \
} \
o->Fieldname##_field_put(offset, value); \
log_debug_if_final_instance_field(thread, "Set<Type>Field", InstanceKlass::cast(k), offset); \
ReturnProbe; \
JNI_END
@ -2072,6 +2100,7 @@ JNI_ENTRY(void, jni_SetStaticObjectField(JNIEnv *env, jclass clazz, jfieldID fie
JvmtiExport::jni_SetField_probe(thread, nullptr, nullptr, id->holder(), fieldID, true, JVM_SIGNATURE_CLASS, (jvalue *)&field_value);
}
id->holder()->java_mirror()->obj_field_put(id->offset(), JNIHandles::resolve(value));
log_debug_if_final_static_field(THREAD, "SetStaticObjectField", id->holder(), id->offset());
HOTSPOT_JNI_SETSTATICOBJECTFIELD_RETURN();
JNI_END
@ -2093,6 +2122,7 @@ JNI_ENTRY(void, jni_SetStatic##Result##Field(JNIEnv *env, jclass clazz, jfieldID
JvmtiExport::jni_SetField_probe(thread, nullptr, nullptr, id->holder(), fieldID, true, SigType, (jvalue *)&field_value); \
} \
id->holder()->java_mirror()-> Fieldname##_field_put (id->offset(), value); \
log_debug_if_final_static_field(THREAD, "SetStatic<Type>Field", id->holder(), id->offset()); \
ReturnProbe;\
JNI_END

View File

@ -233,7 +233,7 @@ functionExit(JavaThread* thr)
}
static inline void
checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype)
checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype, bool setter)
{
fieldDescriptor fd;
@ -258,10 +258,18 @@ checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype)
!(fd.field_type() == T_ARRAY && ftype == T_OBJECT)) {
ReportJNIFatalError(thr, fatal_static_field_mismatch);
}
/* check if setting a final field */
if (setter && fd.is_final() && !fd.is_mutable_static_final()) {
ResourceMark rm(thr);
stringStream ss;
ss.print("SetStatic<Type>Field called to mutate final static field %s.%s", k_oop->external_name(), fd.name()->as_C_string());
ReportJNIWarning(thr, ss.as_string());
}
}
static inline void
checkInstanceFieldID(JavaThread* thr, jfieldID fid, jobject obj, int ftype)
checkInstanceFieldID(JavaThread* thr, jfieldID fid, jobject obj, int ftype, bool setter)
{
fieldDescriptor fd;
@ -287,14 +295,21 @@ checkInstanceFieldID(JavaThread* thr, jfieldID fid, jobject obj, int ftype)
ReportJNIFatalError(thr, fatal_wrong_field);
/* check for proper field type */
if (!InstanceKlass::cast(k_oop)->find_field_from_offset(offset,
false, &fd))
if (!InstanceKlass::cast(k_oop)->find_field_from_offset(offset, false, &fd))
ReportJNIFatalError(thr, fatal_instance_field_not_found);
if ((fd.field_type() != ftype) &&
!(fd.field_type() == T_ARRAY && ftype == T_OBJECT)) {
ReportJNIFatalError(thr, fatal_instance_field_mismatch);
}
/* check if setting a final field */
if (setter && fd.is_final()) {
ResourceMark rm(thr);
stringStream ss;
ss.print("Set<Type>Field called to mutate final instance field %s.%s", k_oop->external_name(), fd.name()->as_C_string());
ReportJNIWarning(thr, ss.as_string());
}
}
static inline void
@ -1204,7 +1219,7 @@ JNI_ENTRY_CHECKED(ReturnType, \
jfieldID fieldID)) \
functionEnter(thr); \
IN_VM( \
checkInstanceFieldID(thr, fieldID, obj, FieldType); \
checkInstanceFieldID(thr, fieldID, obj, FieldType, false); \
) \
ReturnType result = UNCHECKED()->Get##Result##Field(env,obj,fieldID); \
functionExit(thr); \
@ -1229,7 +1244,7 @@ JNI_ENTRY_CHECKED(void, \
ValueType val)) \
functionEnter(thr); \
IN_VM( \
checkInstanceFieldID(thr, fieldID, obj, FieldType); \
checkInstanceFieldID(thr, fieldID, obj, FieldType, true); \
) \
UNCHECKED()->Set##Result##Field(env,obj,fieldID,val); \
functionExit(thr); \
@ -1395,7 +1410,7 @@ JNI_ENTRY_CHECKED(ReturnType, \
functionEnter(thr); \
IN_VM( \
jniCheck::validate_class(thr, clazz, false); \
checkStaticFieldID(thr, fieldID, clazz, FieldType); \
checkStaticFieldID(thr, fieldID, clazz, FieldType, false); \
) \
ReturnType result = UNCHECKED()->GetStatic##Result##Field(env, \
clazz, \
@ -1423,7 +1438,7 @@ JNI_ENTRY_CHECKED(void, \
functionEnter(thr); \
IN_VM( \
jniCheck::validate_class(thr, clazz, false); \
checkStaticFieldID(thr, fieldID, clazz, FieldType); \
checkStaticFieldID(thr, fieldID, clazz, FieldType, true); \
) \
UNCHECKED()->SetStatic##Result##Field(env,clazz,fieldID,value); \
functionExit(thr); \

View File

@ -317,6 +317,10 @@ bool needs_module_property_warning = false;
#define ENABLE_NATIVE_ACCESS_LEN 20
#define ILLEGAL_NATIVE_ACCESS "illegal.native.access"
#define ILLEGAL_NATIVE_ACCESS_LEN 21
#define ENABLE_FINAL_FIELD_MUTATION "enable.final.field.mutation"
#define ENABLE_FINAL_FIELD_MUTATION_LEN 27
#define ILLEGAL_FINAL_FIELD_MUTATION "illegal.final.field.mutation"
#define ILLEGAL_FINAL_FIELD_MUTATION_LEN 28
// Return TRUE if option matches 'property', or 'property=', or 'property.'.
static bool matches_property_suffix(const char* option, const char* property, size_t len) {
@ -343,7 +347,9 @@ bool Arguments::internal_module_property_helper(const char* property, bool check
if (matches_property_suffix(property_suffix, PATCH, PATCH_LEN) ||
matches_property_suffix(property_suffix, LIMITMODS, LIMITMODS_LEN) ||
matches_property_suffix(property_suffix, UPGRADE_PATH, UPGRADE_PATH_LEN) ||
matches_property_suffix(property_suffix, ILLEGAL_NATIVE_ACCESS, ILLEGAL_NATIVE_ACCESS_LEN)) {
matches_property_suffix(property_suffix, ILLEGAL_NATIVE_ACCESS, ILLEGAL_NATIVE_ACCESS_LEN) ||
matches_property_suffix(property_suffix, ENABLE_FINAL_FIELD_MUTATION, ENABLE_FINAL_FIELD_MUTATION_LEN) ||
matches_property_suffix(property_suffix, ILLEGAL_FINAL_FIELD_MUTATION, ILLEGAL_FINAL_FIELD_MUTATION_LEN)) {
return true;
}
@ -1809,6 +1815,7 @@ static unsigned int addexports_count = 0;
static unsigned int addopens_count = 0;
static unsigned int patch_mod_count = 0;
static unsigned int enable_native_access_count = 0;
static unsigned int enable_final_field_mutation = 0;
static bool patch_mod_javabase = false;
// Check the consistency of vm_init_args
@ -2273,6 +2280,19 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin
if (res != JNI_OK) {
return res;
}
} else if (match_option(option, "--enable-final-field-mutation=", &tail)) {
if (!create_numbered_module_property("jdk.module.enable.final.field.mutation", tail, enable_final_field_mutation++)) {
return JNI_ENOMEM;
}
} else if (match_option(option, "--illegal-final-field-mutation=", &tail)) {
if (strcmp(tail, "allow") == 0 || strcmp(tail, "warn") == 0 || strcmp(tail, "debug") == 0 || strcmp(tail, "deny") == 0) {
PropertyList_unique_add(&_system_properties, "jdk.module.illegal.final.field.mutation", tail,
AddProperty, WriteableProperty, InternalProperty);
} else {
jio_fprintf(defaultStream::error_stream(),
"Value specified to --illegal-final-field-mutation not recognized: '%s'\n", tail);
return JNI_ERR;
}
} else if (match_option(option, "--sun-misc-unsafe-memory-access=", &tail)) {
if (strcmp(tail, "allow") == 0 || strcmp(tail, "warn") == 0 || strcmp(tail, "debug") == 0 || strcmp(tail, "deny") == 0) {
PropertyList_unique_add(&_system_properties, "sun.misc.unsafe.memory.access", tail,

View File

@ -46,6 +46,16 @@ bool fieldDescriptor::is_trusted_final() const {
return is_final() && (is_static() || ik->is_hidden() || ik->is_record());
}
bool fieldDescriptor::is_mutable_static_final() const {
InstanceKlass* ik = field_holder();
// write protected fields (JLS 17.5.4)
if (is_final() && is_static() && ik == vmClasses::System_klass() &&
(offset() == java_lang_System::in_offset() || offset() == java_lang_System::out_offset() || offset() == java_lang_System::err_offset())) {
return true;
}
return false;
}
AnnotationArray* fieldDescriptor::annotations() const {
InstanceKlass* ik = field_holder();
Array<AnnotationArray*>* md = ik->fields_annotations();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -98,6 +98,8 @@ class fieldDescriptor {
bool is_trusted_final() const;
bool is_mutable_static_final() const;
inline void set_is_field_access_watched(const bool value);
inline void set_is_field_modification_watched(const bool value);
inline void set_has_initialized_final_update(const bool value);

View File

@ -37,6 +37,7 @@ import java.lang.module.ModuleDescriptor.Version;
import java.lang.module.ResolvedModule;
import java.lang.reflect.AccessFlag;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.security.CodeSource;
@ -115,6 +116,10 @@ public final class Module implements AnnotatedElement {
@Stable
private boolean enableNativeAccess;
// true if this module is allowed to mutate final instance fields
@Stable
private boolean enableFinalMutation;
/**
* Creates a new named Module. The resulting Module will be defined to the
* VM but will not read any other modules, will not have any exports setup
@ -262,7 +267,6 @@ public final class Module implements AnnotatedElement {
* in the outer Module class as that would create a circular initializer dependency.
*/
private static final class EnableNativeAccess {
private EnableNativeAccess() {}
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
@ -331,12 +335,52 @@ public final class Module implements AnnotatedElement {
}
/**
* Update all unnamed modules to allow access to restricted methods.
* Enable code in all unnamed modules to access restricted methods.
*/
static void implAddEnableNativeAccessToAllUnnamed() {
static void addEnableNativeAccessToAllUnnamed() {
EnableNativeAccess.trySetEnableNativeAccess(ALL_UNNAMED_MODULE);
}
/**
* This class exists to avoid using Unsafe during early initialization of Module.
*/
private static final class EnableFinalMutation {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
private static final long ENABLE_FINAL_MUTATION_OFFSET =
UNSAFE.objectFieldOffset(Module.class, "enableFinalMutation");
private static boolean isEnableFinalMutation(Module module) {
return UNSAFE.getBooleanVolatile(module, ENABLE_FINAL_MUTATION_OFFSET);
}
private static boolean tryEnableFinalMutation(Module module) {
return UNSAFE.compareAndSetBoolean(module, ENABLE_FINAL_MUTATION_OFFSET, false, true);
}
}
/**
* Enable code in all unnamed modules to mutate final instance fields.
*/
static void addEnableFinalMutationToAllUnnamed() {
EnableFinalMutation.tryEnableFinalMutation(ALL_UNNAMED_MODULE);
}
/**
* Enable code in this named module to mutate final instance fields.
*/
boolean tryEnableFinalMutation() {
Module m = isNamed() ? this : ALL_UNNAMED_MODULE;
return EnableFinalMutation.tryEnableFinalMutation(m);
}
/**
* Return true if code in this module is allowed to mutate final instance fields.
*/
boolean isFinalMutationEnabled() {
Module m = isNamed() ? this : ALL_UNNAMED_MODULE;
return EnableFinalMutation.isEnableFinalMutation(m);
}
// --
// special Module to mean "all unnamed modules"
@ -718,8 +762,50 @@ public final class Module implements AnnotatedElement {
}
/**
* Returns {@code true} if this module exports or opens a package to
* the given module via its module declaration or CLI options.
* Returns {@code true} if this module statically exports a package to the given module.
* If the package is exported to the given module via {@code addExports} then this method
* returns {@code false}.
*/
boolean isStaticallyExported(String pn, Module other) {
return isStaticallyExportedOrOpened(pn, other, false);
}
/**
* Returns {@code true} if this module statically opens a package to the given module.
* If the package is opened to the given module via {@code addOpens} then this method
* returns {@code false}.
*/
boolean isStaticallyOpened(String pn, Module other) {
return isStaticallyExportedOrOpened(pn, other, true);
}
/**
* Returns {@code true} if this module exports or opens a package to the
* given module via its module declaration or CLI options.
*/
private boolean isStaticallyExportedOrOpened(String pn, Module other, boolean open) {
// all packages in unnamed modules are exported and open
if (!isNamed())
return true;
// all packages are exported/open to self
if (other == this && descriptor.packages().contains(pn))
return true;
// all packages in open and automatic modules are exported/open
if (descriptor.isOpen() || descriptor.isAutomatic())
return descriptor.packages().contains(pn);
// exported/opened via module descriptor
if (isExplicitlyExportedOrOpened(pn, other, open))
return true;
return false;
}
/**
* Returns {@code true} if this module exports or opens a package to the
* given module via its module declaration or CLI options.
*/
private boolean isExplicitlyExportedOrOpened(String pn, Module other, boolean open) {
// test if package is open to everyone or <other>
@ -818,11 +904,16 @@ public final class Module implements AnnotatedElement {
return isReflectivelyExportedOrOpened(pn, other, true);
}
/**
* If the caller's module is this module then update this module to export
* the given package to the given module.
*
* <p> Exporting a package with this method does not allow the given module to
* {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
* java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
* handle with write access} to a public final field declared in a public class
* in the package.
*
* <p> This method has no effect if the package is already exported (or
* <em>open</em>) to the given module. </p>
*
@ -860,21 +951,27 @@ public final class Module implements AnnotatedElement {
if (caller != this) {
throw new IllegalCallerException(caller + " != " + this);
}
implAddExportsOrOpens(pn, other, /*open*/false, /*syncVM*/true);
implAddExports(pn, other);
}
return this;
}
/**
* If this module has <em>opened</em> a package to at least the caller
* module then update this module to open the package to the given module.
* Opening a package with this method allows all types in the package,
* If this module has <em>opened</em> the given package to at least the caller
* module, then update this module to also open the package to the given module.
*
* <p> Opening a package with this method allows all types in the package,
* and all their members, not just public types and their public members,
* to be reflected on by the given module when using APIs that support
* private access or a way to bypass or suppress default Java language
* to be reflected on by the given module when using APIs that either support
* private access or provide a way to bypass or suppress Java language
* access control checks.
*
* <p> Opening a package with this method does not allow the given module to
* {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
* java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
* handle with write access} to a final field declared in a class in the package.
*
* <p> This method has no effect if the package is already <em>open</em>
* to the given module. </p>
*
@ -913,7 +1010,7 @@ public final class Module implements AnnotatedElement {
Module caller = getCallerModule(Reflection.getCallerClass());
if (caller != this && (caller == null || !isOpen(pn, caller)))
throw new IllegalCallerException(pn + " is not open to " + caller);
implAddExportsOrOpens(pn, other, /*open*/true, /*syncVM*/true);
implAddOpens(pn, other);
}
return this;
@ -923,28 +1020,29 @@ public final class Module implements AnnotatedElement {
/**
* Updates this module to export a package unconditionally.
*
* @apiNote This method is for JDK tests only.
* @apiNote Used by Proxy and other dynamic modules.
*/
void implAddExports(String pn) {
implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true);
implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true, true);
}
/**
* Updates this module to export a package to another module.
*
* @apiNote Used by Instrumentation::redefineModule and --add-exports
* @apiNote Used by addExports, Instrumentation::redefineModule, and --add-exports
*/
void implAddExports(String pn, Module other) {
implAddExportsOrOpens(pn, other, false, true);
implAddExportsOrOpens(pn, other, false, VM.isBooted(), true);
}
/**
* Updates this module to export a package to all unnamed modules.
*
* @apiNote Used by the --add-exports command line option.
* @apiNote Used by the --add-exports command line option and the launcher when
* an executable JAR file has the "Add-Exports" attribute in its main manifest.
*/
void implAddExportsToAllUnnamed(String pn) {
implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true);
implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, false, true);
}
/**
@ -954,7 +1052,7 @@ public final class Module implements AnnotatedElement {
* @apiNote This method is for VM white-box testing.
*/
void implAddExportsNoSync(String pn) {
implAddExportsOrOpens(pn.replace('/', '.'), Module.EVERYONE_MODULE, false, false);
implAddExportsOrOpens(pn.replace('/', '.'), Module.EVERYONE_MODULE, false, true, false);
}
/**
@ -964,7 +1062,7 @@ public final class Module implements AnnotatedElement {
* @apiNote This method is for VM white-box testing.
*/
void implAddExportsNoSync(String pn, Module other) {
implAddExportsOrOpens(pn.replace('/', '.'), other, false, false);
implAddExportsOrOpens(pn.replace('/', '.'), other, false, true, false);
}
/**
@ -973,35 +1071,40 @@ public final class Module implements AnnotatedElement {
* @apiNote This method is for JDK tests only.
*/
void implAddOpens(String pn) {
implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true);
implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true, true);
}
/**
* Updates this module to open a package to another module.
*
* @apiNote Used by Instrumentation::redefineModule and --add-opens
* @apiNote Used by addOpens, Instrumentation::redefineModule, and --add-opens
*/
void implAddOpens(String pn, Module other) {
implAddExportsOrOpens(pn, other, true, true);
implAddExportsOrOpens(pn, other, true, VM.isBooted(), true);
}
/**
* Updates this module to open a package to all unnamed modules.
*
* @apiNote Used by the --add-opens command line option.
* @apiNote Used by the --add-opens command line option and the launcher when
* an executable JAR file has the "Add-Opens" attribute in its main manifest.
*/
void implAddOpensToAllUnnamed(String pn) {
implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, true, true);
implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, true, false, true);
}
/**
* Updates a module to export or open a module to another module.
*
* If {@code syncVM} is {@code true} then the VM is notified.
* @param pn package name
* @param other the module to export/open the package to
* @param open true to open, false to export
* @param reflectively true if exported/opened reflectively
* @param syncVM true to update the VM
*/
private void implAddExportsOrOpens(String pn,
Module other,
boolean open,
boolean reflectively,
boolean syncVM) {
Objects.requireNonNull(other);
Objects.requireNonNull(pn);
@ -1031,7 +1134,7 @@ public final class Module implements AnnotatedElement {
}
}
if (VM.isBooted()) {
if (reflectively) {
// add package name to ReflectionData.exports if absent
Map<String, Boolean> map = ReflectionData.exports
.computeIfAbsent(this, other,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,6 +28,7 @@ package java.lang;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ResolvedModule;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@ -252,6 +253,12 @@ public final class ModuleLayer {
* module {@code target}. This method is a no-op if {@code source}
* already exports the package to at least {@code target}.
*
* <p> Exporting a package with this method does not allow the target module to
* {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
* java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
* handle with write access} to a public final field declared in a public class
* in the package.
*
* @param source
* The source module
* @param pn
@ -278,6 +285,11 @@ public final class ModuleLayer {
* module {@code target}. This method is a no-op if {@code source}
* already opens the package to at least {@code target}.
*
* <p> Opening a package with this method does not allow the target module
* to {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
* java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
* handle with write access} to a final field declared in a class in the package.
*
* @param source
* The source module
* @param pn

View File

@ -2096,18 +2096,33 @@ public final class System {
public boolean isReflectivelyOpened(Module m, String pn, Module other) {
return m.isReflectivelyOpened(pn, other);
}
public Module addEnableNativeAccess(Module m) {
return m.implAddEnableNativeAccess();
public void addEnableNativeAccess(Module m) {
m.implAddEnableNativeAccess();
}
public boolean addEnableNativeAccess(ModuleLayer layer, String name) {
return layer.addEnableNativeAccess(name);
}
public void addEnableNativeAccessToAllUnnamed() {
Module.implAddEnableNativeAccessToAllUnnamed();
Module.addEnableNativeAccessToAllUnnamed();
}
public void ensureNativeAccess(Module m, Class<?> owner, String methodName, Class<?> currentClass, boolean jni) {
m.ensureNativeAccess(owner, methodName, currentClass, jni);
}
public boolean isStaticallyExported(Module m, String pn, Module other) {
return m.isStaticallyExported(pn, other);
}
public boolean isStaticallyOpened(Module m, String pn, Module other) {
return m.isStaticallyOpened(pn, other);
}
public boolean isFinalMutationEnabled(Module m) {
return m.isFinalMutationEnabled();
}
public boolean tryEnableFinalMutation(Module m) {
return m.tryEnableFinalMutation();
}
public void addEnableFinalMutationToAllUnnamed() {
Module.addEnableFinalMutationToAllUnnamed();
}
public ServicesCatalog getServicesCatalog(ModuleLayer layer) {
return layer.getServicesCatalog();
}

View File

@ -3429,12 +3429,15 @@ return mh1;
* or if the field is {@code final} and write access
* is not enabled on the {@code Field} object
* @throws NullPointerException if the argument is null
* @see <a href="{@docRoot}/java.base/java/lang/reflect/doc-files/MutationMethods.html">Mutation methods</a>
*/
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
return unreflectField(f, true);
}
private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException {
@SuppressWarnings("deprecation")
boolean isAccessible = f.isAccessible();
MemberName field = new MemberName(f, isSetter);
if (isSetter && field.isFinal()) {
if (field.isTrustedFinalField()) {
@ -3442,12 +3445,15 @@ return mh1;
: "final field has no write access";
throw field.makeAccessException(msg, this);
}
// check if write access to final field allowed
if (!field.isStatic() && isAccessible) {
SharedSecrets.getJavaLangReflectAccess().checkAllowedToUnreflectFinalSetter(lookupClass, f);
}
}
assert(isSetter
? MethodHandleNatives.refKindIsSetter(field.getReferenceKind())
: MethodHandleNatives.refKindIsGetter(field.getReferenceKind()));
@SuppressWarnings("deprecation")
Lookup lookup = f.isAccessible() ? IMPL_LOOKUP : this;
Lookup lookup = isAccessible ? IMPL_LOOKUP : this;
return lookup.getDirectField(field.getReferenceKind(), f.getDeclaringClass(), field);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -160,18 +160,6 @@ public class AccessibleObject implements AnnotatedElement {
* to the caller and the package containing the declaring class is not open
* to the caller's module. </p>
*
* <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:
* <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>
* </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.
*
* @param flag the new value for the {@code accessible} flag
* @throws InaccessibleObjectException if access cannot be enabled
*

View File

@ -25,7 +25,18 @@
package java.lang.reflect;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.security.CodeSource;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import jdk.internal.access.SharedSecrets;
import jdk.internal.event.FinalFieldMutationEvent;
import jdk.internal.loader.ClassLoaders;
import jdk.internal.misc.VM;
import jdk.internal.module.ModuleBootstrap;
import jdk.internal.module.Modules;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.FieldAccessor;
import jdk.internal.reflect.Reflection;
@ -35,10 +46,6 @@ import sun.reflect.generics.repository.FieldRepository;
import sun.reflect.generics.factory.CoreReflectionFactory;
import sun.reflect.generics.factory.GenericsFactory;
import sun.reflect.generics.scope.ClassScope;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import sun.reflect.annotation.AnnotationParser;
import sun.reflect.annotation.AnnotationSupport;
import sun.reflect.annotation.TypeAnnotation;
@ -165,6 +172,27 @@ class Field extends AccessibleObject implements Member {
}
/**
* {@inheritDoc}
*
* <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 class that
* is 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.
*
* @throws InaccessibleObjectException {@inheritDoc}
*/
@Override
@ -762,18 +790,59 @@ class Field extends AccessibleObject implements Member {
* the underlying field is inaccessible, the method throws an
* {@code IllegalAccessException}.
*
* <p>If the underlying field is final, this {@code Field} object has
* <em>write</em> access if and only if the following conditions are met:
* <p>If the underlying field is final, this {@code Field} object has <em>write</em>
* access if and only if all of the following conditions are true, where {@code D} is
* the field's {@linkplain #getDeclaringClass() declaring class}:
*
* <ul>
* <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>the field's declaring class is not a {@linkplain Class#isRecord()
* record class}.</li>
* <li>{@link #setAccessible(boolean) setAccessible(true)} has succeeded for this
* {@code Field} object.</li>
* <li><a href="doc-files/MutationMethods.html">final field mutation is enabled</a>
* for the caller's module.</li>
* <li> At least one of the following conditions holds:
* <ol type="a">
* <li> {@code D} and the caller class are in the same module.</li>
* <li> The field is {@code public} and {@code D} is {@code public} in a package
* that the module containing {@code D} exports to at least the caller's module.</li>
* <li> {@code D} is in a package that is {@linkplain Module#isOpen(String, Module)
* open} to the caller's module.</li>
* </ol>
* </li>
* <li>{@code D} is not a {@linkplain Class#isRecord() record class}.</li>
* <li>{@code D} is not a {@linkplain Class#isHidden() hidden class}.</li>
* <li>The field is non-static.</li>
* </ul>
* If any of the above checks is not met, this method throws an
*
* <p>If any of the above conditions is not met, this method throws an
* {@code IllegalAccessException}.
*
* <p>These conditions are more restrictive than the conditions specified by {@link
* #setAccessible(boolean)} to suppress access checks. In particular, updating a
* module to export or open a package cannot be used to allow <em>write</em> access
* to final fields with the {@code set} methods defined by {@code Field}.
* Condition (b) is not met if the module containing {@code D} has been updated with
* {@linkplain Module#addExports(String, Module) addExports} to export the package to
* the caller's module. Condition (c) is not met if the module containing {@code D}
* has been updated with {@linkplain Module#addOpens(String, Module) addOpens} to open
* the package to the caller's module.
*
* <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 all of the following conditions are true, where {@code D} is the
* field's {@linkplain #getDeclaringClass() declaring class}:
*
* <ul>
* <li>{@code setAccessible(true)} has succeeded for this {@code Field} object.</li>
* <li>final field mutation is enabled for the unnamed module.</li>
* <li>The field is {@code public} and {@code D} is {@code public} in a package that
* is {@linkplain Module#isExported(String) exported} to all modules.</li>
* <li>{@code D} is not a {@linkplain Class#isRecord() record class}.</li>
* <li>{@code D} is not a {@linkplain Class#isHidden() hidden class}.</li>
* <li>The field is non-static.</li>
* </ul>
*
* <p>If any of the above conditions is not met, this method throws an
* {@code IllegalAccessException}.
*
* <p> Setting a final field in this way
@ -818,6 +887,8 @@ class Field extends AccessibleObject implements Member {
* and the field is an instance field.
* @throws ExceptionInInitializerError if the initialization provoked
* by this method fails.
*
* @see <a href="doc-files/MutationMethods.html">Mutation methods</a>
*/
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
@ -828,8 +899,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().set(obj, value);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.set(obj, value);
} else {
getOverrideFieldAccessor().set(obj, value);
setFinal(Reflection.getCallerClass(), obj, () -> fa.set(obj, value));
}
}
@ -867,8 +944,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setBoolean(obj, z);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setBoolean(obj, z);
} else {
getOverrideFieldAccessor().setBoolean(obj, z);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setBoolean(obj, z));
}
}
@ -906,8 +989,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setByte(obj, b);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setByte(obj, b);
} else {
getOverrideFieldAccessor().setByte(obj, b);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setByte(obj, b));
}
}
@ -945,8 +1034,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setChar(obj, c);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setChar(obj, c);
} else {
getOverrideFieldAccessor().setChar(obj, c);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setChar(obj, c));
}
}
@ -984,8 +1079,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setShort(obj, s);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setShort(obj, s);
} else {
getOverrideFieldAccessor().setShort(obj, s);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setShort(obj, s));
}
}
@ -1023,8 +1124,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setInt(obj, i);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setInt(obj, i);
} else {
getOverrideFieldAccessor().setInt(obj, i);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setInt(obj, i));
}
}
@ -1062,8 +1169,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setLong(obj, l);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setLong(obj, l);
} else {
getOverrideFieldAccessor().setLong(obj, l);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setLong(obj, l));
}
}
@ -1101,8 +1214,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setFloat(obj, f);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setFloat(obj, f);
} else {
getOverrideFieldAccessor().setFloat(obj, f);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setFloat(obj, f));
}
}
@ -1140,8 +1259,14 @@ class Field extends AccessibleObject implements Member {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, obj);
getFieldAccessor().setDouble(obj, d);
return;
}
FieldAccessor fa = getOverrideFieldAccessor();
if (!Modifier.isFinal(modifiers)) {
fa.setDouble(obj, d);
} else {
getOverrideFieldAccessor().setDouble(obj, d);
setFinal(Reflection.getCallerClass(), obj, () -> fa.setDouble(obj, d));
}
}
@ -1304,5 +1429,244 @@ class Field extends AccessibleObject implements Member {
getDeclaringClass(),
getGenericType(),
TypeAnnotation.TypeAnnotationTarget.FIELD);
}
}
/**
* A function that sets a field to a value.
*/
@FunctionalInterface
private interface FieldSetter {
void setFieldValue() throws IllegalAccessException;
}
/**
* Attempts to set a final field.
*/
private void setFinal(Class<?> caller, Object obj, FieldSetter setter) throws IllegalAccessException {
if (obj != null && isFinalInstanceInNormalClass()) {
preSetFinal(caller, false);
setter.setFieldValue();
postSetFinal(caller, false);
} else {
// throws IllegalAccessException if static, or field in record or hidden class
setter.setFieldValue();
}
}
/**
* Return true if this field is a final instance field in a normal class (not a
* record class or hidden class),
*/
private boolean isFinalInstanceInNormalClass() {
return Modifier.isFinal(modifiers)
&& !Modifier.isStatic(modifiers)
&& !clazz.isRecord()
&& !clazz.isHidden();
}
/**
* Check that the caller is allowed to unreflect for mutation a final instance field
* in a normal class.
* @throws IllegalAccessException if not allowed
*/
void checkAllowedToUnreflectFinalSetter(Class<?> caller) throws IllegalAccessException {
Objects.requireNonNull(caller);
preSetFinal(caller, true);
postSetFinal(caller, true);
}
/**
* Invoke before attempting to mutate, or unreflect for mutation, a final instance
* field in a normal class.
* @throws IllegalAccessException if not allowed
*/
private void preSetFinal(Class<?> caller, boolean unreflect) throws IllegalAccessException {
assert isFinalInstanceInNormalClass();
if (caller != null) {
// check if declaring class in package that is open to caller, or public field
// and declaring class is public in package exported to caller
if (!isFinalDeeplyAccessible(caller)) {
throw new IllegalAccessException(notAccessibleToCallerMessage(caller, unreflect));
}
} else {
// no java caller, only allowed if field is public in exported package
if (!Reflection.verifyPublicMemberAccess(clazz, modifiers)) {
throw new IllegalAccessException(notAccessibleToNoCallerMessage(unreflect));
}
}
// check if field mutation is enabled for caller module or illegal final field
// mutation is allowed
var mode = ModuleBootstrap.illegalFinalFieldMutation();
if (mode == ModuleBootstrap.IllegalFinalFieldMutation.DENY
&& !Modules.isFinalMutationEnabled(moduleToCheck(caller))) {
throw new IllegalAccessException(callerNotAllowedToMutateMessage(caller, unreflect));
}
}
/**
* Invoke after mutating a final instance field, or when unreflecting a final instance
* field for mutation, to print a warning and record a JFR event.
*/
private void postSetFinal(Class<?> caller, boolean unreflect) {
assert isFinalInstanceInNormalClass();
var mode = ModuleBootstrap.illegalFinalFieldMutation();
if (mode == ModuleBootstrap.IllegalFinalFieldMutation.WARN) {
// first mutation prints warning
Module moduleToCheck = moduleToCheck(caller);
if (Modules.tryEnableFinalMutation(moduleToCheck)) {
String warningMsg = finalFieldMutationWarning(caller, unreflect);
String targetModule = (caller != null && moduleToCheck.isNamed())
? moduleToCheck.getName()
: "ALL-UNNAMED";
VM.initialErr().printf("""
WARNING: %s
WARNING: Use --enable-final-field-mutation=%s to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
""", warningMsg, targetModule);
}
} else if (mode == ModuleBootstrap.IllegalFinalFieldMutation.DEBUG) {
// print warning and stack trace
var sb = new StringBuilder(finalFieldMutationWarning(caller, unreflect));
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.forEach(sf -> {
sb.append(System.lineSeparator()).append("\tat " + sf);
});
VM.initialErr().println(sb);
}
// record JFR event
FinalFieldMutationEvent.offer(getDeclaringClass(), getName());
}
/**
* Returns true if this final field is "deeply accessible" to the caller.
* The field is deeply accessible if declaring class is in a package that is open
* to the caller's module, or the field is public in a public class that is exported
* to the caller's module.
*
* Updates to the module of the declaring class at runtime with {@code Module.addExports}
* or {@code Module.addOpens} have no impact on the result of this method.
*/
private boolean isFinalDeeplyAccessible(Class<?> caller) {
assert isFinalInstanceInNormalClass();
// all fields in unnamed modules are deeply accessible
Module declaringModule = clazz.getModule();
if (!declaringModule.isNamed()) return true;
// all fields in the caller's module are deeply accessible
Module callerModule = caller.getModule();
if (callerModule == declaringModule) return true;
// public field, public class, package exported to caller's module
String pn = clazz.getPackageName();
if (Modifier.isPublic(modifiers)
&& Modifier.isPublic(clazz.getModifiers())
&& Modules.isStaticallyExported(declaringModule, pn, callerModule)) {
return true;
}
// package open to caller's module
return Modules.isStaticallyOpened(declaringModule, pn, callerModule);
}
/**
* Returns the Module to use for access checks with the given caller.
*/
private Module moduleToCheck(Class<?> caller) {
if (caller != null) {
return caller.getModule();
} else {
// no java caller, only allowed if field is public in exported package
return ClassLoaders.appClassLoader().getUnnamedModule();
}
}
/**
* Returns the warning message to print when this final field is mutated by
* the given possibly-null caller.
*/
private String finalFieldMutationWarning(Class<?> caller, boolean unreflect) {
assert Modifier.isFinal(modifiers);
String source;
if (caller != null) {
source = caller + " in " + caller.getModule();
CodeSource cs = caller.getProtectionDomain().getCodeSource();
if (cs != null) {
URL url = cs.getLocation();
if (url != null) {
source += " (" + url + ")";
}
}
} else {
source = "JNI attached thread with no caller frame";
}
return String.format("Final field %s in %s has been %s by %s",
name,
clazz,
(unreflect) ? "unreflected for mutation" : "mutated reflectively",
source);
}
/**
* Returns the message for an IllegalAccessException when a final field cannot be
* mutated because the declaring class is in a package that is not "deeply accessible"
* to the caller.
*/
private String notAccessibleToCallerMessage(Class<?> caller, boolean unreflect) {
String exportsOrOpens = Modifier.isPublic(modifiers)
&& Modifier.isPublic(clazz.getModifiers()) ? "exports" : "opens";
return String.format("%s, %s does not explicitly \"%s\" package %s to %s",
cannotSetFieldMessage(caller, unreflect),
clazz.getModule(),
exportsOrOpens,
clazz.getPackageName(),
caller.getModule());
}
/**
* Returns the exception message for the IllegalAccessException when this
* final field cannot be mutated because the caller module is not allowed
* to mutate final fields.
*/
private String callerNotAllowedToMutateMessage(Class<?> caller, boolean unreflect) {
if (caller != null) {
return String.format("%s, %s is not allowed to mutate final fields",
cannotSetFieldMessage(caller, unreflect),
caller.getModule());
} else {
return notAccessibleToNoCallerMessage(unreflect);
}
}
/**
* Returns the message for an IllegalAccessException when a field is not
* accessible to a JNI attached thread.
*/
private String notAccessibleToNoCallerMessage(boolean unreflect) {
return cannotSetFieldMessage("JNI attached thread with no caller frame cannot", unreflect);
}
/**
* Returns a message to indicate that the caller cannot set/unreflect this final field.
*/
private String cannotSetFieldMessage(Class<?> caller, boolean unreflect) {
return cannotSetFieldMessage(caller + " (in " + caller.getModule() + ") cannot", unreflect);
}
/**
* Returns a message to indicate that a field cannot be set/unreflected.
*/
private String cannotSetFieldMessage(String prefix, boolean unreflect) {
if (unreflect) {
return prefix + " unreflect final field " + clazz.getName() + "." + name
+ " (in " + clazz.getModule() + ") for mutation";
} else {
return prefix + " set final field " + clazz.getName() + "." + name
+ " (in " + clazz.getModule() + ")";
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -78,4 +78,9 @@ final class ReflectAccess implements JavaLangReflectAccess {
{
return ctor.newInstanceWithCaller(args, true, caller);
}
@Override
public void checkAllowedToUnreflectFinalSetter(Class<?> caller, Field f) throws IllegalAccessException {
f.checkAllowedToUnreflectFinalSetter(caller);
}
}

View File

@ -0,0 +1,69 @@
<!doctype html>
<!--
Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
This code is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 only, as
published by the Free Software Foundation. Oracle designates this
particular file as subject to the "Classpath" exception as provided
by Oracle in the LICENSE file that accompanied this code.
This code is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
version 2 for more details (a copy is included in the LICENSE file that
accompanied this code).
You should have received a copy of the GNU General Public License version
2 along with this work; if not, write to the Free Software Foundation,
Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
or visit www.oracle.com if you need additional information or have any
questions.
-->
<html lang="en">
<head>
<title>Mutation methods</title>
</head>
<body>
<h1 id="mutation">Mutation methods</h1>
<p>A number of methods in Java SE API provide <em>write</em> access to non-static
final fields. This means that Java code can alter the value of a final field
after the field has been initialized in a constructor.
The methods that provide write access, known as <em>mutation methods</em> are:
<ul>
<li>{@link java.lang.reflect.Field#set(Object, Object)}</li>
<li>{@link java.lang.reflect.Field#setBoolean(Object, boolean)}</li>
<li>{@link java.lang.reflect.Field#setByte(Object, byte)}</li>
<li>{@link java.lang.reflect.Field#setChar(Object, char)}</li>
<li>{@link java.lang.reflect.Field#setInt(Object, int)}</li>
<li>{@link java.lang.reflect.Field#setLong(Object, long)}</li>
<li>{@link java.lang.reflect.Field#setFloat(Object, float)}</li>
<li>{@link java.lang.reflect.Field#setDouble(Object, double)}</li>
<li>{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter(java.lang.reflect.Field)}</li>
</ul>
<p> The use of mutation methods to alter the values of final fields is
strongly inadvisable because it undermines the correctness of programs written in
expectation of final fields being immutable.
<p> In the reference implementation, a module can be granted the capability to mutate
final instance fields of classes in packages that are open to the module using
the command line option <code>--enable-final-field-mutation=M1,M2, ... Mn}</code> where
<code>M1</code>, <code>M2</code>, <code>...Mn</code> are module names (for the unnamed
module, the special value <code>ALL-UNNAMED</code> can be used). Mutation of final
instance fields of classes from modules not listed by that option is deemed
<em>illegal</em>.
The command line option <code>--illegal-final-field-mutation</code> controls how illegal
final mutation is handled. Valid values for this command line option are "warn", "allow",
"debug, and "deny". If this option is not specified then the default is "warn" so that
illegal final field mutation will result in a warning at runtime.
</body>
</html>

View File

@ -205,7 +205,7 @@ public interface JavaLangAccess {
* Updates the readability so that module m1 reads m2. The new read edge
* does not result in a strong reference to m2 (m2 can be GC'ed).
*
* This method is the same as m1.addReads(m2) but without a permission check.
* This method is the same as m1.addReads(m2) but without a caller check.
*/
void addReads(Module m1, Module m2);
@ -259,11 +259,11 @@ public interface JavaLangAccess {
/**
* Updates module m to allow access to restricted methods.
*/
Module addEnableNativeAccess(Module m);
void addEnableNativeAccess(Module m);
/**
* Updates module named {@code name} in layer {@code layer} to allow access to restricted methods.
* Returns true iff the given module exists in the given layer.
* Updates module named {@code name} in layer {@code layer} to allow access to
* restricted methods. Returns true iff the given module exists in the given layer.
*/
boolean addEnableNativeAccess(ModuleLayer layer, String name);
@ -273,7 +273,8 @@ public interface JavaLangAccess {
void addEnableNativeAccessToAllUnnamed();
/**
* Ensure that the given module has native access. If not, warn or throw exception depending on the configuration.
* Ensure that the given module has native access. If not, warn or throw exception
* depending on the configuration.
* @param m the module in which native access occurred
* @param owner the owner of the restricted method being called (or the JNI method being bound)
* @param methodName the name of the restricted method being called (or the JNI method being bound)
@ -282,6 +283,31 @@ public interface JavaLangAccess {
*/
void ensureNativeAccess(Module m, Class<?> owner, String methodName, Class<?> currentClass, boolean jni);
/**
* Enable code in all unnamed modules to mutate final instance fields.
*/
void addEnableFinalMutationToAllUnnamed();
/**
* Enable code in a given module to mutate final instance fields.
*/
boolean tryEnableFinalMutation(Module m);
/**
* Return true if code in a given module is allowed to mutate final instance fields.
*/
boolean isFinalMutationEnabled(Module m);
/**
* Return true if a given module has statically exported the given package to a given other module.
*/
boolean isStaticallyExported(Module module, String pn, Module other);
/**
* Return true if a given module has statically opened the given package to a given other module.
*/
boolean isStaticallyOpened(Module module, String pn, Module other);
/**
* Returns the ServicesCatalog for the given Layer.
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -67,4 +67,10 @@ public interface JavaLangReflectAccess {
/** Returns a new instance created by the given constructor with access check */
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
throws IllegalAccessException, InstantiationException, InvocationTargetException;
/**
* Check that the caller is allowed to unreflect for mutation a final instance field
* in a class that is not a record or hidden class.
*/
void checkAllowedToUnreflectFinalSetter(Class<?> caller, Field f) throws IllegalAccessException;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.event;
public class FinalFieldMutationEvent extends Event {
public Class<?> declaringClass;
public String fieldName;
/**
* Commit a FinalFieldMutationEvent if enabled.
*/
public static void offer(Class<?> declaringClass, String fieldName) {
if (enabled()) {
var event = new FinalFieldMutationEvent();
event.declaringClass = declaringClass;
event.fieldName = fieldName;
event.commit();
}
}
public static boolean enabled() {
// Generated by JFR
return false;
}
}

View File

@ -452,9 +452,12 @@ public final class ModuleBootstrap {
addExtraReads(bootLayer);
addExtraExportsAndOpens(bootLayer);
// add enable native access
// enable native access to modules specified to --enable-native-access
addEnableNativeAccess(bootLayer);
// allow final mutation by modules specified to --enable-final-field-mutation
addEnableFinalFieldMutation(bootLayer);
Counters.add("jdk.module.boot.7.adjustModulesTime");
// Step 8: CDS dump phase
@ -721,7 +724,6 @@ public final class ModuleBootstrap {
* additional packages specified on the command-line.
*/
private static void addExtraExportsAndOpens(ModuleLayer bootLayer) {
// --add-exports
String prefix = "jdk.module.addexports.";
Map<String, List<String>> extraExports = decode(prefix);
@ -729,14 +731,12 @@ public final class ModuleBootstrap {
addExtraExportsOrOpens(bootLayer, extraExports, false);
}
// --add-opens
prefix = "jdk.module.addopens.";
Map<String, List<String>> extraOpens = decode(prefix);
if (!extraOpens.isEmpty()) {
addExtraExportsOrOpens(bootLayer, extraOpens, true);
}
}
private static void addExtraExportsOrOpens(ModuleLayer bootLayer,
@ -807,6 +807,7 @@ public final class ModuleBootstrap {
private static final Set<String> USER_NATIVE_ACCESS_MODULES;
private static final Set<String> JDK_NATIVE_ACCESS_MODULES;
private static final IllegalNativeAccess ILLEGAL_NATIVE_ACCESS;
private static final IllegalFinalFieldMutation ILLEGAL_FINAL_FIELD_MUTATION;
public enum IllegalNativeAccess {
ALLOW,
@ -814,14 +815,26 @@ public final class ModuleBootstrap {
DENY
}
public enum IllegalFinalFieldMutation {
ALLOW,
WARN,
DEBUG,
DENY
}
static {
ILLEGAL_NATIVE_ACCESS = decodeIllegalNativeAccess();
USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess();
JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules();
ILLEGAL_FINAL_FIELD_MUTATION = decodeIllegalFinalFieldMutation();
}
public static IllegalNativeAccess illegalNativeAccess() {
return ILLEGAL_NATIVE_ACCESS;
}
static {
ILLEGAL_NATIVE_ACCESS = addIllegalNativeAccess();
USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess();
JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules();
public static IllegalFinalFieldMutation illegalFinalFieldMutation() {
return ILLEGAL_FINAL_FIELD_MUTATION;
}
/**
@ -881,7 +894,7 @@ public final class ModuleBootstrap {
/**
* Process the --illegal-native-access option (and its default).
*/
private static IllegalNativeAccess addIllegalNativeAccess() {
private static IllegalNativeAccess decodeIllegalNativeAccess() {
String value = getAndRemoveProperty("jdk.module.illegal.native.access");
// don't use a switch: bootstrapping issues!
if (value == null) {
@ -899,6 +912,71 @@ public final class ModuleBootstrap {
}
}
/**
* Process the --illegal-final-field-mutation option.
*/
private static IllegalFinalFieldMutation decodeIllegalFinalFieldMutation() {
String value = getAndRemoveProperty("jdk.module.illegal.final.field.mutation");
if (value == null) {
return IllegalFinalFieldMutation.WARN; // default
} else if (value.equals("allow")) {
return IllegalFinalFieldMutation.ALLOW;
} else if (value.equals("warn")) {
return IllegalFinalFieldMutation.WARN;
} else if (value.equals("debug")) {
return IllegalFinalFieldMutation.DEBUG;
} else if (value.equals("deny")) {
return IllegalFinalFieldMutation.DENY;
} else {
fail("Value specified to --illegal-final-field-mutation not recognized:"
+ " '" + value + "'");
return null;
}
}
/**
* Process the modules specified to --enable-final-field-mutation and grant the
* capability to mutate finals to specified named modules or all unnamed modules.
*/
private static void addEnableFinalFieldMutation(ModuleLayer bootLayer) {
for (String name : decodeEnableFinalFieldMutation()) {
if (name.equals("ALL-UNNAMED")) {
JLA.addEnableFinalMutationToAllUnnamed();
} else {
Module m = bootLayer.findModule(name).orElse(null);
if (m != null) {
JLA.tryEnableFinalMutation(m);
} else {
warnUnknownModule("--enable-final-field-mutation", name);
}
}
}
}
/**
* Returns the set of module names specified by --enable-final-field-mutation options.
*/
private static Set<String> decodeEnableFinalFieldMutation() {
String prefix = "jdk.module.enable.final.field.mutation.";
int index = 0;
// the system property is removed after decoding
String value = getAndRemoveProperty(prefix + index);
Set<String> modules = new HashSet<>();
if (value == null) {
return modules;
}
while (value != null) {
for (String s : value.split(",")) {
if (!s.isEmpty()) {
modules.add(s);
}
}
index++;
value = getAndRemoveProperty(prefix + index);
}
return modules;
}
/**
* Decodes the values of --add-reads, -add-exports, --add-opens or
* --patch-modules options that are encoded in system properties.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -139,6 +139,45 @@ public class Modules {
JLA.addEnableNativeAccessToAllUnnamed();
}
/**
* Enable code in all unnamed modules to mutate final instance fields.
*/
public static void addEnableFinalMutationToAllUnnamed() {
JLA.addEnableFinalMutationToAllUnnamed();
}
/**
* Enable code in a given module to mutate final instance fields.
*/
public static boolean tryEnableFinalMutation(Module m) {
return JLA.tryEnableFinalMutation(m);
}
/**
* Return true if code in a given module is allowed to mutate final instance fields.
*/
public static boolean isFinalMutationEnabled(Module m) {
return JLA.isFinalMutationEnabled(m);
}
/**
* Return true if a given module has statically exported the given package to a given
* other module. "statically exported" means the module declaration, --add-exports on
* the command line, or Add-Exports in the main manifest of an executable JAR.
*/
public static boolean isStaticallyExported(Module m, String pn, Module other) {
return JLA.isStaticallyExported(m, pn, other);
}
/**
* Return true if a given module has statically opened the given package to a given
* other module. "statically open" means the module declaration, --add-opens on the
* command line, or Add-Opens in the main manifest of an executable JAR.
*/
public static boolean isStaticallyOpened(Module m, String pn, Module other) {
return JLA.isStaticallyOpened(m, pn, other);
}
/**
* Updates module m to use a service.
* Same as m2.addUses(service) but without a caller check.

View File

@ -103,6 +103,7 @@ public final class LauncherHelper {
private static final String ADD_EXPORTS = "Add-Exports";
private static final String ADD_OPENS = "Add-Opens";
private static final String ENABLE_NATIVE_ACCESS = "Enable-Native-Access";
private static final String ENABLE_FINAL_FIELD_MUTATION = "Enable-Final-Field-Mutation";
private static StringBuilder outBuf = new StringBuilder();
@ -647,6 +648,8 @@ public final class LauncherHelper {
if (opens != null) {
addExportsOrOpens(opens, true);
}
// Enable-Native-Access
String enableNativeAccess = mainAttrs.getValue(ENABLE_NATIVE_ACCESS);
if (enableNativeAccess != null) {
if (!enableNativeAccess.equals("ALL-UNNAMED")) {
@ -655,6 +658,16 @@ public final class LauncherHelper {
Modules.addEnableNativeAccessToAllUnnamed();
}
// Enable-Final-Field-Mutation
String enableFinalFieldMutation = mainAttrs.getValue(ENABLE_FINAL_FIELD_MUTATION);
if (enableFinalFieldMutation != null) {
if (!enableFinalFieldMutation.equals("ALL-UNNAMED")) {
abort(null, "java.launcher.jar.error.illegal.effm.value",
enableFinalFieldMutation);
}
Modules.addEnableFinalMutationToAllUnnamed();
}
/*
* Hand off to FXHelper if it detects a JavaFX application
* This must be done after ensuring a Main-Class entry

View File

@ -70,6 +70,14 @@ java.launcher.opt.footer = \
\ by code in modules for which native access is not explicitly enabled.\n\
\ <value> is one of "deny", "warn" or "allow". The default value is "warn".\n\
\ This option will be removed in a future release.\n\
\ --enable-final-field-mutation <module name>[,<module name>...]\n\
\ allow code in the specified modules to mutate final instance fields.\n\
\ <module name> can also be ALL-UNNAMED to indicate code on the class path.\n\
\ --illegal-final-field-mutation=<value>\n\
\ allow or deny final field mutation by code in modules for which final\n\
\ field mutation is not explicitly enabled.\n\
\ <value> is one of "deny", "warn", "debug", or "allow". The default value is "warn".\n\
\ This option will be removed in a future release.\n\
\ --list-modules\n\
\ list observable modules and exit\n\
\ -d <module name>\n\
@ -291,6 +299,8 @@ java.launcher.jar.error5=\
Error: An unexpected error occurred while trying to close file {0}
java.launcher.jar.error.illegal.ena.value=\
Error: illegal value \"{0}\" for Enable-Native-Access manifest attribute. Only 'ALL-UNNAMED' is allowed
java.launcher.jar.error.illegal.effm.value=\
Error: illegal value \"{0}\" for Enable-Final-Field-Mutation manifest attribute. Only 'ALL-UNNAMED' is allowed
java.launcher.init.error=initialization error
java.launcher.javafx.error1=\
Error: The JavaFX launchApplication method has the wrong signature, it\n\

View File

@ -450,7 +450,7 @@ the JVM.
> **Note:** This option will be removed in a future release.
- `allow`: This mode allows illegal native access in all modules,
without any warings.
without any warnings.
- `warn`: This mode is identical to `allow` except that a warning
message is issued for the first illegal native access found in a module.
@ -465,6 +465,39 @@ the JVM.
run it with `--illegal-native-access=deny` along with any necessary `--enable-native-access`
options.
`--enable-final-field-mutation` *module*\[,*module*...\]
: Mutation of final fields is possible with the reflection API of the Java Platform.
However, it compromises safety and performance in all programs.
This option allows code in the specified modules to mutate final fields by reflection.
Attempts by code in any other module to mutate final fields by reflection are deemed _illegal_.
*module* can be 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 a 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 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.
`--finalization=`*value*
: Controls whether the JVM performs finalization of objects. Valid values
are "enabled" and "disabled". Finalization is enabled by default, so the
@ -701,7 +734,8 @@ the Java HotSpot Virtual Machine.
- A class descriptor is in decorated format (`Lname;`) when it should not be.
- A `NULL` parameter is allowed, but its use is questionable.
- Calling other JNI functions in the scope of `Get/ReleasePrimitiveArrayCritical`
or `Get/ReleaseStringCritical`
or `Get/ReleaseStringCritical`.
- A JNI call was made to mutate a final field.
Expect a performance degradation when this option is used.

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.MirrorEvent;
import jdk.jfr.internal.RemoveFields;
@Category("Java Application")
@Label("Final Field Mutation")
@Name(Type.EVENT_NAME_PREFIX + "FinalFieldMutation")
@RemoveFields("duration")
@StackFilter({
"java.lang.reflect.Field",
"java.lang.reflect.ReflectAccess",
"java.lang.invoke.MethodHandles$Lookup"
})
public final class FinalFieldMutationEvent extends MirrorEvent {
@Label("Declaring Class")
@Description("Declaring class with final field")
public Class<?> declaringClass;
@Label("Field Name")
@Description("Field name of final field")
public String fieldName;
}

View File

@ -76,6 +76,7 @@ public final class JDKEvents {
jdk.internal.event.VirtualThreadSubmitFailedEvent.class,
jdk.internal.event.X509CertificateEvent.class,
jdk.internal.event.X509ValidationEvent.class,
jdk.internal.event.FinalFieldMutationEvent.class,
DirectBufferStatisticsEvent.class,
InitialSecurityPropertyEvent.class,
MethodTraceEvent.class,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -34,6 +34,7 @@ import jdk.jfr.events.ExceptionThrownEvent;
import jdk.jfr.events.FileForceEvent;
import jdk.jfr.events.FileReadEvent;
import jdk.jfr.events.FileWriteEvent;
import jdk.jfr.events.FinalFieldMutationEvent;
import jdk.jfr.events.ProcessStartEvent;
import jdk.jfr.events.SecurityPropertyModificationEvent;
import jdk.jfr.events.SecurityProviderServiceEvent;
@ -77,6 +78,7 @@ final class MirrorEvents {
register("jdk.internal.event.ErrorThrownEvent", ErrorThrownEvent.class);
register("jdk.internal.event.ExceptionStatisticsEvent", ExceptionStatisticsEvent.class);
register("jdk.internal.event.ExceptionThrownEvent", ExceptionThrownEvent.class);
register("jdk.internal.event.FinalFieldMutationEvent", FinalFieldMutationEvent.class);
};
private static void register(String eventClassName, Class<? extends MirrorEvent> mirrorClass) {

View File

@ -125,6 +125,7 @@ public final class PlatformEventType extends Type {
Type.EVENT_NAME_PREFIX + "FileWrite" -> 6;
case Type.EVENT_NAME_PREFIX + "FileRead",
Type.EVENT_NAME_PREFIX + "FileForce" -> 5;
case Type.EVENT_NAME_PREFIX + "FinalFieldMutation" -> 4;
default -> 3;
};
}

View File

@ -117,6 +117,11 @@
<setting name="period">everyChunk</setting>
</event>
<event name="jdk.FinalFieldMutation">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>
<event name="jdk.SyncOnValueBasedClass">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>

View File

@ -117,6 +117,11 @@
<setting name="period">everyChunk</setting>
</event>
<event name="jdk.FinalFieldMutation">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>
<event name="jdk.SyncOnValueBasedClass">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>

View File

@ -0,0 +1,356 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.reflect.Method;
import java.util.Objects;
/**
* Invoked by MutateFinalsTest, either directly or in a child VM, with the name of the
* test method in this class to execute.
*/
public class MutateFinals {
/**
* Usage: java MutateFinals <method-name>
*/
public static void main(String[] args) throws Exception {
invoke(args[0]);
}
/**
* Invokes the given method.
*/
static void invoke(String methodName) throws Exception {
Method m = MutateFinals.class.getDeclaredMethod(methodName);
m.invoke(null);
}
/**
* JNI SetObjectField.
*/
private static void testJniSetObjectField() throws Exception {
class C {
final Object value;
C(Object value) {
this.value = value;
}
}
Object oldValue = new Object();
Object newValue = new Object();
var obj = new C(oldValue);
jniSetObjectField(obj, newValue);
assertTrue(obj.value == newValue);
}
/**
* JNI SetBooleanField.
*/
private static void testJniSetBooleanField() throws Exception {
class C {
final boolean value;
C(boolean value) {
this.value = value;
}
}
var obj = new C(false);
jniSetBooleanField(obj, true);
assertTrue(obj.value);
}
/**
* JNI SetByteField.
*/
private static void testJniSetByteField() throws Exception {
class C {
final byte value;
C(byte value) {
this.value = value;
}
}
byte oldValue = (byte) 1;
byte newValue = (byte) 2;
var obj = new C(oldValue);
jniSetByteField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetCharField.
*/
private static void testJniSetCharField() throws Exception {
class C {
final char value;
C(char value) {
this.value = value;
}
}
char oldValue = 'A';
char newValue = 'B';
var obj = new C(oldValue);
jniSetCharField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetShortField.
*/
private static void testJniSetShortField() throws Exception {
class C {
final short value;
C(short value) {
this.value = value;
}
}
short oldValue = (short) 1;
short newValue = (short) 2;
var obj = new C(oldValue);
jniSetShortField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetIntField.
*/
private static void testJniSetIntField() throws Exception {
class C {
final int value;
C(int value) {
this.value = value;
}
}
int oldValue = 1;
int newValue = 2;
var obj = new C(oldValue);
jniSetIntField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetLongField.
*/
private static void testJniSetLongField() throws Exception {
class C {
final long value;
C(long value) {
this.value = value;
}
}
long oldValue = 1L;
long newValue = 2L;
var obj = new C(oldValue);
jniSetLongField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetFloatField.
*/
private static void testJniSetFloatField() throws Exception {
class C {
final float value;
C(float value) {
this.value = value;
}
}
float oldValue = 1.0f;
float newValue = 2.0f;
var obj = new C(oldValue);
jniSetFloatField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetDoubleField.
*/
private static void testJniSetDoubleField() throws Exception {
class C {
final double value;
C(double value) {
this.value = value;
}
}
double oldValue = 1.0d;
double newValue = 2.0d;
var obj = new C(oldValue);
jniSetDoubleField(obj, newValue);
assertEquals(newValue, obj.value);
}
/**
* JNI SetStaticObjectField.
*/
private static void testJniSetStaticObjectField() throws Exception {
class C {
static final Object value = new Object();
}
Object newValue = new Object();
jniSetStaticObjectField(C.class, newValue);
assertTrue(C.value == newValue);
}
/**
* JNI SetStaticBooleanField.
*/
private static void testJniSetStaticBooleanField() throws Exception {
class C {
static final boolean value = false;
}
jniSetStaticBooleanField(C.class, true);
// use reflection as field treated as constant by compiler
boolean value = (boolean) C.class.getDeclaredField("value").get(null);
assertTrue(value);
}
/**
* JNI SetStaticByteField.
*/
private static void testJniSetStaticByteField() throws Exception {
class C {
static final byte value = (byte) 1;
}
byte newValue = (byte) 2;
jniSetStaticByteField(C.class, newValue);
// use reflection as field treated as constant by compiler
byte value = (byte) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
/**
* JNI SetStaticCharField.
*/
private static void testJniSetStaticCharField() throws Exception {
class C {
static final char value = 'A';
}
char newValue = 'B';
jniSetStaticCharField(C.class, newValue);
// use reflection as field treated as constant by compiler
char value = (char) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
/**
* JNI SetStaticShortField.
*/
private static void testJniSetStaticShortField() throws Exception {
class C {
static final short value = (short) 1;
}
short newValue = (short) 2;
jniSetStaticShortField(C.class, newValue);
// use reflection as field treated as constant by compiler
short value = (short) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
/**
* JNI SetStaticIntField.
*/
private static void testJniSetStaticIntField() throws Exception {
class C {
static final int value = 1;
}
int newValue = 2;
jniSetStaticIntField(C.class, newValue);
// use reflection as field treated as constant by compiler
int value = (int) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
/**
* JNI SetStaticLongField.
*/
private static void testJniSetStaticLongField() throws Exception {
class C {
static final long value = 1L;
}
long newValue = 2L;
jniSetStaticLongField(C.class, newValue);
// use reflection as field treated as constant by compiler
long value = (long) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
/**
* JNI SetStaticFloatField.
*/
private static void testJniSetStaticFloatField() throws Exception {
class C {
static final float value = 1.0f;
}
float newValue = 2.0f;
jniSetStaticFloatField(C.class, newValue);
// use reflection as field treated as constant by compiler
float value = (float) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
/**
* JNI SetStaticDoubleField.
*/
private static void testJniSetStaticDoubleField() throws Exception {
class C {
static final double value = 1.0d;
}
double newValue = 2.0f;
jniSetStaticDoubleField(C.class, newValue);
// use reflection as field treated as constant by compiler
double value = (double) C.class.getDeclaredField("value").get(null);
assertEquals(newValue, value);
}
private static native void jniSetObjectField(Object obj, Object value);
private static native void jniSetBooleanField(Object obj, boolean value);
private static native void jniSetByteField(Object obj, byte value);
private static native void jniSetCharField(Object obj, char value);
private static native void jniSetShortField(Object obj, short value);
private static native void jniSetIntField(Object obj, int value);
private static native void jniSetLongField(Object obj, long value);
private static native void jniSetFloatField(Object obj, float value);
private static native void jniSetDoubleField(Object obj, double value);
private static native void jniSetStaticObjectField(Class<?> clazz, Object value);
private static native void jniSetStaticBooleanField(Class<?> clazz, boolean value);
private static native void jniSetStaticByteField(Class<?> clazz, byte value);
private static native void jniSetStaticCharField(Class<?> clazz, char value);
private static native void jniSetStaticShortField(Class<?> clazz, short value);
private static native void jniSetStaticIntField(Class<?> clazz, int value);
private static native void jniSetStaticLongField(Class<?> clazz, long value);
private static native void jniSetStaticFloatField(Class<?> clazz, float value);
private static native void jniSetStaticDoubleField(Class<?> clazz, double value);
static {
System.loadLibrary("MutateFinals");
}
private static void assertTrue(boolean e) {
if (!e) throw new RuntimeException("Not true as expected");
}
private static void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new RuntimeException("Actual: " + actual + ", expected: " + expected);
}
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8353835
* @summary Test JNI SetXXXField methods to set final instance and final static fields
* @key randomness
* @modules java.management
* @library /test/lib
* @compile MutateFinals.java
* @run junit/native/timeout=300 MutateFinalsTest
*/
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
class MutateFinalsTest {
static String javaLibraryPath;
@BeforeAll
static void init() {
javaLibraryPath = System.getProperty("java.library.path");
}
/**
* The names of the test methods that use JNI to set final instance fields.
*/
static Stream<String> mutateInstanceFieldMethods() {
return Stream.of(
"testJniSetObjectField",
"testJniSetBooleanField",
"testJniSetByteField",
"testJniSetCharField",
"testJniSetShortField",
"testJniSetIntField",
"testJniSetLongField",
"testJniSetFloatField",
"testJniSetDoubleField"
);
}
/**
* The names of the test methods that use JNI to set final static fields.
*/
static Stream<String> mutateStaticFieldMethods() {
return Stream.of(
"testJniSetStaticObjectField",
"testJniSetStaticBooleanField",
"testJniSetStaticByteField",
"testJniSetStaticCharField",
"testJniSetStaticShortField",
"testJniSetStaticIntField",
"testJniSetStaticLongField",
"testJniSetStaticFloatField",
"testJniSetStaticDoubleField"
);
}
/**
* The names of all test methods that use JNI to set final fields.
*/
static Stream<String> allMutationMethods() {
return Stream.concat(mutateInstanceFieldMethods(), mutateStaticFieldMethods());
}
/**
* Mutate a final field with JNI.
*/
@ParameterizedTest
@MethodSource("allMutationMethods")
void testMutateFinal(String methodName) throws Exception {
MutateFinals.invoke(methodName);
}
/**
* Mutate a final instance field with JNI. The test launches a child VM with -Xcheck:jni
* and expects a warning in the output.
*/
@ParameterizedTest
@MethodSource("mutateInstanceFieldMethods")
void testMutateInstanceFinalWithXCheckJni(String methodName) throws Exception {
test(methodName, "-Xcheck:jni")
.shouldContain("WARNING in native method: Set<Type>Field called to mutate final instance field")
.shouldHaveExitValue(0);
}
/**
* Mutate final static fields with JNI. The test launches a child VM with -Xcheck:jni
* and expects a warning in the output.
*/
@ParameterizedTest
@MethodSource("mutateStaticFieldMethods")
void testMutateStaticFinalWithXCheckJni(String methodName) throws Exception {
test(methodName, "-Xcheck:jni")
.shouldContain("WARNING in native method: SetStatic<Type>Field called to mutate final static field")
.shouldHaveExitValue(0);
}
/**
* Mutate a final instance field with JNI. The test launches a child VM with -Xlog
* and expects a log message in the output.
*/
@ParameterizedTest
@MethodSource("mutateInstanceFieldMethods")
void testMutateInstanceFinalWithLogging(String methodName) throws Exception {
String type = methodName.contains("Object") ? "Object" : "<Type>";
test(methodName, "-Xlog:jni=debug")
.shouldContain("[debug][jni] Set" + type + "Field mutated final instance field")
.shouldHaveExitValue(0);
}
/**
* Mutate a final static field with JNI. The test launches a child VM with -Xlog
* and expects a log message in the output.
*/
@ParameterizedTest
@MethodSource("mutateStaticFieldMethods")
void testMutateStaticFinalWithLogging(String methodName) throws Exception {
String type = methodName.contains("Object") ? "Object" : "<Type>";
test(methodName, "-Xlog:jni=debug")
.shouldContain("[debug][jni] SetStatic" + type + "Field mutated final static field")
.shouldHaveExitValue(0);
}
/**
* Launches MutateFinals with the given method name as parameter, and the given VM options.
*/
private OutputAnalyzer test(String methodName, String... vmopts) throws Exception {
Stream<String> s1 = Stream.of(
"-Djava.library.path=" + javaLibraryPath,
"--enable-native-access=ALL-UNNAMED");
Stream<String> s2 = Stream.of(vmopts);
Stream<String> s3 = Stream.of("MutateFinals", methodName);
String[] opts = Stream.concat(Stream.concat(s1, s2), s3).toArray(String[]::new);
var outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.err)
.errorTo(System.err);
return outputAnalyzer;
}
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <stdio.h>
#include "jni.h"
JNIEXPORT void JNICALL Java_MutateFinals_jniSetObjectField(JNIEnv *env, jclass ignore, jobject obj, jobject value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "Ljava/lang/Object;");
if (fid != NULL) {
(*env)->SetObjectField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetBooleanField(JNIEnv *env, jclass ignore, jobject obj, jboolean value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "Z");
if (fid != NULL) {
(*env)->SetBooleanField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetByteField(JNIEnv *env, jclass ignore, jobject obj, jbyte value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "B");
if (fid != NULL) {
(*env)->SetByteField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetCharField(JNIEnv *env, jclass ignore, jobject obj, jchar value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "C");
if (fid != NULL) {
(*env)->SetCharField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetShortField(JNIEnv *env, jclass ignore, jobject obj, jshort value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "S");
if (fid != NULL) {
(*env)->SetShortField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetIntField(JNIEnv *env, jclass ignore, jobject obj, jint value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "I");
if (fid != NULL) {
(*env)->SetIntField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetLongField(JNIEnv *env, jclass ignore, jobject obj, jlong value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "J");
if (fid != NULL) {
(*env)->SetLongField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetFloatField(JNIEnv *env, jclass ignore, jobject obj, jfloat value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "F");
if (fid != NULL) {
(*env)->SetFloatField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetDoubleField(JNIEnv *env, jclass ignore, jobject obj, jdouble value) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "D");
if (fid != NULL) {
(*env)->SetDoubleField(env, obj, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticObjectField(JNIEnv *env, jclass ignore, jclass clazz, jobject value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "Ljava/lang/Object;");
if (fid != NULL) {
(*env)->SetStaticObjectField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticBooleanField(JNIEnv *env, jclass ignore, jclass clazz, jboolean value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "Z");
if (fid != NULL) {
(*env)->SetStaticBooleanField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticByteField(JNIEnv *env, jclass ignore, jclass clazz, jbyte value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "B");
if (fid != NULL) {
(*env)->SetStaticByteField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticCharField(JNIEnv *env, jclass ignore, jclass clazz, jchar value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "C");
if (fid != NULL) {
(*env)->SetStaticCharField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticShortField(JNIEnv *env, jclass ignore, jclass clazz, jshort value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "S");
if (fid != NULL) {
(*env)->SetStaticShortField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticIntField(JNIEnv *env, jclass ignore, jclass clazz, jint value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "I");
if (fid != NULL) {
(*env)->SetStaticIntField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticLongField(JNIEnv *env, jclass ignore, jclass clazz, jlong value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "J");
if (fid != NULL) {
(*env)->SetStaticLongField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticFloatField(JNIEnv *env, jclass ignore, jclass clazz, jfloat value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "F");
if (fid != NULL) {
(*env)->SetStaticFloatField(env, clazz, fid, value);
}
}
JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticDoubleField(JNIEnv *env, jclass ignore, jclass clazz, jdouble value) {
jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "D");
if (fid != NULL) {
(*env)->SetStaticDoubleField(env, clazz, fid, value);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,6 +29,7 @@
* @run junit/othervm/timeout=2500 -XX:+IgnoreUnrecognizedVMOptions
* -XX:-VerifyDependencies
* -esa
* --enable-final-field-mutation=ALL-UNNAMED
* test.java.lang.invoke.MethodHandlesGeneralTest
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,10 +27,12 @@
* @compile TestFieldLookupAccessibility.java
* pkg/A.java pkg/B_extends_A.java pkg/C.java
* pkg/subpkg/B_extends_A.java pkg/subpkg/C.java
* @run testng/othervm TestFieldLookupAccessibility
* @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true TestFieldLookupAccessibility
* @run testng/othervm --illegal-final-field-mutation=deny -DwriteAccess=false TestFieldLookupAccessibility
*/
import org.testng.Assert;
import static org.testng.Assert.*;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import pkg.B_extends_A;
@ -48,6 +50,14 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestFieldLookupAccessibility {
static boolean writeAccess;
@BeforeClass
static void setup() {
String s = System.getProperty("writeAccess");
assertNotNull(s);
writeAccess = Boolean.valueOf(s);
}
// The set of possible field lookup mechanisms
enum FieldLookup {
@ -118,7 +128,11 @@ public class TestFieldLookupAccessibility {
}
boolean isAccessible(Field f) {
return !(Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers()));
if (Modifier.isFinal(f.getModifiers())) {
return !Modifier.isStatic(f.getModifiers()) && writeAccess;
} else {
return true;
}
}
// Setting the accessibility bit of a Field grants access to non-static
@ -226,15 +240,15 @@ public class TestFieldLookupAccessibility {
collect(Collectors.toSet());
if (!actualFieldNames.equals(expected)) {
if (actualFieldNames.isEmpty()) {
Assert.assertEquals(actualFieldNames, expected, "No accessibility failures:");
assertEquals(actualFieldNames, expected, "No accessibility failures:");
}
else {
Assert.assertEquals(actualFieldNames, expected, "Accessibility failures differ:");
assertEquals(actualFieldNames, expected, "Accessibility failures differ:");
}
}
else {
if (!actual.values().stream().allMatch(IllegalAccessException.class::isInstance)) {
Assert.fail("Expecting an IllegalArgumentException for all failures " + actual);
fail("Expecting an IllegalArgumentException for all failures " + actual);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,9 +24,10 @@
/*
* @test
* @bug 8238358 8247444
* @run testng/othervm UnreflectTest
* @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle on
* trusted final fields (declared in hidden classes and records)
* @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true UnreflectTest
* @run junit/othervm --illegal-final-field-mutation=deny -DwriteAccess=false UnreflectTest
*/
import java.io.IOException;
@ -38,25 +39,25 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import static org.junit.jupiter.api.Assertions.*;
public class UnreflectTest {
static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
static final Class<?> hiddenClass = defineHiddenClass();
private static Class<?> defineHiddenClass() {
class UnreflectTest {
static Class<?> hiddenClass;
static boolean writeAccess;
@BeforeAll
static void setup() throws Exception {
String classes = System.getProperty("test.classes");
Path cf = Paths.get(classes, "Fields.class");
try {
byte[] bytes = Files.readAllBytes(cf);
return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
Path cf = Path.of(classes, "Fields.class");
byte[] bytes = Files.readAllBytes(cf);
hiddenClass = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
String s = System.getProperty("writeAccess");
assertNotNull(s);
writeAccess = Boolean.valueOf(s);
}
/*
@ -64,7 +65,7 @@ public class UnreflectTest {
* can write the value of a non-static final field in a normal class
*/
@Test
public void testFieldsInNormalClass() throws Throwable {
void testFieldsInNormalClass() throws Throwable {
// despite the name "HiddenClass", this class is loaded by the
// class loader as non-hidden class
Class<?> c = Fields.class;
@ -72,7 +73,11 @@ public class UnreflectTest {
assertFalse(c.isHidden());
readOnlyAccessibleObject(c, "STATIC_FINAL", null, true);
readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false);
readWriteAccessibleObject(c, "FINAL", o, true);
if (writeAccess) {
readWriteAccessibleObject(c, "FINAL", o, true);
} else {
readOnlyAccessibleObject(c, "FINAL", o, true);
}
readWriteAccessibleObject(c, "NON_FINAL", o, false);
}
@ -81,7 +86,7 @@ public class UnreflectTest {
* has NO write the value of a non-static final field in a hidden class
*/
@Test
public void testFieldsInHiddenClass() throws Throwable {
void testFieldsInHiddenClass() throws Throwable {
assertTrue(hiddenClass.isHidden());
Object o = hiddenClass.newInstance();
readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true);
@ -99,7 +104,8 @@ public class UnreflectTest {
* Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
* cannot write the value of a non-static final field in a record class
*/
public void testFieldsInRecordClass() throws Throwable {
@Test
void testFieldsInRecordClass() throws Throwable {
assertTrue(TestRecord.class.isRecord());
Object o = new TestRecord(1);
readOnlyAccessibleObject(TestRecord.class, "STATIC_FINAL", null, true);
@ -121,16 +127,12 @@ public class UnreflectTest {
assertTrue(f.trySetAccessible());
// Field object with read-only access
MethodHandle mh = LOOKUP.unreflectGetter(f);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.unreflectGetter(f);
Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
assertTrue(value == f.get(o));
try {
LOOKUP.unreflectSetter(f);
assertTrue(false, "should fail to unreflect a setter for " + name);
} catch (IllegalAccessException e) {
}
VarHandle vh = LOOKUP.unreflectVarHandle(f);
assertThrows(IllegalAccessException.class, () -> lookup.unreflectSetter(f));
VarHandle vh = lookup.unreflectVarHandle(f);
if (isFinal) {
assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
} else {
@ -163,7 +165,7 @@ public class UnreflectTest {
throw e;
}
VarHandle vh = LOOKUP.unreflectVarHandle(f);
VarHandle vh = MethodHandles.lookup().unreflectVarHandle(f);
if (isFinal) {
assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
} else {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,9 +23,9 @@
/**
* @test
* @build Fields HiddenClassTest
* @run testng/othervm HiddenClassTest
* @summary Test java.lang.reflect.AccessibleObject with modules
* @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true HiddenClassTest
* @run junit/othervm --illegal-final-field-mutation=deny -DwriteAccess=false HiddenClassTest
*/
import java.io.IOException;
@ -37,22 +37,24 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import static org.junit.jupiter.api.Assertions.*;
public class HiddenClassTest {
static final Class<?> hiddenClass = defineHiddenClass();
private static Class<?> defineHiddenClass() {
class HiddenClassTest {
static Class<?> hiddenClass;
static boolean writeAccess;
@BeforeAll
static void setup() throws Exception {
String classes = System.getProperty("test.classes");
Path cf = Paths.get(classes, "Fields.class");
try {
byte[] bytes = Files.readAllBytes(cf);
return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
Path cf = Path.of(classes, "Fields.class");
byte[] bytes = Files.readAllBytes(cf);
hiddenClass = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
String s = System.getProperty("writeAccess");
assertNotNull(s);
writeAccess = Boolean.valueOf(s);
}
/*
@ -60,7 +62,7 @@ public class HiddenClassTest {
* in a normal class
*/
@Test
public void testFieldsInNormalClass() throws Throwable {
void testFieldsInNormalClass() throws Throwable {
// despite the name "HiddenClass", this class is loaded by the
// class loader as non-hidden class
Class<?> c = Fields.class;
@ -68,7 +70,11 @@ public class HiddenClassTest {
assertFalse(c.isHidden());
readOnlyAccessibleObject(c, "STATIC_FINAL", null, true);
readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false);
readWriteAccessibleObject(c, "FINAL", o, true);
if (writeAccess) {
readWriteAccessibleObject(c, "FINAL", o, true);
} else {
readOnlyAccessibleObject(c, "FINAL", o, true);
}
readWriteAccessibleObject(c, "NON_FINAL", o, false);
}
@ -77,7 +83,7 @@ public class HiddenClassTest {
* in a hidden class
*/
@Test
public void testFieldsInHiddenClass() throws Throwable {
void testFieldsInHiddenClass() throws Throwable {
assertTrue(hiddenClass.isHidden());
Object o = hiddenClass.newInstance();
readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true);
@ -96,11 +102,7 @@ public class HiddenClassTest {
}
assertTrue(f.trySetAccessible());
assertTrue(f.get(o) != null);
try {
f.set(o, null);
assertTrue(false, "should fail to set " + name);
} catch (IllegalAccessException e) {
}
assertThrows(IllegalAccessException.class, () -> f.set(o, null));
}
private static void readWriteAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Exception {
@ -113,10 +115,6 @@ public class HiddenClassTest {
}
assertTrue(f.trySetAccessible());
assertTrue(f.get(o) != null);
try {
f.set(o, null);
} catch (IllegalAccessException e) {
throw e;
}
f.set(o, null);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,9 +24,9 @@
/**
* @test
* @bug 8277451
* @run testng NegativeTest
* @summary Test exception thrown due to bad receiver and bad value on
* Field with and without setAccessible(true)
* @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED NegativeTest
*/
import java.lang.reflect.Field;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@
* @bug 4250960 5044412
* @summary Should not be able to set final fields through reflection unless setAccessible(true) passes and is not static
* @author David Bowen (modified by Doug Lea)
* @run main/othervm --enable-final-field-mutation=ALL-UNNAMED Set
*/
import java.lang.reflect.*;

View File

@ -0,0 +1,203 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8353835
* @summary Basic test for JFR FinalFieldMutation event
* @requires vm.hasJFR
* @modules jdk.jfr/jdk.jfr.events
* @run junit FinalFieldMutationEventTest
* @run junit/othervm --illegal-final-field-mutation=allow FinalFieldMutationEventTest
* @run junit/othervm --illegal-final-field-mutation=warn FinalFieldMutationEventTest
* @run junit/othervm --illegal-final-field-mutation=debug FinalFieldMutationEventTest
* @run junit/othervm --illegal-final-field-mutation=deny FinalFieldMutationEventTest
*/
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordingFile;
import jdk.jfr.events.FinalFieldMutationEvent;
import jdk.jfr.events.StackFilter;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class FinalFieldMutationEventTest {
private static final String EVENT_NAME = "jdk.FinalFieldMutation";
/**
* Test class with final field.
*/
private static class C {
final int value;
C(int value) {
this.value = value;
}
}
/**
* Test jdk.FinalFieldMutation is recorded when mutating a final field.
*/
@Test
void testFieldSet() throws Exception {
Field field = C.class.getDeclaredField("value");
field.setAccessible(true);
try (Recording recording = new Recording()) {
recording.enable(EVENT_NAME).withStackTrace();
boolean mutated = false;
recording.start();
try {
var obj = new C(100);
try {
field.setInt(obj, 200);
mutated = true;
} catch (IllegalAccessException e) {
// denied
}
} finally {
recording.stop();
}
// FinalFieldMutation event should be recorded if field mutated
List<RecordedEvent> events = find(recording, EVENT_NAME);
System.err.println(events);
if (mutated) {
assertEquals(1, events.size(), "1 event expected");
checkEvent(events.get(0), field, "FinalFieldMutationEventTest::testFieldSet");
} else {
assertEquals(0, events.size(), "No events expected");
}
}
}
/**
* Test jdk.FinalFieldMutation is recorded when unreflecting a field field for mutation.
*/
@Test
void testUnreflectSetter() throws Exception {
Field field = C.class.getDeclaredField("value");
field.setAccessible(true);
try (Recording recording = new Recording()) {
recording.enable(EVENT_NAME).withStackTrace();
boolean unreflected = false;
recording.start();
try {
MethodHandles.lookup().unreflectSetter(field);
unreflected = true;
} catch (IllegalAccessException e) {
// denied
} finally {
recording.stop();
}
// FinalFieldMutation event should be recorded if field unreflected for set
List<RecordedEvent> events = find(recording, EVENT_NAME);
System.err.println(events);
if (unreflected) {
assertEquals(1, events.size(), "1 event expected");
checkEvent(events.get(0), field, "FinalFieldMutationEventTest::testUnreflectSetter");
} else {
assertEquals(0, events.size(), "No events expected");
}
}
}
/**
* Test that a FinalFieldMutationEvent event has the declaringClass and fieldName of
* the given Field, and the expected top frame.
*/
private void checkEvent(RecordedEvent e, Field f, String expectedTopFrame) {
RecordedClass clazz = e.getClass("declaringClass");
assertNotNull(clazz);
assertEquals(f.getDeclaringClass().getName(), clazz.getName());
assertEquals(f.getName(), e.getString("fieldName"));
// check the top-frame of the stack trace
RecordedMethod m = e.getStackTrace().getFrames().getFirst().getMethod();
assertEquals(expectedTopFrame, m.getType().getName() + "::" + m.getName());
}
/**
* Tests that FinalFieldMutationEvent's stack filter value names classes/methods that
* exist. This will help detect stale values when the implementation is refactored.
*/
@Test
void testFinalFieldMutationEventStackFilter() throws Exception {
String[] filters = FinalFieldMutationEvent.class.getAnnotation(StackFilter.class).value();
for (String filter : filters) {
String[] classAndMethod = filter.split("::");
String cn = classAndMethod[0];
// throws if class not found
Class<?> clazz = Class.forName(cn);
// if the filter has a method name then check a method of that name exists
if (classAndMethod.length > 1) {
String mn = classAndMethod[1];
Method method = Stream.of(clazz.getDeclaredMethods())
.filter(m -> m.getName().equals(mn))
.findFirst()
.orElse(null);
assertNotNull(method, cn + "::" + mn + " not found");
}
}
}
/**
* Returns the list of events in the given recording with the given name.
*/
private List<RecordedEvent> find(Recording recording, String name) throws Exception {
Path recordingFile = recordingFile(recording);
return RecordingFile.readAllEvents(recordingFile)
.stream()
.filter(e -> e.getEventType().getName().equals(name))
.toList();
}
/**
* Return the file path to the recording file.
*/
private Path recordingFile(Recording recording) throws Exception {
Path recordingFile = recording.getDestination();
if (recordingFile == null) {
ProcessHandle h = ProcessHandle.current();
recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
recording.dump(recordingFile);
}
return recordingFile;
}
}

View File

@ -0,0 +1,358 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8353835
* @summary Test Field.set and Lookup.unreflectSetter on final instance fields
* @run junit/othervm -DwriteAccess=true MutateFinalsTest
* @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true MutateFinalsTest
* @run junit/othervm --illegal-final-field-mutation=allow -DwriteAccess=true MutateFinalsTest
* @run junit/othervm --illegal-final-field-mutation=warn -DwriteAccess=true MutateFinalsTest
* @run junit/othervm --illegal-final-field-mutation=debug -DwriteAccess=true MutateFinalsTest
* @run junit/othervm --illegal-final-field-mutation=deny -DwriteAccess=false MutateFinalsTest
*/
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MutateFinalsTest {
static boolean writeAccess;
@BeforeAll
static void setup() throws Exception {
String s = System.getProperty("writeAccess");
assertNotNull(s);
writeAccess = Boolean.valueOf(s);
}
@Test
void testFieldSet() throws Exception {
class C {
final String value;
C(String value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
String oldValue = "oldValue";
String newValue = "newValue";
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.set(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.set(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.set(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.set("not a C", newValue));
assertThrows(IllegalArgumentException.class, () -> f.set(obj, 100)); // not a string
f.set(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.set(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetBoolean() throws Throwable {
class C {
final boolean value;
C(boolean value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
boolean oldValue = false;
boolean newValue = true;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setBoolean(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setBoolean(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setBoolean(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setBoolean("not a C", newValue));
f.setBoolean(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setBoolean(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetByte() throws Exception {
class C {
final byte value;
C(byte value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
byte oldValue = (byte) 1;
byte newValue = (byte) 2;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setByte(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setByte(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setByte(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setByte("not a C", newValue));
f.setByte(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setByte(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetChar() throws Exception {
class C {
final char value;
C(char value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
char oldValue = 'A';
char newValue = 'B';
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setChar(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setChar(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setChar(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setChar("not a C", newValue));
f.setChar(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setChar(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetShort() throws Exception {
class C {
final short value;
C(short value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
short oldValue = (short) 1;
short newValue = (short) 2;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setShort(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setShort(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setShort(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setShort("not a C", newValue));
f.setShort(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setShort(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetInt() throws Exception {
class C {
final int value;
C(int value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
int oldValue = 1;
int newValue = 2;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setInt(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setInt(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setInt(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setInt("not a C", newValue));
f.setInt(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setInt(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetLong() throws Exception {
class C {
final long value;
C(long value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
long oldValue = 1L;
long newValue = 2L;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setLong(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setLong(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setLong(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setLong("not a C", newValue));
f.setLong(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setLong(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetFloat() throws Exception {
class C {
final float value;
C(float value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
float oldValue = 1.0f;
float newValue = 2.0f;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setFloat(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setFloat(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setFloat(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setFloat("not a C", newValue));
f.setFloat(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setFloat(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testFieldSetDouble() throws Exception {
class C {
final double value;
C(double value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
double oldValue = 1.0d;
double newValue = 2.0d;
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(NullPointerException.class, () -> f.setDouble(null, newValue));
assertThrows(IllegalAccessException.class, () -> f.setDouble(obj, newValue));
assertTrue(obj.value == oldValue);
f.setAccessible(true);
assertThrows(NullPointerException.class, () -> f.setDouble(null, newValue));
if (writeAccess) {
assertThrows(IllegalArgumentException.class, () -> f.setDouble("not a C", newValue));
f.setDouble(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> f.setDouble(obj, newValue));
assertTrue(obj.value == oldValue);
}
}
@Test
void testUnreflectSetter() throws Throwable {
class C {
final Object value;
C(Object value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
Object oldValue = new Object();
var obj = new C(oldValue);
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> MethodHandles.lookup().unreflectSetter(f));
f.setAccessible(true);
if (writeAccess) {
Object newValue = new Object();
MethodHandles.lookup().unreflectSetter(f).invoke(obj, newValue);
assertTrue(obj.value == newValue);
} else {
assertThrows(IllegalAccessException.class, () -> MethodHandles.lookup().unreflectSetter(f));
}
}
}

View File

@ -0,0 +1,294 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8353835
* @summary Test the command line option --enable-final-field-mutation
* @library /test/lib
* @build CommandLineTestHelper
* @run junit CommandLineTest
*/
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
class CommandLineTest {
// helper class name
private static final String HELPER = "CommandLineTestHelper";
// warning output
private static final String WARNING_LINE1 =
"WARNING: Final field value in class " + HELPER;
private static final String WARNING_LINE3 =
"WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning";
private static final String WARNING_LINE4 =
"WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled";
// warning line 2 depends on the method
private static final String WARNING_MUTATED =
" has been mutated reflectively by class " + HELPER + " in unnamed module";
private static final String WARNING_UNREFLECTED =
" has been unreflected for mutation by class " + HELPER + " in unnamed module";
/**
* Test that a warning is printed by default.
*/
@Test
void testDefault() throws Exception {
test("testFieldSetInt")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
test("testUnreflectSetter")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_UNREFLECTED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
}
/**
* Test allow mutation of finals.
*/
@Test
void testAllow() throws Exception {
test("testFieldSetInt", "--illegal-final-field-mutation=allow")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_MUTATED)
.shouldHaveExitValue(0);
test("testFieldSetInt", "--enable-final-field-mutation=ALL-UNNAMED")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_MUTATED)
.shouldHaveExitValue(0);
// allow ALL-UNNAMED, deny by default
test("testFieldSetInt", "--enable-final-field-mutation=ALL-UNNAMED", "--illegal-final-field-mutation=deny")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_MUTATED)
.shouldHaveExitValue(0);
test("testUnreflectSetter", "--illegal-final-field-mutation=allow")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0);
test("testUnreflectSetter", "--enable-final-field-mutation=ALL-UNNAMED")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0);
// allow ALL-UNNAMED, deny by default
test("testUnreflectSetter", "--enable-final-field-mutation=ALL-UNNAMED", "--illegal-final-field-mutation=deny")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0);
}
/**
* Test warn on first mutation or unreflect of a final field.
*/
@Test
void testWarn() throws Exception {
test("testFieldSetInt", "--illegal-final-field-mutation=warn")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
test("testUnreflectSetter", "--illegal-final-field-mutation=warn")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_UNREFLECTED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
// should be one warning only, for Field.set
var output = test("testFieldSetInt+testUnreflectSetter", "--illegal-final-field-mutation=warn")
.shouldContain(WARNING_MUTATED)
.shouldNotContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0)
.getOutput();
assertEquals(1, countStrings(output, WARNING_LINE1));
assertEquals(1, countStrings(output, WARNING_LINE3));
assertEquals(1, countStrings(output, WARNING_LINE4));
// should be one warning only, for Lookup.unreflectSetter
output = test("testUnreflectSetter+testFieldSetInt", "--illegal-final-field-mutation=warn")
.shouldNotContain(WARNING_MUTATED)
.shouldContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0)
.getOutput();
assertEquals(1, countStrings(output, WARNING_LINE1));
assertEquals(1, countStrings(output, WARNING_LINE3));
assertEquals(1, countStrings(output, WARNING_LINE4));
}
/**
* Test debug mode.
*/
@Test
void testDebug() throws Exception {
test("testFieldSetInt+testUnreflectSetter", "--illegal-final-field-mutation=debug")
.shouldContain("Final field value in class " + HELPER)
.shouldContain(WARNING_MUTATED)
.shouldContain("java.lang.reflect.Field.setInt")
.shouldContain(WARNING_UNREFLECTED)
.shouldContain("java.lang.invoke.MethodHandles$Lookup.unreflectSetter")
.shouldHaveExitValue(0);
test("testUnreflectSetter+testFieldSetInt", "--illegal-final-field-mutation=debug")
.shouldContain("Final field value in class " + HELPER)
.shouldContain(WARNING_UNREFLECTED)
.shouldContain("java.lang.invoke.MethodHandles$Lookup.unreflectSetter")
.shouldContain(WARNING_MUTATED)
.shouldContain("java.lang.reflect.Field.setInt")
.shouldHaveExitValue(0);
}
/**
* Test deny mutation of finals.
*/
@Test
void testDeny() throws Exception {
test("testFieldSetInt", "--illegal-final-field-mutation=deny")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_MUTATED)
.shouldContain("java.lang.IllegalAccessException")
.shouldNotHaveExitValue(0);
test("testUnreflectSetter", "--illegal-final-field-mutation=deny")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_UNREFLECTED)
.shouldContain("java.lang.IllegalAccessException")
.shouldNotHaveExitValue(0);
}
/**
* Test last usage of --illegal-final-field-mutation "wins".
*/
@Test
void testLastOneWins() throws Exception {
test("testFieldSetInt", "--illegal-final-field-mutation=allow", "--illegal-final-field-mutation=deny")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_MUTATED)
.shouldContain("java.lang.IllegalAccessException")
.shouldNotHaveExitValue(0);
test("testFieldSetInt", "--illegal-final-field-mutation=deny", "--illegal-final-field-mutation=warn")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldHaveExitValue(0);
}
/**
* Test --illegal-final-field-mutation with bad values.
*/
@ParameterizedTest
@ValueSource(strings = { "", "bad" })
void testInvalidValues(String value) throws Exception {
test("testFieldSetInt", "--illegal-final-field-mutation=" + value)
.shouldContain("Value specified to --illegal-final-field-mutation not recognized")
.shouldNotHaveExitValue(0);
}
/**
* Test setting the internal system properties (that correspond to the command line
* options) on the commannd line. They should be ignored.
*/
@Test
void testSetPropertyOnCommandLine() throws Exception {
// --enable-final-field-mutation=ALL-UNNAMED
test("testFieldSetInt", "-Djdk.module.enable.final.field.mutation.0=ALL-UNNAMED")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
// --illegal-final-field-mutation=allow
test("testFieldSetInt", "-Djdk.module.illegal.final.field.mutation=allow")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
}
/**
* Test setting the internal system properties (that correspond to the command line
* options) at runtime. They should be ignored.
*/
@Test
void testSetPropertyAtRuntime() throws Exception {
// --enable-final-field-mutation=ALL-UNNAMED
test("setPropertyIllegalFinalFieldMutationAllow+testFieldSetInt")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
// --illegal-final-field-mutation=allow
test("setPropertyEnableFinalFieldMutationAllUnnamed+testFieldSetInt")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldContain(WARNING_LINE3)
.shouldContain(WARNING_LINE4)
.shouldHaveExitValue(0);
}
/**
* Launch helper with the given arguments and VM options.
*/
private OutputAnalyzer test(String action, String... vmopts) throws Exception {
Stream<String> s1 = Stream.of(vmopts);
Stream<String> s2 = Stream.of(HELPER, action);
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
var outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.err)
.errorTo(System.err);
return outputAnalyzer;
}
/**
* Counts the number of substrings in the given input string.
*/
private int countStrings(String input, String substring) {
return input.split(Pattern.quote(substring)).length - 1;
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.invoke.MethodHandles;
public class CommandLineTestHelper {
/**
* The argument is a list of names of no-arg static methods in this class to invoke.
* The names are separated with a '+'.
*/
public static void main(String[] args) throws Exception {
String[] methodNames = args.length > 0 ? args[0].split("\\+") : new String[0];
for (String methodName : methodNames) {
Method m = CommandLineTestHelper.class.getDeclaredMethod(methodName);
m.invoke(null);
}
}
static void testFieldSetInt() throws Exception {
class C {
final int value;
C(int value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
f.setAccessible(true);
var obj = new C(100);
f.setInt(obj, 200);
if (obj.value != 200) {
throw new RuntimeException("Unexpected value: " + obj.value);
}
}
static void testUnreflectSetter() throws Throwable {
class C {
final int value;
C(int value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
f.setAccessible(true);
var obj = new C(100);
MethodHandles.lookup().unreflectSetter(f).invoke(obj, 200);
if (obj.value != 200) {
throw new RuntimeException("Unexpected value: " + obj.value);
}
}
/**
* Set the internal system property that corresponds to the first usage of
* --enable-final-field-mutation.
*/
static void setPropertyEnableFinalFieldMutationAllUnnamed() {
System.setProperty("jdk.module.enable.final.field.mutation.0", "ALL-UNNAMED");
}
/**
* Set the internal system property that corresponds to --illegal-final-field-mutation.
*/
static void setPropertyIllegalFinalFieldMutationAllow() {
System.setProperty("jdk.module.illegal.final.field.mutation", "allow");
}
}

View File

@ -0,0 +1,227 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8353835
* @summary Test the executable JAR file attribute Enable-Final-Field-Mutation
* @library /test/lib
* @build m/*
* @build ExecutableJarTestHelper jdk.test.lib.util.JarUtils
* @run junit ExecutableJarTest
*/
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.stream.Stream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.util.JarUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class ExecutableJarTest {
// helper class name
private static final String HELPER = "ExecutableJarTestHelper";
// warning output
private static final String WARNING_LINE1 =
"WARNING: Final field value in class " + HELPER;
// warning line 2 depends on the method
private static final String WARNING_MUTATED =
" has been mutated reflectively by class " + HELPER + " in unnamed module";
private static final String WARNING_UNREFLECTED =
" has been unreflected for mutation by class " + HELPER + " in unnamed module";
/**
* Test executable JAR with code that uses Field.set to mutate a final field.
* A warning should be printed.
*/
@Test
void testFieldSetExpectingWarning() throws Exception {
String jarFile = createExecutableJar(Map.of());
testExecutableJar(jarFile, "testFieldSetInt")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_MUTATED)
.shouldHaveExitValue(0);
}
/**
* Test executable JAR with code that uses Lookup.unreflectSetter to get MH to a
* final field. A warning should be printed.
*/
@Test
void testUnreflectExpectingWarning() throws Exception {
String jarFile = createExecutableJar(Map.of());
testExecutableJar(jarFile, "testUnreflectSetter")
.shouldContain(WARNING_LINE1)
.shouldContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0);
}
/**
* Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses
* Field.set to mutate a final field. No warning should be printed.
*/
@Test
void testFieldSetExpectingAllow() throws Exception {
String jarFile = createExecutableJar(Map.of("Enable-Final-Field-Mutation", "ALL-UNNAMED"));
testExecutableJar(jarFile, "testFieldSetInt")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_MUTATED)
.shouldHaveExitValue(0);
}
/**
* Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses
* Lookup.unreflectSetter to get MH to a final field. No warning should be printed.
*/
@Test
void testUnreflectExpectingAllow() throws Exception {
String jarFile = createExecutableJar(Map.of("Enable-Final-Field-Mutation", "ALL-UNNAMED"));
testExecutableJar(jarFile, "testUnreflectSetter")
.shouldNotContain(WARNING_LINE1)
.shouldNotContain(WARNING_UNREFLECTED)
.shouldHaveExitValue(0);
}
/**
* Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses
* Field.set to mutate a final field of class in a named module. The package is opened
* with --add-open.
*/
@Test
void testFieldSetWithAddOpens1() throws Exception {
String jarFile = createExecutableJar(Map.of(
"Enable-Final-Field-Mutation", "ALL-UNNAMED"));
testExecutableJar(jarFile, "testFieldInNamedModule",
"--illegal-final-field-mutation=deny",
"--module-path", modulePath(),
"--add-modules", "m",
"--add-opens", "m/p=ALL-UNNAMED")
.shouldHaveExitValue(0);
}
/**
* Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses
* Field.set to mutate a final field of class in a named module. The package is opened
* with with the Add-Opens attribute.
*/
@Test
void testFieldSetWithAddOpens2() throws Exception {
String jarFile = createExecutableJar(Map.of(
"Enable-Final-Field-Mutation", "ALL-UNNAMED",
"Add-Opens", "m/p"));
testExecutableJar(jarFile, "testFieldInNamedModule",
"--illegal-final-field-mutation=deny",
"--module-path", modulePath(),
"--add-modules", "m")
.shouldHaveExitValue(0);
}
/**
* Test executable JAR with Enable-Final-Field-Mutation with that a value that is not
* "ALL-UNNAMED".
*/
@ParameterizedTest
@ValueSource(strings = {"java.base", "BadValue", " ", ""})
void testFinalFieldMutationBadValue(String value) throws Exception {
String jarFile = createExecutableJar(Map.of("Enable-Final-Field-Mutation", value));
testExecutableJar(jarFile, "testFieldSetInt")
.shouldContain("Error: illegal value \"" + value + "\" for Enable-Final-Field-Mutation" +
" manifest attribute. Only ALL-UNNAMED is allowed")
.shouldNotHaveExitValue(0);
}
/**
* Launch ExecutableJarTestHelper with the given arguments and VM options.
*/
private OutputAnalyzer test(String action, String... vmopts) throws Exception {
Stream<String> s1 = Stream.of(vmopts);
Stream<String> s2 = Stream.of("ExecutableJarTestHelper", action);
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
var outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.err)
.errorTo(System.err);
return outputAnalyzer;
}
/**
* Launch ExecutableJarTestHelper with the given arguments and VM options.
*/
private OutputAnalyzer testExecutableJar(String jarFile,
String action,
String... vmopts) throws Exception {
Stream<String> s1 = Stream.of(vmopts);
Stream<String> s2 = Stream.of("-jar", jarFile, action);
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
var outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.err)
.errorTo(System.err);
return outputAnalyzer;
}
/**
* Creates executable JAR named helper.jar with ExecutableJarTestHelper* classes.
*/
private String createExecutableJar(Map<String, String> map) throws Exception {
Path jarFile = Path.of("helper.jar");
var man = new Manifest();
Attributes attrs = man.getMainAttributes();
attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attrs.put(Attributes.Name.MAIN_CLASS, "ExecutableJarTestHelper");
map.entrySet().forEach(e -> {
var name = new Attributes.Name(e.getKey());
attrs.put(name, e.getValue());
});
Path dir = Path.of(System.getProperty("test.classes"));
try (Stream<Path> stream = Files.list(dir)) {
Path[] files = Files.list(dir).filter(p -> {
String fn = p.getFileName().toString();
return fn.startsWith("ExecutableJarTestHelper") && fn.endsWith(".class");
})
.toArray(Path[]::new);
JarUtils.createJarFile(jarFile, man, dir, files);
}
return jarFile.toString();
}
/**
* Return the module path for the modules used by this test.
*/
private String modulePath() {
return Path.of(System.getProperty("test.classes"), "modules").toString();
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.invoke.MethodHandles;
public class ExecutableJarTestHelper {
/**
* The argument is a list of names of no-arg static methods in this class to invoke.
* The names are separated with a '+'.
*/
public static void main(String[] args) throws Exception {
String[] methodNames = args.length > 0 ? args[0].split("\\+") : new String[0];
for (String methodName : methodNames) {
Method m = ExecutableJarTestHelper.class.getDeclaredMethod(methodName);
m.invoke(null);
}
}
/**
* Uses Field.set to mutate a final field.
*/
static void testFieldSetInt() throws Exception {
class C {
final int value;
C(int value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
f.setAccessible(true);
var obj = new C(100);
f.setInt(obj, 200);
if (obj.value != 200) {
throw new RuntimeException("Unexpected value: " + obj.value);
}
}
/**
* Uses Lookup.unreflectSetter to get a method handle to set a final field.
*/
static void testUnreflectSetter() throws Throwable {
class C {
final int value;
C(int value) {
this.value = value;
}
}
Field f = C.class.getDeclaredField("value");
f.setAccessible(true);
var obj = new C(100);
MethodHandles.lookup().unreflectSetter(f).invoke(obj, 200);
if (obj.value != 200) {
throw new RuntimeException("Unexpected value: " + obj.value);
}
}
/**
* Uses Field.set to mutate a final field of a class in a named module.
*/
static void testFieldInNamedModule() throws Exception {
Class<?> c = Class.forName("p.C");
if (!c.getModule().isNamed()) {
throw new RuntimeException(c + " is not in a named module");
}
Object obj = c.getDeclaredConstructor(int.class).newInstance(100);
Field f = c.getDeclaredField("value");
f.setAccessible(true);
f.setInt(obj, 200);
int newValue = f.getInt(obj);
if (newValue != 200) {
throw new RuntimeException("Unexpected value: " + newValue);
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Module containing a class with a final field used by ExecutableJarTest.
*/
module m {
exports p;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p;
/**
* A class with a final field used by ExecutableJarTest.
*/
public class C {
private final int value;
public C(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
/**
* Launched by JNIAttachMutatorTest to test a JNI attached thread attempting to mutate
* a final field.
*/
public class JNIAttachMutator {
private static final CountDownLatch finished = new CountDownLatch(1);
private static volatile Object obj;
private static volatile Throwable exc;
// public class, public final field
public static class C1 {
public final int value;
C1(int value) {
this.value = value;
}
}
// public class, non-public final field
public static class C2 {
final int value;
C2(int value) {
this.value = value;
}
}
// non-public class, public final field
static class C3 {
public final int value;
C3(int value) {
this.value = value;
}
}
/**
* Usage: java JNIAttachMutator <classname> <true|false>
*/
public static void main(String[] args) throws Exception {
String cn = args[0];
boolean expectIAE = Boolean.parseBoolean(args[1]);
Class<?> clazz = Class.forName(args[0]);
Constructor<?> ctor = clazz.getDeclaredConstructor(int.class);
ctor.setAccessible(true);
obj = ctor.newInstance(100);
// start native thread
startThread();
// wait for native thread to finish
finished.await();
if (expectIAE) {
if (exc == null) {
// IAE expected
throw new RuntimeException("IllegalAccessException not thrown");
} else if (!(exc instanceof IllegalAccessException)) {
// unexpected exception
throw new RuntimeException(exc);
}
} else if (exc != null) {
// no exception expected
throw new RuntimeException(exc);
}
}
/**
* Invoked by JNI attached thread to get object.
*/
static Object getObject() {
return obj;
}
/**
* Invoked by JNI attached thread to get Field object with accessible enabled.
*/
static Field getField() throws NoSuchFieldException {
Field f = obj.getClass().getDeclaredField("value");
f.setAccessible(true);
return f;
}
/**
* Invoked by JNI attached thread when finished.
*/
static void finish(Throwable ex) {
exc = ex;
finished.countDown();
}
private static native void startThread();
static {
System.loadLibrary("JNIAttachMutator");
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8353835
* @summary Test native thread attaching to the VM with JNI AttachCurrentThread and directly
* invoking Field.set to set a final field
* @library /test/lib
* @build m/*
* @compile JNIAttachMutator.java
* @run junit JNIAttachMutatorTest
*/
import java.nio.file.Path;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
class JNIAttachMutatorTest {
private static String testClasses;
private static String modulesDir;
private static String javaLibraryPath;
@BeforeAll
static void setup() {
testClasses = System.getProperty("test.classes");
modulesDir = Path.of(testClasses, "modules").toString();
javaLibraryPath = System.getProperty("java.library.path");
}
/**
* Final final mutation allowed. All final fields are public, in public classes,
* and in packages exported to all modules.
*/
@ParameterizedTest
@ValueSource(strings = {
"JNIAttachMutator$C1", // unnamed module
"p.C1", // named module
})
void testAllowed(String cn) throws Exception {
test(cn, false);
}
/**
* Final final mutation not allowed.
*/
@ParameterizedTest
@ValueSource(strings = {
// unnamed module
"JNIAttachMutator$C2", // public class, non-public final field
"JNIAttachMutator$C3", // non-public class, public final field
// named module
"p.C2", // public class, non-public final field, exported package
"p.C3", // non-public class, public final field, exported package
"q.C" // public class, public final field, package not exported
})
void testDenied(String cn) throws Exception {
test(cn, true);
}
/**
* public final field, public class, package exported to some modules.
*/
@Test
void testQualifiedExports() throws Exception {
test("q.C", true, "--add-exports", "m/q=ALL-UNNAMED");
}
/**
* Launches JNIAttachMutator to test a JNI attached thread mutating a final field.
* @param className the class with the field final
* @param expectIAE if IllegalAccessException is expected
* @param extraOps additional VM options
*/
private void test(String className, boolean expectIAE, String... extraOps) throws Exception {
Stream<String> s1 = Stream.of(extraOps);
Stream<String> s2 = Stream.of(
"-cp", testClasses,
"-Djava.library.path=" + javaLibraryPath,
"--module-path", modulesDir,
"--add-modules", "m",
"--add-opens", "m/p=ALL-UNNAMED", // allow setAccessible
"--add-opens", "m/q=ALL-UNNAMED",
"--enable-native-access=ALL-UNNAMED",
"--enable-final-field-mutation=ALL-UNNAMED",
"--illegal-final-field-mutation=deny",
"JNIAttachMutator",
className,
expectIAE ? "true" : "false");
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
OutputAnalyzer outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.out)
.errorTo(System.out);
outputAnalyzer.shouldHaveExitValue(0);
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
#include "jni.h"
#define STACK_SIZE 0x100000
static JavaVM *vm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* reserved) {
vm = jvm;
return JNI_VERSION_1_8;
}
/**
* Invokes JNIAttachMutator.getObject()
*/
jobject getObject(JNIEnv* env) {
jclass clazz = (*env)->FindClass(env, "JNIAttachMutator");
if (clazz == NULL) {
fprintf(stderr, "FindClass failed\n");
return NULL;
}
jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "getObject", "()Ljava/lang/Object;");
if (mid == NULL) {
fprintf(stderr, "GetMethodID for getObject failed\n");
return NULL;
}
jobject obj = (*env)->CallStaticObjectMethod(env, clazz, mid);
if (obj == NULL) {
fprintf(stderr, "CallObjectMethod to getObject failed\n");
return NULL;
}
return obj;
}
/**
* Invokes JNIAttachMutator.getField()
*/
jobject getField(JNIEnv* env) {
jclass clazz = (*env)->FindClass(env, "JNIAttachMutator");
if (clazz == NULL) {
fprintf(stderr, "FindClass failed\n");
return NULL;
}
jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "getField", "()Ljava/lang/reflect/Field;");
if (mid == NULL) {
fprintf(stderr, "GetStaticMethodID for getField failed\n");
return NULL;
}
jobject obj = (*env)->CallStaticObjectMethod(env, clazz, mid);
if (obj == NULL) {
fprintf(stderr, "CallObjectMethod to getField failed\n");
return NULL;
}
return obj;
}
/**
* Invokes Field.setInt
*/
jboolean setInt(JNIEnv* env, jobject obj, jobject fieldObj, jint newValue) {
jclass fieldClass = (*env)->GetObjectClass(env, fieldObj);
jmethodID mid = (*env)->GetMethodID(env, fieldClass, "setInt", "(Ljava/lang/Object;I)V");
if (mid == NULL) {
fprintf(stderr, "GetMethodID for Field.setInt failed\n");
return JNI_FALSE;
}
(*env)->CallObjectMethod(env, fieldObj, mid, obj, newValue);
return JNI_TRUE;
}
/**
* Invokes JNIAttachMutator.finish
*/
void finish(JNIEnv* env, jthrowable ex) {
jclass clazz = (*env)->FindClass(env, "JNIAttachMutator");
if (clazz == NULL) {
fprintf(stderr, "FindClass failed\n");
return;
}
// invoke finish
jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "finish", "(Ljava/lang/Throwable;)V");
if (mid == NULL) {
fprintf(stderr, "GetStaticMethodID failed\n");
return;
}
(*env)->CallStaticVoidMethod(env, clazz, mid, ex);
if ((*env)->ExceptionOccurred(env)) {
fprintf(stderr, "CallStaticVoidMethod failed\n");
}
}
/**
* Attach the current thread with JNI AttachCurrentThread.
*/
void* thread_main(void* arg) {
JNIEnv *env;
jint res;
jthrowable ex;
res = (*vm)->AttachCurrentThread(vm, (void **) &env, NULL);
if (res != JNI_OK) {
fprintf(stderr, "AttachCurrentThread failed: %d\n", res);
return NULL;
}
// invoke JNIAttachMutator.getObject to get the object to test
jobject obj = getObject(env);
if (obj == NULL) {
goto done;
}
// invoke JNIAttachMutator.getField to get the Field object with access enabled
jobject fieldObj = getField(env);
if (fieldObj == NULL) {
goto done;
}
// invoke Field.setInt to attempt to set the value to 200
if (!setInt(env, obj, fieldObj, 200)) {
goto done;
}
done:
ex = (*env)->ExceptionOccurred(env);
if (ex != NULL) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
finish(env, ex);
res = (*vm)->DetachCurrentThread(vm);
if (res != JNI_OK) {
fprintf(stderr, "DetachCurrentThread failed: %d\n", res);
}
return NULL;
}
#ifdef _WIN32
static DWORD WINAPI win32_thread_main(void* p) {
thread_main(p);
return 0;
}
#endif
JNIEXPORT void JNICALL Java_JNIAttachMutator_startThread(JNIEnv *env, jclass clazz) {
#ifdef _WIN32
HANDLE handle = CreateThread(NULL, STACK_SIZE, win32_thread_main, NULL, 0, NULL);
if (handle == NULL) {
fprintf(stderr, "CreateThread failed: %d\n", GetLastError());
}
#else
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, STACK_SIZE);
int res = pthread_create(&tid, &attr, thread_main, NULL);
if (res != 0) {
fprintf(stderr, "pthread_create failed: %d\n", res);
}
#endif
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Module containing classes with final fields.
*/
module m {
exports p;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p;
/**
* public class, public final field.
*/
public class C1 {
public final int value;
C1(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p;
/**
* public class, non-public final field.
*/
public class C2 {
final int value;
C2(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p;
/**
* non-public class, public final field.
*/
class C3 {
public final int value;
C3(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package q;
/**
* public class, public final field.
*/
public class C {
public final int value;
public C(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8353835
* @summary Test mutating final fields in module test from code in modules test, m1, m2 and m3
* @build test/* m1/* m2/* m3/*
* @run junit/othervm --illegal-final-field-mutation=deny --enable-final-field-mutation=test,m1,m2,m3
* test/test.TestMain
* @run junit/othervm --illegal-final-field-mutation=deny --enable-final-field-mutation=test,m1,m2,m3
* --add-exports test/test.fieldholders=m3 test/test.TestMain
* @run junit/othervm --illegal-final-field-mutation=deny --enable-final-field-mutation=test,m1,m2,m3
* --add-opens test/test.fieldholders=m2 test/test.TestMain
*/

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
@SuppressWarnings("module")
module m1 {
requires test;
provides test.spi.Mutator with p1.M1Mutator;
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p1;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class M1Mutator implements test.spi.Mutator {
@Override
public void set(Field f, Object obj, Object value) throws IllegalAccessException {
f.set(obj, value);
}
@Override
public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException {
f.setBoolean(obj, value);
}
@Override
public void setByte(Field f, Object obj, byte value) throws IllegalAccessException {
f.setByte(obj, value);
}
@Override
public void setChar(Field f, Object obj, char value) throws IllegalAccessException {
f.setChar(obj, value);
}
@Override
public void setShort(Field f, Object obj, short value) throws IllegalAccessException {
f.setShort(obj, value);
}
@Override
public void setInt(Field f, Object obj, int value) throws IllegalAccessException {
f.setInt(obj, value);
}
@Override
public void setLong(Field f, Object obj, long value) throws IllegalAccessException {
f.setLong(obj, value);
}
@Override
public void setFloat(Field f, Object obj, float value) throws IllegalAccessException {
f.setFloat(obj, value);
}
@Override
public void setDouble(Field f, Object obj, double value) throws IllegalAccessException {
f.setDouble(obj, value);
}
@Override
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
return MethodHandles.lookup().unreflectSetter(f);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
@SuppressWarnings("module")
module m2 {
requires test;
provides test.spi.Mutator with p2.M2Mutator;
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p2;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class M2Mutator implements test.spi.Mutator {
@Override
public void set(Field f, Object obj, Object value) throws IllegalAccessException {
f.set(obj, value);
}
@Override
public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException {
f.setBoolean(obj, value);
}
@Override
public void setByte(Field f, Object obj, byte value) throws IllegalAccessException {
f.setByte(obj, value);
}
@Override
public void setChar(Field f, Object obj, char value) throws IllegalAccessException {
f.setChar(obj, value);
}
@Override
public void setShort(Field f, Object obj, short value) throws IllegalAccessException {
f.setShort(obj, value);
}
@Override
public void setInt(Field f, Object obj, int value) throws IllegalAccessException {
f.setInt(obj, value);
}
@Override
public void setLong(Field f, Object obj, long value) throws IllegalAccessException {
f.setLong(obj, value);
}
@Override
public void setFloat(Field f, Object obj, float value) throws IllegalAccessException {
f.setFloat(obj, value);
}
@Override
public void setDouble(Field f, Object obj, double value) throws IllegalAccessException {
f.setDouble(obj, value);
}
@Override
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
return MethodHandles.lookup().unreflectSetter(f);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
@SuppressWarnings("module")
module m3 {
requires test;
provides test.spi.Mutator with p3.M3Mutator;
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p3;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class M3Mutator implements test.spi.Mutator {
@Override
public void set(Field f, Object obj, Object value) throws IllegalAccessException {
f.set(obj, value);
}
@Override
public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException {
f.setBoolean(obj, value);
}
@Override
public void setByte(Field f, Object obj, byte value) throws IllegalAccessException {
f.setByte(obj, value);
}
@Override
public void setChar(Field f, Object obj, char value) throws IllegalAccessException {
f.setChar(obj, value);
}
@Override
public void setShort(Field f, Object obj, short value) throws IllegalAccessException {
f.setShort(obj, value);
}
@Override
public void setInt(Field f, Object obj, int value) throws IllegalAccessException {
f.setInt(obj, value);
}
@Override
public void setLong(Field f, Object obj, long value) throws IllegalAccessException {
f.setLong(obj, value);
}
@Override
public void setFloat(Field f, Object obj, float value) throws IllegalAccessException {
f.setFloat(obj, value);
}
@Override
public void setDouble(Field f, Object obj, double value) throws IllegalAccessException {
f.setDouble(obj, value);
}
@Override
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
return MethodHandles.lookup().unreflectSetter(f);
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module test {
requires org.junit.platform.console.standalone;
opens test to org.junit.platform.console.standalone;
opens test.fieldholders to m1;
exports test.fieldholders to m2;
exports test.spi;
uses test.spi.Mutator;
provides test.spi.Mutator with test.internal.TestMutator;
}

View File

@ -0,0 +1,556 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test;
import java.lang.reflect.Field;
import java.util.ServiceLoader;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import test.spi.Mutator;
import test.fieldholders.PublicFields;
import test.fieldholders.PrivateFields;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
/**
* Test mutating final fields from different modules.
*/
class TestMain {
static Map<Boolean, List<Mutator>> exportedToMutators;
static Map<Boolean, List<Mutator>> openToMutators;
// test module and the name of package with the fieldholder classes
static Module testModule;
static String fieldHoldersPackage;
@BeforeAll
static void setup() throws Exception {
testModule = TestMain.class.getModule();
fieldHoldersPackage = PublicFields.class.getPackageName();
List<Mutator> allMutators = ServiceLoader.load(Mutator.class)
.stream()
.map(ServiceLoader.Provider::get)
.toList();
// mutators that test.fieldholders is exported to
exportedToMutators = allMutators.stream()
.collect(Collectors.partitioningBy(m -> testModule.isExported(fieldHoldersPackage,
m.getClass().getModule()),
Collectors.toList()));
// mutators that test.fieldholders is open to
openToMutators = allMutators.stream()
.collect(Collectors.partitioningBy(m -> testModule.isOpen(fieldHoldersPackage,
m.getClass().getModule()),
Collectors.toList()));
// exported to at least test, m1 and m2
assertTrue(exportedToMutators.get(Boolean.TRUE).size() >= 3);
// open to at least test and m1
assertTrue(openToMutators.get(Boolean.TRUE).size() >= 2);
}
/**
* Returns a stream of mutators that test.fieldholders is exported to.
*/
static Stream<Mutator> exportedToMutators() {
return exportedToMutators.get(Boolean.TRUE).stream();
}
/**
* Returns a stream of mutators that test.fieldholders is open to.
*/
static Stream<Mutator> openToMutators() {
return openToMutators.get(Boolean.TRUE).stream();
}
/**
* Returns a stream of mutators that test.fieldholders is not exported to.
*/
static Stream<Mutator> notExportedToMutators() {
List<Mutator> mutators = exportedToMutators.get(Boolean.FALSE);
if (mutators.isEmpty()) {
// can't return an empty stream at this time
return Stream.of(Mutator.throwing());
} else {
return mutators.stream();
}
}
/**
* Returns a stream of mutators that test.fieldholders is not open to.
*/
static Stream<Mutator> notOpenToMutators() {
List<Mutator> mutators = openToMutators.get(Boolean.FALSE);
if (mutators.isEmpty()) {
// can't return an empty stream at this time
return Stream.of(Mutator.throwing());
} else {
return mutators.stream();
}
}
// public field, public class in package exported to mutator
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.objectField();
var obj = new PublicFields();
Object oldValue = obj.objectValue();
Object newValue = new Object();
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
assertTrue(obj.objectValue() == oldValue);
f.setAccessible(true);
mutator.set(f, obj, newValue);
assertTrue(obj.objectValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetBooleanExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.booleanField();
var obj = new PublicFields();
boolean oldValue = obj.booleanValue();
boolean newValue = true;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setBoolean(f, obj, newValue));
assertTrue(obj.booleanValue() == oldValue);
f.setAccessible(true);
mutator.setBoolean(f, obj, newValue);
assertTrue(obj.booleanValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetByteExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.byteField();
var obj = new PublicFields();
byte oldValue = obj.byteValue();
byte newValue = 10;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setByte(f, obj, newValue));
assertTrue(obj.byteValue() == oldValue);
f.setAccessible(true);
mutator.setByte(f, obj, newValue);
assertTrue(obj.byteValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetCharExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.charField();
var obj = new PublicFields();
char oldValue = obj.charValue();
char newValue = 'Z';
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setChar(f, obj, newValue));
assertTrue(obj.charValue() == oldValue);
f.setAccessible(true);
mutator.setChar(f, obj, newValue);
assertTrue(obj.charValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetShortExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.shortField();
var obj = new PublicFields();
short oldValue = obj.shortValue();
short newValue = 99;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setShort(f, obj, newValue));
assertTrue(obj.shortValue() == oldValue);
f.setAccessible(true);
mutator.setShort(f, obj, newValue);
assertTrue(obj.shortValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetIntExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.intField();
var obj = new PublicFields();
int oldValue = obj.intValue();
int newValue = 999;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setInt(f, obj, newValue));
assertTrue(obj.intValue() == oldValue);
f.setAccessible(true);
mutator.setInt(f, obj, newValue);
assertTrue(obj.intValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetLongExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.longField();
var obj = new PublicFields();
long oldValue = obj.longValue();
long newValue = 9999;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setLong(f, obj, newValue));
assertTrue(obj.longValue() == oldValue);
f.setAccessible(true);
mutator.setLong(f, obj, newValue);
assertTrue(obj.longValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetFloatExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.floatField();
var obj = new PublicFields();
float oldValue = obj.floatValue();
float newValue = 9.9f;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setFloat(f, obj, newValue));
assertTrue(obj.floatValue() == oldValue);
f.setAccessible(true);
mutator.setFloat(f, obj, newValue);
assertTrue(obj.floatValue() == newValue);
}
@ParameterizedTest()
@MethodSource("exportedToMutators")
void testFieldSetDoublExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.doubleField();
var obj = new PublicFields();
double oldValue = obj.doubleValue();
double newValue = 99.9d;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setDouble(f, obj, newValue));
assertTrue(obj.doubleValue() == oldValue);
f.setAccessible(true);
mutator.setDouble(f, obj, newValue);
assertTrue(obj.doubleValue() == newValue);
}
@ParameterizedTest
@MethodSource("exportedToMutators")
void testUnreflectSetterExportedPackage(Mutator mutator) throws Throwable {
Field f = PublicFields.objectField();
var obj = new PublicFields();
Object oldValue = obj.objectValue();
Object newValue = new Object();
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
f.setAccessible(true);
mutator.unreflectSetter(f).invokeExact(obj, newValue);
assertTrue(obj.objectValue() == newValue);
}
// private field, class in package opened to mutator
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.objectField();
var obj = new PrivateFields();
Object oldValue = obj.objectValue();
Object newValue = new Object();
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
assertTrue(obj.objectValue() == oldValue);
f.setAccessible(true);
mutator.set(f, obj, newValue);
assertTrue(obj.objectValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetBooleanOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.booleanField();
var obj = new PrivateFields();
boolean oldValue = obj.booleanValue();
boolean newValue = true;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setBoolean(f, obj, newValue));
assertTrue(obj.booleanValue() == oldValue);
f.setAccessible(true);
mutator.setBoolean(f, obj, newValue);
assertTrue(obj.booleanValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetByteOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.byteField();
var obj = new PrivateFields();
byte oldValue = obj.byteValue();
byte newValue = 10;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setByte(f, obj, newValue));
assertTrue(obj.byteValue() == oldValue);
f.setAccessible(true);
mutator.setByte(f, obj, newValue);
assertTrue(obj.byteValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetCharOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.charField();
var obj = new PrivateFields();
char oldValue = obj.charValue();
char newValue = 'Z';
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setChar(f, obj, newValue));
assertTrue(obj.charValue() == oldValue);
f.setAccessible(true);
mutator.setChar(f, obj, newValue);
assertTrue(obj.charValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetShortOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.shortField();
var obj = new PrivateFields();
short oldValue = obj.shortValue();
short newValue = 'Z';
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setShort(f, obj, newValue));
assertTrue(obj.shortValue() == oldValue);
f.setAccessible(true);
mutator.setShort(f, obj, newValue);
assertTrue(obj.shortValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetIntOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.intField();
var obj = new PrivateFields();
int oldValue = obj.intValue();
int newValue = 99;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setInt(f, obj, newValue));
assertTrue(obj.intValue() == oldValue);
f.setAccessible(true);
mutator.setInt(f, obj, newValue);
assertTrue(obj.intValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetLongOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.longField();
var obj = new PrivateFields();
long oldValue = obj.longValue();
long newValue = 999;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setLong(f, obj, newValue));
assertTrue(obj.longValue() == oldValue);
f.setAccessible(true);
mutator.setLong(f, obj, newValue);
assertTrue(obj.longValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetFloatOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.floatField();
var obj = new PrivateFields();
float oldValue = obj.floatValue();
float newValue = 9.9f;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setFloat(f, obj, newValue));
assertTrue(obj.floatValue() == oldValue);
f.setAccessible(true);
mutator.setFloat(f, obj, newValue);
assertTrue(obj.floatValue() == newValue);
}
@ParameterizedTest()
@MethodSource("openToMutators")
void testFieldSetDoubleOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.doubleField();
var obj = new PrivateFields();
double oldValue = obj.doubleValue();
double newValue = 99.9d;
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.setDouble(f, obj, newValue));
assertTrue(obj.doubleValue() == oldValue);
f.setAccessible(true);
mutator.setDouble(f, obj, newValue);
assertTrue(obj.doubleValue() == newValue);
}
@ParameterizedTest
@MethodSource("openToMutators")
void testUnreflectSetterOpenPackage(Mutator mutator) throws Throwable {
Field f = PrivateFields.objectField();
var obj = new PrivateFields();
Object oldValue = obj.objectValue();
Object newValue = new Object();
f.setAccessible(false);
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
f.setAccessible(true);
mutator.unreflectSetter(f).invokeExact(obj, newValue);
assertTrue(obj.objectValue() == newValue);
}
// public field, public class in package not exported to mutator
@ParameterizedTest
@MethodSource("notExportedToMutators")
void testFieldSetNotExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.objectField();
var obj = new PublicFields();
Object oldValue = obj.objectValue();
Object newValue = new Object();
f.setAccessible(true);
var e1 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
Module mutatorModule = mutator.getClass().getModule();
if (mutatorModule != testModule) {
assertTrue(e1.getMessage().contains("module " + testModule.getName()
+ " does not explicitly \"exports\" package "
+ f.getDeclaringClass().getPackageName()
+ " to module " + mutatorModule.getName()));
}
assertTrue(obj.objectValue() == oldValue);
// export package to mutator module, should have no effect on set method
testModule.addExports(fieldHoldersPackage, mutator.getClass().getModule());
var e2 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
assertEquals(e1.getMessage(), e2.getMessage());
assertTrue(obj.objectValue() == oldValue);
// open package to mutator module, should have no effect on set method
testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule());
var e3 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
assertEquals(e1.getMessage(), e3.getMessage());
assertTrue(obj.objectValue() == oldValue);
}
@ParameterizedTest
@MethodSource("notExportedToMutators")
void testUnreflectSetterNotExportedPackage(Mutator mutator) throws Exception {
Field f = PublicFields.objectField();
f.setAccessible(true);
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
// export package to mutator module, should have no effect on unreflectSetter method
testModule.addExports(fieldHoldersPackage, mutator.getClass().getModule());
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
// open package to mutator module, should have no effect on unreflectSetter method
testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule());
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
}
// private field, class in package not opened to mutator
@ParameterizedTest
@MethodSource("notOpenToMutators")
void testFieldSetNotOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.objectField();
var obj = new PrivateFields();
Object oldValue = obj.objectValue();
Object newValue = new Object();
f.setAccessible(true);
var e1 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
Module mutatorModule = mutator.getClass().getModule();
if (mutatorModule != testModule) {
assertTrue(e1.getMessage().contains("module " + testModule.getName()
+ " does not explicitly \"opens\" package "
+ f.getDeclaringClass().getPackageName()
+ " to module " + mutatorModule.getName()));
}
assertTrue(obj.objectValue() == oldValue);
// open package to mutator module, should have no effect on set method
testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule());
var e2 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue));
assertEquals(e2.getMessage(), e2.getMessage());
assertTrue(obj.objectValue() == oldValue);
}
@ParameterizedTest
@MethodSource("notOpenToMutators")
void testUnreflectSetterNotOpenPackage(Mutator mutator) throws Exception {
Field f = PrivateFields.class.getDeclaredField("obj");
f.setAccessible(true);
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
// open package to mutator module, should have no effect on unreflectSetter method
testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule());
assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f));
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.fieldholders;
import java.lang.reflect.Field;
/**
* public class with private final fields.
*/
public class PrivateFields {
private final Object obj;
private final boolean z;
private final byte b;
private final char c;
private final short s;
private final int i;
private final long l;
private final float f;
private final double d;
public PrivateFields() {
obj = new Object();
z = false;
b = 0;
c = 0;
s = 0;
i = 0;
l = 0;
f = 0.0f;
d = 0.0d;
}
public static Field objectField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("obj");
}
public static Field booleanField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("z");
}
public static Field byteField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("b");
}
public static Field charField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("c");
}
public static Field shortField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("s");
}
public static Field intField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("i");
}
public static Field longField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("l");
}
public static Field floatField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("f");
}
public static Field doubleField() throws NoSuchFieldException {
return PrivateFields.class.getDeclaredField("d");
}
public Object objectValue() {
return obj;
}
public boolean booleanValue() {
return z;
}
public byte byteValue() {
return b;
}
public char charValue() {
return c;
}
public short shortValue() {
return s;
}
public int intValue() {
return i;
}
public long longValue() {
return l;
}
public float floatValue() {
return f;
}
public double doubleValue() {
return d;
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.fieldholders;
import java.lang.reflect.Field;
/**
* public class with public final fields.
*/
public class PublicFields {
public final Object obj;
public final boolean z;
public final byte b;
public final char c;
public final short s;
public final int i;
public final long l;
public final float f;
public final double d;
public PublicFields() {
obj = new Object();
z = false;
b = 0;
c = 0;
s = 0;
i = 0;
l = 0;
f = 0.0f;
d = 0.0d;
}
public static Field objectField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("obj");
}
public static Field booleanField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("z");
}
public static Field byteField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("b");
}
public static Field charField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("c");
}
public static Field shortField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("s");
}
public static Field intField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("i");
}
public static Field longField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("l");
}
public static Field floatField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("f");
}
public static Field doubleField() throws NoSuchFieldException {
return PublicFields.class.getDeclaredField("d");
}
public Object objectValue() {
return obj;
}
public boolean booleanValue() {
return z;
}
public byte byteValue() {
return b;
}
public char charValue() {
return c;
}
public short shortValue() {
return s;
}
public int intValue() {
return i;
}
public long longValue() {
return l;
}
public float floatValue() {
return f;
}
public double doubleValue() {
return d;
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.internal;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class TestMutator implements test.spi.Mutator {
@Override
public void set(Field f, Object obj, Object value) throws IllegalAccessException {
f.set(obj, value);
}
@Override
public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException {
f.setBoolean(obj, value);
}
@Override
public void setByte(Field f, Object obj, byte value) throws IllegalAccessException {
f.setByte(obj, value);
}
@Override
public void setChar(Field f, Object obj, char value) throws IllegalAccessException {
f.setChar(obj, value);
}
@Override
public void setShort(Field f, Object obj, short value) throws IllegalAccessException {
f.setShort(obj, value);
}
@Override
public void setInt(Field f, Object obj, int value) throws IllegalAccessException {
f.setInt(obj, value);
}
@Override
public void setLong(Field f, Object obj, long value) throws IllegalAccessException {
f.setLong(obj, value);
}
@Override
public void setFloat(Field f, Object obj, float value) throws IllegalAccessException {
f.setFloat(obj, value);
}
@Override
public void setDouble(Field f, Object obj, double value) throws IllegalAccessException {
f.setDouble(obj, value);
}
@Override
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
return MethodHandles.lookup().unreflectSetter(f);
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.spi;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
public interface Mutator {
void set(Field f, Object obj, Object value) throws IllegalAccessException;
void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException;
void setByte(Field f, Object obj, byte value) throws IllegalAccessException;
void setChar(Field f, Object obj, char value) throws IllegalAccessException;
void setShort(Field f, Object obj, short value) throws IllegalAccessException;
void setInt(Field f, Object obj, int value) throws IllegalAccessException;
void setLong(Field f, Object obj, long value) throws IllegalAccessException;
void setFloat(Field f, Object obj, float value) throws IllegalAccessException;
void setDouble(Field f, Object obj, double value) throws IllegalAccessException;
MethodHandle unreflectSetter(Field f) throws IllegalAccessException;
static Mutator throwing() {
return new Mutator() {
@Override
public void set(Field f, Object obj, Object value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setByte(Field f, Object obj, byte value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setChar(Field f, Object obj, char value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setShort(Field f, Object obj, short value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setInt(Field f, Object obj, int value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setLong(Field f, Object obj, long value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setFloat(Field f, Object obj, float value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public void setDouble(Field f, Object obj, double value) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
throw new IllegalAccessException();
}
@Override
public String toString() {
return "<throwing>";
}
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -38,7 +38,7 @@ import static org.testng.Assert.*;
* @test
* @bug 8066619
* @modules java.base/java.util.jar:+open
* @run testng/othervm NullAndEmptyKeysAndValues
* @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED NullAndEmptyKeysAndValues
* @summary Tests manifests with {@code null} and empty string {@code ""}
* values as section name, header name, or value in both main and named
* attributes sections.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -42,7 +42,7 @@ import java.util.logging.LogRecord;
* @bug 8059767
* @summary tests that FileHandler can accept a long limit.
* @modules java.logging/java.util.logging:open
* @run main/othervm FileHandlerLongLimit
* @run main/othervm --enable-final-field-mutation=ALL-UNNAMED FileHandlerLongLimit
* @author danielfuchs
* @key randomness
*/

View File

@ -81,6 +81,10 @@ public class TestLookForUntestedEvents {
private static final Set<String> coveredVirtualThreadEvents = Set.of(
"VirtualThreadPinned", "VirtualThreadSubmitFailed");
// This event is tested in test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java
private static final Set<String> coveredFinalFieldMutationEvents = Set.of(
"FinalFieldMutationEvent");
// This is a "known failure list" for this test.
// NOTE: if the event is not covered, a bug should be open, and bug number
// noted in the comments for this set.
@ -128,6 +132,7 @@ public class TestLookForUntestedEvents {
eventsNotCoveredByTest.removeAll(hardToTestEvents);
eventsNotCoveredByTest.removeAll(coveredGcEvents);
eventsNotCoveredByTest.removeAll(coveredVirtualThreadEvents);
eventsNotCoveredByTest.removeAll(coveredFinalFieldMutationEvents);
eventsNotCoveredByTest.removeAll(coveredContainerEvents);
eventsNotCoveredByTest.removeAll(knownNotCoveredEvents);

View File

@ -26,7 +26,7 @@
* @bug 8258833
* @library /test/lib ..
* @modules jdk.crypto.cryptoki/sun.security.pkcs11:open
* @run main/othervm CancelMultipart
* @run main/othervm --enable-final-field-mutation=ALL-UNNAMED CancelMultipart
*/
import java.lang.reflect.Field;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -34,8 +34,8 @@ import java.lang.reflect.Field;
* @bug 8157308
* @modules java.base/sun.security.provider:+open
* @summary Make AbstractDrbg non-Serializable
* @run main DRBGS11n mech
* @run main DRBGS11n capability
* @run main/othervm --enable-final-field-mutation=ALL-UNNAMED DRBGS11n mech
* @run main/othervm --enable-final-field-mutation=ALL-UNNAMED DRBGS11n capability
*/
public class DRBGS11n {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -44,7 +44,7 @@ import static org.testng.Assert.*;
* @bug 8217375
* @modules java.base/sun.security.util:+open
* @compile ../../tools/jarsigner/Utils.java
* @run testng/othervm FindSection
* @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED FindSection
* @summary Check {@link ManifestDigester#findSection}.
*/
public class FindSection {

View File

@ -32,7 +32,7 @@
* jdk.jshell/jdk.jshell:open
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
* @build KullaTesting TestingInputStream Compiler
* @run junit/timeout=480 CompletionSuggestionTest
* @run junit/othervm/timeout=480 --enable-final-field-mutation=ALL-UNNAMED CompletionSuggestionTest
*/
import java.io.IOException;

View File

@ -220,6 +220,7 @@ public class EventNames {
public static final String VirtualThreadEnd = PREFIX + "VirtualThreadEnd";
public static final String VirtualThreadPinned = PREFIX + "VirtualThreadPinned";
public static final String VirtualThreadSubmitFailed = PREFIX + "VirtualThreadSubmitFailed";
public static final String FinalFieldMutation = PREFIX + "FinalFieldMutation";
// Containers
public static final String ContainerConfiguration = PREFIX + "ContainerConfiguration";

View File

@ -0,0 +1,151 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.reflect;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadLocalRandom;
import org.openjdk.jmh.annotations.*;
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(3)
public class FieldSet {
static class FieldHolder {
Object obj;
int intValue;
long longValue;
FieldHolder(Object obj, int i, long l) {
this.obj = obj;
this.intValue = i;
this.longValue = l;
}
}
static class FinalFieldHolder {
final Object obj;
final int intValue;
final long longValue;
FinalFieldHolder(Object obj, int i, long l) {
this.obj = obj;
this.intValue = i;
this.longValue = l;
}
}
private FieldHolder fieldHolder;
private FinalFieldHolder finalFieldHolder;
private Field objField1, objField2, objField3;
private Field intField1, intField2, intField3;
private Field longField1, longField2, longField3;
@Setup
public void setup() throws Exception {
fieldHolder = new FieldHolder(new Object(), 1, 1L);
finalFieldHolder = new FinalFieldHolder(new Object(), 1, 1L);
// non-final && !override
objField1 = FieldHolder.class.getDeclaredField("obj");
intField1 = FieldHolder.class.getDeclaredField("intValue");
longField1 = FieldHolder.class.getDeclaredField("longValue");
// non-final && override
objField2 = FieldHolder.class.getDeclaredField("obj");
objField2.setAccessible(true);
intField2 = FieldHolder.class.getDeclaredField("intValue");
intField2.setAccessible(true);
longField2 = FieldHolder.class.getDeclaredField("longValue");
longField2.setAccessible(true);
// final && override
objField3 = FinalFieldHolder.class.getDeclaredField("obj");
objField3.setAccessible(true);
intField3 = FinalFieldHolder.class.getDeclaredField("intValue");
intField3.setAccessible(true);
longField3 = FinalFieldHolder.class.getDeclaredField("longValue");
longField3.setAccessible(true);
}
// non-final && !override
@Benchmark
public void setNonFinalObjectField() throws Exception {
objField1.set(fieldHolder, new Object());
}
@Benchmark
public void setNonFinalIntField() throws Exception {
int newValue = ThreadLocalRandom.current().nextInt();
intField1.setInt(fieldHolder, newValue);
}
@Benchmark
public void setNonFinalLongField() throws Exception {
long newValue = ThreadLocalRandom.current().nextLong();
longField1.setLong(fieldHolder, newValue);
}
// non-final && override
@Benchmark
public void setNonFinalObjectFieldWithOverride() throws Exception {
objField2.set(fieldHolder, new Object());
}
@Benchmark
public void setNonFinalIntFieldWithOverride() throws Exception {
int newValue = ThreadLocalRandom.current().nextInt();
intField2.setInt(fieldHolder, newValue);
}
@Benchmark
public void setNonFinalLongFieldWithOverride() throws Exception {
long newValue = ThreadLocalRandom.current().nextLong();
longField2.setLong(fieldHolder, newValue);
}
// final && override
@Benchmark
public void setFinalObjectField()throws Exception {
objField3.set(finalFieldHolder, new Object());
}
@Benchmark
public void setFinalIntField() throws Exception {
int newValue = ThreadLocalRandom.current().nextInt();
intField3.setInt(finalFieldHolder, newValue);
}
@Benchmark
public void setFinalLongField() throws Exception {
long newValue = ThreadLocalRandom.current().nextLong();
longField3.setLong(finalFieldHolder, newValue);
}
}