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
17package com.android.server.pm.dex;
18
19import com.android.server.pm.PackageDexOptimizer;
20
21import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK;
22import static org.junit.Assert.assertEquals;
23import static org.junit.Assert.assertNotNull;
24import static org.junit.Assert.assertNull;
25import static org.junit.Assert.assertTrue;
26import static org.junit.Assert.fail;
27
28import android.content.pm.ApplicationInfo;
29import android.support.test.filters.SmallTest;
30import android.support.test.runner.AndroidJUnit4;
31import android.util.SparseArray;
32
33import dalvik.system.DelegateLastClassLoader;
34import dalvik.system.DexClassLoader;
35import dalvik.system.PathClassLoader;
36
37import org.junit.Test;
38import org.junit.runner.RunWith;
39
40import java.io.File;
41import java.util.Arrays;
42import java.util.Collections;
43import java.util.List;
44
45@RunWith(AndroidJUnit4.class)
46@SmallTest
47public 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