mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8353835: Implement JEP 500: Prepare to Make Final Mean Final
Reviewed-by: liach, vlivanov, dholmes, vyazici
This commit is contained in:
parent
8cdfec8d1c
commit
26460b6f12
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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); \
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
*
|
||||
|
||||
@ -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() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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\
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
356
test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinals.java
Normal file
356
test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinals.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
160
test/hotspot/jtreg/runtime/jni/mutateFinals/libMutateFinals.c
Normal file
160
test/hotspot/jtreg/runtime/jni/mutateFinals/libMutateFinals.c
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
*/
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
Path cf = Path.of(classes, "Fields.class");
|
||||
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);
|
||||
}
|
||||
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);
|
||||
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 {
|
||||
|
||||
@ -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 {
|
||||
Path cf = Path.of(classes, "Fields.class");
|
||||
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);
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.*;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
35
test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/p/C.java
Normal file
35
test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/p/C.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
35
test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/q/C.java
Normal file
35
test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/q/C.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
*/
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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";
|
||||
|
||||
151
test/micro/org/openjdk/bench/java/lang/reflect/FieldSet.java
Normal file
151
test/micro/org/openjdk/bench/java/lang/reflect/FieldSet.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user