Add fast path to FindClass for DelegateLastClassLoader
Test: m test-art-host
Bug: 38138251
Change-Id: I8d0f534195c31a5d291e38b0eea47534a5d471fb
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index bcf48fd..b53e222 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -30,6 +30,10 @@
ErroneousA \
ErroneousB \
ErroneousInit \
+ ForClassLoaderA \
+ ForClassLoaderB \
+ ForClassLoaderC \
+ ForClassLoaderD \
ExceptionHandle \
GetMethodSignature \
ImageLayoutA \
@@ -99,7 +103,7 @@
ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested
ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces
-ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
+ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit ForClassLoaderA ForClassLoaderB ForClassLoaderC ForClassLoaderD Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
ART_GTEST_class_table_test_DEX_DEPS := XandY
ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex
ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index e1817c0..097fc4e 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2423,47 +2423,70 @@
soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader));
}
+static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Class* class_loader_class = class_loader->GetClass();
+ return class_loader_class ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
+}
+
bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
Thread* self,
const char* descriptor,
size_t hash,
Handle<mirror::ClassLoader> class_loader,
ObjPtr<mirror::Class>* result) {
- // Termination case: boot class-loader.
+ // Termination case: boot class loader.
if (IsBootClassLoader(soa, class_loader.Get())) {
*result = FindClassInBootClassLoaderClassPath(self, descriptor, hash);
return true;
}
- // Check that we support the class loader.
- if (!IsPathOrDexClassLoader(soa, class_loader)) {
- *result = nullptr;
- return false;
- }
+ if (IsPathOrDexClassLoader(soa, class_loader)) {
+ // For regular path or dex class loader the search order is:
+ // - parent
+ // - class loader dex files
- // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension).
- StackHandleScope<1> hs(self);
- Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent()));
- bool recursive_result = FindClassInBaseDexClassLoader(soa,
- self,
- descriptor,
- hash,
- h_parent,
- result);
- if (!recursive_result) {
- // One of the parents is not supported.
- *result = nullptr;
- return false;
- }
+ // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension).
+ StackHandleScope<1> hs(self);
+ Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent()));
+ if (!FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result)) {
+ return false; // One of the parents is not supported.
+ }
+ if (*result != nullptr) {
+ return true; // Found the class up the chain.
+ }
- if (*result != nullptr) {
- // Found the class up the chain.
+ // Search the current class loader classpath.
+ *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader);
return true;
}
- // Search the current class loader classpath.
- *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader);
- return true;
+ if (IsDelegateLastClassLoader(soa, class_loader)) {
+ // For delegate last, the search order is:
+ // - boot class path
+ // - class loader dex files
+ // - parent
+ *result = FindClassInBootClassLoaderClassPath(self, descriptor, hash);
+ if (*result != nullptr) {
+ return true; // The class is part of the boot class path.
+ }
+
+ *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader);
+ if (*result != nullptr) {
+ return true; // Found the class in the current class loader
+ }
+
+ // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension).
+ StackHandleScope<1> hs(self);
+ Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent()));
+ return FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result);
+ }
+
+ // Unsupported class loader.
+ *result = nullptr;
+ return false;
}
// Finds the class in the boot class loader.
@@ -2498,7 +2521,7 @@
const char* descriptor,
size_t hash,
Handle<mirror::ClassLoader> class_loader) {
- CHECK(IsPathOrDexClassLoader(soa, class_loader))
+ CHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader))
<< "Unexpected class loader for descriptor " << descriptor;
Thread* self = soa.Self();
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 6f06917..a156229 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -1544,4 +1544,104 @@
LoadDexInDelegateLastClassLoader("Interfaces", class_loader_c);
}
+class ClassLinkerClassLoaderTest : public ClassLinkerTest {
+ protected:
+ // Verifies that the class identified by the given descriptor is loaded with
+ // the expected_class_loader_obj when search from class_loader_to_search_obj.
+ // When expected_class_loader_obj is null the check will be done against BootClassLoader.
+ void VerifyClassResolution(const std::string& descriptor,
+ jobject class_loader_to_search_obj,
+ jobject expected_class_loader_obj,
+ bool should_find = true) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ StackHandleScope<3> hs(self);
+ Handle<mirror::ClassLoader> class_loader_to_search(
+ hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_to_search_obj)));
+
+ Handle<mirror::Class> klass = hs.NewHandle(
+ class_linker_->FindClass(soa.Self(), descriptor.c_str(), class_loader_to_search));
+
+ if (!should_find) {
+ if (self->IsExceptionPending()) {
+ self->ClearException();
+ }
+ ASSERT_TRUE(klass == nullptr);
+ } else if (expected_class_loader_obj == nullptr) {
+ ASSERT_TRUE(ClassLinker::IsBootClassLoader(soa, klass->GetClassLoader()));
+ } else {
+ ASSERT_TRUE(klass != nullptr) << descriptor;
+ Handle<mirror::ClassLoader> expected_class_loader(
+ hs.NewHandle(soa.Decode<mirror::ClassLoader>(expected_class_loader_obj)));
+ ASSERT_EQ(klass->GetClassLoader(), expected_class_loader.Get());
+ }
+ }
+};
+
+TEST_F(ClassLinkerClassLoaderTest, CreatePathClassLoader) {
+ jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+ VerifyClassResolution("LDefinedInA;", class_loader_a, class_loader_a);
+ VerifyClassResolution("Ljava/lang/String;", class_loader_a, nullptr);
+ VerifyClassResolution("LDefinedInB;", class_loader_a, nullptr, /*should_find*/ false);
+}
+
+TEST_F(ClassLinkerClassLoaderTest, CreateDelegateLastClassLoader) {
+ jobject class_loader_a = LoadDexInDelegateLastClassLoader("ForClassLoaderA", nullptr);
+ VerifyClassResolution("LDefinedInA;", class_loader_a, class_loader_a);
+ VerifyClassResolution("Ljava/lang/String;", class_loader_a, nullptr);
+ VerifyClassResolution("LDefinedInB;", class_loader_a, nullptr, /*should_find*/ false);
+}
+
+TEST_F(ClassLinkerClassLoaderTest, CreateClassLoaderChain) {
+ // The chain is
+ // ClassLoaderA (PathClassLoader, defines: A, AB, AC, AD)
+ // ^
+ // |
+ // ClassLoaderB (DelegateLastClassLoader, defines: B, AB, BC, BD)
+ // ^
+ // |
+ // ClassLoaderC (PathClassLoader, defines: C, AC, BC, CD)
+ // ^
+ // |
+ // ClassLoaderD (DelegateLastClassLoader, defines: D, AD, BD, CD, Ljava/lang/String;)
+
+ jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+ jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);
+ jobject class_loader_c = LoadDexInPathClassLoader("ForClassLoaderC", class_loader_b);
+ jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c);
+
+ // Verify exclusive classes (present in only one class loader).
+ VerifyClassResolution("LDefinedInD;", class_loader_d, class_loader_d);
+ VerifyClassResolution("LDefinedInC;", class_loader_d, class_loader_c);
+ VerifyClassResolution("LDefinedInB;", class_loader_d, class_loader_b);
+ VerifyClassResolution("LDefinedInA;", class_loader_d, class_loader_a);
+
+ // Verify classes that are defined in multiple classloader.
+
+ // Classes defined in B should be found in B even if they are defined in A or C because
+ // B is a DelegateLastClassLoader.
+ VerifyClassResolution("LDefinedInAB;", class_loader_d, class_loader_b);
+ VerifyClassResolution("LDefinedInABC;", class_loader_d, class_loader_b);
+ VerifyClassResolution("LDefinedInBC;", class_loader_d, class_loader_b);
+
+ // Classes defined in D should be found in D even if they are defined in parent class loaders
+ // as well because D is a DelegateLastClassLoader.
+ VerifyClassResolution("LDefinedInAD;", class_loader_d, class_loader_d);
+ VerifyClassResolution("LDefinedInBD;", class_loader_d, class_loader_d);
+ VerifyClassResolution("LDefinedInCD;", class_loader_d, class_loader_d);
+
+
+ // Classes not defined in the DelegateLastClassLoaders (i.e. D or B) should be found
+ // in the top parent.
+ VerifyClassResolution("LDefinedInAC;", class_loader_d, class_loader_a);
+
+ // Boot classes should be found in the boot class loader even if they are redefined locally.
+ VerifyClassResolution("Ljava/lang/String;", class_loader_d, nullptr);
+ // Sanity check that what seems like a boot class is actually loaded from D.
+ VerifyClassResolution("Ljava/lang/JavaLangFromD;", class_loader_d, class_loader_d);
+
+ // Sanity check that we don't find an undefined class.
+ VerifyClassResolution("LNotDefined;", class_loader_d, nullptr, /*should_find*/ false);
+}
+
} // namespace art
diff --git a/test/ForClassLoaderA/Classes.java b/test/ForClassLoaderA/Classes.java
new file mode 100644
index 0000000..a65ef64
--- /dev/null
+++ b/test/ForClassLoaderA/Classes.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+class DefinedInA {
+}
+
+class DefinedInAB {
+}
+
+class DefinedInABC {
+}
+
+class DefinedInAC {
+}
+
+class DefinedInAD {
+}
+
diff --git a/test/ForClassLoaderB/Classes.java b/test/ForClassLoaderB/Classes.java
new file mode 100644
index 0000000..8c85ed5
--- /dev/null
+++ b/test/ForClassLoaderB/Classes.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+class DefinedInB {
+}
+
+class DefinedInAB {
+}
+
+class DefinedInABC {
+}
+
+class DefinedInBC {
+}
+
+class DefinedInBD {
+}
diff --git a/test/ForClassLoaderC/Classes.java b/test/ForClassLoaderC/Classes.java
new file mode 100644
index 0000000..7b9e83f
--- /dev/null
+++ b/test/ForClassLoaderC/Classes.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+class DefinedInC {
+}
+
+class DefinedInAC {
+}
+
+class DefinedInABC {
+}
+
+class DefinedInBC {
+}
+
+class DefinedInCD {
+}
diff --git a/test/ForClassLoaderD/Classes.java b/test/ForClassLoaderD/Classes.java
new file mode 100644
index 0000000..b34177f
--- /dev/null
+++ b/test/ForClassLoaderD/Classes.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+class DefinedInD {
+}
+
+class DefinedInAD {
+}
+
+class DefinedInBD {
+}
+
+class DefinedInCD {
+}
diff --git a/test/ForClassLoaderD/java/lang/JavaLangFromD.java b/test/ForClassLoaderD/java/lang/JavaLangFromD.java
new file mode 100644
index 0000000..9abae70
--- /dev/null
+++ b/test/ForClassLoaderD/java/lang/JavaLangFromD.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+package java.lang;
+
+public class JavaLangFromD {
+
+}
diff --git a/test/ForClassLoaderD/java/lang/String.java b/test/ForClassLoaderD/java/lang/String.java
new file mode 100644
index 0000000..11afb3d
--- /dev/null
+++ b/test/ForClassLoaderD/java/lang/String.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+public final class String {
+
+}