mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8247444: Trust final fields in records
Co-authored-by: Christoph Dreis <christoph.dreis@freenet.de> Reviewed-by: jrose, dholmes, forax, coleenp, vlivanov
This commit is contained in:
parent
983e012c9f
commit
f2b191a6e9
26 changed files with 227 additions and 49 deletions
|
@ -231,6 +231,9 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) {
|
||||||
// Trust final fields in all boxed classes
|
// Trust final fields in all boxed classes
|
||||||
if (holder->is_box_klass())
|
if (holder->is_box_klass())
|
||||||
return true;
|
return true;
|
||||||
|
// Trust final fields in records
|
||||||
|
if (holder->is_record())
|
||||||
|
return true;
|
||||||
// Trust final fields in String
|
// Trust final fields in String
|
||||||
if (holder->name() == ciSymbol::java_lang_String())
|
if (holder->name() == ciSymbol::java_lang_String())
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -64,6 +64,7 @@ ciInstanceKlass::ciInstanceKlass(Klass* k) :
|
||||||
_has_nonstatic_concrete_methods = ik->has_nonstatic_concrete_methods();
|
_has_nonstatic_concrete_methods = ik->has_nonstatic_concrete_methods();
|
||||||
_is_unsafe_anonymous = ik->is_unsafe_anonymous();
|
_is_unsafe_anonymous = ik->is_unsafe_anonymous();
|
||||||
_is_hidden = ik->is_hidden();
|
_is_hidden = ik->is_hidden();
|
||||||
|
_is_record = ik->is_record();
|
||||||
_nonstatic_fields = NULL; // initialized lazily by compute_nonstatic_fields:
|
_nonstatic_fields = NULL; // initialized lazily by compute_nonstatic_fields:
|
||||||
_has_injected_fields = -1;
|
_has_injected_fields = -1;
|
||||||
_implementor = NULL; // we will fill these lazily
|
_implementor = NULL; // we will fill these lazily
|
||||||
|
@ -125,6 +126,7 @@ ciInstanceKlass::ciInstanceKlass(ciSymbol* name,
|
||||||
_has_injected_fields = -1;
|
_has_injected_fields = -1;
|
||||||
_is_unsafe_anonymous = false;
|
_is_unsafe_anonymous = false;
|
||||||
_is_hidden = false;
|
_is_hidden = false;
|
||||||
|
_is_record = false;
|
||||||
_loader = loader;
|
_loader = loader;
|
||||||
_protection_domain = protection_domain;
|
_protection_domain = protection_domain;
|
||||||
_is_shared = false;
|
_is_shared = false;
|
||||||
|
|
|
@ -57,6 +57,7 @@ private:
|
||||||
bool _has_nonstatic_concrete_methods;
|
bool _has_nonstatic_concrete_methods;
|
||||||
bool _is_unsafe_anonymous;
|
bool _is_unsafe_anonymous;
|
||||||
bool _is_hidden;
|
bool _is_hidden;
|
||||||
|
bool _is_record;
|
||||||
|
|
||||||
ciFlags _flags;
|
ciFlags _flags;
|
||||||
jint _nonstatic_field_size;
|
jint _nonstatic_field_size;
|
||||||
|
@ -200,6 +201,10 @@ public:
|
||||||
return _is_hidden;
|
return _is_hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_record() const {
|
||||||
|
return _is_record;
|
||||||
|
}
|
||||||
|
|
||||||
ciInstanceKlass* get_canonical_holder(int offset);
|
ciInstanceKlass* get_canonical_holder(int offset);
|
||||||
ciField* get_field_by_offset(int field_offset, bool is_static);
|
ciField* get_field_by_offset(int field_offset, bool is_static);
|
||||||
ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static);
|
ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static);
|
||||||
|
|
|
@ -3141,6 +3141,7 @@ int java_lang_reflect_Field::_name_offset;
|
||||||
int java_lang_reflect_Field::_type_offset;
|
int java_lang_reflect_Field::_type_offset;
|
||||||
int java_lang_reflect_Field::_slot_offset;
|
int java_lang_reflect_Field::_slot_offset;
|
||||||
int java_lang_reflect_Field::_modifiers_offset;
|
int java_lang_reflect_Field::_modifiers_offset;
|
||||||
|
int java_lang_reflect_Field::_trusted_final_offset;
|
||||||
int java_lang_reflect_Field::_signature_offset;
|
int java_lang_reflect_Field::_signature_offset;
|
||||||
int java_lang_reflect_Field::_annotations_offset;
|
int java_lang_reflect_Field::_annotations_offset;
|
||||||
|
|
||||||
|
@ -3150,6 +3151,7 @@ int java_lang_reflect_Field::_annotations_offset;
|
||||||
macro(_type_offset, k, vmSymbols::type_name(), class_signature, false); \
|
macro(_type_offset, k, vmSymbols::type_name(), class_signature, false); \
|
||||||
macro(_slot_offset, k, vmSymbols::slot_name(), int_signature, false); \
|
macro(_slot_offset, k, vmSymbols::slot_name(), int_signature, false); \
|
||||||
macro(_modifiers_offset, k, vmSymbols::modifiers_name(), int_signature, false); \
|
macro(_modifiers_offset, k, vmSymbols::modifiers_name(), int_signature, false); \
|
||||||
|
macro(_trusted_final_offset, k, vmSymbols::trusted_final_name(), bool_signature, false); \
|
||||||
macro(_signature_offset, k, vmSymbols::signature_name(), string_signature, false); \
|
macro(_signature_offset, k, vmSymbols::signature_name(), string_signature, false); \
|
||||||
macro(_annotations_offset, k, vmSymbols::annotations_name(), byte_array_signature, false);
|
macro(_annotations_offset, k, vmSymbols::annotations_name(), byte_array_signature, false);
|
||||||
|
|
||||||
|
@ -3214,6 +3216,10 @@ void java_lang_reflect_Field::set_modifiers(oop field, int value) {
|
||||||
field->int_field_put(_modifiers_offset, value);
|
field->int_field_put(_modifiers_offset, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void java_lang_reflect_Field::set_trusted_final(oop field) {
|
||||||
|
field->bool_field_put(_trusted_final_offset, true);
|
||||||
|
}
|
||||||
|
|
||||||
void java_lang_reflect_Field::set_signature(oop field, oop value) {
|
void java_lang_reflect_Field::set_signature(oop field, oop value) {
|
||||||
field->obj_field_put(_signature_offset, value);
|
field->obj_field_put(_signature_offset, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -696,6 +696,7 @@ class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject {
|
||||||
static int _type_offset;
|
static int _type_offset;
|
||||||
static int _slot_offset;
|
static int _slot_offset;
|
||||||
static int _modifiers_offset;
|
static int _modifiers_offset;
|
||||||
|
static int _trusted_final_offset;
|
||||||
static int _signature_offset;
|
static int _signature_offset;
|
||||||
static int _annotations_offset;
|
static int _annotations_offset;
|
||||||
|
|
||||||
|
@ -723,6 +724,8 @@ class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject {
|
||||||
static int modifiers(oop field);
|
static int modifiers(oop field);
|
||||||
static void set_modifiers(oop field, int value);
|
static void set_modifiers(oop field, int value);
|
||||||
|
|
||||||
|
static void set_trusted_final(oop field);
|
||||||
|
|
||||||
static void set_signature(oop constructor, oop value);
|
static void set_signature(oop constructor, oop value);
|
||||||
static void set_annotations(oop constructor, oop value);
|
static void set_annotations(oop constructor, oop value);
|
||||||
static void set_parameter_annotations(oop method, oop value);
|
static void set_parameter_annotations(oop method, oop value);
|
||||||
|
@ -1121,6 +1124,7 @@ class java_lang_invoke_MemberName: AllStatic {
|
||||||
MN_IS_FIELD = 0x00040000, // field
|
MN_IS_FIELD = 0x00040000, // field
|
||||||
MN_IS_TYPE = 0x00080000, // nested type
|
MN_IS_TYPE = 0x00080000, // nested type
|
||||||
MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected
|
MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected
|
||||||
|
MN_TRUSTED_FINAL = 0x00200000, // trusted final field
|
||||||
MN_REFERENCE_KIND_SHIFT = 24, // refKind
|
MN_REFERENCE_KIND_SHIFT = 24, // refKind
|
||||||
MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT,
|
MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT,
|
||||||
// The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers:
|
// The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers:
|
||||||
|
|
|
@ -266,6 +266,7 @@
|
||||||
template(returnType_name, "returnType") \
|
template(returnType_name, "returnType") \
|
||||||
template(signature_name, "signature") \
|
template(signature_name, "signature") \
|
||||||
template(slot_name, "slot") \
|
template(slot_name, "slot") \
|
||||||
|
template(trusted_final_name, "trustedFinal") \
|
||||||
\
|
\
|
||||||
/* Support for annotations (JDK 1.5 and above) */ \
|
/* Support for annotations (JDK 1.5 and above) */ \
|
||||||
\
|
\
|
||||||
|
|
|
@ -126,6 +126,7 @@ enum {
|
||||||
IS_FIELD = java_lang_invoke_MemberName::MN_IS_FIELD,
|
IS_FIELD = java_lang_invoke_MemberName::MN_IS_FIELD,
|
||||||
IS_TYPE = java_lang_invoke_MemberName::MN_IS_TYPE,
|
IS_TYPE = java_lang_invoke_MemberName::MN_IS_TYPE,
|
||||||
CALLER_SENSITIVE = java_lang_invoke_MemberName::MN_CALLER_SENSITIVE,
|
CALLER_SENSITIVE = java_lang_invoke_MemberName::MN_CALLER_SENSITIVE,
|
||||||
|
TRUSTED_FINAL = java_lang_invoke_MemberName::MN_TRUSTED_FINAL,
|
||||||
REFERENCE_KIND_SHIFT = java_lang_invoke_MemberName::MN_REFERENCE_KIND_SHIFT,
|
REFERENCE_KIND_SHIFT = java_lang_invoke_MemberName::MN_REFERENCE_KIND_SHIFT,
|
||||||
REFERENCE_KIND_MASK = java_lang_invoke_MemberName::MN_REFERENCE_KIND_MASK,
|
REFERENCE_KIND_MASK = java_lang_invoke_MemberName::MN_REFERENCE_KIND_MASK,
|
||||||
SEARCH_SUPERCLASSES = java_lang_invoke_MemberName::MN_SEARCH_SUPERCLASSES,
|
SEARCH_SUPERCLASSES = java_lang_invoke_MemberName::MN_SEARCH_SUPERCLASSES,
|
||||||
|
@ -339,8 +340,10 @@ oop MethodHandles::init_method_MemberName(Handle mname, CallInfo& info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
oop MethodHandles::init_field_MemberName(Handle mname, fieldDescriptor& fd, bool is_setter) {
|
oop MethodHandles::init_field_MemberName(Handle mname, fieldDescriptor& fd, bool is_setter) {
|
||||||
|
InstanceKlass* ik = fd.field_holder();
|
||||||
int flags = (jushort)( fd.access_flags().as_short() & JVM_RECOGNIZED_FIELD_MODIFIERS );
|
int flags = (jushort)( fd.access_flags().as_short() & JVM_RECOGNIZED_FIELD_MODIFIERS );
|
||||||
flags |= IS_FIELD | ((fd.is_static() ? JVM_REF_getStatic : JVM_REF_getField) << REFERENCE_KIND_SHIFT);
|
flags |= IS_FIELD | ((fd.is_static() ? JVM_REF_getStatic : JVM_REF_getField) << REFERENCE_KIND_SHIFT);
|
||||||
|
if (fd.is_trusted_final()) flags |= TRUSTED_FINAL;
|
||||||
if (is_setter) flags += ((JVM_REF_putField - JVM_REF_getField) << REFERENCE_KIND_SHIFT);
|
if (is_setter) flags += ((JVM_REF_putField - JVM_REF_getField) << REFERENCE_KIND_SHIFT);
|
||||||
int vmindex = fd.offset(); // determines the field uniquely when combined with static bit
|
int vmindex = fd.offset(); // determines the field uniquely when combined with static bit
|
||||||
|
|
||||||
|
@ -348,7 +351,7 @@ oop MethodHandles::init_field_MemberName(Handle mname, fieldDescriptor& fd, bool
|
||||||
java_lang_invoke_MemberName::set_flags (mname_oop, flags);
|
java_lang_invoke_MemberName::set_flags (mname_oop, flags);
|
||||||
java_lang_invoke_MemberName::set_method (mname_oop, NULL);
|
java_lang_invoke_MemberName::set_method (mname_oop, NULL);
|
||||||
java_lang_invoke_MemberName::set_vmindex(mname_oop, vmindex);
|
java_lang_invoke_MemberName::set_vmindex(mname_oop, vmindex);
|
||||||
java_lang_invoke_MemberName::set_clazz (mname_oop, fd.field_holder()->java_mirror());
|
java_lang_invoke_MemberName::set_clazz (mname_oop, ik->java_mirror());
|
||||||
|
|
||||||
oop type = field_signature_type_or_null(fd.signature());
|
oop type = field_signature_type_or_null(fd.signature());
|
||||||
oop name = field_name_or_null(fd.name());
|
oop name = field_name_or_null(fd.name());
|
||||||
|
@ -1107,6 +1110,7 @@ void MethodHandles::trace_method_handle_interpreter_entry(MacroAssembler* _masm,
|
||||||
template(java_lang_invoke_MemberName,MN_IS_FIELD) \
|
template(java_lang_invoke_MemberName,MN_IS_FIELD) \
|
||||||
template(java_lang_invoke_MemberName,MN_IS_TYPE) \
|
template(java_lang_invoke_MemberName,MN_IS_TYPE) \
|
||||||
template(java_lang_invoke_MemberName,MN_CALLER_SENSITIVE) \
|
template(java_lang_invoke_MemberName,MN_CALLER_SENSITIVE) \
|
||||||
|
template(java_lang_invoke_MemberName,MN_TRUSTED_FINAL) \
|
||||||
template(java_lang_invoke_MemberName,MN_SEARCH_SUPERCLASSES) \
|
template(java_lang_invoke_MemberName,MN_SEARCH_SUPERCLASSES) \
|
||||||
template(java_lang_invoke_MemberName,MN_SEARCH_INTERFACES) \
|
template(java_lang_invoke_MemberName,MN_SEARCH_INTERFACES) \
|
||||||
template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_SHIFT) \
|
template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_SHIFT) \
|
||||||
|
|
|
@ -58,6 +58,11 @@ Symbol* fieldDescriptor::generic_signature() const {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fieldDescriptor::is_trusted_final() const {
|
||||||
|
InstanceKlass* ik = field_holder();
|
||||||
|
return is_final() && (is_static() || ik->is_hidden() || ik->is_record());
|
||||||
|
}
|
||||||
|
|
||||||
AnnotationArray* fieldDescriptor::annotations() const {
|
AnnotationArray* fieldDescriptor::annotations() const {
|
||||||
InstanceKlass* ik = field_holder();
|
InstanceKlass* ik = field_holder();
|
||||||
Array<AnnotationArray*>* md = ik->fields_annotations();
|
Array<AnnotationArray*>* md = ik->fields_annotations();
|
||||||
|
|
|
@ -102,6 +102,8 @@ class fieldDescriptor {
|
||||||
bool has_initialized_final_update() const { return access_flags().has_field_initialized_final_update(); }
|
bool has_initialized_final_update() const { return access_flags().has_field_initialized_final_update(); }
|
||||||
bool has_generic_signature() const { return access_flags().field_has_generic_signature(); }
|
bool has_generic_signature() const { return access_flags().field_has_generic_signature(); }
|
||||||
|
|
||||||
|
bool is_trusted_final() const;
|
||||||
|
|
||||||
inline void set_is_field_access_watched(const bool value);
|
inline void set_is_field_access_watched(const bool value);
|
||||||
inline void set_is_field_modification_watched(const bool value);
|
inline void set_is_field_modification_watched(const bool value);
|
||||||
inline void set_has_initialized_final_update(const bool value);
|
inline void set_has_initialized_final_update(const bool value);
|
||||||
|
|
|
@ -897,6 +897,9 @@ oop Reflection::new_field(fieldDescriptor* fd, TRAPS) {
|
||||||
java_lang_reflect_Field::set_slot(rh(), fd->index());
|
java_lang_reflect_Field::set_slot(rh(), fd->index());
|
||||||
java_lang_reflect_Field::set_name(rh(), name());
|
java_lang_reflect_Field::set_name(rh(), name());
|
||||||
java_lang_reflect_Field::set_type(rh(), type());
|
java_lang_reflect_Field::set_type(rh(), type());
|
||||||
|
if (fd->is_trusted_final()) {
|
||||||
|
java_lang_reflect_Field::set_trusted_final(rh());
|
||||||
|
}
|
||||||
// Note the ACC_ANNOTATION bit, which is a per-class access flag, is never set here.
|
// Note the ACC_ANNOTATION bit, which is a per-class access flag, is never set here.
|
||||||
java_lang_reflect_Field::set_modifiers(rh(), fd->access_flags().as_int() & JVM_RECOGNIZED_FIELD_MODIFIERS);
|
java_lang_reflect_Field::set_modifiers(rh(), fd->access_flags().as_int() & JVM_RECOGNIZED_FIELD_MODIFIERS);
|
||||||
java_lang_reflect_Field::set_override(rh(), false);
|
java_lang_reflect_Field::set_override(rh(), false);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -480,7 +480,8 @@ final class MemberName implements Member, Cloneable {
|
||||||
IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor
|
IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor
|
||||||
IS_FIELD = MN_IS_FIELD, // field
|
IS_FIELD = MN_IS_FIELD, // field
|
||||||
IS_TYPE = MN_IS_TYPE, // nested type
|
IS_TYPE = MN_IS_TYPE, // nested type
|
||||||
CALLER_SENSITIVE = MN_CALLER_SENSITIVE; // @CallerSensitive annotation detected
|
CALLER_SENSITIVE = MN_CALLER_SENSITIVE, // @CallerSensitive annotation detected
|
||||||
|
TRUSTED_FINAL = MN_TRUSTED_FINAL; // trusted final field
|
||||||
|
|
||||||
static final int ALL_ACCESS = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED;
|
static final int ALL_ACCESS = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED;
|
||||||
static final int ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE;
|
static final int ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE;
|
||||||
|
@ -520,6 +521,8 @@ final class MemberName implements Member, Cloneable {
|
||||||
public boolean isCallerSensitive() {
|
public boolean isCallerSensitive() {
|
||||||
return testAllFlags(CALLER_SENSITIVE);
|
return testAllFlags(CALLER_SENSITIVE);
|
||||||
}
|
}
|
||||||
|
/** Query whether this member is a trusted final field. */
|
||||||
|
public boolean isTrustedFinalField() { return testAllFlags(TRUSTED_FINAL|IS_FIELD); }
|
||||||
|
|
||||||
/** Utility method to query whether this member is accessible from a given lookup class. */
|
/** Utility method to query whether this member is accessible from a given lookup class. */
|
||||||
public boolean isAccessibleFrom(Class<?> lookupClass) {
|
public boolean isAccessibleFrom(Class<?> lookupClass) {
|
||||||
|
|
|
@ -118,6 +118,7 @@ class MethodHandleNatives {
|
||||||
MN_IS_FIELD = 0x00040000, // field
|
MN_IS_FIELD = 0x00040000, // field
|
||||||
MN_IS_TYPE = 0x00080000, // nested type
|
MN_IS_TYPE = 0x00080000, // nested type
|
||||||
MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected
|
MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected
|
||||||
|
MN_TRUSTED_FINAL = 0x00200000, // trusted final field
|
||||||
MN_REFERENCE_KIND_SHIFT = 24, // refKind
|
MN_REFERENCE_KIND_SHIFT = 24, // refKind
|
||||||
MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT,
|
MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT,
|
||||||
// The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers:
|
// The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers:
|
||||||
|
|
|
@ -3273,10 +3273,10 @@ return mh1;
|
||||||
private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException {
|
private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException {
|
||||||
MemberName field = new MemberName(f, isSetter);
|
MemberName field = new MemberName(f, isSetter);
|
||||||
if (isSetter && field.isFinal()) {
|
if (isSetter && field.isFinal()) {
|
||||||
if (field.isStatic()) {
|
if (field.isTrustedFinalField()) {
|
||||||
throw field.makeAccessException("static final field has no write access", this);
|
String msg = field.isStatic() ? "static final field has no write access"
|
||||||
} else if (field.getDeclaringClass().isHidden()){
|
: "final field has no write access";
|
||||||
throw field.makeAccessException("final field in a hidden class has no write access", this);
|
throw field.makeAccessException(msg, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(isSetter
|
assert(isSetter
|
||||||
|
@ -3839,7 +3839,7 @@ return mh1;
|
||||||
refc = lookupClass();
|
refc = lookupClass();
|
||||||
}
|
}
|
||||||
return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(),
|
return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(),
|
||||||
this.allowedModes == TRUSTED && !getField.getDeclaringClass().isHidden());
|
this.allowedModes == TRUSTED && !getField.isTrustedFinalField());
|
||||||
}
|
}
|
||||||
/** Check access and get the requested constructor. */
|
/** Check access and get the requested constructor. */
|
||||||
private MethodHandle getDirectConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
|
private MethodHandle getDirectConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
|
||||||
|
|
|
@ -177,10 +177,16 @@ public class AccessibleObject implements AnnotatedElement {
|
||||||
* to the caller's module. </p>
|
* to the caller's module. </p>
|
||||||
*
|
*
|
||||||
* <p> This method cannot be used to enable {@linkplain Field#set <em>write</em>}
|
* <p> This method cannot be used to enable {@linkplain Field#set <em>write</em>}
|
||||||
* access to a final field declared in a {@linkplain Class#isHidden() hidden class},
|
* access to a <em>non-modifiable</em> final field. The following fields
|
||||||
* since such fields are not modifiable. The {@code accessible} flag when
|
* are non-modifiable:
|
||||||
* {@code true} suppresses Java language access control checks to only
|
* <ul>
|
||||||
* enable {@linkplain Field#get <em>read</em>} access to such fields.
|
* <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.
|
||||||
*
|
*
|
||||||
* <p> If there is a security manager, its
|
* <p> If there is a security manager, its
|
||||||
* {@code checkPermission} method is first called with a
|
* {@code checkPermission} method is first called with a
|
||||||
|
|
|
@ -72,6 +72,7 @@ class Field extends AccessibleObject implements Member {
|
||||||
private String name;
|
private String name;
|
||||||
private Class<?> type;
|
private Class<?> type;
|
||||||
private int modifiers;
|
private int modifiers;
|
||||||
|
private boolean trustedFinal;
|
||||||
// Generics and annotations support
|
// Generics and annotations support
|
||||||
private transient String signature;
|
private transient String signature;
|
||||||
// generic info repository; lazily initialized
|
// generic info repository; lazily initialized
|
||||||
|
@ -119,6 +120,7 @@ class Field extends AccessibleObject implements Member {
|
||||||
String name,
|
String name,
|
||||||
Class<?> type,
|
Class<?> type,
|
||||||
int modifiers,
|
int modifiers,
|
||||||
|
boolean trustedFinal,
|
||||||
int slot,
|
int slot,
|
||||||
String signature,
|
String signature,
|
||||||
byte[] annotations)
|
byte[] annotations)
|
||||||
|
@ -127,6 +129,7 @@ class Field extends AccessibleObject implements Member {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.modifiers = modifiers;
|
this.modifiers = modifiers;
|
||||||
|
this.trustedFinal = trustedFinal;
|
||||||
this.slot = slot;
|
this.slot = slot;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
this.annotations = annotations;
|
this.annotations = annotations;
|
||||||
|
@ -148,7 +151,7 @@ class Field extends AccessibleObject implements Member {
|
||||||
if (this.root != null)
|
if (this.root != null)
|
||||||
throw new IllegalArgumentException("Can not copy a non-root Field");
|
throw new IllegalArgumentException("Can not copy a non-root Field");
|
||||||
|
|
||||||
Field res = new Field(clazz, name, type, modifiers, slot, signature, annotations);
|
Field res = new Field(clazz, name, type, modifiers, trustedFinal, slot, signature, annotations);
|
||||||
res.root = this;
|
res.root = this;
|
||||||
// Might as well eagerly propagate this if already present
|
// Might as well eagerly propagate this if already present
|
||||||
res.fieldAccessor = fieldAccessor;
|
res.fieldAccessor = fieldAccessor;
|
||||||
|
@ -728,7 +731,9 @@ class Field extends AccessibleObject implements Member {
|
||||||
* this {@code Field} object;</li>
|
* this {@code Field} object;</li>
|
||||||
* <li>the field is non-static; and</li>
|
* <li>the field is non-static; and</li>
|
||||||
* <li>the field's declaring class is not a {@linkplain Class#isHidden()
|
* <li>the field's declaring class is not a {@linkplain Class#isHidden()
|
||||||
* hidden class}.</li>
|
* hidden class}; and</li>
|
||||||
|
* <li>the field's declaring class is not a {@linkplain Class#isRecord()
|
||||||
|
* record class}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* If any of the above checks is not met, this method throws an
|
* If any of the above checks is not met, this method throws an
|
||||||
* {@code IllegalAccessException}.
|
* {@code IllegalAccessException}.
|
||||||
|
@ -1145,10 +1150,14 @@ class Field extends AccessibleObject implements Member {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Field getRoot() {
|
/* package-private */ Field getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package-private */ boolean isTrustedFinal() {
|
||||||
|
return trustedFinal;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -118,6 +118,10 @@ class ReflectAccess implements jdk.internal.access.JavaLangReflectAccess {
|
||||||
return (T) obj.getRoot();
|
return (T) obj.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTrustedFinalField(Field f) {
|
||||||
|
return f.isTrustedFinal();
|
||||||
|
}
|
||||||
|
|
||||||
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
|
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
|
||||||
throws IllegalAccessException, InstantiationException, InvocationTargetException
|
throws IllegalAccessException, InstantiationException, InvocationTargetException
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -96,6 +96,9 @@ public interface JavaLangReflectAccess {
|
||||||
/** Gets the root of the given AccessibleObject object; null if arg is the root */
|
/** Gets the root of the given AccessibleObject object; null if arg is the root */
|
||||||
public <T extends AccessibleObject> T getRoot(T obj);
|
public <T extends AccessibleObject> T getRoot(T obj);
|
||||||
|
|
||||||
|
/** Tests if this is a trusted final field */
|
||||||
|
public boolean isTrustedFinalField(Field f);
|
||||||
|
|
||||||
/** Returns a new instance created by the given constructor with access check */
|
/** Returns a new instance created by the given constructor with access check */
|
||||||
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
|
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
|
||||||
throws IllegalAccessException, InstantiationException, InvocationTargetException;
|
throws IllegalAccessException, InstantiationException, InvocationTargetException;
|
||||||
|
|
|
@ -31,6 +31,7 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import jdk.internal.HotSpotIntrinsicCandidate;
|
import jdk.internal.HotSpotIntrinsicCandidate;
|
||||||
|
import jdk.internal.access.SharedSecrets;
|
||||||
import jdk.internal.misc.VM;
|
import jdk.internal.misc.VM;
|
||||||
|
|
||||||
/** Common utility routines used by both java.lang and
|
/** Common utility routines used by both java.lang and
|
||||||
|
@ -336,6 +337,14 @@ public class Reflection {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests if the given Field is a trusted final field and it cannot be
|
||||||
|
* modified reflectively regardless of the value of its accessible flag.
|
||||||
|
*/
|
||||||
|
public static boolean isTrustedFinalField(Field field) {
|
||||||
|
return SharedSecrets.getJavaLangReflectAccess().isTrustedFinalField(field);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an IllegalAccessException with an exception message based on
|
* Returns an IllegalAccessException with an exception message based on
|
||||||
* the access that is denied.
|
* the access that is denied.
|
||||||
|
|
|
@ -181,7 +181,9 @@ public class ReflectionFactory {
|
||||||
field = root;
|
field = root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return UnsafeFieldAccessorFactory.newFieldAccessor(field, override);
|
boolean isFinal = Modifier.isFinal(field.getModifiers());
|
||||||
|
boolean isReadOnly = isFinal && (!override || langReflectAccess.isTrustedFinalField(field));
|
||||||
|
return UnsafeFieldAccessorFactory.newFieldAccessor(field, isReadOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodAccessor newMethodAccessor(Method method) {
|
public MethodAccessor newMethodAccessor(Method method) {
|
||||||
|
|
|
@ -29,13 +29,12 @@ import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
class UnsafeFieldAccessorFactory {
|
class UnsafeFieldAccessorFactory {
|
||||||
static FieldAccessor newFieldAccessor(Field field, boolean override) {
|
static FieldAccessor newFieldAccessor(Field field, boolean isReadOnly) {
|
||||||
Class<?> type = field.getType();
|
Class<?> type = field.getType();
|
||||||
boolean isStatic = Modifier.isStatic(field.getModifiers());
|
boolean isStatic = Modifier.isStatic(field.getModifiers());
|
||||||
boolean isFinal = Modifier.isFinal(field.getModifiers());
|
boolean isFinal = Modifier.isFinal(field.getModifiers());
|
||||||
boolean isVolatile = Modifier.isVolatile(field.getModifiers());
|
boolean isVolatile = Modifier.isVolatile(field.getModifiers());
|
||||||
boolean isQualified = isFinal || isVolatile;
|
boolean isQualified = isFinal || isVolatile;
|
||||||
boolean isReadOnly = isFinal && (isStatic || !override || field.getDeclaringClass().isHidden());
|
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
// This code path does not guarantee that the field's
|
// This code path does not guarantee that the field's
|
||||||
// declaring class has been initialized, but it must be
|
// declaring class has been initialized, but it must be
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -636,13 +636,18 @@ public final class Unsafe {
|
||||||
* @see #getInt(Object, long)
|
* @see #getInt(Object, long)
|
||||||
*/
|
*/
|
||||||
@ForceInline
|
@ForceInline
|
||||||
|
@SuppressWarnings("preview")
|
||||||
public long objectFieldOffset(Field f) {
|
public long objectFieldOffset(Field f) {
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
if (f.getDeclaringClass().isHidden()) {
|
Class<?> declaringClass = f.getDeclaringClass();
|
||||||
|
if (declaringClass.isHidden()) {
|
||||||
throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
|
throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
|
||||||
}
|
}
|
||||||
|
if (declaringClass.isRecord()) {
|
||||||
|
throw new UnsupportedOperationException("can't get field offset on a record (preview): " + f);
|
||||||
|
}
|
||||||
return theInternalUnsafe.objectFieldOffset(f);
|
return theInternalUnsafe.objectFieldOffset(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,13 +669,18 @@ public final class Unsafe {
|
||||||
* @see #getInt(Object, long)
|
* @see #getInt(Object, long)
|
||||||
*/
|
*/
|
||||||
@ForceInline
|
@ForceInline
|
||||||
|
@SuppressWarnings("preview")
|
||||||
public long staticFieldOffset(Field f) {
|
public long staticFieldOffset(Field f) {
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
if (f.getDeclaringClass().isHidden()) {
|
Class<?> declaringClass = f.getDeclaringClass();
|
||||||
|
if (declaringClass.isHidden()) {
|
||||||
throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
|
throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
|
||||||
}
|
}
|
||||||
|
if (declaringClass.isRecord()) {
|
||||||
|
throw new UnsupportedOperationException("can't get field offset on a record (preview): " + f);
|
||||||
|
}
|
||||||
return theInternalUnsafe.staticFieldOffset(f);
|
return theInternalUnsafe.staticFieldOffset(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -685,13 +695,18 @@ public final class Unsafe {
|
||||||
* this class.
|
* this class.
|
||||||
*/
|
*/
|
||||||
@ForceInline
|
@ForceInline
|
||||||
|
@SuppressWarnings("preview")
|
||||||
public Object staticFieldBase(Field f) {
|
public Object staticFieldBase(Field f) {
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
if (f.getDeclaringClass().isHidden()) {
|
Class<?> declaringClass = f.getDeclaringClass();
|
||||||
|
if (declaringClass.isHidden()) {
|
||||||
throw new UnsupportedOperationException("can't get base address on a hidden class: " + f);
|
throw new UnsupportedOperationException("can't get base address on a hidden class: " + f);
|
||||||
}
|
}
|
||||||
|
if (declaringClass.isRecord()) {
|
||||||
|
throw new UnsupportedOperationException("can't get base address on a record (preview): " + f);
|
||||||
|
}
|
||||||
return theInternalUnsafe.staticFieldBase(f);
|
return theInternalUnsafe.staticFieldBase(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
test/jdk/java/lang/invoke/unreflect/TEST.properties
Normal file
24
test/jdk/java/lang/invoke/unreflect/TEST.properties
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020, 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
enablePreview=true
|
|
@ -23,9 +23,10 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @compile src/Fields.java
|
* @bug 8238358 8247444
|
||||||
* @run testng/othervm UnreflectTest
|
* @run testng/othervm --enable-preview UnreflectTest
|
||||||
* @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle
|
* @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle on
|
||||||
|
* trusted final fields (declared in hidden classes and records)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -89,6 +90,24 @@ public class UnreflectTest {
|
||||||
readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false);
|
readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static record TestRecord(int i) {
|
||||||
|
static final Object STATIC_FINAL = new Object();
|
||||||
|
static Object STATIC_NON_FINAL = new Object();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
|
||||||
|
* cannot write the value of a non-static final field in a record class
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("preview")
|
||||||
|
public void testFieldsInRecordClass() throws Throwable {
|
||||||
|
assertTrue(TestRecord.class.isRecord());
|
||||||
|
Object o = new TestRecord(1);
|
||||||
|
readOnlyAccessibleObject(TestRecord.class, "STATIC_FINAL", null, true);
|
||||||
|
readWriteAccessibleObject(TestRecord.class, "STATIC_NON_FINAL", null, false);
|
||||||
|
readOnlyAccessibleObject(TestRecord.class, "i", o, true);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify read-only access via unreflectSetter and unreflectVarHandle
|
* Verify read-only access via unreflectSetter and unreflectVarHandle
|
||||||
*/
|
*/
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @bug 8235369 8235550
|
* @bug 8235369 8235550 8247444
|
||||||
* @summary reflection test for records
|
* @summary reflection test for records
|
||||||
* @compile --enable-preview -source ${jdk.version} RecordReflectionTest.java
|
* @compile --enable-preview -source ${jdk.version} RecordReflectionTest.java
|
||||||
* @run testng/othervm --enable-preview RecordReflectionTest
|
* @run testng/othervm --enable-preview RecordReflectionTest
|
||||||
|
@ -187,4 +187,19 @@ public class RecordReflectionTest {
|
||||||
assertEquals(f.getAnnotatedType().getAnnotations().length, 1);
|
assertEquals(f.getAnnotatedType().getAnnotations().length, 1);
|
||||||
assertEquals(f.getAnnotatedType().getAnnotations()[0].toString(), annos[0].toString());
|
assertEquals(f.getAnnotatedType().getAnnotations()[0].toString(), annos[0].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testReadOnlyFieldInRecord() throws Throwable {
|
||||||
|
R2 o = new R2(1, 2);
|
||||||
|
Class<?> recordClass = R2.class;
|
||||||
|
String fieldName = "i";
|
||||||
|
Field f = recordClass.getDeclaredField(fieldName);
|
||||||
|
assertTrue(f.trySetAccessible());
|
||||||
|
assertTrue(f.get(o) != null);
|
||||||
|
try {
|
||||||
|
f.set(o, null);
|
||||||
|
assertTrue(false, "should fail to set " + fieldName);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* @test
|
/* @test
|
||||||
|
* @bug 8238358 8247444
|
||||||
* @summary Ensure that sun.misc.Unsafe::objectFieldOffset and staticFieldOffset
|
* @summary Ensure that sun.misc.Unsafe::objectFieldOffset and staticFieldOffset
|
||||||
* throw UnsupportedOperationException on Field of a hidden class
|
* throw UnsupportedOperationException on Field of a hidden or record class
|
||||||
* @modules jdk.unsupported
|
* @modules jdk.unsupported
|
||||||
* @run main UnsafeFieldOffsets
|
* @compile --enable-preview -source ${jdk.version} UnsafeFieldOffsets.java
|
||||||
|
* @run testng/othervm --enable-preview UnsafeFieldOffsets
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
@ -38,6 +40,9 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
public class UnsafeFieldOffsets {
|
public class UnsafeFieldOffsets {
|
||||||
static class Fields {
|
static class Fields {
|
||||||
static final Object STATIC_FINAL = new Object();
|
static final Object STATIC_FINAL = new Object();
|
||||||
|
@ -45,9 +50,14 @@ public class UnsafeFieldOffsets {
|
||||||
final Object FINAL = new Object();
|
final Object FINAL = new Object();
|
||||||
Object NON_FINAL = new Object();
|
Object NON_FINAL = new Object();
|
||||||
}
|
}
|
||||||
|
record TestRecord(int i) {
|
||||||
|
static final Object STATIC_FINAL = new Object();
|
||||||
|
static Object STATIC_NON_FINAL = new Object();
|
||||||
|
}
|
||||||
|
|
||||||
private static Unsafe UNSAFE = getUnsafe();
|
private static Unsafe UNSAFE = getUnsafe();
|
||||||
private static final Class<?> HIDDEN_CLASS = defineHiddenClass();
|
private static final Class<?> HIDDEN_CLASS = defineHiddenClass();
|
||||||
|
private static final Class<?> RECORD_CLASS = TestRecord.class;
|
||||||
|
|
||||||
private static Unsafe getUnsafe() {
|
private static Unsafe getUnsafe() {
|
||||||
try {
|
try {
|
||||||
|
@ -65,7 +75,7 @@ public class UnsafeFieldOffsets {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = Files.readAllBytes(cf);
|
byte[] bytes = Files.readAllBytes(cf);
|
||||||
Class<?> c = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
|
Class<?> c = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
|
||||||
assertHiddenClass(c);
|
assertTrue(c.isHidden());
|
||||||
return c;
|
return c;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
|
@ -74,13 +84,8 @@ public class UnsafeFieldOffsets {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
@Test
|
||||||
// non-hidden class
|
public void testNormalClass() throws Throwable {
|
||||||
testStaticField(Fields.class, "STATIC_FINAL");
|
|
||||||
testStaticField(Fields.class, "STATIC_NON_FINAL");
|
|
||||||
testInstanceField(Fields.class, "FINAL");
|
|
||||||
testInstanceField(Fields.class, "NON_FINAL");
|
|
||||||
|
|
||||||
// hidden class
|
// hidden class
|
||||||
testStaticField(HIDDEN_CLASS, "STATIC_FINAL");
|
testStaticField(HIDDEN_CLASS, "STATIC_FINAL");
|
||||||
testStaticField(HIDDEN_CLASS, "STATIC_NON_FINAL");
|
testStaticField(HIDDEN_CLASS, "STATIC_NON_FINAL");
|
||||||
|
@ -88,13 +93,30 @@ public class UnsafeFieldOffsets {
|
||||||
testInstanceField(HIDDEN_CLASS, "NON_FINAL");
|
testInstanceField(HIDDEN_CLASS, "NON_FINAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHiddenClass() throws Throwable {
|
||||||
|
// hidden class
|
||||||
|
testStaticField(HIDDEN_CLASS, "STATIC_FINAL");
|
||||||
|
testStaticField(HIDDEN_CLASS, "STATIC_NON_FINAL");
|
||||||
|
testInstanceField(HIDDEN_CLASS, "FINAL");
|
||||||
|
testInstanceField(HIDDEN_CLASS, "NON_FINAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecordClass() throws Throwable {
|
||||||
|
// record class
|
||||||
|
testRecordStaticField(RECORD_CLASS, "STATIC_FINAL");
|
||||||
|
testRecordStaticField(RECORD_CLASS, "STATIC_NON_FINAL");
|
||||||
|
testRecordInstanceField(RECORD_CLASS, "i");
|
||||||
|
}
|
||||||
|
|
||||||
private static void testStaticField(Class<?> c, String name) throws Exception {
|
private static void testStaticField(Class<?> c, String name) throws Exception {
|
||||||
Field f = c.getDeclaredField(name);
|
Field f = c.getDeclaredField(name);
|
||||||
try {
|
try {
|
||||||
UNSAFE.staticFieldOffset(f);
|
UNSAFE.staticFieldOffset(f);
|
||||||
assertNonHiddenClass(c);
|
assertFalse(c.isHidden(), "Expected UOE thrown: " + c);
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
assertHiddenClass(c);
|
assertTrue(c.isHidden(), "Expected hidden class: " + c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,19 +124,31 @@ public class UnsafeFieldOffsets {
|
||||||
Field f = c.getDeclaredField(name);
|
Field f = c.getDeclaredField(name);
|
||||||
try {
|
try {
|
||||||
UNSAFE.objectFieldOffset(f);
|
UNSAFE.objectFieldOffset(f);
|
||||||
assertNonHiddenClass(c);
|
assertFalse(c.isHidden(), "Expected UOE thrown: " + c);
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
assertHiddenClass(c);
|
assertTrue(c.isHidden(), "Expected hidden class: " + c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertNonHiddenClass(Class<?> c) {
|
@SuppressWarnings("preview")
|
||||||
if (c.isHidden())
|
private static void testRecordStaticField(Class<?> c, String name) throws Exception {
|
||||||
throw new RuntimeException("Expected UOE but not thrown: " + c);
|
Field f = c.getDeclaredField(name);
|
||||||
|
try {
|
||||||
|
UNSAFE.staticFieldOffset(f);
|
||||||
|
assertFalse(c.isRecord(), "Expected UOE thrown: " + c);
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
assertTrue(c.isRecord(), "Expected record class: " + c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertHiddenClass(Class<?> c) {
|
@SuppressWarnings("preview")
|
||||||
if (!c.isHidden())
|
private static void testRecordInstanceField(Class<?> c, String name) throws Exception {
|
||||||
throw new RuntimeException("Expected hidden class but is not: " + c);
|
Field f = c.getDeclaredField(name);
|
||||||
|
try {
|
||||||
|
UNSAFE.objectFieldOffset(f);
|
||||||
|
assertFalse(c.isRecord(), "Expected UOE thrown: " + c);
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
assertTrue(c.isRecord(), "Expected record class: " + c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue