diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 970db778efd..1c10bfcba5c 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -1112,6 +1112,7 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, C1StubId stub_id )) bool deoptimize_for_atomic = false; bool deoptimize_for_null_free = false; bool deoptimize_for_flat = false; + bool deoptimize_for_strict_static = false; int patch_field_offset = -1; Klass* init_klass = nullptr; // klass needed by load_klass_patching code Klass* load_klass = nullptr; // klass needed by load_klass_patching code @@ -1165,6 +1166,9 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, C1StubId stub_id )) // field because it was unknown at compile time. deoptimize_for_flat = result.is_flat(); + // 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. + deoptimize_for_strict_static = result.is_strict_static_unset(); } else if (load_klass_or_mirror_patch_id) { Klass* k = nullptr; switch (code) { @@ -1237,7 +1241,11 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, C1StubId stub_id )) ShouldNotReachHere(); } - if (deoptimize_for_volatile || deoptimize_for_atomic || deoptimize_for_null_free || deoptimize_for_flat) { + if (deoptimize_for_volatile || + deoptimize_for_atomic || + deoptimize_for_null_free || + deoptimize_for_flat || + deoptimize_for_strict_static) { // At compile time we assumed the field wasn't volatile/atomic but after // loading it turns out it was volatile/atomic so we have to throw the // compiled code out and let it be regenerated. @@ -1254,6 +1262,9 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, C1StubId stub_id )) if (deoptimize_for_flat) { tty->print_cr("Deoptimizing for patching flat field reference"); } + if (deoptimize_for_strict_static) { + tty->print_cr("Deoptimizing for patching strict static field reference"); + } } // It's possible the nmethod was invalidated in the last diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 62270fae17c..53d0b332e75 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -479,6 +479,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 9bc5f400792..4a7a707f5b1 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), @@ -6238,6 +6243,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/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 41f5ae7a4a3..f38fdc75abe 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..76a5195cf4d 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -2055,8 +2055,11 @@ 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) || + // strict fields need special tracking during ; do not hand them out so early + (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..d7d3761f71c 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..f563e1bff26 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*/ @@ -820,11 +825,12 @@ private static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVol final int F_OFFSET = nameCursor++; // Either static offset or field offset. final int OBJ_CHECK = (OBJ_BASE >= 0 ? nameCursor++ : -1); final int U_HOLDER = nameCursor++; // UNSAFE holder - final int INIT_BAR = (needsInit ? nameCursor++ : -1); final int LAYOUT = (isFlat ? nameCursor++ : -1); // field must be instance final int VALUE_TYPE = (isFlat ? nameCursor++ : -1); final int NULL_CHECK = (isNullRestricted && !isGetter ? nameCursor++ : -1); + // N.B. pre-cast must happen before init barrier, if both are present final int PRE_CAST = (needsCast && !isGetter ? nameCursor++ : -1); + final int INIT_BAR = (needsInit ? nameCursor++ : -1); // no exceptions after barrier final int LINKER_CALL = nameCursor++; final int POST_CAST = (needsCast && isGetter ? nameCursor++ : -1); final int RESULT = nameCursor-1; // either the call, or the cast 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/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java index 2e0609264bd..60c8e6e0cd7 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java @@ -62,7 +62,7 @@ public boolean getBoolean(Object obj) throws IllegalArgumentException { } else { return (boolean) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -131,7 +131,7 @@ public void setBoolean(Object obj, boolean z) } else { setter.invokeExact(obj, z); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java index b56fbbcbcb2..d4ced504bbf 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java @@ -66,7 +66,7 @@ public byte getByte(Object obj) throws IllegalArgumentException { } else { return (byte) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -137,7 +137,7 @@ public void setByte(Object obj, byte b) } else { setter.invokeExact(obj, b); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java index c1f357326f4..308702718ef 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java @@ -70,7 +70,7 @@ public char getChar(Object obj) throws IllegalArgumentException { } else { return (char) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -143,7 +143,7 @@ public void setChar(Object obj, char c) } else { setter.invokeExact(obj, c); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java index 01652951e42..71e33622d2b 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java @@ -90,7 +90,7 @@ public double getDouble(Object obj) throws IllegalArgumentException { } else { return (double) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -192,7 +192,7 @@ public void setDouble(Object obj, double d) } else { setter.invokeExact(obj, d); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java index 5ac00ec5ea8..314a25a6506 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java @@ -86,7 +86,7 @@ public float getFloat(Object obj) throws IllegalArgumentException { } else { return (float) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -183,7 +183,7 @@ public void setFloat(Object obj, float f) } else { setter.invokeExact(obj, f); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java index 62e3ab083db..d1c887d5923 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java @@ -78,7 +78,7 @@ public int getInt(Object obj) throws IllegalArgumentException { } else { return (int) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -165,7 +165,7 @@ public void setInt(Object obj, int i) } else { setter.invokeExact(obj, i); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java index a0e02204b31..0090e7dc58c 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java @@ -82,7 +82,7 @@ public long getLong(Object obj) throws IllegalArgumentException { } else { return (long) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -174,7 +174,7 @@ public void setLong(Object obj, long l) } else { setter.invokeExact(obj, l); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java index 722d73d22a6..df94dc9bce1 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java @@ -55,7 +55,7 @@ static FieldAccessorImpl fieldAccessor(Field field, MethodHandle getter, MethodH public Object get(Object obj) throws IllegalArgumentException { try { return isStatic() ? getter.invokeExact() : getter.invokeExact(obj); - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -108,7 +108,7 @@ public void set(Object obj, Object value) throws IllegalAccessException { } else { setter.invokeExact(obj, value); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // already ensure the receiver type. So this CCE is due to the value. diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java index 265c555421a..6dad0f3ebbb 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java @@ -74,7 +74,7 @@ public short getShort(Object obj) throws IllegalArgumentException { } else { return (short) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -153,7 +153,7 @@ public void setShort(Object obj, short s) } else { setter.invokeExact(obj, s); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Bnoinit_BAD.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Bnoinit_BAD.jasm new file mode 100644 index 00000000000..aa9f9aab17b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Bnoinit_BAD.jasm @@ -0,0 +1,45 @@ +/* + * 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. + */ + +identity class Bnoinit_BAD version 69:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static Field F2__STRICT:I; + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 0 locals 0 + { + return; + } + +} // end Class Bnoinit_BAD diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Brbefore_BAD.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Brbefore_BAD.jasm new file mode 100644 index 00000000000..b82cd742328 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Brbefore_BAD.jasm @@ -0,0 +1,50 @@ +/* + * 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. + */ + +identity class Brbefore_BAD version 69:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static Field F2__STRICT:I; + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 1 locals 1 + { + getstatic Field F2__STRICT:"I"; + istore_0; + ldc String "hello"; + putstatic Field F1__STRICT:"Ljava/lang/String;"; + bipush 42; + putstatic Field F2__STRICT:"I"; + return; + } +} // end Class Brbefore_BAD diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java new file mode 100644 index 00000000000..be1e0891d22 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java @@ -0,0 +1,119 @@ +/* + * 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 8349945 + * @summary Tracking of strict static fields + * @enablePreview + * @compile Bnoinit_BAD.jasm Brbefore_BAD.jasm + * @compile --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED StrictStaticFieldsTest.java + * @run main/othervm -XX:+UnlockDiagnosticVMOptions StrictStaticFieldsTest + */ + +import java.lang.reflect.*; +import jdk.internal.vm.annotation.Strict; + +public class StrictStaticFieldsTest { + public static void main(String[] args) throws Exception { + // -------------- + // POSITIVE TESTS + // -------------- + + // Base Case + printStatics(Aregular_OK.class); + + // Strict statics initialized to null and zero + printStatics(Anulls_OK.class); + + // Assign strict static twice + printStatics(Arepeat_OK.class); + + // Read and write initialized strict static + printStatics(Aupdate_OK.class); + + // -------------- + // NEGATIVE TESTS + // -------------- + + // Strict statics not initialized + try { + printStatics(Bnoinit_BAD.class); + throw new RuntimeException("Should throw"); + } catch(ExceptionInInitializerError ex) { + Throwable e = (ex.getCause() != null) ? ex.getCause() : ex; + if (!e.getMessage().contains("unset after initialization")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + try { + printStatics(Brbefore_BAD.class); + throw new RuntimeException("Should throw"); + } catch(ExceptionInInitializerError ex) { + Throwable e = (ex.getCause() != null) ? ex.getCause() : ex; + if (!e.getMessage().contains("is unset before first read")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + System.out.println("Passed"); + } + + static void printStatics(Class cls) throws Exception { + Field f1 = cls.getDeclaredField("F1__STRICT"); + Field f2 = cls.getDeclaredField("F1__STRICT"); + System.out.println(f1.get(null)); + System.out.println(f2.get(null)); + } +} + +class Aregular_OK { + @Strict static final String F1__STRICT = "hello"; + @Strict static final int F2__STRICT = 42; + } + +class Anulls_OK { + @Strict static String F1__STRICT = null; + @Strict static int F2__STRICT = 0; +} + +class Arepeat_OK { + @Strict static String F1__STRICT = "hello"; + @Strict static int F2__STRICT = 42; + static { + System.out.print("(making second putstatic)"); + F2__STRICT = 43; + } +} + +class Aupdate_OK { + @Strict static String F1__STRICT = "hello"; + @Strict static int F2__STRICT = 42; + static { + System.out.println("(making getstatic and second putstatic)"); + F2__STRICT++; + } +} 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..19e24a3d3f8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java @@ -0,0 +1,293 @@ +/* + * 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 8349945 + * @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 RuntimeException(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 RuntimeException(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 RuntimeException(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 RuntimeException(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"); + } +}