diff options
| -rw-r--r-- | openjdkjvmti/OpenjdkJvmTi.cc | 1 | ||||
| -rw-r--r-- | openjdkjvmti/events-inl.h | 21 | ||||
| -rw-r--r-- | openjdkjvmti/events.cc | 6 | ||||
| -rw-r--r-- | openjdkjvmti/events.h | 20 | ||||
| -rw-r--r-- | openjdkjvmti/ti_extension.cc | 34 | ||||
| -rw-r--r-- | openjdkjvmti/ti_heap.cc | 66 | ||||
| -rw-r--r-- | openjdkjvmti/ti_heap.h | 6 | ||||
| -rw-r--r-- | test/1974-resize-array/expected.txt | 6 | ||||
| -rw-r--r-- | test/1974-resize-array/resize_array.cc | 119 | ||||
| -rw-r--r-- | test/1974-resize-array/src/art/Test1974.java | 61 |
10 files changed, 334 insertions, 6 deletions
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index e4ce825c2a..3a6fcef784 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -1532,6 +1532,7 @@ extern "C" bool ArtPlugin_Initialize() { ClassUtil::Register(gEventHandler); DumpUtil::Register(gEventHandler); MethodUtil::Register(gEventHandler); + HeapExtensions::Register(gEventHandler); SearchUtil::Register(); HeapUtil::Register(); Transformer::Setup(); diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 627129a20b..22822f8b40 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -123,7 +123,8 @@ class ScopedEventDispatchEnvironment final : public art::ValueObject { fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ - fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) + fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ + fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) template <ArtJvmtiEvent kEvent> struct EventFnType { @@ -318,6 +319,24 @@ inline void EventHandler::DispatchEventOnEnv( } } +template <> +inline void EventHandler::DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>( + ArtJvmTiEnv* env, art::Thread* thread, jlong* obsolete_tag, jlong* new_tag) const { + static constexpr ArtJvmtiEvent kEvent = ArtJvmtiEvent::kObsoleteObjectCreated; + DCHECK(env != nullptr); + if (ShouldDispatch<kEvent>(env, thread, obsolete_tag, new_tag)) { + art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); + impl::EventHandlerFunc<kEvent> func(env); + ExecuteCallback<kEvent>(func, obsolete_tag, new_tag); + } else { + // Unlike most others this has a default action to make sure that agents without knowledge of + // this extension get reasonable behavior. + jlong temp = *obsolete_tag; + *obsolete_tag = *new_tag; + *new_tag = temp; + } +} + template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc<kEvent> handler, Args... args) { handler.ExecuteCallback(args...); diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index faa47c53dc..77acd562d1 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -92,6 +92,9 @@ void ArtJvmtiEventCallbacks::CopyExtensionsFrom(const ArtJvmtiEventCallbacks* cb jvmtiError ArtJvmtiEventCallbacks::Set(jint index, jvmtiExtensionEvent cb) { switch (index) { + case static_cast<jint>(ArtJvmtiEvent::kObsoleteObjectCreated): + ObsoleteObjectCreated = reinterpret_cast<ArtJvmtiEventObsoleteObjectCreated>(cb); + return OK; case static_cast<jint>(ArtJvmtiEvent::kDdmPublishChunk): DdmPublishChunk = reinterpret_cast<ArtJvmtiEventDdmPublishChunk>(cb); return OK; @@ -110,6 +113,7 @@ bool IsExtensionEvent(jint e) { bool IsExtensionEvent(ArtJvmtiEvent e) { switch (e) { case ArtJvmtiEvent::kDdmPublishChunk: + case ArtJvmtiEvent::kObsoleteObjectCreated: return true; default: return false; @@ -243,6 +247,7 @@ static bool IsThreadControllable(ArtJvmtiEvent event) { case ArtJvmtiEvent::kCompiledMethodUnload: case ArtJvmtiEvent::kDynamicCodeGenerated: case ArtJvmtiEvent::kDataDumpRequest: + case ArtJvmtiEvent::kObsoleteObjectCreated: return false; default: @@ -1158,6 +1163,7 @@ static DeoptRequirement GetDeoptRequirement(ArtJvmtiEvent event, jthread thread) case ArtJvmtiEvent::kVmObjectAlloc: case ArtJvmtiEvent::kClassFileLoadHookRetransformable: case ArtJvmtiEvent::kDdmPublishChunk: + case ArtJvmtiEvent::kObsoleteObjectCreated: return DeoptRequirement::kNone; } } diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index ac86d0cd9b..d5ab4fbc98 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -80,7 +80,8 @@ enum class ArtJvmtiEvent : jint { // capability. kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1, kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2, - kMaxNormalEventTypeVal = kDdmPublishChunk, + kObsoleteObjectCreated = JVMTI_MAX_EVENT_TYPE_VAL + 3, + kMaxNormalEventTypeVal = kObsoleteObjectCreated, // All that follow are events used to implement internal JVMTI functions. They are not settable // directly by agents. @@ -102,6 +103,10 @@ using ArtJvmtiEventDdmPublishChunk = void (*)(jvmtiEnv *jvmti_env, jint data_len, const jbyte* data); +using ArtJvmtiEventObsoleteObjectCreated = void (*)(jvmtiEnv *jvmti_env, + jlong* obsolete_tag, + jlong* new_tag); + // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the // thread id. // Note: We could just use the tid like tracing does. @@ -114,7 +119,7 @@ struct UniqueThreadHasher { }; struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { - ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr) { + ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr), ObsoleteObjectCreated(nullptr) { memset(this, 0, sizeof(jvmtiEventCallbacks)); } @@ -125,6 +130,7 @@ struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { jvmtiError Set(jint index, jvmtiExtensionEvent cb); ArtJvmtiEventDdmPublishChunk DdmPublishChunk; + ArtJvmtiEventObsoleteObjectCreated ObsoleteObjectCreated; }; bool IsExtensionEvent(jint e); @@ -285,6 +291,16 @@ class EventHandler { REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(art::Locks::user_code_suspension_lock_, art::Locks::thread_list_lock_); + template<typename Visitor> + void ForEachEnv(art::Thread* self, Visitor v) REQUIRES(!envs_lock_) { + art::ReaderMutexLock mu(self, envs_lock_); + for (ArtJvmTiEnv* e : envs) { + if (e != nullptr) { + v(e); + } + } + } + private: void SetupTraceListener(JvmtiMethodTraceListener* listener, ArtJvmtiEvent event, bool enable); diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc index 9d31a934f3..9666562ceb 100644 --- a/openjdkjvmti/ti_extension.cc +++ b/openjdkjvmti/ti_extension.cc @@ -496,6 +496,40 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env, if (error != OK) { return error; } + error = add_extension( + ArtJvmtiEvent::kObsoleteObjectCreated, + "com.android.art.heap.obsolete_object_created", + "Called when an obsolete object is created.\n" + "An object becomes obsolete when, due to some jvmti function call all references to the" + " object are replaced with a reference to a different object. After this call finishes there" + " will be no strong references to the obsolete object anywere. If the object is retrieved" + " using GetObjectsWithTags its type (class) may have changed and any data it contains may" + " have been deleted. This is primarily designed to support memory tracking agents which make" + " use of the ObjectFree and VMObjectAlloc events for tracking. To support this use-case if" + " this event is not being handled it will by default act as though the following code was" + " registered as a handler:\n" + "\n" + " void HandleObsoleteObjectCreated(jvmtiEnv* env, jlong* obsolete_tag, jlong* new_tag) {\n" + " jlong temp = *obsolete_tag;\n" + " *obsolete_tag = *new_tag;\n" + " *new_tag = temp;\n" + " }\n" + "\n" + "Note that this event does not support filtering based on thread. This event has the same" + " restrictions on JNI and JVMTI function calls as the ObjectFree event.\n" + "\n" + "Arguments:\n" + " obsolete_tag: Pointer to the tag the old object (now obsolete) has. Setting the pointer" + " will update the tag value.\n" + " new_tag: Pointer to the tag the new object (replacing the obsolete one) has. Setting the" + " pointer will update the tag value.", + { + { "obsolete_tag", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JLONG, false }, + { "new_tag", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JLONG, false }, + }); + if (error != OK) { + return error; + } // Copy into output buffer. diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc index 3c2b82e9bc..0c06ae1169 100644 --- a/openjdkjvmti/ti_heap.cc +++ b/openjdkjvmti/ti_heap.cc @@ -30,6 +30,7 @@ #include "class_root.h" #include "deopt_manager.h" #include "dex/primitive.h" +#include "events-inl.h" #include "gc/collector_type.h" #include "gc/gc_cause.h" #include "gc/heap-visit-objects-inl.h" @@ -67,6 +68,8 @@ namespace openjdkjvmti { +EventHandler* HeapExtensions::gEventHandler = nullptr; + namespace { struct IndexCache { @@ -1736,10 +1739,49 @@ static void ReplaceStrongRoots(art::Thread* self, ArrayPtr old_arr_ptr, ArrayPtr } } -static void ReplaceWeakRoots(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr) +static void ReplaceWeakRoots(art::Thread* self, + EventHandler* event_handler, + ArrayPtr old_arr_ptr, + ArrayPtr new_arr_ptr) REQUIRES(art::Locks::mutator_lock_, art::Locks::user_code_suspension_lock_, art::Roles::uninterruptible_) { + // Handle tags. We want to do this seprately from other weak-refs (handled below) because we need + // to send additional events and handle cases where the agent might have tagged the new + // replacement object during the VMObjectAlloc. We do this by removing all tags associated with + // both the obsolete and the new arrays. Then we send the ObsoleteObjectCreated event and cache + // the new tag values. We next update all the other weak-references (the tags have been removed) + // and finally update the tag table with the new values. Doing things in this way (1) keeps all + // code relating to updating weak-references together and (2) ensures we don't end up in strange + // situations where the order of weak-ref visiting affects the final tagging state. Since we have + // the mutator_lock_ and gc-paused throughout this whole process no threads should be able to see + // the interval where the objects are not tagged. + std::unordered_map<ArtJvmTiEnv*, jlong> obsolete_tags; + std::unordered_map<ArtJvmTiEnv*, jlong> non_obsolete_tags; + event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) { + // Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it. + art::Locks::mutator_lock_->AssertExclusiveHeld(self); + env->object_tag_table->Lock(); + // Get the tags and clear them (so we don't need to special-case the normal weak-ref visitor) + jlong new_tag = 0; + jlong obsolete_tag = 0; + bool had_new_tag = env->object_tag_table->RemoveLocked(new_arr_ptr, &new_tag); + bool had_obsolete_tag = env->object_tag_table->RemoveLocked(old_arr_ptr, &obsolete_tag); + // Dispatch event. + if (had_obsolete_tag || had_new_tag) { + event_handler->DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>(env, + self, + &obsolete_tag, + &new_tag); + obsolete_tags[env] = obsolete_tag; + non_obsolete_tags[env] = new_tag; + } + // After weak-ref update we need to go back and re-add obsoletes. We wait to avoid having to + // deal with the visit-weaks overwriting the initial new_arr_ptr tag and generally making things + // difficult. + env->object_tag_table->Unlock(); + }); + // Handle weak-refs. struct ReplaceWeaksVisitor : public art::IsMarkedVisitor { public: ReplaceWeaksVisitor(ArrayPtr old_arr, ArrayPtr new_arr) @@ -1760,9 +1802,23 @@ static void ReplaceWeakRoots(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr) }; ReplaceWeaksVisitor rwv(old_arr_ptr, new_arr_ptr); art::Runtime::Current()->SweepSystemWeaks(&rwv); + // Re-add the object tags. At this point all weak-references to the old_arr_ptr are gone. + event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) { + // Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it. + art::Locks::mutator_lock_->AssertExclusiveHeld(self); + env->object_tag_table->Lock(); + if (obsolete_tags.find(env) != obsolete_tags.end()) { + env->object_tag_table->SetLocked(old_arr_ptr, obsolete_tags[env]); + } + if (non_obsolete_tags.find(env) != non_obsolete_tags.end()) { + env->object_tag_table->SetLocked(new_arr_ptr, non_obsolete_tags[env]); + } + env->object_tag_table->Unlock(); + }); } static void PerformArrayReferenceReplacement(art::Thread* self, + EventHandler* event_handler, ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr) REQUIRES(art::Locks::mutator_lock_, @@ -1770,7 +1826,7 @@ static void PerformArrayReferenceReplacement(art::Thread* self, art::Roles::uninterruptible_) { ReplaceObjectReferences(old_arr_ptr, new_arr_ptr); ReplaceStrongRoots(self, old_arr_ptr, new_arr_ptr); - ReplaceWeakRoots(old_arr_ptr, new_arr_ptr); + ReplaceWeakRoots(self, event_handler, old_arr_ptr, new_arr_ptr); } } // namespace @@ -1863,8 +1919,12 @@ jvmtiError HeapExtensions::ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new UNREACHABLE(); } // Actually replace all the pointers. - PerformArrayReferenceReplacement(self, old_arr.Get(), new_arr.Get()); + PerformArrayReferenceReplacement(self, gEventHandler, old_arr.Get(), new_arr.Get()); return OK; } +void HeapExtensions::Register(EventHandler* eh) { + gEventHandler = eh; +} + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h index 5a32436aa5..bf6fce6ba8 100644 --- a/openjdkjvmti/ti_heap.h +++ b/openjdkjvmti/ti_heap.h @@ -21,6 +21,7 @@ namespace openjdkjvmti { +class EventHandler; class ObjectTagTable; class HeapUtil { @@ -64,6 +65,8 @@ class HeapUtil { class HeapExtensions { public: + static void Register(EventHandler* eh); + static jvmtiError JNICALL GetObjectHeapId(jvmtiEnv* env, jlong tag, jint* heap_id, ...); static jvmtiError JNICALL GetHeapName(jvmtiEnv* env, jint heap_id, char** heap_name, ...); @@ -74,6 +77,9 @@ class HeapExtensions { const void* user_data); static jvmtiError JNICALL ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size); + + private: + static EventHandler* gEventHandler; }; } // namespace openjdkjvmti diff --git a/test/1974-resize-array/expected.txt b/test/1974-resize-array/expected.txt index 4b0d432f77..4eeb651f83 100644 --- a/test/1974-resize-array/expected.txt +++ b/test/1974-resize-array/expected.txt @@ -76,3 +76,9 @@ val is: [[3, 33, 333]] resize +5 val is: [[3, 33, 333, null, null, null, null, null]] Same value? true +Test jvmti-tags with obsolete +val is: [[4, 44, 444]] resize +5 +val is: [[4, 44, 444, null, null, null, null, null]] +Same value? true +Everything looks good WRT obsolete object + diff --git a/test/1974-resize-array/resize_array.cc b/test/1974-resize-array/resize_array.cc index dadcbeae22..60037b8045 100644 --- a/test/1974-resize-array/resize_array.cc +++ b/test/1974-resize-array/resize_array.cc @@ -16,6 +16,7 @@ #include <cstdio> #include <memory> +#include <mutex> #include <string> #include <vector> @@ -54,6 +55,34 @@ static void DeallocParams(jvmtiParamInfo* params, jint n_params) { } } +static jint FindExtensionEvent(JNIEnv* env, const std::string& name) { + jint n_ext; + jvmtiExtensionEventInfo* infos; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionEvents(&n_ext, &infos))) { + return -1; + } + jint res = -1; + bool found = false; + for (jint i = 0; i < n_ext; i++) { + jvmtiExtensionEventInfo* cur_info = &infos[i]; + if (strcmp(name.c_str(), cur_info->id) == 0) { + res = cur_info->extension_event_index; + found = true; + } + // Cleanup the cur_info + DeallocParams(cur_info->params, cur_info->param_count); + Dealloc(cur_info->id, cur_info->short_description, cur_info->params); + } + // Cleanup the array. + Dealloc(infos); + if (!found) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), (name + " extensions not found").c_str()); + return -1; + } + return res; +} + static jvmtiExtensionFunction FindExtensionMethod(JNIEnv* env, const std::string& name) { jint n_ext; jvmtiExtensionFunctionInfo* infos; @@ -145,5 +174,95 @@ extern "C" JNIEXPORT void JNICALL Java_art_Test1974_runNativeTest(JNIEnv* env, env->CallVoidMethod(print, accept, arr); env->CallVoidMethod(check, accept, arr); } + +struct JvmtiInfo { + std::mutex mu_; + std::vector<jlong> freed_tags_; +}; + +extern "C" JNIEXPORT void JNICALL Java_art_Test1974_StartCollectFrees(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED) { + jvmtiEventCallbacks cb{ + .ObjectFree = + [](jvmtiEnv* jvmti, jlong tag) { + JvmtiInfo* dat = nullptr; + CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&dat)), + JVMTI_ERROR_NONE); + std::lock_guard<std::mutex> mu(dat->mu_); + dat->freed_tags_.push_back(tag); + }, + }; + JvmtiInfo* info = new JvmtiInfo; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(info))) { + LOG(INFO) << "couldn't set env-local storage"; + return; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + LOG(INFO) << "couldn't set event callback"; + return; + } + JvmtiErrorToException( + env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_OBJECT_FREE, nullptr)); +} + +extern "C" JNIEXPORT void JNICALL +Java_art_Test1974_StartAssignObsoleteIncrementedId(JNIEnv* env, jclass k ATTRIBUTE_UNUSED) { + jint id = FindExtensionEvent(env, "com.android.art.heap.obsolete_object_created"); + if (env->ExceptionCheck()) { + LOG(INFO) << "Could not find extension event!"; + return; + } + using ObsoleteEvent = void (*)(jvmtiEnv * env, jlong * obsolete, jlong * non_obsolete); + ObsoleteEvent oe = [](jvmtiEnv* env ATTRIBUTE_UNUSED, jlong* obsolete, jlong* non_obsolete) { + *non_obsolete = *obsolete; + *obsolete = *obsolete + 1; + }; + JvmtiErrorToException( + env, + jvmti_env, + jvmti_env->SetExtensionEventCallback(id, reinterpret_cast<jvmtiExtensionEvent>(oe))); +} + +extern "C" JNIEXPORT void JNICALL +Java_art_Test1974_EndAssignObsoleteIncrementedId(JNIEnv* env, jclass k ATTRIBUTE_UNUSED) { + jint id = FindExtensionEvent(env, "com.android.art.heap.obsolete_object_created"); + if (env->ExceptionCheck()) { + LOG(INFO) << "Could not find extension event!"; + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetExtensionEventCallback(id, nullptr)); +} + +extern "C" JNIEXPORT jlongArray JNICALL +Java_art_Test1974_CollectFreedTags(JNIEnv* env, jclass k ATTRIBUTE_UNUSED) { + if (JvmtiErrorToException( + env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_OBJECT_FREE, nullptr))) { + return nullptr; + } + JvmtiInfo* info_p = nullptr; + if (JvmtiErrorToException( + env, + jvmti_env, + jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&info_p)))) { + return nullptr; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(nullptr))) { + return nullptr; + } + std::unique_ptr<JvmtiInfo> info(info_p); + ScopedLocalRef<jlongArray> arr(env, env->NewLongArray(info->freed_tags_.size())); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetLongArrayRegion(arr.get(), 0, info->freed_tags_.size(), info->freed_tags_.data()); + if (env->ExceptionCheck()) { + return nullptr; + } + return arr.release(); +} } // namespace Test1974ResizeArray } // namespace art diff --git a/test/1974-resize-array/src/art/Test1974.java b/test/1974-resize-array/src/art/Test1974.java index 8460e827eb..c4427c7b62 100644 --- a/test/1974-resize-array/src/art/Test1974.java +++ b/test/1974-resize-array/src/art/Test1974.java @@ -422,6 +422,56 @@ public class Test1974 { System.out.println("Same value? " + (after_tagged_obj[0] == arr)); } + public static void runWithJvmtiTagsObsolete() throws Exception { + Object[] arr = new Object[] {"4", "44", "444"}; + long globalID = 444_444_444l; + System.out.println("Test jvmti-tags with obsolete"); + Main.setTag(arr, globalID); + StartCollectFrees(); + StartAssignObsoleteIncrementedId(); + DbgPrintln("Pre hash: " + arr.hashCode()); + System.out.println( + "val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)) + " resize +5"); + ResizeArray(() -> arr, arr.length + 5); + Object[] after_tagged_obj = GetObjectsWithTag(globalID); + Object[] obsolete_tagged_obj = GetObjectsWithTag(globalID + 1); + System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID))); + EndAssignObsoleteIncrementedId(); + long[] obsoletes_freed = CollectFreedTags(); + DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode()); + System.out.println("Same value? " + (after_tagged_obj[0] == arr)); + if (obsolete_tagged_obj.length >= 1) { + DbgPrintln("Found objects with obsolete tag: " + Arrays.deepToString(obsolete_tagged_obj)); + boolean bad = false; + if (obsolete_tagged_obj.length != 1) { + System.out.println( + "Found obsolete tag'd objects: " + + Arrays.deepHashCode(obsolete_tagged_obj) + + " but only expected one!"); + bad = true; + } + if (!Arrays.deepEquals( + Arrays.copyOf(arr, ((Object[]) obsolete_tagged_obj[0]).length), + (Object[]) obsolete_tagged_obj[0])) { + System.out.println("Obsolete array was unexpectedly different than non-obsolete one!"); + bad = true; + } + if (!Arrays.stream(obsoletes_freed).anyMatch((l) -> l == globalID + 1)) { + DbgPrintln("Didn't see a free of the obsolete id"); + } + if (!bad) { + System.out.println("Everything looks good WRT obsolete object"); + } + } else { + if (!Arrays.stream(obsoletes_freed).anyMatch((l) -> l == globalID + 1)) { + System.out.println("Didn't see a free of the obsolete id"); + } else { + DbgPrintln("Saw a free of obsolete id!"); + System.out.println("Everything looks good WRT obsolete object!"); + } + } + } + public static void run() throws Exception { // Simple runAsThread(Test1974::runInstance); @@ -457,6 +507,9 @@ public class Test1974 { // Basic jvmti tags runAsThread(Test1974::runWithJvmtiTags); + + // Grab obsolete reference using tags/detect free + runAsThread(Test1974::runWithJvmtiTagsObsolete); } // Use a supplier so that we don't have to have a local ref to the resized @@ -470,4 +523,12 @@ public class Test1974 { public static native <T> T ReadJniRef(long t); public static native Object[] GetObjectsWithTag(long tag); + + public static native void StartCollectFrees(); + + public static native void StartAssignObsoleteIncrementedId(); + + public static native void EndAssignObsoleteIncrementedId(); + + public static native long[] CollectFreedTags(); } |