| /* |
| * 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 com.android.server.pm.dex; |
| |
| import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import android.content.pm.ApplicationInfo; |
| import android.util.SparseArray; |
| |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import dalvik.system.DelegateLastClassLoader; |
| import dalvik.system.DexClassLoader; |
| import dalvik.system.PathClassLoader; |
| |
| import java.io.File; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| public class DexoptUtilsTest { |
| private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName(); |
| private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); |
| private static final String DELEGATE_LAST_CLASS_LOADER_NAME = |
| DelegateLastClassLoader.class.getName(); |
| |
| private static class TestData { |
| ApplicationInfo info; |
| boolean[] pathsWithCode; |
| } |
| |
| private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits, |
| boolean addSplitDependencies) { |
| ApplicationInfo ai = new ApplicationInfo(); |
| String codeDir = "/data/app/mock.android.com"; |
| ai.setBaseCodePath(codeDir + "/base.dex"); |
| ai.classLoaderName = baseClassLoader; |
| ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING; |
| boolean[] pathsWithCode; |
| if (!addSplits) { |
| pathsWithCode = new boolean[] {true}; |
| } else { |
| pathsWithCode = new boolean[9]; |
| Arrays.fill(pathsWithCode, true); |
| pathsWithCode[7] = false; // config split |
| |
| ai.setSplitCodePaths(new String[]{ |
| codeDir + "/base-1.dex", |
| codeDir + "/base-2.dex", |
| codeDir + "/base-3.dex", |
| codeDir + "/base-4.dex", |
| codeDir + "/base-5.dex", |
| codeDir + "/base-6.dex", |
| codeDir + "/config-split-7.dex", |
| codeDir + "/feature-no-deps.dex"}); |
| |
| ai.splitClassLoaderNames = new String[]{ |
| DELEGATE_LAST_CLASS_LOADER_NAME, |
| DELEGATE_LAST_CLASS_LOADER_NAME, |
| PATH_CLASS_LOADER_NAME, |
| DEX_CLASS_LOADER_NAME, |
| PATH_CLASS_LOADER_NAME, |
| null, // A null class loader name should default to PathClassLoader. |
| null, // The config split gets a null class loader. |
| null}; // The feature split with no dependency and no specified class loader. |
| if (addSplitDependencies) { |
| ai.splitDependencies = new SparseArray<>(ai.splitClassLoaderNames.length + 1); |
| ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency |
| ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2 |
| ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4 |
| ai.splitDependencies.put(3, new int[] {4}); // split 3 depends on 4 |
| ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base |
| ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base |
| ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 |
| // Do not add the config split to the dependency list. |
| // Do not add the feature split with no dependency to the dependency list. |
| } |
| } |
| TestData data = new TestData(); |
| data.info = ai; |
| data.pathsWithCode = pathsWithCode; |
| return data; |
| } |
| |
| @Test |
| public void testSplitChain() { |
| TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals("PCL[a.dex:b.dex]", contexts[0]); |
| assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", |
| contexts[1]); |
| assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); |
| assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); |
| assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); |
| assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); |
| assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); |
| assertEquals(null, contexts[7]); // config split |
| assertEquals("PCL[]", contexts[8]); // feature split with no dependency |
| } |
| |
| @Test |
| public void testSplitChainNoSplitDependencies() { |
| TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals("PCL[a.dex:b.dex]", contexts[0]); |
| assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]); |
| assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]); |
| assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex]", contexts[3]); |
| assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); |
| assertEquals( |
| "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", |
| contexts[5]); |
| assertEquals( |
| "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", |
| contexts[6]); |
| assertEquals(null, contexts[7]); // config split |
| assertEquals( |
| "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", |
| contexts[8]); // feature split with no dependency |
| } |
| |
| @Test |
| public void testSplitChainNoIsolationNoSharedLibrary() { |
| TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); |
| data.info.privateFlags = data.info.privateFlags |
| & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, null, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals("PCL[]", contexts[0]); |
| assertEquals("PCL[base.dex]", contexts[1]); |
| assertEquals("PCL[base.dex:base-1.dex]", contexts[2]); |
| assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]); |
| assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); |
| assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]); |
| assertEquals( |
| "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", |
| contexts[6]); |
| assertEquals(null, contexts[7]); // config split |
| assertEquals( |
| "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", |
| contexts[8]); // feature split with no dependency |
| } |
| |
| @Test |
| public void testSplitChainNoSharedLibraries() { |
| TestData data = createMockApplicationInfo( |
| DELEGATE_LAST_CLASS_LOADER_NAME, true, true); |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, null, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals("DLC[]", contexts[0]); |
| assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];DLC[base.dex]", contexts[1]); |
| assertEquals("DLC[];PCL[base-4.dex];DLC[base.dex]", contexts[2]); |
| assertEquals("PCL[];PCL[base-4.dex];DLC[base.dex]", contexts[3]); |
| assertEquals("PCL[];DLC[base.dex]", contexts[4]); |
| assertEquals("PCL[];DLC[base.dex]", contexts[5]); |
| assertEquals("PCL[];PCL[base-5.dex];DLC[base.dex]", contexts[6]); |
| assertEquals(null, contexts[7]); // config split |
| assertEquals("PCL[]", contexts[8]); // feature split with no dependency |
| } |
| |
| @Test |
| public void testSplitChainWithNullPrimaryClassLoader() { |
| // A null classLoaderName should mean PathClassLoader. |
| TestData data = createMockApplicationInfo(null, true, true); |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals("PCL[a.dex:b.dex]", contexts[0]); |
| assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); |
| assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); |
| assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); |
| assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); |
| assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); |
| assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); |
| assertEquals(null, contexts[7]); // config split |
| assertEquals("PCL[]", contexts[8]); // feature split with no dependency |
| } |
| |
| @Test |
| public void tesNoSplits() { |
| TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(1, contexts.length); |
| assertEquals("PCL[a.dex:b.dex]", contexts[0]); |
| } |
| |
| @Test |
| public void tesNoSplitsNullClassLoaderName() { |
| TestData data = createMockApplicationInfo(null, false, false); |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(1, contexts.length); |
| assertEquals("PCL[a.dex:b.dex]", contexts[0]); |
| } |
| |
| @Test |
| public void tesNoSplitDelegateLast() { |
| TestData data = createMockApplicationInfo( |
| DELEGATE_LAST_CLASS_LOADER_NAME, false, false); |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(1, contexts.length); |
| assertEquals("DLC[a.dex:b.dex]", contexts[0]); |
| } |
| |
| @Test |
| public void tesNoSplitsNoSharedLibraries() { |
| TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, null, data.pathsWithCode); |
| |
| assertEquals(1, contexts.length); |
| assertEquals("PCL[]", contexts[0]); |
| } |
| |
| @Test |
| public void tesNoSplitDelegateLastNoSharedLibraries() { |
| TestData data = createMockApplicationInfo( |
| DELEGATE_LAST_CLASS_LOADER_NAME, false, false); |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, null, data.pathsWithCode); |
| |
| assertEquals(1, contexts.length); |
| assertEquals("DLC[]", contexts[0]); |
| } |
| |
| @Test |
| public void testContextWithNoCode() { |
| TestData data = createMockApplicationInfo(null, true, false); |
| Arrays.fill(data.pathsWithCode, false); |
| |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals(null, contexts[0]); |
| assertEquals(null, contexts[1]); |
| assertEquals(null, contexts[2]); |
| assertEquals(null, contexts[3]); |
| assertEquals(null, contexts[4]); |
| assertEquals(null, contexts[5]); |
| assertEquals(null, contexts[6]); |
| assertEquals(null, contexts[7]); |
| } |
| |
| @Test |
| public void testContextBaseNoCode() { |
| TestData data = createMockApplicationInfo(null, true, true); |
| data.pathsWithCode[0] = false; |
| String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; |
| String[] contexts = DexoptUtils.getClassLoaderContexts( |
| data.info, sharedLibrary, data.pathsWithCode); |
| |
| assertEquals(9, contexts.length); |
| assertEquals(null, contexts[0]); |
| assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); |
| assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); |
| assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); |
| assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); |
| assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); |
| assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); |
| assertEquals(null, contexts[7]); |
| } |
| |
| @Test |
| public void testProcessContextForDexLoad() { |
| List<String> classLoaders = Arrays.asList( |
| DELEGATE_LAST_CLASS_LOADER_NAME, |
| PATH_CLASS_LOADER_NAME, |
| PATH_CLASS_LOADER_NAME); |
| List<String> classPaths = Arrays.asList( |
| String.join(File.pathSeparator, "foo.dex", "bar.dex"), |
| String.join(File.pathSeparator, "parent1.dex"), |
| String.join(File.pathSeparator, "parent2.dex", "parent3.dex")); |
| String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); |
| assertNotNull(context); |
| assertEquals(2, context.length); |
| assertEquals("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); |
| assertEquals("DLC[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); |
| } |
| |
| @Test |
| public void testProcessContextForDexLoadSingleElement() { |
| List<String> classLoaders = Arrays.asList(PATH_CLASS_LOADER_NAME); |
| List<String> classPaths = Arrays.asList( |
| String.join(File.pathSeparator, "foo.dex", "bar.dex", "zoo.dex")); |
| String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); |
| assertNotNull(context); |
| assertEquals(3, context.length); |
| assertEquals("PCL[]", context[0]); |
| assertEquals("PCL[foo.dex]", context[1]); |
| assertEquals("PCL[foo.dex:bar.dex]", context[2]); |
| } |
| |
| @Test |
| public void testProcessContextForDexLoadUnsupported() { |
| List<String> classLoaders = Arrays.asList( |
| DELEGATE_LAST_CLASS_LOADER_NAME, |
| "unsupported.class.loader"); |
| List<String> classPaths = Arrays.asList( |
| String.join(File.pathSeparator, "foo.dex", "bar.dex"), |
| String.join(File.pathSeparator, "parent1.dex")); |
| String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); |
| assertNull(context); |
| } |
| |
| @Test |
| public void testProcessContextForDexLoadIllegalCallEmptyList() { |
| boolean gotException = false; |
| try { |
| DexoptUtils.processContextForDexLoad(Collections.emptyList(), Collections.emptyList()); |
| } catch (IllegalArgumentException ignore) { |
| gotException = true; |
| } |
| assertTrue(gotException); |
| } |
| |
| @Test |
| public void testProcessContextForDexLoadIllegalCallDifferentSize() { |
| boolean gotException = false; |
| try { |
| DexoptUtils.processContextForDexLoad(Collections.emptyList(), Arrays.asList("a")); |
| } catch (IllegalArgumentException ignore) { |
| gotException = true; |
| } |
| assertTrue(gotException); |
| } |
| |
| @Test |
| public void testEncodeClassLoader() { |
| assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( |
| SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.PathClassLoader")); |
| assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( |
| SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DexClassLoader")); |
| assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( |
| SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DelegateLastClassLoader")); |
| assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", |
| "dalvik.system.PathClassLoader")); |
| assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", |
| "dalvik.system.DexClassLoader")); |
| assertEquals("DLC[xyz]", DexoptUtils.encodeClassLoader("xyz", |
| "dalvik.system.DelegateLastClassLoader")); |
| assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", null)); |
| assertEquals("abc[xyz]", DexoptUtils.encodeClassLoader("xyz", "abc")); |
| |
| try { |
| DexoptUtils.encodeClassLoader(null, "abc"); |
| fail(); // Exception should be caught. |
| } catch (NullPointerException expected) {} |
| } |
| |
| @Test |
| public void testEncodeClassLoaderChain() { |
| assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain( |
| SKIP_SHARED_LIBRARY_CHECK, "PCL[a]")); |
| assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", |
| SKIP_SHARED_LIBRARY_CHECK)); |
| assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]", |
| "DLC[b]")); |
| assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", |
| SKIP_SHARED_LIBRARY_CHECK)); |
| |
| try { |
| DexoptUtils.encodeClassLoaderChain("a", null); |
| fail(); // exception is expected |
| } catch (NullPointerException expected) {} |
| |
| try { |
| DexoptUtils.encodeClassLoaderChain(null, "b"); |
| fail(); // exception is expected |
| } catch (NullPointerException expected) {} |
| } |
| } |