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 
32 package org.jf.dexlib2;
33 
34 import com.beust.jcommander.internal.Maps;
35 import com.google.common.collect.Lists;
36 import org.jf.dexlib2.DexFileFactory.DexEntryFinder;
37 import org.jf.dexlib2.DexFileFactory.DexFileNotFoundException;
38 import org.jf.dexlib2.DexFileFactory.MultipleMatchingDexEntriesException;
39 import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
40 import org.jf.dexlib2.dexbacked.DexBackedDexFile;
41 import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
42 import org.jf.dexlib2.iface.MultiDexContainer;
43 import org.junit.Assert;
44 import org.junit.Test;
45 
46 import javax.annotation.Nonnull;
47 import javax.annotation.Nullable;
48 import java.io.IOException;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Map.Entry;
52 
53 import static org.mockito.Mockito.mock;
54 
55 public class DexEntryFinderTest {
56 
57     @Test
testNormalStuff()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
testSimilarEntries()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
testMatchingSuffix()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
testNonDexEntries()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 
assertEntryNotFound(DexEntryFinder finder, String entry, boolean exactMatch)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 
assertMultipleMatchingEntries(DexEntryFinder finder, String entry)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 
assertUnsupportedFileType(DexEntryFinder finder, String entry, boolean exactMatch)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 
assertDexFileNotFound(DexEntryFinder finder, String entry, boolean exactMatch)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 
TestMultiDexContainer(@onnull Map<String, DexBackedDexFile> entries)200         public TestMultiDexContainer(@Nonnull Map<String, DexBackedDexFile> entries) {
201             this.entries = entries;
202         }
203 
getDexEntryNames()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 
getEntry(@onnull String entryName)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 
getOpcodes()227         @Nonnull @Override public Opcodes getOpcodes() {
228             return Opcodes.getDefault();
229         }
230     }
231 }
232