1/*
2 * Copyright 2016, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2;
33
34import com.beust.jcommander.internal.Maps;
35import com.google.common.collect.Lists;
36import org.jf.dexlib2.DexFileFactory.DexEntryFinder;
37import org.jf.dexlib2.DexFileFactory.DexFileNotFoundException;
38import org.jf.dexlib2.DexFileFactory.MultipleMatchingDexEntriesException;
39import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
40import org.jf.dexlib2.dexbacked.DexBackedDexFile;
41import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
42import org.jf.dexlib2.iface.MultiDexContainer;
43import org.junit.Assert;
44import org.junit.Test;
45
46import javax.annotation.Nonnull;
47import javax.annotation.Nullable;
48import java.io.IOException;
49import java.util.List;
50import java.util.Map;
51import java.util.Map.Entry;
52
53import static org.mockito.Mockito.mock;
54
55public class DexEntryFinderTest {
56
57    @Test
58    public void testNormalStuff() throws Exception {
59        Map<String, DexBackedDexFile> entries = Maps.newHashMap();
60        DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
61        entries.put("/system/framework/framework.jar", dexFile1);
62        DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
63        entries.put("/system/framework/framework.jar:classes2.dex", dexFile2);
64        DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
65
66        Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
67
68        assertEntryNotFound(testFinder, "system/framework/framework.jar", true);
69        assertEntryNotFound(testFinder, "/framework/framework.jar", true);
70        assertEntryNotFound(testFinder, "framework/framework.jar", true);
71        assertEntryNotFound(testFinder, "/framework.jar", true);
72        assertEntryNotFound(testFinder, "framework.jar", true);
73
74        Assert.assertEquals(dexFile1, testFinder.findEntry("system/framework/framework.jar", false));
75        Assert.assertEquals(dexFile1, testFinder.findEntry("/framework/framework.jar", false));
76        Assert.assertEquals(dexFile1, testFinder.findEntry("framework/framework.jar", false));
77        Assert.assertEquals(dexFile1, testFinder.findEntry("/framework.jar", false));
78        Assert.assertEquals(dexFile1, testFinder.findEntry("framework.jar", false));
79
80        assertEntryNotFound(testFinder, "ystem/framework/framework.jar", false);
81        assertEntryNotFound(testFinder, "ssystem/framework/framework.jar", false);
82        assertEntryNotFound(testFinder, "ramework/framework.jar", false);
83        assertEntryNotFound(testFinder, "ramework.jar", false);
84        assertEntryNotFound(testFinder, "framework", false);
85
86        Assert.assertEquals(dexFile2, testFinder.findEntry("/system/framework/framework.jar:classes2.dex", true));
87
88        assertEntryNotFound(testFinder, "system/framework/framework.jar:classes2.dex", true);
89        assertEntryNotFound(testFinder, "framework.jar:classes2.dex", true);
90        assertEntryNotFound(testFinder, "classes2.dex", true);
91
92        Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar:classes2.dex", false));
93        Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar:classes2.dex", false));
94        Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar:classes2.dex", false));
95        Assert.assertEquals(dexFile2, testFinder.findEntry("/framework.jar:classes2.dex", false));
96        Assert.assertEquals(dexFile2, testFinder.findEntry("framework.jar:classes2.dex", false));
97        Assert.assertEquals(dexFile2, testFinder.findEntry(":classes2.dex", false));
98        Assert.assertEquals(dexFile2, testFinder.findEntry("classes2.dex", false));
99
100        assertEntryNotFound(testFinder, "ystem/framework/framework.jar:classes2.dex", false);
101        assertEntryNotFound(testFinder, "ramework.jar:classes2.dex", false);
102        assertEntryNotFound(testFinder, "lasses2.dex", false);
103        assertEntryNotFound(testFinder, "classes2", false);
104    }
105
106    @Test
107    public void testSimilarEntries() throws Exception {
108        Map<String, DexBackedDexFile> entries = Maps.newHashMap();
109        DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
110        entries.put("/system/framework/framework.jar", dexFile1);
111        DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
112        entries.put("system/framework/framework.jar", dexFile2);
113        DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
114
115        Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
116        Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar", true));
117
118        assertMultipleMatchingEntries(testFinder, "/system/framework/framework.jar");
119        assertMultipleMatchingEntries(testFinder, "system/framework/framework.jar");
120
121        assertMultipleMatchingEntries(testFinder, "/framework/framework.jar");
122        assertMultipleMatchingEntries(testFinder, "framework/framework.jar");
123        assertMultipleMatchingEntries(testFinder, "/framework.jar");
124        assertMultipleMatchingEntries(testFinder, "framework.jar");
125    }
126
127    @Test
128    public void testMatchingSuffix() throws Exception {
129        Map<String, DexBackedDexFile> entries = Maps.newHashMap();
130        DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
131        entries.put("/system/framework/framework.jar", dexFile1);
132        DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
133        entries.put("/framework/framework.jar", dexFile2);
134        DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
135
136        Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
137        Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", true));
138
139        Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", false));
140        Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar", false));
141
142        assertMultipleMatchingEntries(testFinder, "/framework.jar");
143        assertMultipleMatchingEntries(testFinder, "framework.jar");
144    }
145
146    @Test
147    public void testNonDexEntries() throws Exception {
148        Map<String, DexBackedDexFile> entries = Maps.newHashMap();
149        DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
150        entries.put("classes.dex", dexFile1);
151        entries.put("/blah/classes.dex", null);
152        DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
153
154        Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", true));
155        Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", false));
156
157        assertUnsupportedFileType(testFinder, "/blah/classes.dex", true);
158        assertDexFileNotFound(testFinder, "/blah/classes.dex", false);
159    }
160
161    private void assertEntryNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
162        try {
163            finder.findEntry(entry, exactMatch);
164            Assert.fail();
165        } catch (DexFileNotFoundException ex) {
166            // expected exception
167        }
168    }
169
170    private void assertMultipleMatchingEntries(DexEntryFinder finder, String entry) throws IOException {
171        try {
172            finder.findEntry(entry, false);
173            Assert.fail();
174        } catch (MultipleMatchingDexEntriesException ex) {
175            // expected exception
176        }
177    }
178
179    private void assertUnsupportedFileType(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
180        try {
181            finder.findEntry(entry, exactMatch);
182            Assert.fail();
183        } catch (UnsupportedFileTypeException ex) {
184            // expected exception
185        }
186    }
187
188    private void assertDexFileNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
189        try {
190            finder.findEntry(entry, exactMatch);
191            Assert.fail();
192        } catch (DexFileNotFoundException ex) {
193            // expected exception
194        }
195    }
196
197    public static class TestMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
198        @Nonnull private final Map<String, DexBackedDexFile> entries;
199
200        public TestMultiDexContainer(@Nonnull Map<String, DexBackedDexFile> entries) {
201            this.entries = entries;
202        }
203
204        @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
205            List<String> entryNames = Lists.newArrayList();
206
207            for (Entry<String, DexBackedDexFile> entry: entries.entrySet()) {
208                if (entry.getValue() != null) {
209                    entryNames.add(entry.getKey());
210                }
211            }
212
213            return entryNames;
214        }
215
216        @Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
217            if (entries.containsKey(entryName)) {
218                DexBackedDexFile entry = entries.get(entryName);
219                if (entry == null) {
220                    throw new NotADexFile();
221                }
222                return entry;
223            }
224            return null;
225        }
226
227        @Nonnull @Override public Opcodes getOpcodes() {
228            return Opcodes.getDefault();
229        }
230    }
231}
232