ART: Fix Unstarted Runtime Class.forName
We really only support the boot classloader, and only in the null
denotation. Otherwise the class-linker would want to install a
class table into the fake BootClassLoader.
Add tests.
Bug: 34956610
Test: m test-art-host-gtest-unstarted_runtime_test
Change-Id: I47e284fbd17eb8d33665d2788afcbcc3d09d3d2e
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 66f14b9..fc21945 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -175,56 +175,61 @@
return param->AsString();
}
-void UnstartedRuntime::UnstartedClassForName(
- Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self,
+ ShadowFrame* shadow_frame,
+ JValue* result,
+ size_t arg_offset,
+ bool long_form,
+ const char* caller) {
mirror::String* class_name = GetClassName(self, shadow_frame, arg_offset);
if (class_name == nullptr) {
return;
}
+ bool initialize_class;
+ mirror::ClassLoader* class_loader;
+ if (long_form) {
+ initialize_class = shadow_frame->GetVReg(arg_offset + 1) != 0;
+ class_loader = down_cast<mirror::ClassLoader*>(shadow_frame->GetVRegReference(arg_offset + 2));
+ } else {
+ initialize_class = true;
+ // TODO: This is really only correct for the boot classpath, and for robustness we should
+ // check the caller.
+ class_loader = nullptr;
+ }
+
+ ScopedObjectAccessUnchecked soa(self);
+ if (class_loader != nullptr && !ClassLinker::IsBootClassLoader(soa, class_loader)) {
+ AbortTransactionOrFail(self,
+ "Only the boot classloader is supported: %s",
+ mirror::Object::PrettyTypeOf(class_loader).c_str());
+ return;
+ }
+
StackHandleScope<1> hs(self);
Handle<mirror::String> h_class_name(hs.NewHandle(class_name));
UnstartedRuntimeFindClass(self,
h_class_name,
ScopedNullHandle<mirror::ClassLoader>(),
result,
- "Class.forName",
- true,
+ caller,
+ initialize_class,
false);
CheckExceptionGenerateClassNotFound(self);
}
+void UnstartedRuntime::UnstartedClassForName(
+ Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+ UnstartedClassForNameCommon(self, shadow_frame, result, arg_offset, false, "Class.forName");
+}
+
void UnstartedRuntime::UnstartedClassForNameLong(
Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
- mirror::String* class_name = GetClassName(self, shadow_frame, arg_offset);
- if (class_name == nullptr) {
- return;
- }
- bool initialize_class = shadow_frame->GetVReg(arg_offset + 1) != 0;
- mirror::ClassLoader* class_loader =
- down_cast<mirror::ClassLoader*>(shadow_frame->GetVRegReference(arg_offset + 2));
- StackHandleScope<2> hs(self);
- Handle<mirror::String> h_class_name(hs.NewHandle(class_name));
- Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(class_loader));
- UnstartedRuntimeFindClass(self, h_class_name, h_class_loader, result, "Class.forName",
- initialize_class, false);
- CheckExceptionGenerateClassNotFound(self);
+ UnstartedClassForNameCommon(self, shadow_frame, result, arg_offset, true, "Class.forName");
}
void UnstartedRuntime::UnstartedClassClassForName(
Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
- mirror::String* class_name = GetClassName(self, shadow_frame, arg_offset);
- if (class_name == nullptr) {
- return;
- }
- bool initialize_class = shadow_frame->GetVReg(arg_offset + 1) != 0;
- mirror::ClassLoader* class_loader =
- down_cast<mirror::ClassLoader*>(shadow_frame->GetVRegReference(arg_offset + 2));
- StackHandleScope<2> hs(self);
- Handle<mirror::String> h_class_name(hs.NewHandle(class_name));
- Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(class_loader));
- UnstartedRuntimeFindClass(self, h_class_name, h_class_loader, result, "Class.classForName",
- initialize_class, false);
- CheckExceptionGenerateClassNotFound(self);
+ UnstartedClassForNameCommon(self, shadow_frame, result, arg_offset, true, "Class.classForName");
}
void UnstartedRuntime::UnstartedClassNewInstance(
diff --git a/runtime/interpreter/unstarted_runtime.h b/runtime/interpreter/unstarted_runtime.h
index 3f36a27..bc9ead8 100644
--- a/runtime/interpreter/unstarted_runtime.h
+++ b/runtime/interpreter/unstarted_runtime.h
@@ -89,6 +89,13 @@
#undef UNSTARTED_RUNTIME_JNI_LIST
#undef UNSTARTED_JNI
+ static void UnstartedClassForNameCommon(Thread* self,
+ ShadowFrame* shadow_frame,
+ JValue* result,
+ size_t arg_offset,
+ bool long_form,
+ const char* caller) REQUIRES_SHARED(Locks::mutator_lock_);
+
static void InitializeInvokeHandlers();
static void InitializeJNIHandlers();
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index 98a17e4..3a0d0e7 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -1084,5 +1084,199 @@
ASSERT_TRUE(class_linker->EnsureInitialized(self, log_manager_class, true, true));
}
+class UnstartedClassForNameTest : public UnstartedRuntimeTest {
+ public:
+ template <typename T>
+ void RunTest(T& runner, bool in_transaction, bool should_succeed) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ // Ensure that Class is initialized.
+ {
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ StackHandleScope<1> hs(self);
+ Handle<mirror::Class> h_class = hs.NewHandle(mirror::Class::GetJavaLangClass());
+ CHECK(class_linker->EnsureInitialized(self, h_class, true, true));
+ }
+
+ // A selection of classes from different core classpath components.
+ constexpr const char* kTestCases[] = {
+ "java.net.CookieManager", // From libcore.
+ "dalvik.system.ClassExt", // From libart.
+ };
+
+ if (in_transaction) {
+ // For transaction mode, we cannot load any classes, as the pre-fence initialization of
+ // classes isn't transactional. Load them ahead of time.
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ for (const char* name : kTestCases) {
+ class_linker->FindClass(self,
+ DotToDescriptor(name).c_str(),
+ ScopedNullHandle<mirror::ClassLoader>());
+ CHECK(!self->IsExceptionPending()) << self->GetException()->Dump();
+ }
+ }
+
+ if (!should_succeed) {
+ // Negative test. In general, currentThread should fail (as we should not leak a peer that will
+ // be recreated at runtime).
+ PrepareForAborts();
+ }
+
+ JValue result;
+ ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+ for (const char* name : kTestCases) {
+ mirror::String* name_string = mirror::String::AllocFromModifiedUtf8(self, name);
+ CHECK(name_string != nullptr);
+
+ Transaction transaction;
+ if (in_transaction) {
+ Runtime::Current()->EnterTransactionMode(&transaction);
+ }
+ CHECK(!self->IsExceptionPending());
+
+ runner(self, shadow_frame, name_string, &result);
+
+ if (in_transaction) {
+ Runtime::Current()->ExitTransactionMode();
+ }
+
+ if (should_succeed) {
+ CHECK(!self->IsExceptionPending()) << name << " " << self->GetException()->Dump();
+ CHECK(result.GetL() != nullptr) << name;
+ } else {
+ CHECK(self->IsExceptionPending()) << name;
+ if (in_transaction) {
+ ASSERT_TRUE(transaction.IsAborted());
+ }
+ self->ClearException();
+ }
+ }
+
+ ShadowFrame::DeleteDeoptimizedFrame(shadow_frame);
+ }
+
+ mirror::ClassLoader* GetBootClassLoader() REQUIRES_SHARED(Locks::mutator_lock_) {
+ Thread* self = Thread::Current();
+ StackHandleScope<2> hs(self);
+ MutableHandle<mirror::ClassLoader> boot_cp = hs.NewHandle<mirror::ClassLoader>(nullptr);
+
+ {
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+
+ // Create the fake boot classloader. Any instance is fine, they are technically interchangeable.
+ Handle<mirror::Class> boot_cp_class = hs.NewHandle(
+ class_linker->FindClass(self,
+ "Ljava/lang/BootClassLoader;",
+ ScopedNullHandle<mirror::ClassLoader>()));
+ CHECK(boot_cp_class != nullptr);
+ CHECK(class_linker->EnsureInitialized(self, boot_cp_class, true, true));
+
+ boot_cp.Assign(boot_cp_class->AllocObject(self)->AsClassLoader());
+ CHECK(boot_cp != nullptr);
+
+ ArtMethod* boot_cp_init = boot_cp_class->FindDeclaredDirectMethod(
+ "<init>", "()V", class_linker->GetImagePointerSize());
+ CHECK(boot_cp_init != nullptr);
+
+ JValue result;
+ ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, boot_cp_init, 0);
+ shadow_frame->SetVRegReference(0, boot_cp.Get());
+
+ // create instruction data for invoke-direct {v0} of method with fake index
+ uint16_t inst_data[3] = { 0x1070, 0x0000, 0x0010 };
+ const Instruction* inst = Instruction::At(inst_data);
+
+ interpreter::DoCall<false, false>(boot_cp_init,
+ self,
+ *shadow_frame,
+ inst,
+ inst_data[0],
+ &result);
+ CHECK(!self->IsExceptionPending());
+
+ ShadowFrame::DeleteDeoptimizedFrame(shadow_frame);
+ }
+
+ return boot_cp.Get();
+ }
+};
+
+TEST_F(UnstartedClassForNameTest, ClassForName) {
+ auto runner = [](Thread* self, ShadowFrame* shadow_frame, mirror::String* name, JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ UnstartedClassForName(self, shadow_frame, result, 0);
+ };
+ RunTest(runner, false, true);
+}
+
+TEST_F(UnstartedClassForNameTest, ClassForNameLong) {
+ auto runner = [](Thread* self, ShadowFrame* shadow_frame, mirror::String* name, JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ shadow_frame->SetVReg(1, 0);
+ shadow_frame->SetVRegReference(2, nullptr);
+ UnstartedClassForNameLong(self, shadow_frame, result, 0);
+ };
+ RunTest(runner, false, true);
+}
+
+TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoader) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ StackHandleScope<1> hs(self);
+ Handle<mirror::ClassLoader> boot_cp = hs.NewHandle(GetBootClassLoader());
+
+ auto runner = [&](Thread* th, ShadowFrame* shadow_frame, mirror::String* name, JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ shadow_frame->SetVReg(1, 0);
+ shadow_frame->SetVRegReference(2, boot_cp.Get());
+ UnstartedClassForNameLong(th, shadow_frame, result, 0);
+ };
+ RunTest(runner, false, true);
+}
+
+TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoaderTransaction) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ StackHandleScope<1> hs(self);
+ Handle<mirror::ClassLoader> boot_cp = hs.NewHandle(GetBootClassLoader());
+
+ auto runner = [&](Thread* th, ShadowFrame* shadow_frame, mirror::String* name, JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ shadow_frame->SetVReg(1, 0);
+ shadow_frame->SetVRegReference(2, boot_cp.Get());
+ UnstartedClassForNameLong(th, shadow_frame, result, 0);
+ };
+ RunTest(runner, true, true);
+}
+
+TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoaderFail) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ StackHandleScope<2> hs(self);
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ jobject path_jobj = class_linker->CreatePathClassLoader(self, {});
+ ASSERT_TRUE(path_jobj != nullptr);
+ Handle<mirror::ClassLoader> path_cp = hs.NewHandle<mirror::ClassLoader>(
+ self->DecodeJObject(path_jobj)->AsClassLoader());
+
+ auto runner = [&](Thread* th, ShadowFrame* shadow_frame, mirror::String* name, JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ shadow_frame->SetVReg(1, 0);
+ shadow_frame->SetVRegReference(2, path_cp.Get());
+ UnstartedClassForNameLong(th, shadow_frame, result, 0);
+ };
+ RunTest(runner, true, false);
+}
+
} // namespace interpreter
} // namespace art