diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 0e5280386e3..ac8b43d4dba 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -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 + // 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 with "-XX:TieredStopAtLevel=2 -Xcomp -Xbatch". + // Test C2 on 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) { diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 858331e1711..5a1f65cb773 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -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"); @@ -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(); @@ -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), @@ -6237,6 +6242,27 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st _fields_status = MetadataFactory::new_array(_loader_data, _temp_field_info->length(), FieldStatus(0), CHECK); + + // Strict static fields track initialization status from the beginning of time. + // After this class runs , 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) { diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp index 0a85ad1614d..d0a61186a83 100644 --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -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; diff --git a/src/hotspot/share/classfile/stackMapFrame.cpp b/src/hotspot/share/classfile/stackMapFrame.cpp index 220e5a4f06f..073422417f1 100644 --- a/src/hotspot/share/classfile/stackMapFrame.cpp +++ b/src/hotspot/share/classfile/stackMapFrame.cpp @@ -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; diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp index c4d043c5df2..ee12f075e71 100644 --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -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."); diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index 9f69f634175..7ffbfaa6b9e 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -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; @@ -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 , 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), + " 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); diff --git a/src/hotspot/share/oops/fieldInfo.hpp b/src/hotspot/share/oops/fieldInfo.hpp index 07fed489ee3..990acc045e9 100644 --- a/src/hotspot/share/oops/fieldInfo.hpp +++ b/src/hotspot/share/oops/fieldInfo.hpp @@ -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 }; @@ -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); }; diff --git a/src/hotspot/share/oops/fieldInfo.inline.hpp b/src/hotspot/share/oops/fieldInfo.inline.hpp index f29482739c8..679fc7560b4 100644 --- a/src/hotspot/share/oops/fieldInfo.inline.hpp +++ b/src/hotspot/share/oops/fieldInfo.inline.hpp @@ -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 diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index e24a3dcb14c..cb308b1e05d 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -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 , 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 @@ -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 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 . 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 ended early + // (maybe we could trust the constant a little earlier, before 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) { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index fd2bd6491f8..586e92600e9 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -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; diff --git a/src/hotspot/share/oops/instanceKlassFlags.hpp b/src/hotspot/share/oops/instanceKlassFlags.hpp index bb50859cc27..5fc2e6c35bd 100644 --- a/src/hotspot/share/oops/instanceKlassFlags.hpp +++ b/src/hotspot/share/oops/instanceKlassFlags.hpp @@ -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 diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 05278e7dcde..34d1a410a35 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -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 ; 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); } diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index a078b7517f5..b06119ee5d2 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -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. +// 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"); @@ -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)}, }; diff --git a/src/hotspot/share/runtime/fieldDescriptor.hpp b/src/hotspot/share/runtime/fieldDescriptor.hpp index 4463b45af97..50a6173c49a 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.hpp +++ b/src/hotspot/share/runtime/fieldDescriptor.hpp @@ -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; @@ -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; diff --git a/src/hotspot/share/runtime/fieldDescriptor.inline.hpp b/src/hotspot/share/runtime/fieldDescriptor.inline.hpp index b92090e79df..bb146cd6395 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.inline.hpp +++ b/src/hotspot/share/runtime/fieldDescriptor.inline.hpp @@ -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); } diff --git a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java index 7fd4bfc2166..f1dd4bf40d9 100644 --- a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java @@ -374,7 +374,7 @@ static boolean shouldBeInitialized(MemberName member) { } private void ensureInitialized() { - if (checkInitialized(member)) { + if (checkInitialized()) { // The coast is clear. Delete the barrier. updateForm(new Function<>() { public LambdaForm apply(LambdaForm oldForm) { @@ -384,14 +384,19 @@ public LambdaForm apply(LambdaForm oldForm) { }); } } - private static boolean checkInitialized(MemberName member) { + private boolean checkInitialized() { Class defc = member.getDeclaringClass(); UNSAFE.ensureClassInitialized(defc); // Once we get here either defc was fully initialized by another thread, or // defc was already being initialized by the current thread. In the latter case // the barrier must remain. We can detect this simply by checking if initialization // is still needed. - return !UNSAFE.shouldBeInitialized(defc); + boolean initializingStill = UNSAFE.shouldBeInitialized(defc); + if (initializingStill && member.isStrict()) { + // while is running, we track access to strict static fields + UNSAFE.notifyStrictStaticAccess(defc, staticOffset(this), member.isSetter()); + } + return !initializingStill; } /*non-public*/ diff --git a/src/java.base/share/classes/java/lang/invoke/MemberName.java b/src/java.base/share/classes/java/lang/invoke/MemberName.java index e02d79e85c2..f997442542b 100644 --- a/src/java.base/share/classes/java/lang/invoke/MemberName.java +++ b/src/java.base/share/classes/java/lang/invoke/MemberName.java @@ -389,6 +389,10 @@ public boolean isProtected() { public boolean isFinal() { return Modifier.isFinal(flags); } + /** Utility method to query the modifier flags of this member. */ + public boolean isStrict() { + return Modifier.isStrict(flags); + } /** Utility method to query whether this member or its defining class is final. */ public boolean canBeStaticallyBound() { return Modifier.isFinal(flags | clazz.getModifiers()); diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index b86d1c4855f..c828af3974d 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -1374,6 +1374,21 @@ public void ensureClassInitialized(Class c) { ensureClassInitialized0(c); } + /** + * The reading or writing of strict static fields may require + * special processing. Notify the VM that such an event is about + * to happen. The VM may respond by throwing an exception, in the + * case of a read of an uninitialized field. If the VM allows the + * method to return normally, no further calls are needed, with + * the same arguments. + */ + public void notifyStrictStaticAccess(Class c, long staticFieldOffset, boolean writing) { + if (c == null) { + throw new NullPointerException(); + } + notifyStrictStaticAccess0(c, staticFieldOffset, writing); + } + /** * Reports the offset of the first element in the storage allocation of a * given array class. If {@link #arrayIndexScale} returns a non-zero value @@ -4380,6 +4395,7 @@ private void putShortParts(Object o, long offset, byte i0, byte i1) { private native Object staticFieldBase0(Field f); private native boolean shouldBeInitialized0(Class c); private native void ensureClassInitialized0(Class c); + private native void notifyStrictStaticAccess0(Class c, long staticFieldOffset, boolean writing); private native int arrayBaseOffset0(Class arrayClass); // public version returns long to promote correct arithmetic private native int arrayIndexScale0(Class arrayClass); private native long getObjectSize0(Object o); diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/HelloStrict.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/HelloStrict.java new file mode 100644 index 00000000000..f80c2ff8cee --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/HelloStrict.java @@ -0,0 +1,319 @@ +/* + * 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 8888888 + * @summary demo of tracking of strict static fields + * @run main/othervm -XX:+UnlockDiagnosticVMOptions HelloStrict + */ + +import java.lang.reflect.*; +import java.lang.classfile.*; +import java.lang.constant.*; +import java.lang.invoke.*; + +public interface HelloStrict { + @interface Strict { } // placeholder, ignored + class Aregular_OK { + @Strict static final String F1__STRICT = "hello"; + @Strict static final int F2__STRICT = 42; + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Anulls_OK { + @Strict static String F1__STRICT = null; + @Strict static int F2__STRICT = 0; + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Arepeat_OK3 { + @Strict static String F1__STRICT = "hello"; + @Strict static int F2__STRICT = 42; + static { + System.out.print("(making second putstatic)"); + F2__STRICT = 43; + } + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Aupdate_OK23 { + @Strict static String F1__STRICT = "hello"; + @Strict static int F2__STRICT = 42; + static { + System.out.print("(making getstatic and second putstatic)"); + F2__STRICT++; + } + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Bnoinit_BAD { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + if (false) { + F1__STRICT = "hello"; + F2__STRICT = 42; + } + //FAIL + } + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Brbefore_BAD { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + int x = F2__STRICT; //FAIL + F1__STRICT = "hello"; + F2__STRICT = 42; + } + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Cwreflective_OK { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + Field FIELD_F1 = findField(Cwreflective_OK.class, "F1__STRICT"); + Field FIELD_F2 = findField(Cwreflective_OK.class, "F2__STRICT"); + if (STRICTS_ARE_FINALS) { + // reflective setting of larval static finals not implemented + F1__STRICT = "hello"; + F2__STRICT = 42; + } else { + putstaticReflective(FIELD_F1, "hello"); + putstaticReflective(FIELD_F2, 42); + } + } + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + class Creflbefore_BAD { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + Field FIELD_F1 = findField(Creflbefore_BAD.class, "F1__STRICT"); + Field FIELD_F2 = findField(Creflbefore_BAD.class, "F2__STRICT"); + int x = (int) getstaticReflective(FIELD_F2); //FAIL + System.out.print("(early read of F2="+x+")"); + if (STRICTS_ARE_FINALS) { + // reflective setting of larval static finals not implemented + F1__STRICT = "hello"; + F2__STRICT = 42; + } else { + putstaticReflective(FIELD_F1, "hello"); + putstaticReflective(FIELD_F2, 42); + } + } + public String toString() { return displayString(this, F1__STRICT, F2__STRICT); } + } + static Class[] TEST_CLASSES = { + Aregular_OK.class, + Anulls_OK.class, + Arepeat_OK3.class, + Aupdate_OK23.class, + Bnoinit_BAD.class, + Brbefore_BAD.class, + Cwreflective_OK.class, + Creflbefore_BAD.class, + }; + static String displayString(Object x, Object f1, Object f2) { + return (displayName(x.getClass()) + "(" + f1 + "," + f2 + ")"); + } + static String displayName(Class cls) { + var n = cls.getSimpleName(); + int dl = n.indexOf('$'); + if (dl > 0) n = n.substring(dl+1); + int sl = n.indexOf('/'); + if (sl > 0) n = n.substring(0, sl+1); + return n; + } + static Field findField(Classcls, String name) { + return noroe(()-> { + var f = cls.getDeclaredField(name); + if (!Modifier.isStatic(f.getModifiers()) || + !Modifier.isStrict(f.getModifiers()) || + (STRICTS_ARE_FINALS && + !Modifier.isFinal(f.getModifiers()))) + throw new AssertionError("not strict static: "+f); + if (Modifier.isFinal(f.getModifiers())) { + f.setAccessible(true); + } + return f; + }); + } + static Object getstaticReflective(Field f) { + return noroe(()-> f.get(null) ); + } + static void putstaticReflective(Field f, Object x) { + noroe(()->{ f.set(null, x); return null; }); + } + interface NoROE { T get() throws ReflectiveOperationException; } + static T noroe(NoROE fn) { + try { + return fn.get(); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } + } + static void tryClass(Class cls) { + String cn = displayName(cls); + System.out.print(cn + ": "); + try { + @SuppressWarnings("deprecation") + Object obj = cls.newInstance(); + System.out.println(obj); + if (!cn.contains("_OK")) { + System.out.printf("FAILED: bad class %s did NOT throw\n", cn); + FAILS[0]++; + } + } catch (Throwable ex) { + if (ex instanceof ExceptionInInitializerError && ex.getCause() != null) + ex = ex.getCause(); + if (!(cn.contains("_BAD") || (MODE != 1 && cn.contains(MODE+"")))) { + System.out.printf("FAILED: ok class %s threw\n", cn); + FAILS[1]++; + } else if (!(ex instanceof IllegalStateException)) { + System.out.printf("FAILED: bad class %s threw wrong exception\n", cn); + FAILS[2]++; FAILS[1]++; + } else if (!VERBOSE) { + var msg = ex.getMessage(); + int clip = msg.indexOf("HelloStrict$"); + if (clip > 0) msg = msg.substring(0, clip) + "..."; + var modemsg = (cn.contains("_BAD") ? "" : "(MODE="+MODE+") "); + System.out.println("ok: "+modemsg+"threw ISE: "+msg); + return; + } + ex.printStackTrace(); + } + } + int[] FAILS = new int[3]; + boolean HIDDEN_ONLY = true; + boolean VERBOSE = Boolean.getBoolean("VERBOSE"); + int MODE = Integer.getInteger("MODE", 1); + boolean STRICTS_ARE_FINALS = Boolean.getBoolean("STRICTS_ARE_FINALS"); + static void tryClass(Class cls, boolean tryHidden) { + if (!(HIDDEN_ONLY && tryHidden)){ + tryClass(cls); + } + if (tryHidden) { + Class scls = null; + try { + scls = strictify(cls); + } catch (Exception ex) { + System.out.println("strictify " + cls); + ex.printStackTrace(); + } + if (scls != null) tryClass(scls); + } + } + static void main(String... av) { + System.out.println("VERBOSE="+VERBOSE+ + " HIDDEN_ONLY="+HIDDEN_ONLY+ + " STRICTS_ARE_FINALS="+STRICTS_ARE_FINALS); + for (var cls : TEST_CLASSES) { + tryClass(cls, true); + } + int fails = 0; + for (int f : FAILS) fails += f; + if (fails != 0) { + var msg = String.format("FAILED: %d failures [ok,unset,rbw]=%s", + fails, java.util.Arrays.toString(FAILS)); + throw new AssertionError(msg); + } + /* + Expected output (MODE=1): + --- + Aregular_OK/: Aregular_OK/(hello,42) + Anulls_OK/: Anulls_OK/(null,0) + Arepeat_OK3/: (making second putstatic)Arepeat_OK3/(hello,43) + Aupdate_OK23/: (making getstatic and second putstatic)Aupdate_OK23/(hello,43) + Bnoinit_BAD/: ok: threw ISE: Strict static "F1__STRICT" is unset after initialization of ... + Brbefore_BAD/: ok: threw ISE: Strict static "F2__STRICT" is unset before first read in ... + Cwreflective_OK/: Cwreflective_OK/(hello,42) + Creflbefore_BAD/: ok: threw ISE: Strict static "F2__STRICT" is unset before first read in ... + --- + Expected output (MODE=2, STRICTS_ARE_FINALS=true): + --- + Aregular_OK/: Aregular_OK/(hello,42) + Anulls_OK/: Anulls_OK/(null,0) + Arepeat_OK3/: (making second putstatic)Arepeat_OK3/(hello,43) + Aupdate_OK23/: (making getstatic and second putstatic)ok: (MODE=2) threw ISE: Strict static "F2__STRICT" is set after read (as final) in ... + Bnoinit_BAD/: ok: threw ISE: Strict static "F1__STRICT" is unset after initialization of ... + Brbefore_BAD/: ok: threw ISE: Strict static "F2__STRICT" is unset before first read in ... + Cwreflective_OK/: Cwreflective_OK/(hello,42) + Creflbefore_BAD/: ok: threw ISE: Strict static "F2__STRICT" is unset before first read in ... + --- + Expected output (MODE=3, STRICTS_ARE_FINALS=true): + --- + Aregular_OK/: Aregular_OK/(hello,42) + Anulls_OK/: Anulls_OK/(null,0) + Arepeat_OK3/: (making second putstatic)ok: (MODE=3) threw ISE: Strict static "F2__STRICT" is set twice (as final) in ... + Aupdate_OK23/: (making getstatic and second putstatic)ok: (MODE=3) threw ISE: Strict static "F2__STRICT" is set twice (as final) in ... + Bnoinit_BAD/: ok: threw ISE: Strict static "F1__STRICT" is unset after initialization of ... + Brbefore_BAD/: ok: threw ISE: Strict static "F2__STRICT" is unset before first read in ... + Cwreflective_OK/: Cwreflective_OK/(hello,42) + Creflbefore_BAD/: ok: threw ISE: Strict static "F2__STRICT" is unset before first read in ... + --- + */ + } + + // Classfile transform to inject ACC_STRICT on fields named foo__STRICT. + // This temporary trick avoids dependencies on toolchains. + static Class strictify(Class c) throws Exception { + var fullName = c.getName(); + var lastDot = fullName.lastIndexOf('.'); + var uri = c.getResource(fullName.substring(Math.min(lastDot, -1) + 1) + ".class").toURI(); + ClassModel model = null; + if (uri.getScheme().equals("jar")) { + var parts = uri.toString().split("!"); + if (parts.length == 2) { + try (var fs = java.nio.file.FileSystems. + newFileSystem(java.net.URI.create(parts[0]), new java.util.HashMap<>())) { + model = ClassFile.of().parse(fs.getPath(parts[1])); + } + } + } + if (model == null) model = ClassFile.of().parse(java.nio.file.Paths.get(uri)); + FieldTransform addStrict = (b, e) -> { + if (e instanceof AccessFlags af) + b.withFlags(af.flagsMask() | Modifier.STRICT + | (STRICTS_ARE_FINALS ? Modifier.FINAL : 0)); + else b.with(e); + }; + ClassTransform addStrictToFields = (b, e) -> { + if (e instanceof FieldModel fm && + fm.fieldName().stringValue().endsWith("__STRICT")) + b.transformField(fm, addStrict); + else if (e instanceof Attribute a && + a.attributeName().equalsString("InnerClasses")) + { } + else b.with(e); + }; + var classBytes = ClassFile.of().transformClass(model, addStrictToFields); + var vererrs = ClassFile.of().verify(classBytes); + if (vererrs != null && !vererrs.isEmpty()) { + System.out.println(vererrs); + var cm = ClassFile.of().parse(classBytes); + System.out.println("" + cm + cm.fields() + cm.methods() + cm.methods().get(0).code().orElseThrow().elementList()); + } + try { + return MethodHandles.lookup().defineHiddenClass(classBytes, false).lookupClass(); + } catch (IllegalAccessException ex) { + throw new AssertionError(ex); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java new file mode 100644 index 00000000000..3e90245db96 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java @@ -0,0 +1,292 @@ +/* + * 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 id=default + * @bug 8888888 + * @summary test tracking of strict static fields + * @enablePreview + * @run main/othervm StrictStaticTests + * + * @test id=C1only + * @run main/othervm -XX:TieredStopAtLevel=2 -Xcomp -Xbatch StrictStaticTests + * + * @test id=C2only + * @run main/othervm -XX:-TieredCompilation -Xcomp -Xbatch StrictStaticTests + */ + +import java.io.File; +import java.lang.classfile.*; +import java.lang.constant.*; +import java.lang.reflect.*; +import java.lang.invoke.*; +import java.nio.file.Files; + +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +import static java.lang.constant.ConstantDescs.*; + +public class StrictStaticTests { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final String THIS_PACKAGE = LOOKUP.lookupClass().getPackageName(); + + static Class buildClass(String className, + Class staticType, + int writeCount, // how many putstatics? (0=>error) + byte readFlag, // read before (-1) or after (1)? + int extraCount, // how many extra strict statics? + boolean finals // make them all finals? + ) throws Exception { + ClassDesc cn = ClassDesc.of(className); + ClassDesc CD_Integer = Integer.class.describeConstable().orElseThrow(); + String VO_NAME = "valueOf"; + MethodTypeDesc VO_TYPE = MethodTypeDesc.of(CD_Integer, CD_int); + String SS_NAME = "SS"; + ClassDesc SS_TYPE = staticType.describeConstable().orElseThrow(); + String XS_NAME = "EXTRAS"; + ClassDesc XS_TYPE = CD_boolean; + String PS_NAME = "PLAIN"; + ClassDesc PS_TYPE = CD_byte; + + boolean prim = staticType.isPrimitive(); + boolean pop2 = staticType == double.class; + final ConstantDesc SS_INIT, SS_INIT_2; + if (!prim) { + SS_INIT = "foo"; + SS_INIT_2 = null; + } else if (!pop2) { + SS_INIT = 1; + SS_INIT_2 = 0; + } else { + SS_INIT = 3.14; + SS_INIT_2 = 0.0; + } + int mods = (ClassFile.ACC_STATIC | ClassFile.ACC_STRICT | + (finals ? ClassFile.ACC_FINAL : 0)); + byte[] classBytes = ClassFile.of().build(cn, clb -> { + clb.withFlags(ClassFile.ACC_FINAL); + clb.withVersion(ClassFile.latestMajorVersion(), ClassFile.PREVIEW_MINOR_VERSION); + clb.withField(SS_NAME, SS_TYPE, mods); + for (int i = 0; i < extraCount; i++) { + clb.withField(XS_NAME+i, XS_TYPE, mods); + clb.withField(PS_NAME+i, PS_TYPE, mods & ~ClassFile.ACC_STRICT); + } + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ClassFile.ACC_STATIC, cob -> { + // always store into the extra strict static(s) + for (int i = 0; i < extraCount/2; i++) { + cob.loadConstant(i&1); + cob.putstatic(cn, XS_NAME+i, XS_TYPE); + } + if (readFlag < 0) { + // perform an early read, which must fail + cob.getstatic(cn, SS_NAME, SS_TYPE); + if (pop2) cob.pop2(); else cob.pop(); + } + // perform any writes on the test field + ConstantDesc initializer = SS_INIT; + for (int i = 0; i < writeCount; i++) { + if (i == 1 && readFlag > 0) { + // do an extra read after the first write + if (readFlag > 0) { + cob.getstatic(cn, SS_NAME, SS_TYPE); + if (pop2) cob.pop2(); else cob.pop(); + } + } + if (i > 0) initializer = SS_INIT_2; + cob.loadConstant(initializer); + cob.putstatic(cn, SS_NAME, SS_TYPE); + // if we write zero times we must fail + } + if (readFlag > 0) { + // do more extra reads + cob.getstatic(cn, SS_NAME, SS_TYPE); + if (prim) { + if (pop2) cob.pop2(); else cob.pop(); + } else { + cob.loadConstant(initializer); + var L_skip = cob.newLabel(); + cob.if_acmpeq(L_skip); + cob.loadConstant(null); + cob.athrow(); // NPE! + cob.labelBinding(L_skip); + } + } + // finish storing into the extra strict static(s) + for (int i = extraCount/2; i < extraCount; i++) { + cob.loadConstant(i&1); + cob.putstatic(cn, XS_NAME+i, XS_TYPE); + } + cob.return_(); + }); + }); + File c = new File(className + ".class"); + c.createNewFile(); + Files.write(c.toPath(), classBytes); + + var vererrs = ClassFile.of().verify(classBytes); + if (vererrs != null && !vererrs.isEmpty()) { + System.out.println(vererrs); + var cm = ClassFile.of().parse(classBytes); + System.out.println("" + cm + cm.fields() + cm.methods() + cm.methods().get(0).code().orElseThrow().elementList()); + } + ++COUNT; + try { + return LOOKUP.defineHiddenClass(classBytes, false).lookupClass(); + } catch (IllegalAccessException ex) { + throw new AssertionError(ex); + } + } + + static int COUNT = 0; + + private static final Class STATIC_TYPES[] = { + Object.class, + String.class, + int.class, + boolean.class, + double.class + }; + + static boolean isSSFailure(Throwable ex) { + return ex instanceof ExceptionInInitializerError && + ex.getCause() instanceof IllegalStateException; + } + static void testPositives() throws Exception { + testPositives(false); + testPositives(true); + } + static void testPositives(boolean finals) throws Exception { + for (var staticType : STATIC_TYPES) { + for (int writeCount = 1; writeCount <= 3; writeCount++) { + for (byte readFlag = 0; readFlag <= 1; readFlag++) { + if (writeCount > readFlag) + continue; + for (int extraCount = 0; extraCount <= 3; extraCount++) { + if (extraCount > 0 && staticType != String.class) continue; + var cn = String.format("Positive_T%s%s_W%d%s%s", + staticType.getSimpleName(), + (finals ? "_SSFinal" : ""), + writeCount, + (readFlag > 0 ? "_Rafter" : + readFlag < 0 ? "_Rbefore" : ""), + (extraCount > 0 ? "_E"+extraCount : "")); + var cls = buildClass(cn, staticType, writeCount, readFlag, extraCount, finals); + try { + LOOKUP.ensureInitialized(cls); + if (VERBOSE) + System.out.printf("ok: %s: no throw\n", cn); + } catch (Throwable ex) { + reportThrow(false, ex, cn); + } + } + } + } + } + } + static void testFailedWrites() throws Exception { + testFailedWrites(false); + testFailedWrites(true); + } + static void testFailedWrites(boolean finals) throws Exception { + for (var staticType : STATIC_TYPES) { + for (int writeCount = 0; writeCount <= 2; writeCount++) { + for (byte readFlag = 0; readFlag <= 1; readFlag++) { + if (readFlag > 0 || writeCount > 0) { + if (!finals || writeCount < 2) + continue; + if (readFlag <= 0) + continue; // Mode 2 fails only with R between 2W: W-R-W + } + for (int extraCount = 0; extraCount <= 3; extraCount++) { + if (extraCount > 0 && staticType != String.class) continue; + var cn = String.format("BadWrite_T%s%s_W%d%s%s", + staticType.getSimpleName(), + (finals ? "_SSFinal" : ""), + writeCount, + (readFlag > 0 ? "_Rafter" : + readFlag < 0 ? "_Rbefore" : ""), + (extraCount > 0 ? "_E"+extraCount : "")); + var cls = buildClass(cn, staticType, writeCount, readFlag, extraCount, finals); + try { + LOOKUP.ensureInitialized(cls); + throw new AssertionError(cn); + } catch (Throwable ex) { + reportThrow(isSSFailure(ex), ex, cn); + } + } + } + } + } + } + static void testFailedReads() throws Exception { + testFailedReads(false); + testFailedReads(true); + } + static void testFailedReads(boolean finals) throws Exception { + for (var staticType : STATIC_TYPES) { + for (int writeCount = 0; writeCount <= 1; writeCount++) { + for (byte readFlag = -1; readFlag <= -1; readFlag++) { + for (int extraCount = 0; extraCount <= 3; extraCount++) { + if (extraCount > 0 && staticType != String.class) continue; + var cn = String.format("BadRead_T%s%s_W%d_%s%s", + staticType.getSimpleName(), + (finals ? "_SSFinal" : ""), + writeCount, + (readFlag > 0 ? "_Rafter" : + readFlag < 0 ? "_Rbefore" : ""), + (extraCount > 0 ? "_E"+extraCount : "")); + var cls = buildClass(cn, staticType, writeCount, readFlag, extraCount, finals); + try { + LOOKUP.ensureInitialized(cls); + throw new AssertionError(cn); + } catch (Throwable ex) { + reportThrow(isSSFailure(ex), ex, cn); + } + } + } + } + } + } + + static boolean VERBOSE = true; + + private static void reportThrow(boolean ok, Throwable ex, String cn) { + if (!ok) throw new AssertionError(ex); + if (VERBOSE) { + if (ex instanceof ExceptionInInitializerError && ex.getCause() != null) + ex = ex.getCause(); + String exs = ex.toString(); + exs = exs.replaceFirst("^[^ ]*IllegalStateException: ", "ISE: "); + exs = exs.replaceFirst(" strictStatic[.][^ ]+/0x[^ ]+", "..."); + System.out.printf("%s: %s: %s\n", ok ? "ok" : "FAIL", cn, exs); + } + } + + + public static void main(String... av) throws Exception { + testPositives(); + testFailedWrites(); + testFailedReads(); + System.out.println("tested "+COUNT+" classes"); + System.out.println("Passed"); + } +}