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 static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.content.pm.SharedLibraryInfo; 26 import android.platform.test.annotations.Presubmit; 27 import android.util.SparseArray; 28 29 import androidx.test.filters.SmallTest; 30 import androidx.test.runner.AndroidJUnit4; 31 32 import com.android.server.pm.parsing.pkg.PackageImpl; 33 import com.android.server.pm.parsing.pkg.ParsedPackage; 34 import com.android.server.pm.pkg.AndroidPackage; 35 import com.android.server.pm.pkg.parsing.ParsingPackage; 36 37 import dalvik.system.DelegateLastClassLoader; 38 import dalvik.system.DexClassLoader; 39 import dalvik.system.PathClassLoader; 40 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.io.File; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collections; 48 import java.util.List; 49 50 @Presubmit 51 @RunWith(AndroidJUnit4.class) 52 @SmallTest 53 public class DexoptUtilsTest { 54 private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName(); 55 private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); 56 private static final String DELEGATE_LAST_CLASS_LOADER_NAME = 57 DelegateLastClassLoader.class.getName(); 58 59 private static class TestData { 60 AndroidPackage pkg; 61 boolean[] pathsWithCode; 62 } 63 createMockApplicationInfo(String baseClassLoader, boolean addSplits, boolean addSplitDependencies, boolean isolatedSplitLoading)64 private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits, 65 boolean addSplitDependencies, boolean isolatedSplitLoading) { 66 String codeDir = "/data/app/mock.android.com"; 67 ParsingPackage parsingPackage = PackageImpl.forTesting("mock.android.com", 68 codeDir + "/base.dex") 69 .setClassLoaderName(baseClassLoader); 70 71 parsingPackage.setIsolatedSplitLoading(isolatedSplitLoading); 72 73 boolean[] pathsWithCode; 74 if (!addSplits) { 75 pathsWithCode = new boolean[] {true}; 76 } else { 77 pathsWithCode = new boolean[9]; 78 Arrays.fill(pathsWithCode, true); 79 pathsWithCode[7] = false; // config split 80 81 String[] splitCodePaths = new String[]{ 82 codeDir + "/base-1.dex", 83 codeDir + "/base-2.dex", 84 codeDir + "/base-3.dex", 85 codeDir + "/base-4.dex", 86 codeDir + "/base-5.dex", 87 codeDir + "/base-6.dex", 88 codeDir + "/config-split-7.dex", 89 codeDir + "/feature-no-deps.dex" 90 }; 91 92 String[] splitNames = new String[splitCodePaths.length]; 93 int[] splitRevisionCodes = new int[splitCodePaths.length]; 94 SparseArray<int[]> splitDependencies = null; 95 96 if (addSplitDependencies) { 97 splitDependencies = new SparseArray<>(splitCodePaths.length); 98 splitDependencies.put(0, new int[] {-1}); // base has no dependency 99 splitDependencies.put(1, new int[] {2}); // split 1 depends on 2 100 splitDependencies.put(2, new int[] {4}); // split 2 depends on 4 101 splitDependencies.put(3, new int[] {4}); // split 3 depends on 4 102 splitDependencies.put(4, new int[] {0}); // split 4 depends on base 103 splitDependencies.put(5, new int[] {0}); // split 5 depends on base 104 splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 105 // Do not add the config split to the dependency list. 106 // Do not add the feature split with no dependency to the dependency list. 107 } 108 109 parsingPackage 110 .asSplit( 111 splitNames, 112 splitCodePaths, 113 splitRevisionCodes, 114 splitDependencies 115 ) 116 .setSplitClassLoaderName(0, DELEGATE_LAST_CLASS_LOADER_NAME) 117 .setSplitClassLoaderName(1, DELEGATE_LAST_CLASS_LOADER_NAME) 118 .setSplitClassLoaderName(2, PATH_CLASS_LOADER_NAME) 119 .setSplitClassLoaderName(3, DEX_CLASS_LOADER_NAME) 120 .setSplitClassLoaderName(4, PATH_CLASS_LOADER_NAME) 121 // A null class loader name should default to PathClassLoader 122 .setSplitClassLoaderName(5, null) 123 // The config split gets a null class loader 124 .setSplitClassLoaderName(6, null) 125 // The feature split with no dependency and no specified class loader. 126 .setSplitClassLoaderName(7, null); 127 } 128 129 ParsedPackage parsedPackage = (ParsedPackage) parsingPackage.hideAsParsed(); 130 131 TestData data = new TestData(); 132 data.pkg = parsedPackage.hideAsFinal(); 133 data.pathsWithCode = pathsWithCode; 134 return data; 135 } 136 createMockSharedLibrary(String [] sharedLibrary)137 private List<SharedLibraryInfo> createMockSharedLibrary(String [] sharedLibrary) { 138 SharedLibraryInfo info = new SharedLibraryInfo(null, null, Arrays.asList(sharedLibrary), 139 null, 0L, SharedLibraryInfo.TYPE_STATIC, null, null, null, false /* isNative */); 140 ArrayList<SharedLibraryInfo> libraries = new ArrayList<>(); 141 libraries.add(info); 142 return libraries; 143 } 144 145 @Test testSplitChain()146 public void testSplitChain() { 147 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true, true); 148 List<SharedLibraryInfo> sharedLibrary = 149 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 150 String[] contexts = DexoptUtils.getClassLoaderContexts( 151 data.pkg, sharedLibrary, data.pathsWithCode); 152 153 assertEquals(9, contexts.length); 154 assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]); 155 assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", 156 contexts[1]); 157 assertEquals("DLC[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[2]); 158 assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[3]); 159 assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[4]); 160 assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[5]); 161 assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[6]); 162 assertEquals(null, contexts[7]); // config split 163 assertEquals("PCL[]", contexts[8]); // feature split with no dependency 164 } 165 166 @Test testSplitChainNoSplitDependencies()167 public void testSplitChainNoSplitDependencies() { 168 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false, true); 169 List<SharedLibraryInfo> sharedLibrary = 170 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 171 String[] contexts = DexoptUtils.getClassLoaderContexts( 172 data.pkg, sharedLibrary, data.pathsWithCode); 173 174 assertEquals(9, contexts.length); 175 assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]); 176 assertEquals("PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[1]); 177 assertEquals("PCL[base.dex:base-1.dex]{PCL[a.dex:b.dex]}", contexts[2]); 178 assertEquals("PCL[base.dex:base-1.dex:base-2.dex]{PCL[a.dex:b.dex]}", contexts[3]); 179 assertEquals( 180 "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]{PCL[a.dex:b.dex]}", 181 contexts[4]); 182 assertEquals( 183 "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]{PCL[a.dex:b.dex]}", 184 contexts[5]); 185 assertEquals( 186 "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]{PCL[a.dex:b.dex]}", 187 contexts[6]); 188 assertEquals(null, contexts[7]); // config split 189 assertEquals( 190 "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]}", 191 contexts[8]); // feature split with no dependency 192 } 193 194 @Test testSplitChainNoIsolationNoSharedLibrary()195 public void testSplitChainNoIsolationNoSharedLibrary() { 196 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true, false); 197 String[] contexts = DexoptUtils.getClassLoaderContexts( 198 data.pkg, null, data.pathsWithCode); 199 200 assertEquals(9, contexts.length); 201 assertEquals("PCL[]", contexts[0]); 202 assertEquals("PCL[base.dex]", contexts[1]); 203 assertEquals("PCL[base.dex:base-1.dex]", contexts[2]); 204 assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]); 205 assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); 206 assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]); 207 assertEquals( 208 "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", 209 contexts[6]); 210 assertEquals(null, contexts[7]); // config split 211 assertEquals( 212 "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]", 213 contexts[8]); // feature split with no dependency 214 } 215 216 @Test testSplitChainNoSharedLibraries()217 public void testSplitChainNoSharedLibraries() { 218 TestData data = createMockApplicationInfo( 219 DELEGATE_LAST_CLASS_LOADER_NAME, true, true, true); 220 String[] contexts = DexoptUtils.getClassLoaderContexts( 221 data.pkg, null, data.pathsWithCode); 222 223 assertEquals(9, contexts.length); 224 assertEquals("DLC[]", contexts[0]); 225 assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];DLC[base.dex]", contexts[1]); 226 assertEquals("DLC[];PCL[base-4.dex];DLC[base.dex]", contexts[2]); 227 assertEquals("PCL[];PCL[base-4.dex];DLC[base.dex]", contexts[3]); 228 assertEquals("PCL[];DLC[base.dex]", contexts[4]); 229 assertEquals("PCL[];DLC[base.dex]", contexts[5]); 230 assertEquals("PCL[];PCL[base-5.dex];DLC[base.dex]", contexts[6]); 231 assertEquals(null, contexts[7]); // config split 232 assertEquals("PCL[]", contexts[8]); // feature split with no dependency 233 } 234 235 @Test testSplitChainWithNullPrimaryClassLoader()236 public void testSplitChainWithNullPrimaryClassLoader() { 237 // A null classLoaderName should mean PathClassLoader. 238 TestData data = createMockApplicationInfo(null, true, true, true); 239 List<SharedLibraryInfo> sharedLibrary = 240 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 241 String[] contexts = DexoptUtils.getClassLoaderContexts( 242 data.pkg, sharedLibrary, data.pathsWithCode); 243 244 assertEquals(9, contexts.length); 245 assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]); 246 assertEquals( 247 "DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", 248 contexts[1]); 249 assertEquals("DLC[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[2]); 250 assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[3]); 251 assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[4]); 252 assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[5]); 253 assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[6]); 254 assertEquals(null, contexts[7]); // config split 255 assertEquals("PCL[]", contexts[8]); // feature split with no dependency 256 } 257 258 @Test tesNoSplits()259 public void tesNoSplits() { 260 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false, true); 261 List<SharedLibraryInfo> sharedLibrary = 262 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 263 String[] contexts = DexoptUtils.getClassLoaderContexts( 264 data.pkg, sharedLibrary, data.pathsWithCode); 265 266 assertEquals(1, contexts.length); 267 assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]); 268 } 269 270 @Test tesNoSplitsNullClassLoaderName()271 public void tesNoSplitsNullClassLoaderName() { 272 TestData data = createMockApplicationInfo(null, false, false, true); 273 List<SharedLibraryInfo> sharedLibrary = 274 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 275 String[] contexts = DexoptUtils.getClassLoaderContexts( 276 data.pkg, sharedLibrary, data.pathsWithCode); 277 278 assertEquals(1, contexts.length); 279 assertEquals("PCL[]{PCL[a.dex:b.dex]}", contexts[0]); 280 } 281 282 @Test tesNoSplitDelegateLast()283 public void tesNoSplitDelegateLast() { 284 TestData data = createMockApplicationInfo( 285 DELEGATE_LAST_CLASS_LOADER_NAME, false, false, true); 286 List<SharedLibraryInfo> sharedLibrary = 287 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 288 String[] contexts = DexoptUtils.getClassLoaderContexts( 289 data.pkg, sharedLibrary, data.pathsWithCode); 290 291 assertEquals(1, contexts.length); 292 assertEquals("DLC[]{PCL[a.dex:b.dex]}", contexts[0]); 293 } 294 295 @Test tesNoSplitsNoSharedLibraries()296 public void tesNoSplitsNoSharedLibraries() { 297 TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false, true); 298 String[] contexts = DexoptUtils.getClassLoaderContexts( 299 data.pkg, null, data.pathsWithCode); 300 301 assertEquals(1, contexts.length); 302 assertEquals("PCL[]", contexts[0]); 303 } 304 305 @Test tesNoSplitDelegateLastNoSharedLibraries()306 public void tesNoSplitDelegateLastNoSharedLibraries() { 307 TestData data = createMockApplicationInfo( 308 DELEGATE_LAST_CLASS_LOADER_NAME, false, false, true); 309 String[] contexts = DexoptUtils.getClassLoaderContexts( 310 data.pkg, null, data.pathsWithCode); 311 312 assertEquals(1, contexts.length); 313 assertEquals("DLC[]", contexts[0]); 314 } 315 316 @Test testContextWithNoCode()317 public void testContextWithNoCode() { 318 TestData data = createMockApplicationInfo(null, true, false, true); 319 Arrays.fill(data.pathsWithCode, false); 320 321 List<SharedLibraryInfo> sharedLibrary = 322 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 323 String[] contexts = DexoptUtils.getClassLoaderContexts( 324 data.pkg, sharedLibrary, data.pathsWithCode); 325 326 assertEquals(9, contexts.length); 327 assertEquals(null, contexts[0]); 328 assertEquals(null, contexts[1]); 329 assertEquals(null, contexts[2]); 330 assertEquals(null, contexts[3]); 331 assertEquals(null, contexts[4]); 332 assertEquals(null, contexts[5]); 333 assertEquals(null, contexts[6]); 334 assertEquals(null, contexts[7]); 335 } 336 337 @Test testContextBaseNoCode()338 public void testContextBaseNoCode() { 339 TestData data = createMockApplicationInfo(null, true, true, true); 340 data.pathsWithCode[0] = false; 341 List<SharedLibraryInfo> sharedLibrary = 342 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}); 343 String[] contexts = DexoptUtils.getClassLoaderContexts( 344 data.pkg, sharedLibrary, data.pathsWithCode); 345 346 assertEquals(9, contexts.length); 347 assertEquals(null, contexts[0]); 348 assertEquals( 349 "DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", 350 contexts[1]); 351 assertEquals("DLC[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[2]); 352 assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[3]); 353 assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[4]); 354 assertEquals("PCL[];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[5]); 355 assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]{PCL[a.dex:b.dex]}", contexts[6]); 356 assertEquals(null, contexts[7]); 357 } 358 359 @Test testSharedLibraryContext()360 public void testSharedLibraryContext() { 361 SharedLibraryInfo sharedLibrary = 362 createMockSharedLibrary(new String[] {"a.dex", "b.dex"}).get(0); 363 String context = DexoptUtils.getClassLoaderContext(sharedLibrary); 364 assertEquals("PCL[]", context); 365 366 SharedLibraryInfo otherSharedLibrary = 367 createMockSharedLibrary(new String[] {"c.dex"}).get(0); 368 otherSharedLibrary.addDependency(sharedLibrary); 369 context = DexoptUtils.getClassLoaderContext(otherSharedLibrary); 370 assertEquals("PCL[]{PCL[a.dex:b.dex]}", context); 371 } 372 373 @Test testProcessContextForDexLoad()374 public void testProcessContextForDexLoad() { 375 List<String> classLoaders = Arrays.asList( 376 DELEGATE_LAST_CLASS_LOADER_NAME, 377 PATH_CLASS_LOADER_NAME, 378 PATH_CLASS_LOADER_NAME); 379 List<String> classPaths = Arrays.asList( 380 String.join(File.pathSeparator, "foo.dex", "bar.dex"), 381 String.join(File.pathSeparator, "parent1.dex"), 382 String.join(File.pathSeparator, "parent2.dex", "parent3.dex")); 383 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 384 assertNotNull(context); 385 assertEquals(2, context.length); 386 assertEquals("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); 387 assertEquals("DLC[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); 388 } 389 390 @Test testProcessContextForDexLoadSingleElement()391 public void testProcessContextForDexLoadSingleElement() { 392 List<String> classLoaders = Arrays.asList(PATH_CLASS_LOADER_NAME); 393 List<String> classPaths = Arrays.asList( 394 String.join(File.pathSeparator, "foo.dex", "bar.dex", "zoo.dex")); 395 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 396 assertNotNull(context); 397 assertEquals(3, context.length); 398 assertEquals("PCL[]", context[0]); 399 assertEquals("PCL[foo.dex]", context[1]); 400 assertEquals("PCL[foo.dex:bar.dex]", context[2]); 401 } 402 403 @Test testProcessContextForDexLoadUnsupported()404 public void testProcessContextForDexLoadUnsupported() { 405 List<String> classLoaders = Arrays.asList( 406 DELEGATE_LAST_CLASS_LOADER_NAME, 407 "unsupported.class.loader"); 408 List<String> classPaths = Arrays.asList( 409 String.join(File.pathSeparator, "foo.dex", "bar.dex"), 410 String.join(File.pathSeparator, "parent1.dex")); 411 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 412 assertNull(context); 413 } 414 415 @Test testProcessContextForDexLoadNoClassPath()416 public void testProcessContextForDexLoadNoClassPath() { 417 List<String> classLoaders = Arrays.asList( 418 DELEGATE_LAST_CLASS_LOADER_NAME, 419 PATH_CLASS_LOADER_NAME); 420 List<String> classPaths = Arrays.asList( 421 String.join(File.pathSeparator, "foo.dex", "bar.dex"), 422 null); 423 String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); 424 assertNull(context); 425 } 426 427 @Test testProcessContextForDexLoadIllegalCallEmptyList()428 public void testProcessContextForDexLoadIllegalCallEmptyList() { 429 boolean gotException = false; 430 try { 431 DexoptUtils.processContextForDexLoad(Collections.emptyList(), Collections.emptyList()); 432 } catch (IllegalArgumentException ignore) { 433 gotException = true; 434 } 435 assertTrue(gotException); 436 } 437 438 @Test testProcessContextForDexLoadIllegalCallDifferentSize()439 public void testProcessContextForDexLoadIllegalCallDifferentSize() { 440 boolean gotException = false; 441 try { 442 DexoptUtils.processContextForDexLoad(Collections.emptyList(), Arrays.asList("a")); 443 } catch (IllegalArgumentException ignore) { 444 gotException = true; 445 } 446 assertTrue(gotException); 447 } 448 449 @Test testEncodeClassLoader()450 public void testEncodeClassLoader() { 451 assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", 452 "dalvik.system.PathClassLoader")); 453 assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", 454 "dalvik.system.DexClassLoader")); 455 assertEquals("DLC[xyz]", DexoptUtils.encodeClassLoader("xyz", 456 "dalvik.system.DelegateLastClassLoader")); 457 assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", null)); 458 assertEquals("abc[xyz]", DexoptUtils.encodeClassLoader("xyz", "abc")); 459 460 try { 461 DexoptUtils.encodeClassLoader(null, "abc"); 462 fail(); // Exception should be caught. 463 } catch (NullPointerException expected) {} 464 } 465 466 @Test testEncodeClassLoaderChain()467 public void testEncodeClassLoaderChain() { 468 assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]", 469 "DLC[b]")); 470 try { 471 DexoptUtils.encodeClassLoaderChain("a", null); 472 fail(); // exception is expected 473 } catch (NullPointerException expected) {} 474 475 try { 476 DexoptUtils.encodeClassLoaderChain(null, "b"); 477 fail(); // exception is expected 478 } catch (NullPointerException expected) {} 479 } 480 } 481