ART: Add currentThread cutout to Unstarted Runtime
Add currentThread() and getThreadState() cutouts to the unstarted
runtime to allow further compile-time initialization. The cutouts
are protected by call-stack checks.
Add tests.
Bug: 34956610
Test: m test-art-host-gtest-unstarted_runtime_test
Change-Id: I6335bccda8bedae90376fc7c47b303576f1ac78b
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 545cc1a..272dfa9 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -897,11 +897,13 @@
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::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 96b35e4..929b747 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 31be587..4be5745 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -1039,5 +1039,50 @@
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