mirror of
https://github.com/revanced/smali.git
synced 2025-05-29 20:20:12 +02:00
Refactor DexFileFactory and implement new syntax for dex entries
This commit is contained in:
parent
41a5b4953c
commit
3587c6f2a6
@ -32,9 +32,10 @@
|
||||
package org.jf.baksmali;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.google.common.base.Strings;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile;
|
||||
import org.jf.util.jcommander.Command;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -55,65 +56,79 @@ public abstract class DexInputCommand extends Command {
|
||||
/**
|
||||
* Parses a dex file input from the user and loads the given dex file.
|
||||
*
|
||||
* @param input The name of a dex, apk, odex or oat file. For apk or oat files with multiple dex files, the input
|
||||
* can additionally consist of a colon followed by a specific dex entry to load.
|
||||
* In some cases, the input file can contain multiple dex files. If this is the case, you can refer to a specific
|
||||
* dex file with a slash, followed by the entry name, optionally in quotes.
|
||||
*
|
||||
* If the entry name is enclosed in quotes, then it will strip the first and last quote and look for an entry with
|
||||
* exactly that name. Otherwise, it will perform a partial filename match against the entry to find any candidates.
|
||||
* If there is a single matching candidate, it will be used. Otherwise, an error will be generated.
|
||||
*
|
||||
* For example, to refer to the "/system/framework/framework.jar:classes2.dex" entry within the
|
||||
* "framework/arm/framework.oat" oat file, you could use any of:
|
||||
*
|
||||
* framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
|
||||
* framework/arm/framework.oat/system/framework/framework.jar:classes2.dex
|
||||
* framework/arm/framework.oat/framework/framework.jar:classes2.dex
|
||||
* framework/arm/framework.oat/framework.jar:classes2.dex
|
||||
* framework/arm/framework.oat/classes2.dex
|
||||
*
|
||||
* The last option is the easiest, but only works if the oat file doesn't contain another entry with the
|
||||
* "classes2.dex" name. e.g. "/system/framework/blah.jar:classes2.dex"
|
||||
*
|
||||
* It's technically possible (although unlikely) for an oat file to contain 2 entries like:
|
||||
* /system/framework/framework.jar:classes2.dex
|
||||
* system/framework/framework.jar:classes2.dex
|
||||
*
|
||||
* In this case, the "framework/arm/framework.oat/system/framework/framework.jar:classes2.dex" syntax will generate
|
||||
* an error because both entries match the partial entry name. Instead, you could use the following for the
|
||||
* first and second entry respectively:
|
||||
*
|
||||
* framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
|
||||
* framework/arm/framework.oat/"system/framework/framework.jar:classes2.dex"
|
||||
*
|
||||
* @param input The name of a dex, apk, odex or oat file/entry.
|
||||
* @param apiLevel The api level to load the dex file with
|
||||
* @param experimentalOpcodes whether experimental opcodes should be allowed
|
||||
* @return The loaded DexBackedDexFile
|
||||
*/
|
||||
@Nonnull
|
||||
protected DexBackedDexFile loadDexFile(@Nonnull String input, int apiLevel, boolean experimentalOpcodes) {
|
||||
File dexFileFile = new File(input);
|
||||
String dexFileEntry = null;
|
||||
File file = new File(input);
|
||||
|
||||
int previousIndex = input.length();
|
||||
while (!dexFileFile.exists()) {
|
||||
int colonIndex = input.lastIndexOf(':', previousIndex - 1);
|
||||
|
||||
if (colonIndex >= 0) {
|
||||
dexFileFile = new File(input.substring(0, colonIndex));
|
||||
dexFileEntry = input.substring(colonIndex + 1);
|
||||
previousIndex = colonIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
while (file != null && !file.exists()) {
|
||||
file = file.getParentFile();
|
||||
}
|
||||
|
||||
if (!dexFileFile.exists()) {
|
||||
System.err.println("Can't find the file " + input);
|
||||
if (file == null || !file.exists() || file.isDirectory()) {
|
||||
System.err.println("Can't find file: " + input);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (!dexFileFile.exists()) {
|
||||
int colonIndex = input.lastIndexOf(':');
|
||||
|
||||
if (colonIndex >= 0) {
|
||||
dexFileFile = new File(input.substring(0, colonIndex));
|
||||
dexFileEntry = input.substring(colonIndex + 1);
|
||||
File dexFile = file;
|
||||
String dexEntry = null;
|
||||
if (dexFile.getPath().length() < input.length()) {
|
||||
dexEntry = input.substring(dexFile.getPath().length() + 1);
|
||||
}
|
||||
|
||||
if (!dexFileFile.exists()) {
|
||||
System.err.println("Can't find the file " + input);
|
||||
System.exit(1);
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(dexEntry)) {
|
||||
boolean exactMatch = false;
|
||||
if (dexEntry.length() > 2 && dexEntry.charAt(0) == '"' && dexEntry.charAt(dexEntry.length() - 1) == '"') {
|
||||
dexEntry = dexEntry.substring(1, dexEntry.length() - 1);
|
||||
exactMatch = true;
|
||||
}
|
||||
|
||||
try {
|
||||
return DexFileFactory.loadDexFile(dexFileFile, dexFileEntry, apiLevel, experimentalOpcodes);
|
||||
} catch (DexFileFactory.MultipleDexFilesException ex) {
|
||||
System.err.println(String.format("%s is an oat file that contains multiple dex files. You must specify " +
|
||||
"which one to load. E.g. To load the \"core.dex\" entry from boot.oat, you should use " +
|
||||
"\"boot.oat:core.dex\"", dexFileFile));
|
||||
System.err.println("Valid entries include:");
|
||||
|
||||
for (OatFile.OatDexFile oatDexFile : ex.oatFile.getDexFiles()) {
|
||||
System.err.println(oatDexFile.filename);
|
||||
}
|
||||
return DexFileFactory.loadDexEntry(dexFile, dexEntry, exactMatch,
|
||||
Opcodes.forApi(apiLevel, experimentalOpcodes));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
// execution can never actually reach here
|
||||
throw new IllegalStateException();
|
||||
} else {
|
||||
try {
|
||||
return DexFileFactory.loadDexFile(dexFile, Opcodes.forApi(apiLevel, experimentalOpcodes));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,12 +35,10 @@ import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.beust.jcommander.validators.PositiveInteger;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jf.dexlib2.analysis.ClassPath;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
import org.jf.dexlib2.util.SyntheticAccessorResolver;
|
||||
import org.jf.util.StringWrapper;
|
||||
@ -50,7 +48,6 @@ import org.jf.util.jcommander.ExtendedParameters;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -187,40 +184,7 @@ public class DisassembleCommand extends DexInputCommand {
|
||||
}
|
||||
|
||||
String input = inputList.get(0);
|
||||
File dexFileFile = new File(input);
|
||||
String dexFileEntry = null;
|
||||
if (!dexFileFile.exists()) {
|
||||
String filename = dexFileFile.getName();
|
||||
int colonIndex = filename.indexOf(':');
|
||||
|
||||
if (colonIndex >= 0) {
|
||||
dexFileFile = new File(dexFileFile.getParent(), filename.substring(0, colonIndex));
|
||||
dexFileEntry = filename.substring(colonIndex + 1);
|
||||
}
|
||||
|
||||
if (!dexFileFile.exists()) {
|
||||
System.err.println("Can't find the file " + input);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//Read in and parse the dex file
|
||||
DexBackedDexFile dexFile = null;
|
||||
try {
|
||||
dexFile = DexFileFactory.loadDexFile(dexFileFile, dexFileEntry, apiLevel, experimentalOpcodes);
|
||||
} catch (DexFileFactory.MultipleDexFilesException ex) {
|
||||
System.err.println(String.format("%s is an oat file that contains multiple dex files. You must specify " +
|
||||
"which one to load. E.g. To load the \"classes2.dex\" entry from blah.apk, you should use " +
|
||||
"\"blah.apk:classes2.dex\"", dexFileFile));
|
||||
System.err.println("Valid entries include:");
|
||||
|
||||
for (OatFile.OatDexFile oatDexFile : ex.oatFile.getDexFiles()) {
|
||||
System.err.println(oatDexFile.filename);
|
||||
}
|
||||
System.exit(1);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
DexBackedDexFile dexFile = loadDexFile(input, 15, false);
|
||||
|
||||
if (showDeodexWarning() && dexFile.hasOdexOpcodes()) {
|
||||
StringWrapper.printWrappedString(System.err,
|
||||
|
@ -85,7 +85,7 @@ public class AnalysisTest {
|
||||
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
|
||||
String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar);
|
||||
|
||||
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
|
||||
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath));
|
||||
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
if (registerInfo) {
|
||||
|
@ -101,6 +101,7 @@ subprojects {
|
||||
guava: 'com.google.guava:guava:18.0',
|
||||
findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
|
||||
junit: 'junit:junit:4.6',
|
||||
mockito: 'org.mockito:mockito-core:1.+',
|
||||
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
|
||||
antlr: 'org.antlr:antlr:3.5.2',
|
||||
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
|
||||
|
@ -51,6 +51,7 @@ dependencies {
|
||||
compile depends.guava
|
||||
|
||||
testCompile depends.junit
|
||||
testCompile depends.mockito
|
||||
|
||||
accessorTestGenerator project('accessorTestGenerator')
|
||||
|
||||
|
@ -31,9 +31,10 @@
|
||||
|
||||
package org.jf.dexlib2;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
|
||||
@ -45,81 +46,65 @@ import org.jf.util.ExceptionWithContext;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class DexFileFactory {
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException {
|
||||
return loadDexFile(path, api, false);
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path) throws IOException {
|
||||
return loadDexFile(new File(path), Opcodes.forApi(15));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental)
|
||||
throws IOException {
|
||||
return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental));
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException {
|
||||
return loadDexFile(new File(path), opcodes);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException {
|
||||
return loadDexFile(dexFile, api, false);
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File file) throws IOException {
|
||||
return loadDexFile(file, Opcodes.forApi(15));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a dex/apk/odex/oat file.
|
||||
*
|
||||
* For oat files with multiple dex files, the first will be opened. For zip/apk files, the "classes.dex" entry
|
||||
* will be opened.
|
||||
*
|
||||
* @param file The file to open
|
||||
* @param opcodes The set of opcodes to use
|
||||
* @return A DexBackedDexFile for the given file
|
||||
*
|
||||
* @throws UnsupportedOatVersionException If file refers to an unsupported oat file
|
||||
* @throws DexFileNotFoundException If file does not exist, if file is a zip file but does not have a "classes.dex"
|
||||
* entry, or if file is an oat file that has no dex entries.
|
||||
* @throws UnsupportedFileTypeException If file is not a valid dex/zip/odex/oat file, or if the "classes.dex" entry
|
||||
* in a zip file is not a valid dex file
|
||||
*/
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental)
|
||||
throws IOException {
|
||||
return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental));
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
|
||||
if (!file.exists()) {
|
||||
throw new DexFileNotFoundException("%s does not exist", file.getName());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, int api,
|
||||
boolean experimental) throws IOException {
|
||||
return loadDexFile(dexFile, dexEntry, Opcodes.forApi(api, experimental));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry,
|
||||
@Nonnull Opcodes opcodes) throws IOException {
|
||||
ZipFile zipFile = null;
|
||||
boolean isZipFile = false;
|
||||
try {
|
||||
zipFile = new ZipFile(dexFile);
|
||||
// if we get here, it's safe to assume we have a zip file
|
||||
isZipFile = true;
|
||||
|
||||
String zipEntryName = MoreObjects.firstNonNull(dexEntry, "classes.dex");
|
||||
ZipEntry zipEntry = zipFile.getEntry(zipEntryName);
|
||||
if (zipEntry == null) {
|
||||
throw new DexFileNotFound("zip file %s does not contain a %s file", dexFile.getName(), zipEntryName);
|
||||
}
|
||||
long fileLength = zipEntry.getSize();
|
||||
if (fileLength < 40) {
|
||||
throw new ExceptionWithContext("The %s file in %s is too small to be a valid dex file",
|
||||
zipEntryName, dexFile.getName());
|
||||
} else if (fileLength > Integer.MAX_VALUE) {
|
||||
throw new ExceptionWithContext("The %s file in %s is too large to read in",
|
||||
zipEntryName, dexFile.getName());
|
||||
}
|
||||
byte[] dexBytes = new byte[(int)fileLength];
|
||||
ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes);
|
||||
return new DexBackedDexFile(opcodes, dexBytes);
|
||||
zipFile = new ZipFile(file);
|
||||
} catch (IOException ex) {
|
||||
// don't continue on if we know it's a zip file
|
||||
if (isZipFile) {
|
||||
throw ex;
|
||||
// ignore and continue
|
||||
}
|
||||
} finally {
|
||||
|
||||
if (zipFile != null) {
|
||||
try {
|
||||
return new ZipDexEntryFinder(zipFile, opcodes).findEntry("classes.dex", true);
|
||||
} finally {
|
||||
zipFile.close();
|
||||
} catch (IOException ex) {
|
||||
// just eat it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
try {
|
||||
try {
|
||||
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
|
||||
@ -127,14 +112,15 @@ public final class DexFileFactory {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
// Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
|
||||
|
||||
try {
|
||||
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
|
||||
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
|
||||
// back to the same position, if they fails
|
||||
|
||||
OatFile oatFile = null;
|
||||
try {
|
||||
oatFile = OatFile.fromInputStream(inputStream);
|
||||
@ -150,71 +136,127 @@ public final class DexFileFactory {
|
||||
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
|
||||
|
||||
if (oatDexFiles.size() == 0) {
|
||||
throw new DexFileNotFound("Oat file %s contains no dex files", dexFile.getName());
|
||||
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
|
||||
}
|
||||
|
||||
if (dexEntry == null) {
|
||||
if (oatDexFiles.size() > 1) {
|
||||
throw new MultipleDexFilesException(oatFile);
|
||||
}
|
||||
return oatDexFiles.get(0);
|
||||
} else {
|
||||
// first check for an exact match
|
||||
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
|
||||
if (oatDexFile.filename.equals(dexEntry)) {
|
||||
return oatDexFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dexEntry.contains("/")) {
|
||||
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
|
||||
File oatEntryFile = new File(oatDexFile.filename);
|
||||
if (oatEntryFile.getName().equals(dexEntry)) {
|
||||
return oatDexFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new DexFileNotFound("oat file %s does not contain a dex file named %s",
|
||||
dexFile.getName(), dexEntry);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
throw new ExceptionWithContext("%s is not an apk, dex, odex or oat file.", dexFile.getPath());
|
||||
throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a dex entry from a container format (zip/oat)
|
||||
*
|
||||
* This has two modes of operation, depending on the exactMatch parameter. When exactMatch is true, it will only
|
||||
* load an entry whose name exactly matches that provided by the dexEntry parameter.
|
||||
*
|
||||
* When exactMatch is false, then it will search for any entry that dexEntry is a path suffix of. "path suffix"
|
||||
* meaning all the path components in dexEntry must fully match the corresponding path components in the entry name,
|
||||
* but some path components at the beginning of entry name can be missing.
|
||||
*
|
||||
* For example, if an oat file contains a "/system/framework/framework.jar:classes2.dex" entry, then the following
|
||||
* will match (not an exhaustive list):
|
||||
*
|
||||
* "/system/framework/framework.jar:classes2.dex"
|
||||
* "system/framework/framework.jar:classes2.dex"
|
||||
* "framework/framework.jar:classes2.dex"
|
||||
* "framework.jar:classes2.dex"
|
||||
* "classes2.dex"
|
||||
*
|
||||
* Note that partial path components specifically don't match. So something like "work/framework.jar:classes2.dex"
|
||||
* would not match.
|
||||
*
|
||||
* If dexEntry contains an initial slash, it will be ignored for purposes of this suffix match -- but not when
|
||||
* performing an exact match.
|
||||
*
|
||||
* If multiple entries match the given dexEntry, a MultipleMatchingDexEntriesException will be thrown
|
||||
*
|
||||
* @param file The container file. This must be either a zip (apk) file or an oat file.
|
||||
* @param dexEntry The name of the entry to load. This can either be the exact entry name, if exactMatch is true,
|
||||
* or it can be a path suffix.
|
||||
* @param exactMatch If true, dexE
|
||||
* @param opcodes The set of opcodes to use
|
||||
* @return A DexBackedDexFile for the given entry
|
||||
*
|
||||
* @throws UnsupportedOatVersionException If file refers to an unsupported oat file
|
||||
* @throws DexFileNotFoundException If the file does not exist, or if no matching entry could be found
|
||||
* @throws UnsupportedFileTypeException If file is not a valid zip/oat file, or if the matching entry is not a
|
||||
* valid dex file
|
||||
* @throws MultipleMatchingDexEntriesException If multiple entries match the given dexEntry
|
||||
*/
|
||||
public static DexBackedDexFile loadDexEntry(@Nonnull File file, @Nonnull String dexEntry,
|
||||
boolean exactMatch, @Nonnull Opcodes opcodes) throws IOException {
|
||||
if (!file.exists()) {
|
||||
throw new DexFileNotFoundException("Container file %s does not exist", file.getName());
|
||||
}
|
||||
|
||||
ZipFile zipFile = null;
|
||||
try {
|
||||
zipFile = new ZipFile(file);
|
||||
} catch (IOException ex) {
|
||||
// ignore and continue
|
||||
}
|
||||
|
||||
if (zipFile != null) {
|
||||
try {
|
||||
return new ZipDexEntryFinder(zipFile, opcodes).findEntry(dexEntry, exactMatch);
|
||||
} finally {
|
||||
zipFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
try {
|
||||
OatFile oatFile = null;
|
||||
try {
|
||||
oatFile = OatFile.fromInputStream(inputStream);
|
||||
} catch (NotAnOatFileException ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
if (oatFile != null) {
|
||||
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
|
||||
throw new UnsupportedOatVersionException(oatFile);
|
||||
}
|
||||
|
||||
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
|
||||
|
||||
if (oatDexFiles.size() == 0) {
|
||||
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
|
||||
}
|
||||
|
||||
return new OatDexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
throw new UnsupportedFileTypeException("%s is not an apk or oat file.", file.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a DexFile out to disk
|
||||
*
|
||||
* @param path The path to write the dex file to
|
||||
* @param dexFile a Dexfile to write
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
|
||||
DexPool.writeTo(path, dexFile);
|
||||
}
|
||||
|
||||
private DexFileFactory() {}
|
||||
|
||||
public static class DexFileNotFound extends ExceptionWithContext {
|
||||
public DexFileNotFound(@Nullable Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
|
||||
super(cause, message, formatArgs);
|
||||
}
|
||||
|
||||
public DexFileNotFound(@Nullable String message, Object... formatArgs) {
|
||||
public static class DexFileNotFoundException extends ExceptionWithContext {
|
||||
public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
|
||||
super(message, formatArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleDexFilesException extends ExceptionWithContext {
|
||||
@Nonnull public final OatFile oatFile;
|
||||
|
||||
public MultipleDexFilesException(@Nonnull OatFile oatFile) {
|
||||
super("Oat file has multiple dex files.");
|
||||
this.oatFile = oatFile;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnsupportedOatVersionException extends ExceptionWithContext {
|
||||
@Nonnull public final OatFile oatFile;
|
||||
|
||||
@ -223,4 +265,212 @@ public final class DexFileFactory {
|
||||
this.oatFile = oatFile;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleMatchingDexEntriesException extends ExceptionWithContext {
|
||||
public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) {
|
||||
super(String.format(message, formatArgs));
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnsupportedFileTypeException extends ExceptionWithContext {
|
||||
public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) {
|
||||
super(String.format(message, formatArgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches two entries fully, ignoring any initial slash, if any
|
||||
*/
|
||||
private static boolean fullEntryMatch(@Nonnull String entry, @Nonnull String targetEntry) {
|
||||
if (entry.equals(targetEntry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.charAt(0) == '/') {
|
||||
entry = entry.substring(1);
|
||||
}
|
||||
|
||||
if (targetEntry.charAt(0) == '/') {
|
||||
targetEntry = targetEntry.substring(1);
|
||||
}
|
||||
|
||||
return entry.equals(targetEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a partial match against entry and targetEntry.
|
||||
*
|
||||
* This is considered a partial match if targetEntry is a suffix of entry, and if the suffix starts
|
||||
* on a path "part" (ignoring the initial separator, if any). Both '/' and ':' are considered separators for this.
|
||||
*
|
||||
* So entry="/blah/blah/something.dex" and targetEntry="lah/something.dex" shouldn't match, but
|
||||
* both targetEntry="blah/something.dex" and "/blah/something.dex" should match.
|
||||
*/
|
||||
private static boolean partialEntryMatch(String entry, String targetEntry) {
|
||||
if (entry.equals(targetEntry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!entry.endsWith(targetEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the first matching part is a full entry. We don't want to match "/blah/blah/something.dex" with
|
||||
// "lah/something.dex", but both "/blah/something.dex" and "blah/something.dex" should match
|
||||
char precedingChar = entry.charAt(entry.length() - targetEntry.length() - 1);
|
||||
char firstTargetChar = targetEntry.charAt(0);
|
||||
// This is a device path, so we should always use the linux separator '/', rather than the current platform's
|
||||
// separator
|
||||
return firstTargetChar == ':' || firstTargetChar == '/' || precedingChar == ':' || precedingChar == '/';
|
||||
}
|
||||
|
||||
protected abstract static class DexEntryFinder {
|
||||
@Nullable
|
||||
protected abstract DexBackedDexFile getEntry(@Nonnull String entry) throws IOException;
|
||||
|
||||
@Nonnull
|
||||
protected abstract List<String> getEntryNames();
|
||||
|
||||
@Nonnull
|
||||
protected abstract String getFilename();
|
||||
|
||||
@Nonnull
|
||||
public DexBackedDexFile findEntry(@Nonnull String targetEntry, boolean exactMatch) throws IOException {
|
||||
if (exactMatch) {
|
||||
DexBackedDexFile dexFile = getEntry(targetEntry);
|
||||
if (dexFile == null) {
|
||||
if (getEntryNames().contains(targetEntry)) {
|
||||
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry,
|
||||
getFilename());
|
||||
} else {
|
||||
throw new DexFileNotFoundException("Could not find %s in %s.", targetEntry, getFilename());
|
||||
}
|
||||
}
|
||||
return dexFile;
|
||||
}
|
||||
|
||||
// find all full and partial matches
|
||||
List<String> fullMatches = Lists.newArrayList();
|
||||
List<DexBackedDexFile> fullEntries = Lists.newArrayList();
|
||||
List<String> partialMatches = Lists.newArrayList();
|
||||
List<DexBackedDexFile> partialEntries = Lists.newArrayList();
|
||||
for (String entry: getEntryNames()) {
|
||||
if (fullEntryMatch(entry, targetEntry)) {
|
||||
// We want to grab all full matches, regardless of whether they're actually a dex file.
|
||||
fullMatches.add(entry);
|
||||
fullEntries.add(getEntry(entry));
|
||||
} else if (partialEntryMatch(entry, targetEntry)) {
|
||||
DexBackedDexFile dexFile = getEntry(entry);
|
||||
// We only want to grab a partial match if it is actually a dex file.
|
||||
if (dexFile != null) {
|
||||
partialMatches.add(entry);
|
||||
partialEntries.add(dexFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// full matches always take priority
|
||||
if (fullEntries.size() == 1) {
|
||||
DexBackedDexFile dexFile = fullEntries.get(0);
|
||||
if (dexFile == null) {
|
||||
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
|
||||
fullMatches.get(0), getFilename());
|
||||
}
|
||||
return dexFile;
|
||||
}
|
||||
if (fullEntries.size() > 1) {
|
||||
// This should be quite rare. This would only happen if an oat file has two entries that differ
|
||||
// only by an initial path separator. e.g. "/blah/blah.dex" and "blah/blah.dex"
|
||||
throw new MultipleMatchingDexEntriesException(String.format(
|
||||
"Multiple entries in %s match %s: %s", getFilename(), targetEntry,
|
||||
Joiner.on(", ").join(fullMatches)));
|
||||
}
|
||||
|
||||
if (partialEntries.size() == 0) {
|
||||
throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s",
|
||||
getFilename(), targetEntry);
|
||||
}
|
||||
if (partialEntries.size() > 1) {
|
||||
throw new MultipleMatchingDexEntriesException(String.format(
|
||||
"Multiple dex entries in %s match %s: %s", getFilename(), targetEntry,
|
||||
Joiner.on(", ").join(partialMatches)));
|
||||
}
|
||||
return partialEntries.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ZipDexEntryFinder extends DexEntryFinder {
|
||||
@Nonnull private final ZipFile zipFile;
|
||||
@Nonnull private final Opcodes opcodes;
|
||||
|
||||
public ZipDexEntryFinder(@Nonnull ZipFile zipFile, @Nonnull Opcodes opcodes) {
|
||||
this.zipFile = zipFile;
|
||||
this.opcodes = opcodes;
|
||||
}
|
||||
|
||||
@Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException {
|
||||
ZipEntry zipEntry = zipFile.getEntry(entry);
|
||||
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = zipFile.getInputStream(zipEntry);
|
||||
return DexBackedDexFile.fromInputStream(opcodes, stream);
|
||||
} catch (NotADexFile ex) {
|
||||
return null;
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull @Override protected List<String> getEntryNames() {
|
||||
List<String> entries = Lists.newArrayList();
|
||||
Enumeration<? extends ZipEntry> entriesEnumeration = zipFile.entries();
|
||||
|
||||
while (entriesEnumeration.hasMoreElements()) {
|
||||
ZipEntry entry = entriesEnumeration.nextElement();
|
||||
entries.add(entry.getName());
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Nonnull @Override protected String getFilename() {
|
||||
return zipFile.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private static class OatDexEntryFinder extends DexEntryFinder {
|
||||
@Nonnull private final String fileName;
|
||||
@Nonnull private final OatFile oatFile;
|
||||
|
||||
public OatDexEntryFinder(@Nonnull String fileName, @Nonnull OatFile oatFile) {
|
||||
this.fileName = fileName;
|
||||
this.oatFile = oatFile;
|
||||
}
|
||||
|
||||
@Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException {
|
||||
for (OatDexFile dexFile: oatFile.getDexFiles()) {
|
||||
if (dexFile.filename.equals(entry)) {
|
||||
return dexFile;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull @Override protected List<String> getEntryNames() {
|
||||
List<String> entries = Lists.newArrayList();
|
||||
|
||||
for (OatDexFile oatDexFile: oatFile.getDexFiles()) {
|
||||
entries.add(oatDexFile.filename);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Nonnull @Override protected String getFilename() {
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ import com.google.common.collect.*;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||
@ -231,14 +230,16 @@ public class ClassPath {
|
||||
}
|
||||
|
||||
File bestMatch = Collections.max(files, new ClassPathEntryComparator(entry));
|
||||
try {
|
||||
DexFile entryDexFile = DexFileFactory.loadDexFile(bestMatch, api, experimental);
|
||||
DexFile entryDexFile = DexFileFactory.loadDexFile(bestMatch, Opcodes.forApi(api, experimental));
|
||||
classProviders.add(new DexClassProvider(entryDexFile));
|
||||
// TODO: DexFileFactory.loadAllDexFiles?
|
||||
/*try {
|
||||
|
||||
} catch (MultipleDexFilesException ex) {
|
||||
for (DexFile entryDexFile: ex.oatFile.getDexFiles()) {
|
||||
classProviders.add(new DexClassProvider(entryDexFile));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
int oatVersion = -1;
|
||||
@ -329,13 +330,10 @@ public class ClassPath {
|
||||
} else {
|
||||
if (name.equals(child.getName())) {
|
||||
try {
|
||||
DexFileFactory.loadDexFile(child, 15);
|
||||
DexFileFactory.loadDexFile(child);
|
||||
} catch (ExceptionWithContext ex) {
|
||||
if (!(ex instanceof MultipleDexFilesException)) {
|
||||
// Don't add it to the results if it can't be loaded
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(child);
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class AccessorTest {
|
||||
public void testAccessors() throws IOException {
|
||||
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
|
||||
Assert.assertNotNull(url);
|
||||
DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15, false);
|
||||
DexFile f = DexFileFactory.loadDexFile(url.getFile());
|
||||
|
||||
SyntheticAccessorResolver sar = new SyntheticAccessorResolver(f.getOpcodes(), f.getClasses());
|
||||
|
||||
|
210
dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
Normal file
210
dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2016, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.jf.dexlib2;
|
||||
|
||||
import com.beust.jcommander.internal.Maps;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.dexlib2.DexEntryFinderTest.TestDexFileFactory.TestDexEntryFinder;
|
||||
import org.jf.dexlib2.DexFileFactory.DexEntryFinder;
|
||||
import org.jf.dexlib2.DexFileFactory.DexFileNotFoundException;
|
||||
import org.jf.dexlib2.DexFileFactory.MultipleMatchingDexEntriesException;
|
||||
import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class DexEntryFinderTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testNormalStuff() throws Exception {
|
||||
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
|
||||
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
|
||||
entries.put("/system/framework/framework.jar", dexFile1);
|
||||
DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
|
||||
entries.put("/system/framework/framework.jar:classes2.dex", dexFile2);
|
||||
TestDexEntryFinder testFinder = new TestDexEntryFinder("blah.oat", entries);
|
||||
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
|
||||
|
||||
assertEntryNotFound(testFinder, "system/framework/framework.jar", true);
|
||||
assertEntryNotFound(testFinder, "/framework/framework.jar", true);
|
||||
assertEntryNotFound(testFinder, "framework/framework.jar", true);
|
||||
assertEntryNotFound(testFinder, "/framework.jar", true);
|
||||
assertEntryNotFound(testFinder, "framework.jar", true);
|
||||
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("system/framework/framework.jar", false));
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("/framework/framework.jar", false));
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("framework/framework.jar", false));
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("/framework.jar", false));
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("framework.jar", false));
|
||||
|
||||
assertEntryNotFound(testFinder, "ystem/framework/framework.jar", false);
|
||||
assertEntryNotFound(testFinder, "ssystem/framework/framework.jar", false);
|
||||
assertEntryNotFound(testFinder, "ramework/framework.jar", false);
|
||||
assertEntryNotFound(testFinder, "ramework.jar", false);
|
||||
assertEntryNotFound(testFinder, "framework", false);
|
||||
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("/system/framework/framework.jar:classes2.dex", true));
|
||||
|
||||
assertEntryNotFound(testFinder, "system/framework/framework.jar:classes2.dex", true);
|
||||
assertEntryNotFound(testFinder, "framework.jar:classes2.dex", true);
|
||||
assertEntryNotFound(testFinder, "classes2.dex", true);
|
||||
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar:classes2.dex", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar:classes2.dex", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar:classes2.dex", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework.jar:classes2.dex", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("framework.jar:classes2.dex", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry(":classes2.dex", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("classes2.dex", false));
|
||||
|
||||
assertEntryNotFound(testFinder, "ystem/framework/framework.jar:classes2.dex", false);
|
||||
assertEntryNotFound(testFinder, "ramework.jar:classes2.dex", false);
|
||||
assertEntryNotFound(testFinder, "lasses2.dex", false);
|
||||
assertEntryNotFound(testFinder, "classes2", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimilarEntries() throws Exception {
|
||||
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
|
||||
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
|
||||
entries.put("/system/framework/framework.jar", dexFile1);
|
||||
DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
|
||||
entries.put("system/framework/framework.jar", dexFile2);
|
||||
TestDexEntryFinder testFinder = new TestDexEntryFinder("blah.oat", entries);
|
||||
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar", true));
|
||||
|
||||
assertMultipleMatchingEntries(testFinder, "/system/framework/framework.jar");
|
||||
assertMultipleMatchingEntries(testFinder, "system/framework/framework.jar");
|
||||
|
||||
assertMultipleMatchingEntries(testFinder, "/framework/framework.jar");
|
||||
assertMultipleMatchingEntries(testFinder, "framework/framework.jar");
|
||||
assertMultipleMatchingEntries(testFinder, "/framework.jar");
|
||||
assertMultipleMatchingEntries(testFinder, "framework.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchingSuffix() throws Exception {
|
||||
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
|
||||
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
|
||||
entries.put("/system/framework/framework.jar", dexFile1);
|
||||
DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
|
||||
entries.put("/framework/framework.jar", dexFile2);
|
||||
TestDexEntryFinder testFinder = new TestDexEntryFinder("blah.oat", entries);
|
||||
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", true));
|
||||
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", false));
|
||||
Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar", false));
|
||||
|
||||
assertMultipleMatchingEntries(testFinder, "/framework.jar");
|
||||
assertMultipleMatchingEntries(testFinder, "framework.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonDexEntries() throws Exception {
|
||||
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
|
||||
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
|
||||
entries.put("classes.dex", dexFile1);
|
||||
entries.put("/blah/classes.dex", null);
|
||||
TestDexEntryFinder testFinder = new TestDexEntryFinder("blah.oat", entries);
|
||||
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", true));
|
||||
Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", false));
|
||||
|
||||
assertUnsupportedFileType(testFinder, "/blah/classes.dex", true);
|
||||
assertUnsupportedFileType(testFinder, "/blah/classes.dex", false);
|
||||
}
|
||||
|
||||
private void assertEntryNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
|
||||
try {
|
||||
finder.findEntry(entry, exactMatch);
|
||||
Assert.fail();
|
||||
} catch (DexFileNotFoundException ex) {
|
||||
// expected exception
|
||||
}
|
||||
}
|
||||
|
||||
private void assertMultipleMatchingEntries(DexEntryFinder finder, String entry) throws IOException {
|
||||
try {
|
||||
finder.findEntry(entry, false);
|
||||
Assert.fail();
|
||||
} catch (MultipleMatchingDexEntriesException ex) {
|
||||
// expected exception
|
||||
}
|
||||
}
|
||||
|
||||
private void assertUnsupportedFileType(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
|
||||
try {
|
||||
finder.findEntry(entry, exactMatch);
|
||||
Assert.fail();
|
||||
} catch (UnsupportedFileTypeException ex) {
|
||||
// expected exception
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestDexFileFactory {
|
||||
public static class TestDexEntryFinder extends DexEntryFinder {
|
||||
@Nonnull private final String fileName;
|
||||
@Nonnull private final Map<String, DexBackedDexFile> entries;
|
||||
|
||||
public TestDexEntryFinder(@Nonnull String fileName, @Nonnull Map<String, DexBackedDexFile> entries) {
|
||||
this.fileName = fileName;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
@Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException {
|
||||
return entries.get(entry);
|
||||
}
|
||||
|
||||
@Nonnull @Override protected List<String> getEntryNames() {
|
||||
return Lists.newArrayList(entries.keySet());
|
||||
}
|
||||
|
||||
@Nonnull @Override protected String getFilename() {
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user