blob: 66a4946ecc20db2fca2b80362d7485a431625fb9 [file] [log] [blame]
/*
* 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 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.SharedLibraryInfo;
import android.content.pm.parsing.AndroidPackage;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.ParsedPackage;
import android.content.pm.parsing.ParsingPackage;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import dalvik.system.DelegateLastClassLoader;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.ArrayList;
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 {
AndroidPackage pkg;
boolean[] pathsWithCode;
}
private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits,
boolean addSplitDependencies, boolean isolatedSplitLoading) {
String codeDir = "/data/app/mock.android.com";
ParsingPackage parsingPackage = PackageImpl.forParsing("mock.android.com")
.setClassLoaderName(baseClassLoader);
parsingPackage.setIsolatedSplitLoading(isolatedSplitLoading);
boolean[] pathsWithCode;
if (!addSplits) {
pathsWithCode = new boolean[] {true};
} else {
pathsWithCode = new boolean[9];
Arrays.fill(pathsWithCode, true);
pathsWithCode[7] = false; // config split
String[] splitCodePaths = 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"
};
String[] splitNames = new String[splitCodePaths.length];
int[] splitRevisionCodes = new int[splitCodePaths.length];
SparseArray<int[]> splitDependencies = null;
if (addSplitDependencies) {
splitDependencies = new SparseArray<>(splitCodePaths.length);
splitDependencies.put(0, new int[] {-1}); // base has no dependency
splitDependencies.put(1, new int[] {2}); // split 1 depends on 2
splitDependencies.put(2, new int[] {4}); // split 2 depends on 4
splitDependencies.put(3, new int[] {4}); // split 3 depends on 4
splitDependencies.put(4, new int[] {0}); // split 4 depends on base
splitDependencies.put(5, new int[] {0}); // split 5 depends on base
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.
}
parsingPackage
.asSplit(
splitNames,
splitCodePaths,
splitRevisionCodes,
splitDependencies
)
.setSplitClassLoaderName(0, DELEGATE_LAST_CLASS_LOADER_NAME)
.setSplitClassLoaderName(1, DELEGATE_LAST_CLASS_LOADER_NAME)
.setSplitClassLoaderName(2, PATH_CLASS_LOADER_NAME)
.setSplitClassLoaderName(3, DEX_CLASS_LOADER_NAME)
.setSplitClassLoaderName(4, PATH_CLASS_LOADER_NAME)
// A null class loader name should default to PathClassLoader
.setSplitClassLoaderName(5, null)
// The config split gets a null class loader
.setSplitClassLoaderName(6, null)
// The feature split with no dependency and no specified class loader.
.setSplitClassLoaderName(7, null);
}
ParsedPackage parsedPackage = parsingPackage.hideAsParsed()
.setBaseCodePath(codeDir + "/base.dex");
TestData data = new TestData();
data.pkg = parsedPackage.hideAsFinal();
data.pathsWithCode = pathsWithCode;
return data;
}
private List<SharedLibraryInfo> createMockSharedLibrary(String [] sharedLibrary) {
SharedLibraryInfo info = new SharedLibraryInfo(null, null, Arrays.asList(sharedLibrary),
null, 0L, SharedLibraryInfo.TYPE_STATIC, null, null, null);
ArrayList<SharedLibraryInfo> libraries = new ArrayList<>();
libraries.add(info);
return libraries;
}
@Test
public void testSplitChain() {
TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true, true);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(9, contexts.length);
assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]);
assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}",
contexts[1]);
assertEquals("DLC[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[2]);
assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[3]);
assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[4]);
assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[5]);
assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]{PCL[a.dex:b.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, true);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(9, contexts.length);
assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]);
assertEquals("PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[1]);
assertEquals("PCL[base.dex:base-1.dex]{PCL[a.dex:b.dex]}", contexts[2]);
assertEquals("PCL[base.dex:base-1.dex:base-2.dex]{PCL[a.dex:b.dex]}", contexts[3]);
assertEquals(
"PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]{PCL[a.dex:b.dex]}",
contexts[4]);
assertEquals(
"PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]{PCL[a.dex:b.dex]}",
contexts[5]);
assertEquals(
"PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]{PCL[a.dex:b.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]{PCL[a.dex:b.dex]}",
contexts[8]); // feature split with no dependency
}
@Test
public void testSplitChainNoIsolationNoSharedLibrary() {
TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true, false);
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, 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, true);
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, 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, true);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(9, contexts.length);
assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]);
assertEquals(
"DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}",
contexts[1]);
assertEquals("DLC[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[2]);
assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[3]);
assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[4]);
assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[5]);
assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]{PCL[a.dex:b.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, true);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(1, contexts.length);
assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]);
}
@Test
public void tesNoSplitsNullClassLoaderName() {
TestData data = createMockApplicationInfo(null, false, false, true);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(1, contexts.length);
assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]);
}
@Test
public void tesNoSplitDelegateLast() {
TestData data = createMockApplicationInfo(
DELEGATE_LAST_CLASS_LOADER_NAME, false, false, true);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(1, contexts.length);
assertEquals("DLC[]{PCL[a.dex:b.dex]}", contexts[0]);
}
@Test
public void tesNoSplitsNoSharedLibraries() {
TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false, true);
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, 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, true);
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, null, data.pathsWithCode);
assertEquals(1, contexts.length);
assertEquals("DLC[]", contexts[0]);
}
@Test
public void testContextWithNoCode() {
TestData data = createMockApplicationInfo(null, true, false, true);
Arrays.fill(data.pathsWithCode, false);
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, 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, true);
data.pathsWithCode[0] = false;
List<SharedLibraryInfo> sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"});
String[] contexts = DexoptUtils.getClassLoaderContexts(
data.pkg, sharedLibrary, data.pathsWithCode);
assertEquals(9, contexts.length);
assertEquals(null, contexts[0]);
assertEquals(
"DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}",
contexts[1]);
assertEquals("DLC[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[2]);
assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[3]);
assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[4]);
assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[5]);
assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[6]);
assertEquals(null, contexts[7]);
}
@Test
public void testSharedLibraryContext() {
SharedLibraryInfo sharedLibrary =
createMockSharedLibrary(new String[] {"a.dex", "b.dex"}).get(0);
String context = DexoptUtils.getClassLoaderContext(sharedLibrary);
assertEquals("PCL[]", context);
SharedLibraryInfo otherSharedLibrary =
createMockSharedLibrary(new String[] {"c.dex"}).get(0);
otherSharedLibrary.addDependency(sharedLibrary);
context = DexoptUtils.getClassLoaderContext(otherSharedLibrary);
assertEquals("PCL[]{PCL[a.dex:b.dex]}", context);
}
@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 testProcessContextForDexLoadNoClassPath() {
List<String> classLoaders = Arrays.asList(
DELEGATE_LAST_CLASS_LOADER_NAME,
PATH_CLASS_LOADER_NAME);
List<String> classPaths = Arrays.asList(
String.join(File.pathSeparator, "foo.dex", "bar.dex"),
null);
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("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("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]",
"DLC[b]"));
try {
DexoptUtils.encodeClassLoaderChain("a", null);
fail(); // exception is expected
} catch (NullPointerException expected) {}
try {
DexoptUtils.encodeClassLoaderChain(null, "b");
fail(); // exception is expected
} catch (NullPointerException expected) {}
}
}