diff options
| -rw-r--r-- | runtime/interpreter/unstarted_runtime.cc | 55 | ||||
| -rw-r--r-- | runtime/interpreter/unstarted_runtime_list.h | 2 | ||||
| -rw-r--r-- | runtime/interpreter/unstarted_runtime_test.cc | 45 | ||||
| -rw-r--r-- | runtime/runtime.h | 3 | ||||
| -rw-r--r-- | runtime/thread.cc | 81 | ||||
| -rw-r--r-- | runtime/thread.h | 14 |
6 files changed, 189 insertions, 11 deletions
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index 545cc1ad42..272dfa96c8 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -897,11 +897,13 @@ static bool CheckCallers(ShadowFrame* shadow_frame, REQUIRES_SHARED(Locks::mutator_lock_) { for (const std::string& allowed_caller : allowed_call_stack) { if (shadow_frame->GetLink() == nullptr) { + LOG(ERROR) << "Link is unexpectedly null"; return false; } std::string found_caller = ArtMethod::PrettyMethod(shadow_frame->GetLink()->GetMethod()); if (allowed_caller != found_caller) { + LOG(ERROR) << "Non-match: " << allowed_caller << " vs " << found_caller; return false; } @@ -955,6 +957,59 @@ void UnstartedRuntime::UnstartedThreadLocalGet( } } +void UnstartedRuntime::UnstartedThreadCurrentThread( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset ATTRIBUTE_UNUSED) { + if (CheckCallers(shadow_frame, + { "void java.lang.Thread.init(java.lang.ThreadGroup, java.lang.Runnable, " + "java.lang.String, long)", + "void java.lang.Thread.<init>()", + "void java.util.logging.LogManager$Cleaner.<init>(" + "java.util.logging.LogManager)" })) { + // Whitelist LogManager$Cleaner, which is an unstarted Thread (for a shutdown hook). The + // Thread constructor only asks for the current thread to set up defaults and add the + // thread as unstarted to the ThreadGroup. A faked-up main thread peer is good enough for + // these purposes. + Runtime::Current()->InitThreadGroups(self); + jobject main_peer = + self->CreateCompileTimePeer(self->GetJniEnv(), + "main", + false, + Runtime::Current()->GetMainThreadGroup()); + if (main_peer == nullptr) { + AbortTransactionOrFail(self, "Failed allocating peer"); + return; + } + + result->SetL(self->DecodeJObject(main_peer)); + self->GetJniEnv()->DeleteLocalRef(main_peer); + } else { + AbortTransactionOrFail(self, + "Thread.currentThread() does not support %s", + GetImmediateCaller(shadow_frame).c_str()); + } +} + +void UnstartedRuntime::UnstartedThreadGetNativeState( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset ATTRIBUTE_UNUSED) { + if (CheckCallers(shadow_frame, + { "java.lang.Thread$State java.lang.Thread.getState()", + "java.lang.ThreadGroup java.lang.Thread.getThreadGroup()", + "void java.lang.Thread.init(java.lang.ThreadGroup, java.lang.Runnable, " + "java.lang.String, long)", + "void java.lang.Thread.<init>()", + "void java.util.logging.LogManager$Cleaner.<init>(" + "java.util.logging.LogManager)" })) { + // Whitelist reading the state of the "main" thread when creating another (unstarted) thread + // for LogManager. Report the thread as "new" (it really only counts that it isn't terminated). + constexpr int32_t kJavaRunnable = 1; + result->SetI(kJavaRunnable); + } else { + AbortTransactionOrFail(self, + "Thread.getNativeState() does not support %s", + GetImmediateCaller(shadow_frame).c_str()); + } +} + void UnstartedRuntime::UnstartedMathCeil( Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { result->SetD(ceil(shadow_frame->GetVRegDouble(arg_offset))); diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h index 96b35e4e9c..929b747840 100644 --- a/runtime/interpreter/unstarted_runtime_list.h +++ b/runtime/interpreter/unstarted_runtime_list.h @@ -66,6 +66,8 @@ V(StringFactoryNewStringFromString, "java.lang.String java.lang.StringFactory.newStringFromString(java.lang.String)") \ V(StringFastSubstring, "java.lang.String java.lang.String.fastSubstring(int, int)") \ V(StringToCharArray, "char[] java.lang.String.toCharArray()") \ + V(ThreadCurrentThread, "java.lang.Thread java.lang.Thread.currentThread()") \ + V(ThreadGetNativeState, "int java.lang.Thread.nativeGetStatus(boolean)") \ V(UnsafeCompareAndSwapLong, "boolean sun.misc.Unsafe.compareAndSwapLong(java.lang.Object, long, long, long)") \ V(UnsafeCompareAndSwapObject, "boolean sun.misc.Unsafe.compareAndSwapObject(java.lang.Object, long, java.lang.Object, java.lang.Object)") \ V(UnsafeGetObjectVolatile, "java.lang.Object sun.misc.Unsafe.getObjectVolatile(java.lang.Object, long)") \ diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc index 31be587e9c..4be57451f5 100644 --- a/runtime/interpreter/unstarted_runtime_test.cc +++ b/runtime/interpreter/unstarted_runtime_test.cc @@ -1039,5 +1039,50 @@ TEST_F(UnstartedRuntimeTest, FloatConversion) { ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); } +TEST_F(UnstartedRuntimeTest, ThreadCurrentThread) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + JValue result; + ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + StackHandleScope<1> hs(self); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::Class> thread_class = hs.NewHandle( + class_linker->FindClass(self, "Ljava/lang/Thread;", ScopedNullHandle<mirror::ClassLoader>())); + ASSERT_TRUE(thread_class.Get() != nullptr); + ASSERT_TRUE(class_linker->EnsureInitialized(self, thread_class, true, true)); + + // Negative test. In general, currentThread should fail (as we should not leak a peer that will + // be recreated at runtime). + PrepareForAborts(); + + { + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedThreadCurrentThread(self, shadow_frame, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + self->ClearException(); + } + + ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); +} + +TEST_F(UnstartedRuntimeTest, LogManager) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + StackHandleScope<1> hs(self); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::Class> log_manager_class = hs.NewHandle( + class_linker->FindClass(self, + "Ljava/util/logging/LogManager;", + ScopedNullHandle<mirror::ClassLoader>())); + ASSERT_TRUE(log_manager_class.Get() != nullptr); + ASSERT_TRUE(class_linker->EnsureInitialized(self, log_manager_class, true, true)); +} + } // namespace interpreter } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index 30b1756d5d..4a0169db68 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -662,6 +662,8 @@ class Runtime { RuntimeCallbacks* GetRuntimeCallbacks(); + void InitThreadGroups(Thread* self); + private: static void InitPlatformSignalHandlers(); @@ -672,7 +674,6 @@ class Runtime { bool Init(RuntimeArgumentMap&& runtime_options) SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_); void InitNativeMethods() REQUIRES(!Locks::mutator_lock_); - void InitThreadGroups(Thread* self); void RegisterRuntimeNativeMethods(JNIEnv* env); void StartDaemonThreads(); diff --git a/runtime/thread.cc b/runtime/thread.cc index 7b6540436a..3c2b99fa7d 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -880,9 +880,19 @@ void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) // available (in the compiler, in tests), we manually assign the // fields the constructor should have set. if (runtime->IsActiveTransaction()) { - InitPeer<true>(soa, thread_is_daemon, thread_group, thread_name.get(), thread_priority); + InitPeer<true>(soa, + tlsPtr_.opeer, + thread_is_daemon, + thread_group, + thread_name.get(), + thread_priority); } else { - InitPeer<false>(soa, thread_is_daemon, thread_group, thread_name.get(), thread_priority); + InitPeer<false>(soa, + tlsPtr_.opeer, + thread_is_daemon, + thread_group, + thread_name.get(), + thread_priority); } peer_thread_name.Assign(GetThreadName()); } @@ -892,17 +902,72 @@ void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) } } +jobject Thread::CreateCompileTimePeer(JNIEnv* env, + const char* name, + bool as_daemon, + jobject thread_group) { + Runtime* runtime = Runtime::Current(); + CHECK(!runtime->IsStarted()); + + if (thread_group == nullptr) { + thread_group = runtime->GetMainThreadGroup(); + } + ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name)); + // Add missing null check in case of OOM b/18297817 + if (name != nullptr && thread_name.get() == nullptr) { + CHECK(Thread::Current()->IsExceptionPending()); + return nullptr; + } + jint thread_priority = GetNativePriority(); + jboolean thread_is_daemon = as_daemon; + + ScopedLocalRef<jobject> peer(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); + if (peer.get() == nullptr) { + CHECK(Thread::Current()->IsExceptionPending()); + return nullptr; + } + + // We cannot call Thread.init, as it will recursively ask for currentThread. + + // The Thread constructor should have set the Thread.name to a + // non-null value. However, because we can run without code + // available (in the compiler, in tests), we manually assign the + // fields the constructor should have set. + ScopedObjectAccessUnchecked soa(Thread::Current()); + if (runtime->IsActiveTransaction()) { + InitPeer<true>(soa, + soa.Decode<mirror::Object>(peer.get()), + thread_is_daemon, + thread_group, + thread_name.get(), + thread_priority); + } else { + InitPeer<false>(soa, + soa.Decode<mirror::Object>(peer.get()), + thread_is_daemon, + thread_group, + thread_name.get(), + thread_priority); + } + + return peer.release(); +} + template<bool kTransactionActive> -void Thread::InitPeer(ScopedObjectAccess& soa, jboolean thread_is_daemon, jobject thread_group, - jobject thread_name, jint thread_priority) { +void Thread::InitPeer(ScopedObjectAccessAlreadyRunnable& soa, + ObjPtr<mirror::Object> peer, + jboolean thread_is_daemon, + jobject thread_group, + jobject thread_name, + jint thread_priority) { jni::DecodeArtField(WellKnownClasses::java_lang_Thread_daemon)-> - SetBoolean<kTransactionActive>(tlsPtr_.opeer, thread_is_daemon); + SetBoolean<kTransactionActive>(peer, thread_is_daemon); jni::DecodeArtField(WellKnownClasses::java_lang_Thread_group)-> - SetObject<kTransactionActive>(tlsPtr_.opeer, soa.Decode<mirror::Object>(thread_group)); + SetObject<kTransactionActive>(peer, soa.Decode<mirror::Object>(thread_group)); jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name)-> - SetObject<kTransactionActive>(tlsPtr_.opeer, soa.Decode<mirror::Object>(thread_name)); + SetObject<kTransactionActive>(peer, soa.Decode<mirror::Object>(thread_name)); jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority)-> - SetInt<kTransactionActive>(tlsPtr_.opeer, thread_priority); + SetInt<kTransactionActive>(peer, thread_priority); } void Thread::SetThreadName(const char* name) { diff --git a/runtime/thread.h b/runtime/thread.h index a46e799d72..dc846853f1 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -1173,6 +1173,12 @@ class Thread { return false; } + static jobject CreateCompileTimePeer(JNIEnv* env, + const char* name, + bool as_daemon, + jobject thread_group) + REQUIRES_SHARED(Locks::mutator_lock_); + private: explicit Thread(bool daemon); ~Thread() REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_); @@ -1188,8 +1194,12 @@ class Thread { void CreatePeer(const char* name, bool as_daemon, jobject thread_group); template<bool kTransactionActive> - void InitPeer(ScopedObjectAccess& soa, jboolean thread_is_daemon, jobject thread_group, - jobject thread_name, jint thread_priority) + static void InitPeer(ScopedObjectAccessAlreadyRunnable& soa, + ObjPtr<mirror::Object> peer, + jboolean thread_is_daemon, + jobject thread_group, + jobject thread_name, + jint thread_priority) REQUIRES_SHARED(Locks::mutator_lock_); // Avoid use, callers should use SetState. Used only by SignalCatcher::HandleSigQuit, ~Thread and |