1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.pm.dex; 18 19 import com.android.server.pm.PackageDexOptimizer; 20 21 import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 28 import android.content.pm.ApplicationInfo; 29 import android.support.test.filters.SmallTest; 30 import android.support.test.runner.AndroidJUnit4; 31 import android.util.SparseArray; 32 33 import dalvik.system.DelegateLastClassLoader; 34 import dalvik.system.DexClassLoader; 35 import dalvik.system.PathClassLoader; 36 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 40 import java.io.File; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.List; 44 45 @RunWith(AndroidJUnit4.class) 46 @SmallTest 47 public class DexoptUtilsTest { 48 private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName(); 49 private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); 50 private static final String DELEGATE_LAST_CLASS_LOADER_NAME = 51 DelegateLastClassLoader.class.getName(); 52 53 private static class TestData { 54 ApplicationInfo info; 55 boolean[] pathsWithCode; 56 } 57 58 private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits, 59 boolean addSplitDependencies) { 60 ApplicationInfo ai = new ApplicationInfo(); 61 String codeDir = "/data/app/mock.android.com"; 62 ai.setBaseCodePath(codeDir + "/base.dex"); 63 ai.classLoaderName = baseClassLoader; 64 ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING; 65 boolean[] pathsWithCode; 66 if (!addSplits) { 67 pathsWithCode = new boolean[] {true}; 68 } else { 69 pathsWithCode = new boolean[9]; 70 Arrays.fill(pathsWithCode, true); 71 pathsWithCode[7] = false; // config split 72 73 ai.setSplitCodePaths(new String[]{ 74 codeDir + "/base-1.dex", 75 codeDir + "/base-2.dex", 76 codeDir + "/base-3.dex", 77 codeDir + "/base-4.dex", 78 codeDir + "/base-5.dex", 79 codeDir + "/base-6.dex", 80 codeDir + "/config-split-7.dex", 81 codeDir + "/feature-no-deps.dex"}); 82 83 ai.splitClassLoaderNames = new String[]{ 84 DELEGATE_LAST_CLASS_LOADER_NAME, 85 DELEGATE_LAST_CLASS_LOADER_NAME, 86 PATH_CLASS_LOADER_NAME, 87 DEX_CLASS_LOADER_NAME, 88 PATH_CLASS_LOADER_NAME, 89 null, // A null class loader name should default to PathClassLoader. 90 null, // The config split gets a null class loader. 91 null}; // The feature split with no dependency and no specified class loader. 92 if (addSplitDependencies) { 93 ai.splitDependencies = new SparseArray<>(ai.splitClassLoaderNames.length + 1); 94 ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency 95 ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2 96 ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4 97 ai.splitDependencies.put(3, new int[] {4}); // split 3 depends on 4 98 ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base 99 ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base 100 ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 101 // Do not add the config split to the dependency list. 102 // Do not add the feature split with no dependency to the dependency list. 103 } 104 } 105 TestData data = new TestData(); 106 data.info = ai; 107 data.pathsWithCode = pathsWithCode; 108 return data; 109 } 110 111 @Test 112 public void testSplitChain() { 113 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); 114 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 115 String[] contexts = DexoptUtils.getClassLoaderContexts( 116 data.info, sharedLibrary, data.pathsWithCode); 117 118 assertEquals(9, contexts.length); 119 assertEquals("PCL[a.dex:b.dex]", contexts[0]); 120 assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", 121 contexts[1]); 122 assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); 123 assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); 124 assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); 125 assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); 126 assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); 127 assertEquals(null, contexts[7]); // config split 128 assertEquals("PCL[]", contexts[8]); // feature split with no dependency 129 } 130 131 @Test 132 public void testSplitChainNoSplitDependencies() { 133 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); 134 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 135 String[] contexts = DexoptUtils.getClassLoaderContexts( 136 data.info, sharedLibrary, data.pathsWithCode); 137 138 assertEquals(9, contexts.length); 139 assertEquals("PCL[a.dex:b.dex]", contexts[0]); 140 assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]); 141 assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]); 142 assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex]", contexts[3]); 143 assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); 144 assertEquals( 145 "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", 146 contexts[5]); 147 assertEquals( 148 "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", 149 contexts[6]); 150 assertEquals(null, contexts[7]); // config split 151 assertEquals( 152 "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]", 153 contexts[8]); // feature split with no dependency 154 } 155 156 @Test 157 public void testSplitChainNoIsolationNoSharedLibrary() { 158 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); 159 data.info.privateFlags = data.info.privateFlags 160 & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); 161 String[] contexts = DexoptUtils.getClassLoaderContexts( 162 data.info, null, data.pathsWithCode); 163 164 assertEquals(9, contexts.length); 165 assertEquals("PCL[]", contexts[0]); 166 assertEquals("PCL[base.dex]", contexts[1]); 167 assertEquals("PCL[base.dex:base-1.dex]", contexts[2]); 168 assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]); 169 assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); 170 assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]); 171 assertEquals( 172 "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", 173 contexts[6]); 174 assertEquals(null, contexts[7]); // config split 175 assertEquals( 176 "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]", 177 contexts[8]); // feature split with no dependency 178 } 179 180 @Test 181 public void testSplitChainNoSharedLibraries() { 182 TestData data = createMockApplicationInfo( 183 DELEGATE_LAST_CLASS_LOADER_NAME, true, true); 184 String[] contexts = DexoptUtils.getClassLoaderContexts( 185 data.info, null, data.pathsWithCode); 186 187 assertEquals(9, contexts.length); 188 assertEquals("DLC[]", contexts[0]); 189 assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];DLC[base.dex]", contexts[1]); 190 assertEquals("DLC[];PCL[base-4.dex];DLC[base.dex]", contexts[2]); 191 assertEquals("PCL[];PCL[base-4.dex];DLC[base.dex]", contexts[3]); 192 assertEquals("PCL[];DLC[base.dex]", contexts[4]); 193 assertEquals("PCL[];DLC[base.dex]", contexts[5]); 194 assertEquals("PCL[];PCL[base-5.dex];DLC[base.dex]", contexts[6]); 195 assertEquals(null, contexts[7]); // config split 196 assertEquals("PCL[]", contexts[8]); // feature split with no dependency 197 } 198 199 @Test 200 public void testSplitChainWithNullPrimaryClassLoader() { 201 // A null classLoaderName should mean PathClassLoader. 202 TestData data = createMockApplicationInfo(null, true, true); 203 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 204 String[] contexts = DexoptUtils.getClassLoaderContexts( 205 data.info, sharedLibrary, data.pathsWithCode); 206 207 assertEquals(9, contexts.length); 208 assertEquals("PCL[a.dex:b.dex]", contexts[0]); 209 assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); 210 assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); 211 assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); 212 assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); 213 assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); 214 assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); 215 assertEquals(null, contexts[7]); // config split 216 assertEquals("PCL[]", contexts[8]); // feature split with no dependency 217 } 218 219 @Test 220 public void tesNoSplits() { 221 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); 222 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 223 String[] contexts = DexoptUtils.getClassLoaderContexts( 224 data.info, sharedLibrary, data.pathsWithCode); 225 226 assertEquals(1, contexts.length); 227 assertEquals("PCL[a.dex:b.dex]", contexts[0]); 228 } 229 230 @Test 231 public void tesNoSplitsNullClassLoaderName() { 232 TestData data = createMockApplicationInfo(null, false, false); 233 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 234 String[] contexts = DexoptUtils.getClassLoaderContexts( 235 data.info, sharedLibrary, data.pathsWithCode); 236 237 assertEquals(1, contexts.length); 238 assertEquals("PCL[a.dex:b.dex]", contexts[0]); 239 } 240 241 @Test 242 public void tesNoSplitDelegateLast() { 243 TestData data = createMockApplicationInfo( 244 DELEGATE_LAST_CLASS_LOADER_NAME, false, false); 245 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 246 String[] contexts = DexoptUtils.getClassLoaderContexts( 247 data.info, sharedLibrary, data.pathsWithCode); 248 249 assertEquals(1, contexts.length); 250 assertEquals("DLC[a.dex:b.dex]", contexts[0]); 251 } 252 253 @Test 254 public void tesNoSplitsNoSharedLibraries() { 255 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); 256 String[] contexts = DexoptUtils.getClassLoaderContexts( 257 data.info, null, data.pathsWithCode); 258 259 assertEquals(1, contexts.length); 260 assertEquals("PCL[]", contexts[0]); 261 } 262 263 @Test 264 public void tesNoSplitDelegateLastNoSharedLibraries() { 265 TestData data = createMockApplicationInfo( 266 DELEGATE_LAST_CLASS_LOADER_NAME, false, false); 267 String[] contexts = DexoptUtils.getClassLoaderContexts( 268 data.info, null, data.pathsWithCode); 269 270 assertEquals(1, contexts.length); 271 assertEquals("DLC[]", contexts[0]); 272 } 273 274 @Test 275 public void testContextWithNoCode() { 276 TestData data = createMockApplicationInfo(null, true, false); 277 Arrays.fill(data.pathsWithCode, false); 278 279 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 280 String[] contexts = DexoptUtils.getClassLoaderContexts( 281 data.info, sharedLibrary, data.pathsWithCode); 282 283 assertEquals(9, contexts.length); 284 assertEquals(null, contexts[0]); 285 assertEquals(null, contexts[1]); 286 assertEquals(null, contexts[2]); 287 assertEquals(null, contexts[3]); 288 assertEquals(null, contexts[4]); 289 assertEquals(null, contexts[5]); 290 assertEquals(null, contexts[6]); 291 assertEquals(null, contexts[7]); 292 } 293 294 @Test 295 public void testContextBaseNoCode() { 296 TestData data = createMockApplicationInfo(null, true, true); 297 data.pathsWithCode[0] = false; 298 String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; 299 String[] contexts = DexoptUtils.getClassLoaderContexts( 300 data.info, sharedLibrary, data.pathsWithCode); 301 302 assertEquals(9, contexts.length); 303 assertEquals(null, contexts[0]); 304 assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); 305 assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); 306 assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); 307 assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); 308 assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); 309 assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); 310 assertEquals(null, contexts[7]); 311 } 312 313 @Test 314 public void testProcessContextForDexLoad() { 315 List<String> classLoaders = Arrays.asList( 316 DELEGATE_LAST_CLASS_LOADER_NAME, 317 PATH_CLASS_LOADER_NAME, 318 PATH_CLASS_LOADER_NAME); 319 List<String> classPaths = Arrays.asList( 320 String.join(File.pathSeparator, "foo.dex", "bar.dex"), 321 String.join(File.pathSeparator, "parent1.dex"), 322 String.join(File.pathSeparator, "parent2.dex", "parent3.dex")); 323 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 324 assertNotNull(context); 325 assertEquals(2, context.length); 326 assertEquals("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); 327 assertEquals("DLC[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); 328 } 329 330 @Test 331 public void testProcessContextForDexLoadSingleElement() { 332 List<String> classLoaders = Arrays.asList(PATH_CLASS_LOADER_NAME); 333 List<String> classPaths = Arrays.asList( 334 String.join(File.pathSeparator, "foo.dex", "bar.dex", "zoo.dex")); 335 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 336 assertNotNull(context); 337 assertEquals(3, context.length); 338 assertEquals("PCL[]", context[0]); 339 assertEquals("PCL[foo.dex]", context[1]); 340 assertEquals("PCL[foo.dex:bar.dex]", context[2]); 341 } 342 343 @Test 344 public void testProcessContextForDexLoadUnsupported() { 345 List<String> classLoaders = Arrays.asList( 346 DELEGATE_LAST_CLASS_LOADER_NAME, 347 "unsupported.class.loader"); 348 List<String> classPaths = Arrays.asList( 349 String.join(File.pathSeparator, "foo.dex", "bar.dex"), 350 String.join(File.pathSeparator, "parent1.dex")); 351 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 352 assertNull(context); 353 } 354 355 @Test 356 public void testProcessContextForDexLoadIllegalCallEmptyList() { 357 boolean gotException = false; 358 try { 359 DexoptUtils.processContextForDexLoad(Collections.emptyList(), Collections.emptyList()); 360 } catch (IllegalArgumentException ignore) { 361 gotException = true; 362 } 363 assertTrue(gotException); 364 } 365 366 @Test 367 public void testProcessContextForDexLoadIllegalCallDifferentSize() { 368 boolean gotException = false; 369 try { 370 DexoptUtils.processContextForDexLoad(Collections.emptyList(), Arrays.asList("a")); 371 } catch (IllegalArgumentException ignore) { 372 gotException = true; 373 } 374 assertTrue(gotException); 375 } 376 377 @Test 378 public void testEncodeClassLoader() { 379 assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( 380 SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.PathClassLoader")); 381 assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( 382 SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DexClassLoader")); 383 assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( 384 SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DelegateLastClassLoader")); 385 assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", 386 "dalvik.system.PathClassLoader")); 387 assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", 388 "dalvik.system.DexClassLoader")); 389 assertEquals("DLC[xyz]", DexoptUtils.encodeClassLoader("xyz", 390 "dalvik.system.DelegateLastClassLoader")); 391 assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", null)); 392 assertEquals("abc[xyz]", DexoptUtils.encodeClassLoader("xyz", "abc")); 393 394 try { 395 DexoptUtils.encodeClassLoader(null, "abc"); 396 fail(); // Exception should be caught. 397 } catch (NullPointerException expected) {} 398 } 399 400 @Test 401 public void testEncodeClassLoaderChain() { 402 assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain( 403 SKIP_SHARED_LIBRARY_CHECK, "PCL[a]")); 404 assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", 405 SKIP_SHARED_LIBRARY_CHECK)); 406 assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]", 407 "DLC[b]")); 408 assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", 409 SKIP_SHARED_LIBRARY_CHECK)); 410 411 try { 412 DexoptUtils.encodeClassLoaderChain("a", null); 413 fail(); // exception is expected 414 } catch (NullPointerException expected) {} 415 416 try { 417 DexoptUtils.encodeClassLoaderChain(null, "b"); 418 fail(); // exception is expected 419 } catch (NullPointerException expected) {} 420 } 421 } 422