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;
|
package org.jf.baksmali;
|
||||||
|
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import org.jf.dexlib2.DexFileFactory;
|
import org.jf.dexlib2.DexFileFactory;
|
||||||
|
import org.jf.dexlib2.Opcodes;
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||||
import org.jf.dexlib2.dexbacked.OatFile;
|
|
||||||
import org.jf.util.jcommander.Command;
|
import org.jf.util.jcommander.Command;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
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.
|
* 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
|
* In some cases, the input file can contain multiple dex files. If this is the case, you can refer to a specific
|
||||||
* can additionally consist of a colon followed by a specific dex entry to load.
|
* 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 apiLevel The api level to load the dex file with
|
||||||
* @param experimentalOpcodes whether experimental opcodes should be allowed
|
* @param experimentalOpcodes whether experimental opcodes should be allowed
|
||||||
* @return The loaded DexBackedDexFile
|
* @return The loaded DexBackedDexFile
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
protected DexBackedDexFile loadDexFile(@Nonnull String input, int apiLevel, boolean experimentalOpcodes) {
|
protected DexBackedDexFile loadDexFile(@Nonnull String input, int apiLevel, boolean experimentalOpcodes) {
|
||||||
File dexFileFile = new File(input);
|
File file = new File(input);
|
||||||
String dexFileEntry = null;
|
|
||||||
|
|
||||||
int previousIndex = input.length();
|
while (file != null && !file.exists()) {
|
||||||
while (!dexFileFile.exists()) {
|
file = file.getParentFile();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dexFileFile.exists()) {
|
if (file == null || !file.exists() || file.isDirectory()) {
|
||||||
System.err.println("Can't find the file " + input);
|
System.err.println("Can't find file: " + input);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dexFileFile.exists()) {
|
File dexFile = file;
|
||||||
int colonIndex = input.lastIndexOf(':');
|
String dexEntry = null;
|
||||||
|
if (dexFile.getPath().length() < input.length()) {
|
||||||
if (colonIndex >= 0) {
|
dexEntry = input.substring(dexFile.getPath().length() + 1);
|
||||||
dexFileFile = new File(input.substring(0, colonIndex));
|
|
||||||
dexFileEntry = input.substring(colonIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dexFileFile.exists()) {
|
|
||||||
System.err.println("Can't find the file " + input);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (!Strings.isNullOrEmpty(dexEntry)) {
|
||||||
return DexFileFactory.loadDexFile(dexFileFile, dexFileEntry, apiLevel, experimentalOpcodes);
|
boolean exactMatch = false;
|
||||||
} catch (DexFileFactory.MultipleDexFilesException ex) {
|
if (dexEntry.length() > 2 && dexEntry.charAt(0) == '"' && dexEntry.charAt(dexEntry.length() - 1) == '"') {
|
||||||
System.err.println(String.format("%s is an oat file that contains multiple dex files. You must specify " +
|
dexEntry = dexEntry.substring(1, dexEntry.length() - 1);
|
||||||
"which one to load. E.g. To load the \"core.dex\" entry from boot.oat, you should use " +
|
exactMatch = true;
|
||||||
"\"boot.oat:core.dex\"", dexFileFile));
|
|
||||||
System.err.println("Valid entries include:");
|
|
||||||
|
|
||||||
for (OatFile.OatDexFile oatDexFile : ex.oatFile.getDexFiles()) {
|
|
||||||
System.err.println(oatDexFile.filename);
|
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// execution can never actually reach here
|
try {
|
||||||
throw new IllegalStateException();
|
return DexFileFactory.loadDexEntry(dexFile, dexEntry, exactMatch,
|
||||||
|
Opcodes.forApi(apiLevel, experimentalOpcodes));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
} 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.Parameter;
|
||||||
import com.beust.jcommander.Parameters;
|
import com.beust.jcommander.Parameters;
|
||||||
import com.beust.jcommander.validators.PositiveInteger;
|
import com.beust.jcommander.validators.PositiveInteger;
|
||||||
import org.jf.dexlib2.DexFileFactory;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import org.jf.dexlib2.analysis.ClassPath;
|
import org.jf.dexlib2.analysis.ClassPath;
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||||
import org.jf.dexlib2.dexbacked.OatFile;
|
|
||||||
import org.jf.dexlib2.iface.DexFile;
|
import org.jf.dexlib2.iface.DexFile;
|
||||||
import org.jf.dexlib2.util.SyntheticAccessorResolver;
|
import org.jf.dexlib2.util.SyntheticAccessorResolver;
|
||||||
import org.jf.util.StringWrapper;
|
import org.jf.util.StringWrapper;
|
||||||
@ -50,7 +48,6 @@ import org.jf.util.jcommander.ExtendedParameters;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -187,40 +184,7 @@ public class DisassembleCommand extends DexInputCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String input = inputList.get(0);
|
String input = inputList.get(0);
|
||||||
File dexFileFile = new File(input);
|
DexBackedDexFile dexFile = loadDexFile(input, 15, false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showDeodexWarning() && dexFile.hasOdexOpcodes()) {
|
if (showDeodexWarning() && dexFile.hasOdexOpcodes()) {
|
||||||
StringWrapper.printWrappedString(System.err,
|
StringWrapper.printWrappedString(System.err,
|
||||||
|
@ -85,7 +85,7 @@ public class AnalysisTest {
|
|||||||
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
|
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
|
||||||
String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar);
|
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();
|
BaksmaliOptions options = new BaksmaliOptions();
|
||||||
if (registerInfo) {
|
if (registerInfo) {
|
||||||
|
@ -101,6 +101,7 @@ subprojects {
|
|||||||
guava: 'com.google.guava:guava:18.0',
|
guava: 'com.google.guava:guava:18.0',
|
||||||
findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
|
findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
|
||||||
junit: 'junit:junit:4.6',
|
junit: 'junit:junit:4.6',
|
||||||
|
mockito: 'org.mockito:mockito-core:1.+',
|
||||||
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
|
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
|
||||||
antlr: 'org.antlr:antlr:3.5.2',
|
antlr: 'org.antlr:antlr:3.5.2',
|
||||||
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
|
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
|
||||||
|
@ -51,6 +51,7 @@ dependencies {
|
|||||||
compile depends.guava
|
compile depends.guava
|
||||||
|
|
||||||
testCompile depends.junit
|
testCompile depends.junit
|
||||||
|
testCompile depends.mockito
|
||||||
|
|
||||||
accessorTestGenerator project('accessorTestGenerator')
|
accessorTestGenerator project('accessorTestGenerator')
|
||||||
|
|
||||||
|
@ -31,9 +31,10 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2;
|
package org.jf.dexlib2;
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.collect.Lists;
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||||
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||||
import org.jf.dexlib2.dexbacked.OatFile;
|
import org.jf.dexlib2.dexbacked.OatFile;
|
||||||
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
|
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
|
||||||
@ -45,81 +46,65 @@ import org.jf.util.ExceptionWithContext;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public final class DexFileFactory {
|
public final class DexFileFactory {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException {
|
public static DexBackedDexFile loadDexFile(@Nonnull String path) throws IOException {
|
||||||
return loadDexFile(path, api, false);
|
return loadDexFile(new File(path), Opcodes.forApi(15));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental)
|
public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException {
|
||||||
throws IOException {
|
return loadDexFile(new File(path), opcodes);
|
||||||
return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException {
|
public static DexBackedDexFile loadDexFile(@Nonnull File file) throws IOException {
|
||||||
return loadDexFile(dexFile, api, false);
|
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
|
@Nonnull
|
||||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental)
|
public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
|
||||||
throws IOException {
|
if (!file.exists()) {
|
||||||
return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental));
|
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;
|
ZipFile zipFile = null;
|
||||||
boolean isZipFile = false;
|
|
||||||
try {
|
try {
|
||||||
zipFile = new ZipFile(dexFile);
|
zipFile = new ZipFile(file);
|
||||||
// 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);
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
// don't continue on if we know it's a zip file
|
// ignore and continue
|
||||||
if (isZipFile) {
|
}
|
||||||
throw ex;
|
|
||||||
}
|
if (zipFile != null) {
|
||||||
} finally {
|
try {
|
||||||
if (zipFile != null) {
|
return new ZipDexEntryFinder(zipFile, opcodes).findEntry("classes.dex", true);
|
||||||
try {
|
} finally {
|
||||||
zipFile.close();
|
zipFile.close();
|
||||||
} catch (IOException ex) {
|
|
||||||
// just eat it
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
|
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
|
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
|
||||||
@ -127,14 +112,15 @@ public final class DexFileFactory {
|
|||||||
// just eat it
|
// just eat it
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
|
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
|
||||||
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
|
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
|
||||||
// just eat it
|
// just eat it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
|
||||||
|
// back to the same position, if they fails
|
||||||
|
|
||||||
OatFile oatFile = null;
|
OatFile oatFile = null;
|
||||||
try {
|
try {
|
||||||
oatFile = OatFile.fromInputStream(inputStream);
|
oatFile = OatFile.fromInputStream(inputStream);
|
||||||
@ -150,71 +136,127 @@ public final class DexFileFactory {
|
|||||||
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
|
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
|
||||||
|
|
||||||
if (oatDexFiles.size() == 0) {
|
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) {
|
return oatDexFiles.get(0);
|
||||||
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 {
|
} finally {
|
||||||
inputStream.close();
|
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 {
|
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
|
||||||
DexPool.writeTo(path, dexFile);
|
DexPool.writeTo(path, dexFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DexFileFactory() {}
|
private DexFileFactory() {}
|
||||||
|
|
||||||
public static class DexFileNotFound extends ExceptionWithContext {
|
public static class DexFileNotFoundException extends ExceptionWithContext {
|
||||||
public DexFileNotFound(@Nullable Throwable cause) {
|
public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
|
|
||||||
super(cause, message, formatArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DexFileNotFound(@Nullable String message, Object... formatArgs) {
|
|
||||||
super(message, 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 {
|
public static class UnsupportedOatVersionException extends ExceptionWithContext {
|
||||||
@Nonnull public final OatFile oatFile;
|
@Nonnull public final OatFile oatFile;
|
||||||
|
|
||||||
@ -223,4 +265,212 @@ public final class DexFileFactory {
|
|||||||
this.oatFile = oatFile;
|
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.io.Files;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import org.jf.dexlib2.DexFileFactory;
|
import org.jf.dexlib2.DexFileFactory;
|
||||||
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
|
|
||||||
import org.jf.dexlib2.Opcodes;
|
import org.jf.dexlib2.Opcodes;
|
||||||
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
|
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||||
@ -231,14 +230,16 @@ public class ClassPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File bestMatch = Collections.max(files, new ClassPathEntryComparator(entry));
|
File bestMatch = Collections.max(files, new ClassPathEntryComparator(entry));
|
||||||
try {
|
DexFile entryDexFile = DexFileFactory.loadDexFile(bestMatch, Opcodes.forApi(api, experimental));
|
||||||
DexFile entryDexFile = DexFileFactory.loadDexFile(bestMatch, api, experimental);
|
classProviders.add(new DexClassProvider(entryDexFile));
|
||||||
classProviders.add(new DexClassProvider(entryDexFile));
|
// TODO: DexFileFactory.loadAllDexFiles?
|
||||||
|
/*try {
|
||||||
|
|
||||||
} catch (MultipleDexFilesException ex) {
|
} catch (MultipleDexFilesException ex) {
|
||||||
for (DexFile entryDexFile: ex.oatFile.getDexFiles()) {
|
for (DexFile entryDexFile: ex.oatFile.getDexFiles()) {
|
||||||
classProviders.add(new DexClassProvider(entryDexFile));
|
classProviders.add(new DexClassProvider(entryDexFile));
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
int oatVersion = -1;
|
int oatVersion = -1;
|
||||||
@ -329,12 +330,9 @@ public class ClassPath {
|
|||||||
} else {
|
} else {
|
||||||
if (name.equals(child.getName())) {
|
if (name.equals(child.getName())) {
|
||||||
try {
|
try {
|
||||||
DexFileFactory.loadDexFile(child, 15);
|
DexFileFactory.loadDexFile(child);
|
||||||
} catch (ExceptionWithContext ex) {
|
} catch (ExceptionWithContext ex) {
|
||||||
if (!(ex instanceof MultipleDexFilesException)) {
|
continue;
|
||||||
// Don't add it to the results if it can't be loaded
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.add(child);
|
result.add(child);
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ public class AccessorTest {
|
|||||||
public void testAccessors() throws IOException {
|
public void testAccessors() throws IOException {
|
||||||
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
|
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
|
||||||
Assert.assertNotNull(url);
|
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());
|
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