ART: Add ClassLoadCallback
Add callback for class-load and class-prepare events. Move Dbg
over. Add tests.
Bug: 31684920
Test: m test-art-host-gtest-runtime_callbacks_test
Test: art/tools/run-jdwp-tests.sh --mode=host
Change-Id: I871f6b3c54448fd6ece8d9a7571b2042be50d525
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 5bdfbc7..f708e26 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -107,6 +107,7 @@
ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods
ART_GTEST_profile_assistant_test_DEX_DEPS := ProfileTestMultiDex
ART_GTEST_profile_compilation_info_test_DEX_DEPS := ProfileTestMultiDex
+ART_GTEST_runtime_callbacks_test_DEX_DEPS := XandY
ART_GTEST_stub_test_DEX_DEPS := AllFields
ART_GTEST_transaction_test_DEX_DEPS := Transaction
ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index f0a64f1..92d1554 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2678,6 +2678,11 @@
return nullptr;
}
CHECK(klass->IsLoaded());
+
+ // At this point the class is loaded. Publish a ClassLoad even.
+ // Note: this may be a temporary class. It is a listener's responsibility to handle this.
+ Runtime::Current()->GetRuntimeCallbacks().ClassLoad(klass);
+
// Link the class (if necessary)
CHECK(!klass->IsResolved());
// TODO: Use fast jobjects?
@@ -2718,7 +2723,7 @@
* The class has been prepared and resolved but possibly not yet verified
* at this point.
*/
- Dbg::PostClassPrepare(h_new_class.Get());
+ Runtime::Current()->GetRuntimeCallbacks().ClassPrepare(klass, h_new_class);
// Notify native debugger of the new class and its layout.
jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get());
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 9b98671..8da979b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -34,6 +34,7 @@
#include "dex_file.h"
#include "dex_file_types.h"
#include "gc_root.h"
+#include "handle.h"
#include "jni.h"
#include "mirror/class.h"
#include "object_callbacks.h"
@@ -1194,6 +1195,21 @@
DISALLOW_COPY_AND_ASSIGN(ClassLinker);
};
+class ClassLoadCallback {
+ public:
+ virtual ~ClassLoadCallback() {}
+
+ // A class has been loaded.
+ // Note: the class may be temporary, in which case a following ClassPrepare event will be a
+ // different object. It is the listener's responsibility to handle this.
+ virtual void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ // A class has been prepared, i.e., resolved. As the ClassLoad event might have been for a
+ // temporary class, provide both the former and the current class.
+ virtual void ClassPrepare(Handle<mirror::Class> temp_klass,
+ Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
} // namespace art
#endif // ART_RUNTIME_CLASS_LINKER_H_
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 6da7e3a..22a3163 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -321,6 +321,7 @@
uint32_t Dbg::instrumentation_events_ = 0;
Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_;
+Dbg::DbgClassLoadCallback Dbg::class_load_callback_;
// Breakpoints.
static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_);
@@ -5145,4 +5146,12 @@
Dbg::PostThreadDeath(self);
}
+void Dbg::DbgClassLoadCallback::ClassLoad(Handle<mirror::Class> klass ATTRIBUTE_UNUSED) {
+ // Ignore ClassLoad;
+}
+void Dbg::DbgClassLoadCallback::ClassPrepare(Handle<mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+ Handle<mirror::Class> klass) {
+ Dbg::PostClassPrepare(klass.Get());
+}
+
} // namespace art
diff --git a/runtime/debugger.h b/runtime/debugger.h
index 0135990..a7fd160 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -28,6 +28,8 @@
#include <vector>
#include "gc_root.h"
+#include "class_linker.h"
+#include "handle.h"
#include "jdwp/jdwp.h"
#include "jni.h"
#include "jvalue.h"
@@ -502,8 +504,6 @@
REQUIRES_SHARED(Locks::mutator_lock_);
static void PostException(mirror::Throwable* exception)
REQUIRES_SHARED(Locks::mutator_lock_);
- static void PostClassPrepare(mirror::Class* c)
- REQUIRES_SHARED(Locks::mutator_lock_);
static void UpdateDebugger(Thread* thread, mirror::Object* this_object,
ArtMethod* method, uint32_t new_dex_pc,
@@ -706,6 +706,9 @@
static ThreadLifecycleCallback* GetThreadLifecycleCallback() {
return &thread_lifecycle_callback_;
}
+ static ClassLoadCallback* GetClassLoadCallback() {
+ return &class_load_callback_;
+ }
private:
static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq)
@@ -733,6 +736,9 @@
static void PostThreadStartOrStop(Thread*, uint32_t)
REQUIRES_SHARED(Locks::mutator_lock_);
+ static void PostClassPrepare(mirror::Class* c)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
static void PostLocationEvent(ArtMethod* method, int pcOffset,
mirror::Object* thisPtr, int eventFlags,
const JValue* return_value)
@@ -800,7 +806,15 @@
void ThreadDeath(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
};
+ class DbgClassLoadCallback : public ClassLoadCallback {
+ public:
+ void ClassLoad(Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+ void ClassPrepare(Handle<mirror::Class> temp_klass,
+ Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+ };
+
static DbgThreadLifecycleCallback thread_lifecycle_callback_;
+ static DbgClassLoadCallback class_load_callback_;
DISALLOW_COPY_AND_ASSIGN(Dbg);
};
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 6ef5f26..a2b462e 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1101,6 +1101,7 @@
Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions));
}
callbacks_.AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback());
+ callbacks_.AddClassLoadCallback(Dbg::GetClassLoadCallback());
jit_options_.reset(jit::JitOptions::CreateFromRuntimeArguments(runtime_options));
if (IsAotCompiler()) {
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index a523ddf..ee9edda 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -18,6 +18,7 @@
#include <algorithm>
+#include "class_linker.h"
#include "thread.h"
namespace art {
@@ -45,4 +46,27 @@
}
}
+void RuntimeCallbacks::AddClassLoadCallback(ClassLoadCallback* cb) {
+ class_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveClassLoadCallback(ClassLoadCallback* cb) {
+ auto it = std::find(class_callbacks_.begin(), class_callbacks_.end(), cb);
+ if (it != class_callbacks_.end()) {
+ class_callbacks_.erase(it);
+ }
+}
+
+void RuntimeCallbacks::ClassLoad(Handle<mirror::Class> klass) {
+ for (ClassLoadCallback* cb : class_callbacks_) {
+ cb->ClassLoad(klass);
+ }
+}
+
+void RuntimeCallbacks::ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass) {
+ for (ClassLoadCallback* cb : class_callbacks_) {
+ cb->ClassPrepare(temp_klass, klass);
+ }
+}
+
} // namespace art
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index 1060d85..5bdb44a 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -21,9 +21,15 @@
#include "base/macros.h"
#include "base/mutex.h"
+#include "handle.h"
namespace art {
+namespace mirror {
+class Class;
+} // namespace mirror
+
+class ClassLoadCallback;
class Thread;
class ThreadLifecycleCallback;
@@ -44,19 +50,24 @@
class RuntimeCallbacks {
public:
- void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb)
- REQUIRES(Locks::mutator_lock_);
- void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb)
- REQUIRES(Locks::mutator_lock_);
+ void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
+ void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
- void ThreadStart(Thread* self)
- REQUIRES_SHARED(Locks::mutator_lock_);
- void ThreadDeath(Thread* self)
+ void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
+ void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void AddClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
+ void RemoveClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
+
+ void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
+ void ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass)
REQUIRES_SHARED(Locks::mutator_lock_);
private:
std::vector<ThreadLifecycleCallback*> thread_callbacks_
GUARDED_BY(Locks::mutator_lock_);
+ std::vector<ClassLoadCallback*> class_callbacks_
+ GUARDED_BY(Locks::mutator_lock_);
};
} // namespace art
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index 62729ca..8cd39a0 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -17,14 +17,20 @@
#include "runtime_callbacks.h"
#include "jni.h"
+
+#include <initializer_list>
#include <memory>
#include <string>
#include "art_method-inl.h"
#include "base/mutex.h"
-#include "mirror/class-inl.h"
+#include "class_linker.h"
#include "common_runtime_test.h"
+#include "handle.h"
+#include "handle_scope-inl.h"
#include "mem_map.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
#include "obj_ptr.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
@@ -129,7 +135,6 @@
Callback cb_;
};
-
TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) {
Thread* self = Thread::Current();
@@ -207,4 +212,82 @@
EXPECT_TRUE(cb_.state == CallbackState::kStarted) << static_cast<int>(cb_.state);
}
+class ClassLoadCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+ void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks().AddClassLoadCallback(&cb_);
+ }
+ void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks().RemoveClassLoadCallback(&cb_);
+ }
+
+ bool Expect(std::initializer_list<const char*> list) {
+ if (cb_.data.size() != list.size()) {
+ PrintError(list);
+ return false;
+ }
+
+ if (!std::equal(cb_.data.begin(), cb_.data.end(), list.begin())) {
+ PrintError(list);
+ return false;
+ }
+
+ return true;
+ }
+
+ void PrintError(std::initializer_list<const char*> list) {
+ LOG(ERROR) << "Expected:";
+ for (const char* expected : list) {
+ LOG(ERROR) << " " << expected;
+ }
+ LOG(ERROR) << "Found:";
+ for (const auto& s : cb_.data) {
+ LOG(ERROR) << " " << s;
+ }
+ }
+
+ struct Callback : public ClassLoadCallback {
+ void ClassLoad(Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::string tmp;
+ std::string event = std::string("Load:") + klass->GetDescriptor(&tmp);
+ data.push_back(event);
+ }
+
+ void ClassPrepare(Handle<mirror::Class> temp_klass,
+ Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::string tmp, tmp2;
+ std::string event = std::string("Prepare:") + klass->GetDescriptor(&tmp)
+ + "[" + temp_klass->GetDescriptor(&tmp2) + "]";
+ data.push_back(event);
+ }
+
+ std::vector<std::string> data;
+ };
+
+ Callback cb_;
+};
+
+TEST_F(ClassLoadCallbackRuntimeCallbacksTest, ClassLoadCallback) {
+ ScopedObjectAccess soa(Thread::Current());
+ jobject jclass_loader = LoadDex("XandY");
+ VariableSizedHandleScope hs(soa.Self());
+ Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
+ soa.Decode<mirror::ClassLoader>(jclass_loader)));
+
+ const char* descriptor_y = "LY;";
+ Handle<mirror::Class> h_Y(
+ hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_y, class_loader)));
+ ASSERT_TRUE(h_Y.Get() != nullptr);
+
+ bool expect1 = Expect({ "Load:LX;", "Prepare:LX;[LX;]", "Load:LY;", "Prepare:LY;[LY;]" });
+ EXPECT_TRUE(expect1);
+
+ cb_.data.clear();
+
+ ASSERT_TRUE(class_linker_->EnsureInitialized(Thread::Current(), h_Y, true, true));
+
+ bool expect2 = Expect({ "Load:LY$Z;", "Prepare:LY$Z;[LY$Z;]" });
+ EXPECT_TRUE(expect2);
+}
+
} // namespace art
diff --git a/test/XandY/Y.java b/test/XandY/Y.java
index ecead6e..2a1f036 100644
--- a/test/XandY/Y.java
+++ b/test/XandY/Y.java
@@ -14,4 +14,8 @@
* limitations under the License.
*/
-class Y extends X {}
+class Y extends X {
+ static Z z = new Z();
+ static class Z {
+ }
+}