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