summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--openjdkjvmti/OpenjdkJvmTi.cc1
-rw-r--r--openjdkjvmti/events-inl.h21
-rw-r--r--openjdkjvmti/events.cc6
-rw-r--r--openjdkjvmti/events.h20
-rw-r--r--openjdkjvmti/ti_extension.cc34
-rw-r--r--openjdkjvmti/ti_heap.cc66
-rw-r--r--openjdkjvmti/ti_heap.h6
-rw-r--r--test/1974-resize-array/expected.txt6
-rw-r--r--test/1974-resize-array/resize_array.cc119
-rw-r--r--test/1974-resize-array/src/art/Test1974.java61
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();
}