| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "runtime_callbacks.h" |
| |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <initializer_list> |
| #include <memory> |
| #include <mutex> |
| #include <string> |
| |
| #include "jni.h" |
| |
| #include "art_method-inl.h" |
| #include "base/mem_map.h" |
| #include "base/mutex.h" |
| #include "class_linker.h" |
| #include "common_runtime_test.h" |
| #include "dex/class_reference.h" |
| #include "handle.h" |
| #include "handle_scope-inl.h" |
| #include "mirror/class-inl.h" |
| #include "mirror/class_loader.h" |
| #include "monitor-inl.h" |
| #include "nativehelper/scoped_local_ref.h" |
| #include "obj_ptr-inl.h" |
| #include "runtime.h" |
| #include "scoped_thread_state_change-inl.h" |
| #include "thread-inl.h" |
| #include "thread_list.h" |
| #include "well_known_classes.h" |
| |
| namespace art { |
| |
| class RuntimeCallbacksTest : public CommonRuntimeTest { |
| protected: |
| void SetUp() override { |
| CommonRuntimeTest::SetUp(); |
| |
| Thread* self = Thread::Current(); |
| ScopedObjectAccess soa(self); |
| ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach); |
| ScopedSuspendAll ssa("RuntimeCallbacksTest SetUp"); |
| AddListener(); |
| } |
| |
| void TearDown() override { |
| { |
| Thread* self = Thread::Current(); |
| ScopedObjectAccess soa(self); |
| ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach); |
| ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown"); |
| RemoveListener(); |
| } |
| |
| CommonRuntimeTest::TearDown(); |
| } |
| |
| virtual void AddListener() REQUIRES(Locks::mutator_lock_) = 0; |
| virtual void RemoveListener() REQUIRES(Locks::mutator_lock_) = 0; |
| |
| void MakeExecutable(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { |
| CHECK(klass != nullptr); |
| PointerSize pointer_size = class_linker_->GetImagePointerSize(); |
| for (auto& m : klass->GetMethods(pointer_size)) { |
| if (!m.IsAbstract()) { |
| class_linker_->SetEntryPointsToInterpreter(&m); |
| } |
| } |
| } |
| }; |
| |
| class ThreadLifecycleCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { |
| public: |
| static void* PthreadsCallback(void* arg ATTRIBUTE_UNUSED) { |
| // Attach. |
| Runtime* runtime = Runtime::Current(); |
| CHECK(runtime->AttachCurrentThread("ThreadLifecycle test thread", true, nullptr, false)); |
| |
| // Detach. |
| runtime->DetachCurrentThread(); |
| |
| // Die... |
| return nullptr; |
| } |
| |
| protected: |
| void AddListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&cb_); |
| } |
| void RemoveListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&cb_); |
| } |
| |
| enum CallbackState { |
| kBase, |
| kStarted, |
| kDied, |
| kWrongStart, |
| kWrongDeath, |
| }; |
| |
| struct Callback : public ThreadLifecycleCallback { |
| void ThreadStart(Thread* self) override { |
| if (state == CallbackState::kBase) { |
| state = CallbackState::kStarted; |
| stored_self = self; |
| } else { |
| state = CallbackState::kWrongStart; |
| } |
| } |
| |
| void ThreadDeath(Thread* self) override { |
| if (state == CallbackState::kStarted && self == stored_self) { |
| state = CallbackState::kDied; |
| } else { |
| state = CallbackState::kWrongDeath; |
| } |
| } |
| |
| Thread* stored_self; |
| CallbackState state = CallbackState::kBase; |
| }; |
| |
| Callback cb_; |
| }; |
| |
| TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) { |
| Thread* self = Thread::Current(); |
| |
| self->TransitionFromSuspendedToRunnable(); |
| bool started = runtime_->Start(); |
| ASSERT_TRUE(started); |
| // Make sure the workers are done starting so we don't get callbacks for them. |
| runtime_->WaitForThreadPoolWorkersToStart(); |
| |
| cb_.state = CallbackState::kBase; // Ignore main thread attach. |
| |
| { |
| ScopedObjectAccess soa(self); |
| MakeExecutable(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread)); |
| } |
| |
| JNIEnv* env = self->GetJniEnv(); |
| |
| ScopedLocalRef<jobject> thread_name(env, |
| env->NewStringUTF("ThreadLifecycleCallback test thread")); |
| ASSERT_TRUE(thread_name.get() != nullptr); |
| |
| ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); |
| ASSERT_TRUE(thread.get() != nullptr); |
| |
| env->CallNonvirtualVoidMethod(thread.get(), |
| WellKnownClasses::java_lang_Thread, |
| WellKnownClasses::java_lang_Thread_init, |
| runtime_->GetMainThreadGroup(), |
| thread_name.get(), |
| kMinThreadPriority, |
| JNI_FALSE); |
| ASSERT_FALSE(env->ExceptionCheck()); |
| |
| jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V"); |
| ASSERT_TRUE(start_id != nullptr); |
| |
| env->CallVoidMethod(thread.get(), start_id); |
| ASSERT_FALSE(env->ExceptionCheck()); |
| |
| jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V"); |
| ASSERT_TRUE(join_id != nullptr); |
| |
| env->CallVoidMethod(thread.get(), join_id); |
| ASSERT_FALSE(env->ExceptionCheck()); |
| |
| EXPECT_TRUE(cb_.state == CallbackState::kDied) << static_cast<int>(cb_.state); |
| } |
| |
| TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) { |
| std::string error_msg; |
| MemMap stack = MemMap::MapAnonymous("ThreadLifecycleCallback Thread", |
| 128 * kPageSize, // Just some small stack. |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ false, |
| &error_msg); |
| ASSERT_TRUE(stack.IsValid()) << error_msg; |
| |
| const char* reason = "ThreadLifecycleCallback test thread"; |
| pthread_attr_t attr; |
| CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason); |
| CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack.Begin(), stack.Size()), reason); |
| pthread_t pthread; |
| CHECK_PTHREAD_CALL(pthread_create, |
| (&pthread, |
| &attr, |
| &ThreadLifecycleCallbackRuntimeCallbacksTest::PthreadsCallback, |
| this), |
| reason); |
| CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason); |
| |
| CHECK_PTHREAD_CALL(pthread_join, (pthread, nullptr), "ThreadLifecycleCallback test shutdown"); |
| |
| // Detach is not a ThreadDeath event, so we expect to be in state Started. |
| 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 ClassPreDefine(const char* descriptor, |
| Handle<mirror::Class> klass ATTRIBUTE_UNUSED, |
| Handle<mirror::ClassLoader> class_loader ATTRIBUTE_UNUSED, |
| const DexFile& initial_dex_file, |
| const dex::ClassDef& initial_class_def ATTRIBUTE_UNUSED, |
| /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, |
| /*out*/dex::ClassDef const** final_class_def ATTRIBUTE_UNUSED) override |
| REQUIRES_SHARED(Locks::mutator_lock_) { |
| const std::string& location = initial_dex_file.GetLocation(); |
| std::string event = |
| std::string("PreDefine:") + descriptor + " <" + |
| location.substr(location.rfind('/') + 1, location.size()) + ">"; |
| data.push_back(event); |
| } |
| |
| 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 != nullptr); |
| |
| bool expect1 = Expect({ "PreDefine:LY; <art-gtest-XandY.jar>", |
| "PreDefine:LX; <art-gtest-XandY.jar>", |
| "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({ "PreDefine:LY$Z; <art-gtest-XandY.jar>", |
| "Load:LY$Z;", |
| "Prepare:LY$Z;[LY$Z;]" }); |
| EXPECT_TRUE(expect2); |
| } |
| |
| class RuntimeSigQuitCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { |
| protected: |
| void AddListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&cb_); |
| } |
| void RemoveListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&cb_); |
| } |
| |
| struct Callback : public RuntimeSigQuitCallback { |
| void SigQuit() override { |
| ++sigquit_count; |
| } |
| |
| size_t sigquit_count = 0; |
| }; |
| |
| Callback cb_; |
| }; |
| |
| TEST_F(RuntimeSigQuitCallbackRuntimeCallbacksTest, SigQuit) { |
| // The runtime needs to be started for the signal handler. |
| Thread* self = Thread::Current(); |
| |
| self->TransitionFromSuspendedToRunnable(); |
| bool started = runtime_->Start(); |
| ASSERT_TRUE(started); |
| |
| EXPECT_EQ(0u, cb_.sigquit_count); |
| |
| kill(getpid(), SIGQUIT); |
| |
| // Try a few times. |
| for (size_t i = 0; i != 30; ++i) { |
| if (cb_.sigquit_count == 0) { |
| sleep(1); |
| } else { |
| break; |
| } |
| } |
| EXPECT_EQ(1u, cb_.sigquit_count); |
| } |
| |
| class RuntimePhaseCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { |
| protected: |
| void AddListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&cb_); |
| } |
| void RemoveListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&cb_); |
| } |
| |
| void TearDown() override { |
| // Bypass RuntimeCallbacksTest::TearDown, as the runtime is already gone. |
| CommonRuntimeTest::TearDown(); |
| } |
| |
| struct Callback : public RuntimePhaseCallback { |
| void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) override { |
| if (p == RuntimePhaseCallback::RuntimePhase::kInitialAgents) { |
| if (start_seen > 0 || init_seen > 0 || death_seen > 0) { |
| LOG(FATAL) << "Unexpected order"; |
| } |
| ++initial_agents_seen; |
| } else if (p == RuntimePhaseCallback::RuntimePhase::kStart) { |
| if (init_seen > 0 || death_seen > 0) { |
| LOG(FATAL) << "Init seen before start."; |
| } |
| ++start_seen; |
| } else if (p == RuntimePhaseCallback::RuntimePhase::kInit) { |
| ++init_seen; |
| } else if (p == RuntimePhaseCallback::RuntimePhase::kDeath) { |
| ++death_seen; |
| } else { |
| LOG(FATAL) << "Unknown phase " << static_cast<uint32_t>(p); |
| } |
| } |
| |
| size_t initial_agents_seen = 0; |
| size_t start_seen = 0; |
| size_t init_seen = 0; |
| size_t death_seen = 0; |
| }; |
| |
| Callback cb_; |
| }; |
| |
| TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) { |
| ASSERT_EQ(0u, cb_.initial_agents_seen); |
| ASSERT_EQ(0u, cb_.start_seen); |
| ASSERT_EQ(0u, cb_.init_seen); |
| ASSERT_EQ(0u, cb_.death_seen); |
| |
| // Start the runtime. |
| { |
| Thread* self = Thread::Current(); |
| self->TransitionFromSuspendedToRunnable(); |
| bool started = runtime_->Start(); |
| ASSERT_TRUE(started); |
| } |
| |
| ASSERT_EQ(0u, cb_.initial_agents_seen); |
| ASSERT_EQ(1u, cb_.start_seen); |
| ASSERT_EQ(1u, cb_.init_seen); |
| ASSERT_EQ(0u, cb_.death_seen); |
| |
| // Delete the runtime. |
| runtime_.reset(); |
| |
| ASSERT_EQ(0u, cb_.initial_agents_seen); |
| ASSERT_EQ(1u, cb_.start_seen); |
| ASSERT_EQ(1u, cb_.init_seen); |
| ASSERT_EQ(1u, cb_.death_seen); |
| } |
| |
| class MonitorWaitCallbacksTest : public RuntimeCallbacksTest { |
| protected: |
| void AddListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_); |
| } |
| void RemoveListener() override REQUIRES(Locks::mutator_lock_) { |
| Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_); |
| } |
| |
| struct Callback : public MonitorCallback { |
| bool IsInterestingObject(ObjPtr<mirror::Object> obj) |
| REQUIRES_SHARED(art::Locks::mutator_lock_) { |
| if (!obj->IsClass()) { |
| return false; |
| } |
| std::lock_guard<std::mutex> lock(ref_guard_); |
| ObjPtr<mirror::Class> k = obj->AsClass(); |
| ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() }; |
| return ref_ == test; |
| } |
| |
| void SetInterestingObject(ObjPtr<mirror::Object> obj) |
| REQUIRES_SHARED(art::Locks::mutator_lock_) { |
| std::lock_guard<std::mutex> lock(ref_guard_); |
| ObjPtr<mirror::Class> k = obj->AsClass(); |
| ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() }; |
| } |
| |
| void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED) override |
| REQUIRES_SHARED(Locks::mutator_lock_) { } |
| |
| void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED) override |
| REQUIRES_SHARED(Locks::mutator_lock_) { } |
| |
| void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED) override |
| REQUIRES_SHARED(Locks::mutator_lock_) { |
| if (IsInterestingObject(obj.Get())) { |
| saw_wait_start_ = true; |
| } |
| } |
| |
| void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED) override |
| REQUIRES_SHARED(Locks::mutator_lock_) { |
| if (IsInterestingObject(m->GetObject())) { |
| saw_wait_finished_ = true; |
| } |
| } |
| |
| std::mutex ref_guard_; |
| ClassReference ref_ = {nullptr, 0}; |
| bool saw_wait_start_ = false; |
| bool saw_wait_finished_ = false; |
| }; |
| |
| Callback cb_; |
| }; |
| |
| // TODO It would be good to have more tests for this but due to the multi-threaded nature of the |
| // callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient. |
| TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) { |
| ASSERT_FALSE(cb_.saw_wait_finished_); |
| ASSERT_FALSE(cb_.saw_wait_start_); |
| { |
| Thread* self = Thread::Current(); |
| self->TransitionFromSuspendedToRunnable(); |
| bool started = runtime_->Start(); |
| ASSERT_TRUE(started); |
| { |
| ScopedObjectAccess soa(self); |
| cb_.SetInterestingObject( |
| soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections)); |
| Monitor::Wait( |
| self, |
| // Just a random class |
| soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections), |
| /*ms=*/0, |
| /*ns=*/0, |
| /*interruptShouldThrow=*/false, |
| /*why=*/kWaiting); |
| } |
| } |
| ASSERT_TRUE(cb_.saw_wait_start_); |
| ASSERT_FALSE(cb_.saw_wait_finished_); |
| } |
| |
| } // namespace art |