Skip to content

Strict static 8349945 #1443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: lworld
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/hotspot/share/ci/ciField.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,18 @@ bool ciField::will_link(ciMethod* accessing_method,
fieldDescriptor result;
LinkResolver::resolve_field(result, link_info, bc, false, CHECK_AND_CLEAR_(false));

// Strict statics may require tracking if their class is not fully initialized.
// For now we can bail out of the compiler and let the interpreter handle it.
if (is_static && result.is_strict_static_unset()) {
// If we left out this logic, we would get (a) spurious <clinit>
// failures for C2 code because compiled putstatic would not write
// the "unset" bits, and (b) missed failures for too-early reads,
// since the compiled getstatic would not check the "unset" bits.
// Test C1 on <clinit> with "-XX:TieredStopAtLevel=2 -Xcomp -Xbatch".
// Test C2 on <clinit> with "-XX:-TieredCompilation -Xcomp -Xbatch".
return false;
}

// update the hit-cache, unless there is a problem with memory scoping:
if (accessing_method->holder()->is_shared() || !is_shared()) {
if (is_put) {
Expand Down
26 changes: 26 additions & 0 deletions src/hotspot/share/classfile/classFileParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,9 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
if (fi.field_flags().is_contended()) {
_has_contended_fields = true;
}
if (access_flags.is_strict() && access_flags.is_static()) {
_has_strict_static_fields = true;
}
_temp_field_info->append(fi);
}
assert(_temp_field_info->length() == length, "Must be");
Expand Down Expand Up @@ -5289,6 +5292,7 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik,
// Not yet: supers are done below to support the new subtype-checking fields
ik->set_nonstatic_field_size(_layout_info->_nonstatic_field_size);
ik->set_has_nonstatic_fields(_layout_info->_has_nonstatic_fields);
ik->set_has_strict_static_fields(_has_strict_static_fields);

if (_layout_info->_is_naturally_atomic) {
ik->set_is_naturally_atomic();
Expand Down Expand Up @@ -5600,6 +5604,7 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream,
_has_localvariable_table(false),
_has_final_method(false),
_has_contended_fields(false),
_has_strict_static_fields(false),
_has_inline_type_fields(false),
_is_naturally_atomic(false),
_must_be_atomic(true),
Expand Down Expand Up @@ -6237,6 +6242,27 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st
_fields_status =
MetadataFactory::new_array<FieldStatus>(_loader_data, _temp_field_info->length(),
FieldStatus(0), CHECK);

// Strict static fields track initialization status from the beginning of time.
// After this class runs <clinit>, they will be verified as being "not unset".
// See Step 8 of InstanceKlass::initialize_impl.
if (_has_strict_static_fields) {
bool found_one = false;
for (int i = 0; i < _temp_field_info->length(); i++) {
FieldInfo& fi = *_temp_field_info->adr_at(i);
if (fi.access_flags().is_strict() && fi.access_flags().is_static()) {
found_one = true;
if (fi.initializer_index() != 0) {
// skip strict static fields with ConstantValue attributes
} else {
_fields_status->adr_at(fi.index())->update_strict_static_unset(true);
_fields_status->adr_at(fi.index())->update_strict_static_unread(true);
}
}
}
assert(found_one == _has_strict_static_fields,
"correct prediction = %d", (int)_has_strict_static_fields);
}
}

void ClassFileParser::set_klass(InstanceKlass* klass) {
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/classfile/classFileParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ class ClassFileParser {
bool _has_localvariable_table;
bool _has_final_method;
bool _has_contended_fields;
bool _has_strict_static_fields;

bool _has_inline_type_fields;
bool _is_naturally_atomic;
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/classfile/stackMapFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ bool StackMapFrame::is_assignable_to(
// Check that assert unset fields are compatible
bool compatible = verify_unset_fields_compatibility(target->assert_unset_fields());
if (!compatible) {
print_strict_fields(assert_unset_fields());
print_strict_fields(target->assert_unset_fields());
*ctx = ErrorContext::strict_fields_mismatch(target->offset(),
(StackMapFrame*)this, (StackMapFrame*)target);
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/classfile/verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ void ErrorContext::reason_details(outputStream* ss) const {
ss->print("Current frame's stack size doesn't match stackmap.");
break;
case STRICT_FIELDS_MISMATCH:
ss->print("Current frame's strict instance fields not compatible with stackmap.");
ss->print("Current frame's strict fields do not match target's strict fields");
break;
case STACK_OVERFLOW:
ss->print("Exceeded max stack size.");
Expand Down
14 changes: 14 additions & 0 deletions src/hotspot/share/interpreter/interpreterRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ void InterpreterRuntime::resolve_get_put(Bytecodes::Code bytecode, int field_ind
bool uninitialized_static = is_static && !klass->is_initialized();
bool has_initialized_final_update = info.field_holder()->major_version() >= 53 &&
info.has_initialized_final_update();
bool strict_static_final = info.is_strict() && info.is_static() && info.is_final();
assert(!(has_initialized_final_update && !info.access_flags().is_final()), "Fields with initialized final updates must be final");

Bytecodes::Code get_code = (Bytecodes::Code)0;
Expand All @@ -805,6 +806,19 @@ void InterpreterRuntime::resolve_get_put(Bytecodes::Code bytecode, int field_ind
if ((is_put && !has_initialized_final_update) || !info.access_flags().is_final()) {
put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield);
}
assert(!info.is_strict_static_unset(), "after initialization, no unset flags");
} else if (is_static && (info.is_strict_static_unset() || strict_static_final)) {
// During <clinit>, closely track the state of strict statics.
// 1. if we are reading an uninitialized strict static, throw
// 2. if we are writing one, clear the "unset" flag
//
// Note: If we were handling an attempted write of a null to a
// null-restricted strict static, we would NOT clear the "unset"
// flag. This does not happen today, but will with NR fields.
assert(klass->is_being_initialized(), "else should have thrown");
assert(klass->is_reentrant_initialization(THREAD),
"<clinit> must be running in current thread");
klass->notify_strict_static_access(info.index(), is_put, CHECK);
}

ResolvedFieldEntry* entry = pool->resolved_field_entry_at(field_index);
Expand Down
10 changes: 8 additions & 2 deletions src/hotspot/share/oops/fieldInfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ class FieldStatus {
enum FieldStatusBitPosition {
_fs_access_watched, // field access is watched by JVMTI
_fs_modification_watched, // field modification is watched by JVMTI
_fs_strict_static_unset, // JVM_ACC_STRICT static field has not yet been set
_fs_strict_static_unread, // SS field has not yet been read (EnforceStrictStatics=2 only)
_initialized_final_update // (static) final field updated outside (class) initializer
};

Expand All @@ -331,12 +333,16 @@ class FieldStatus {
FieldStatus(u1 flags) { _flags = flags; }
u1 as_uint() { return _flags; }

bool is_access_watched() { return test_flag(_fs_access_watched); }
bool is_modification_watched() { return test_flag(_fs_modification_watched); }
bool is_access_watched() { return test_flag(_fs_access_watched); }
bool is_modification_watched() { return test_flag(_fs_modification_watched); }
bool is_strict_static_unset() { return test_flag(_fs_strict_static_unset); }
bool is_strict_static_unread() { return test_flag(_fs_strict_static_unread); }
bool is_initialized_final_update() { return test_flag(_initialized_final_update); }

void update_access_watched(bool z);
void update_modification_watched(bool z);
void update_strict_static_unset(bool z);
void update_strict_static_unread(bool z);
void update_initialized_final_update(bool z);
};

Expand Down
6 changes: 4 additions & 2 deletions src/hotspot/share/oops/fieldInfo.inline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ inline void FieldStatus::update_flag(FieldStatusBitPosition pos, bool z) {
else atomic_clear_bits(_flags, flag_mask(pos));
}

inline void FieldStatus::update_access_watched(bool z) { update_flag(_fs_access_watched, z); }
inline void FieldStatus::update_modification_watched(bool z) { update_flag(_fs_modification_watched, z); }
inline void FieldStatus::update_access_watched(bool z) { update_flag(_fs_access_watched, z); }
inline void FieldStatus::update_modification_watched(bool z) { update_flag(_fs_modification_watched, z); }
inline void FieldStatus::update_strict_static_unset(bool z) { update_flag(_fs_strict_static_unset, z); }
inline void FieldStatus::update_strict_static_unread(bool z) { update_flag(_fs_strict_static_unread, z); }
inline void FieldStatus::update_initialized_final_update(bool z) { update_flag(_initialized_final_update, z); }

#endif // SHARE_OOPS_FIELDINFO_INLINE_HPP
112 changes: 112 additions & 0 deletions src/hotspot/share/oops/instanceKlass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,36 @@ void InstanceKlass::initialize_impl(TRAPS) {
}
call_class_initializer(THREAD);
}

if (has_strict_static_fields() && !HAS_PENDING_EXCEPTION) {
// Step 9 also verifies that strict static fields have been initialized.
// Status bits were set in ClassFileParser::post_process_parsed_stream.
// After <clinit>, bits must all be clear, or else we must throw an error.
// This is an extremely fast check, so we won't bother with a timer.
assert(fields_status() != nullptr, "");
Symbol* bad_strict_static = nullptr;
for (int index = 0; index < fields_status()->length(); index++) {
// Very fast loop over single byte array looking for a set bit.
if (fields_status()->adr_at(index)->is_strict_static_unset()) {
// This strict static field has not been set by the class initializer.
// Note that in the common no-error case, we read no field metadata.
// We only unpack it when we need to report an error.
FieldInfo fi = field(index);
bad_strict_static = fi.name(constants());
if (debug_logging_enabled) {
ResourceMark rm(jt);
const char* msg = format_strict_static_message(bad_strict_static);
log_debug(class, init)("%s", msg);
} else {
// If we are not logging, do not bother to look for a second offense.
break;
}
}
}
if (bad_strict_static != nullptr) {
throw_strict_static_exception(bad_strict_static, "is unset after initialization of", THREAD);
}
}
}

// Step 9
Expand Down Expand Up @@ -1561,6 +1591,88 @@ void InstanceKlass::set_initialization_state_and_notify(ClassState state, TRAPS)
}
}

void InstanceKlass::notify_strict_static_access(int field_index, bool is_writing, TRAPS) {
guarantee(field_index >= 0 && field_index < fields_status()->length(), "valid field index");
DEBUG_ONLY(FieldInfo debugfi = field(field_index));
assert(debugfi.access_flags().is_strict(), "");
assert(debugfi.access_flags().is_static(), "");
FieldStatus& fs = *fields_status()->adr_at(field_index);
LogTarget(Trace, class, init) lt;
if (lt.is_enabled()) {
ResourceMark rm(THREAD);
LogStream ls(lt);
FieldInfo fi = field(field_index);
ls.print("notify %s %s %s%s ",
external_name(), is_writing? "Write" : "Read",
fs.is_strict_static_unset() ? "Unset" : "(set)",
fs.is_strict_static_unread() ? "+Unread" : "");
fi.print(&ls, constants());
}
if (fs.is_strict_static_unset()) {
assert(fs.is_strict_static_unread(), "ClassFileParser resp.");
// If it is not set, there are only two reasonable things we can do here:
// - mark it set if this is putstatic
// - throw an error (Read-Before-Write) if this is getstatic
//
// A third less-reasonable thing is note an internal error if the
// JDK reflection logic has allowed another thread to sneak into
// the <clinit> critical section.
if (!is_reentrant_initialization(THREAD)) {
// The unset state is (or should be) transient, and observable only in one
// thread during the execution of <clinit>. Something is wrong here, and
// we should just throw.
if (is_in_error_state()) {
oop init_error = get_initialization_error(THREAD);
if (init_error != nullptr) {
THROW_OOP(init_error);
}
}
THROW_MSG(vmSymbols::java_lang_InternalError(), "unscoped access to strict static");
} else if (is_writing) {
// clear the "unset" bit, since the field is actually going to be written
fs.update_strict_static_unset(false);
} else {
// throw an IllegalStateException, since we are reading before writing
// see also InstanceKlass::initialize_impl, Step 8 (at end)
Symbol* bad_strict_static = field(field_index).name(constants());
throw_strict_static_exception(bad_strict_static, "is unset before first read in", CHECK);
}
} else {
// Experimentally, enforce additional proposed conditions after the first write.
FieldInfo fi = field(field_index);
bool is_final = fi.access_flags().is_final();
if (is_final) {
// no final write after read, so observing a constant freezes it, as if <clinit> ended early
// (maybe we could trust the constant a little earlier, before <clinit> ends)
if (is_writing && !fs.is_strict_static_unread()) {
Symbol* bad_strict_static = fi.name(constants());
throw_strict_static_exception(bad_strict_static, "is set after read (as final) in", CHECK);
} else if (!is_writing && fs.is_strict_static_unread()) {
// log the read (this requires an extra status bit, with extra tests on it)
fs.update_strict_static_unread(false);
}
}
}
}

void InstanceKlass::throw_strict_static_exception(Symbol* field_name, const char* when, TRAPS) {
ResourceMark rm(THREAD);
const char* msg = format_strict_static_message(field_name, when);
// FIXME: Maybe replace IllegalStateException with something more precise.
// Perhaps a new-fangled UninitializedFieldException?
THROW_MSG(vmSymbols::java_lang_IllegalStateException(), msg);
}

const char* InstanceKlass::format_strict_static_message(Symbol* field_name, const char* when) {
stringStream ss;
// we can use similar format strings for both -Xlog:class+init and for the ISE throw
ss.print("Strict static \"%s\" %s %s",
field_name->as_C_string(),
when == nullptr ? "is unset in" : when,
external_name());
return ss.as_string();
}

// Update hierarchy. This is done before the new klass has been added to the SystemDictionary. The Compile_lock
// is grabbed, to ensure that the compiler is not using the class hierarchy.
void InstanceKlass::add_to_hierarchy(JavaThread* current) {
Expand Down
7 changes: 7 additions & 0 deletions src/hotspot/share/oops/instanceKlass.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,13 @@ class InstanceKlass: public Klass {
inline u2 next_method_idnum();
void set_initial_method_idnum(u2 value) { _idnum_allocated_count = value; }

// runtime support for strict statics
bool has_strict_static_fields() const { return _misc_flags.has_strict_static_fields(); }
void set_has_strict_static_fields(bool b) { _misc_flags.set_has_strict_static_fields(b); }
void notify_strict_static_access(int field_index, bool is_writing, TRAPS);
const char* format_strict_static_message(Symbol* field_name, const char* doing_what = nullptr);
void throw_strict_static_exception(Symbol* field_name, const char* when, TRAPS);

// generics support
Symbol* generic_signature() const;
u2 generic_signature_index() const;
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/oops/instanceKlassFlags.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class InstanceKlassFlags {
flag(is_naturally_atomic , 1 << 16) /* loaded/stored in one instruction*/ \
flag(must_be_atomic , 1 << 17) /* doesn't allow tearing */ \
flag(has_loosely_consistent_annotation , 1 << 18) /* the class has the LooselyConsistentValue annotation WARNING: it doesn't automatically mean that the class allows tearing */ \
flag(is_implicitly_constructible , 1 << 19) /* the class has the ImplicitlyConstrutible annotation */ \
flag(has_strict_static_fields , 1 << 20) /* True if strict static fields declared */ \
/* end of list */

/* (*) An inline type is considered empty if it contains no non-static fields or
Expand Down
4 changes: 3 additions & 1 deletion src/hotspot/share/prims/jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2055,8 +2055,10 @@ JNI_ENTRY(jfieldID, jni_GetStaticFieldID(JNIEnv *env, jclass clazz,
k->initialize(CHECK_NULL);

fieldDescriptor fd;
// strict fields need special tracking during <clinit>; do not hand them out so early
if (!k->is_instance_klass() ||
!InstanceKlass::cast(k)->find_field(fieldname, signame, true, &fd)) {
!InstanceKlass::cast(k)->find_field(fieldname, signame, true, &fd) ||
(fd.access_flags().is_strict() && !InstanceKlass::cast(k)->is_initialized())) {
THROW_MSG_NULL(vmSymbols::java_lang_NoSuchFieldError(), (char*) name);
}

Expand Down
22 changes: 22 additions & 0 deletions src/hotspot/share/prims/unsafe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,27 @@ UNSAFE_ENTRY(jboolean, Unsafe_ShouldBeInitialized0(JNIEnv *env, jobject unsafe,
}
UNSAFE_END

UNSAFE_ENTRY(void, Unsafe_NotifyStrictStaticAccess0(JNIEnv *env, jobject unsafe, jobject clazz,
jlong sfoffset, jboolean writing)) {
assert(clazz != nullptr, "clazz must not be null");

oop mirror = JNIHandles::resolve_non_null(clazz);
Klass* klass = java_lang_Class::as_Klass(mirror);

if (klass != nullptr && klass->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(klass);
fieldDescriptor fd;
if (ik->find_local_field_from_offset((int)sfoffset, true, &fd)) {
// Note: The Unsafe API takes an OFFSET, but the InstanceKlass wants the INDEX.
Copy link
Member

@liach liach Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the index available for MethodHandles::init_field_MemberName? Should we pass it there?

A counterargument may be that this only happens in the class initializer so the overhead shouldn't be that hefty.

// We could surface field indexes into Unsafe, but that's too much churn.
ik->notify_strict_static_access(fd.index(), writing, CHECK);
return;
}
}
THROW(vmSymbols::java_lang_InternalError());
}
UNSAFE_END

static void getBaseAndScale(int& base, int& scale, jclass clazz, TRAPS) {
assert(clazz != nullptr, "clazz must not be null");

Expand Down Expand Up @@ -1191,6 +1212,7 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = {
{CC "setMemory0", CC "(" OBJ "JJB)V", FN_PTR(Unsafe_SetMemory0)},

{CC "shouldBeInitialized0", CC "(" CLS ")Z", FN_PTR(Unsafe_ShouldBeInitialized0)},
{CC "notifyStrictStaticAccess0", CC "(" CLS "JZ)V", FN_PTR(Unsafe_NotifyStrictStaticAccess0)},

{CC "fullFence", CC "()V", FN_PTR(Unsafe_FullFence)},
};
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/runtime/fieldDescriptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class fieldDescriptor {
jdouble double_initial_value() const;
oop string_initial_value(TRAPS) const;

// Unset strict static
inline bool is_strict_static_unset() const;

// Field signature type
inline BasicType field_type() const;

Expand All @@ -87,6 +90,7 @@ class fieldDescriptor {
bool is_stable() const { return field_flags().is_stable(); }
bool is_volatile() const { return access_flags().is_volatile(); }
bool is_transient() const { return access_flags().is_transient(); }
bool is_strict() const { return access_flags().is_strict(); }
inline bool is_flat() const;
inline bool is_null_free_inline_type() const;
inline bool has_null_marker() const;
Expand Down
5 changes: 5 additions & 0 deletions src/hotspot/share/runtime/fieldDescriptor.inline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ inline int fieldDescriptor::offset() const { return field(
inline bool fieldDescriptor::has_initial_value() const { return field().field_flags().is_initialized(); }
inline int fieldDescriptor::initial_value_index() const { return field().initializer_index(); }

inline bool fieldDescriptor::is_strict_static_unset() const {
return (is_strict() && is_static() &&
field_holder()->field_status(index()).is_strict_static_unset());
}

inline void fieldDescriptor::set_is_field_access_watched(const bool value) {
field_holder()->fields_status()->adr_at(index())->update_access_watched(value);
}
Expand Down
Loading