Refactor how classpath loading works

This commit is contained in:
Ben Gruver 2016-09-18 12:41:46 -07:00
parent 4c77ad7617
commit 31ad2bc100
13 changed files with 995 additions and 631 deletions

View File

@ -34,14 +34,19 @@ package org.jf.baksmali;
import com.beust.jcommander.Parameter;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.ClassPathResolver;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.jcommander.ColonParameterSplitter;
import org.jf.util.jcommander.ExtendedParameter;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.List;
import static org.jf.dexlib2.analysis.ClassPath.NOT_ART;
public class AnalysisArguments {
@Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being disassembled.")
@ -68,7 +73,7 @@ public class AnalysisArguments {
description = "A directory to search for classpath files. This option can be used multiple times to " +
"specify multiple directories to search. They will be searched in the order they are provided.")
@ExtendedParameter(argumentNames = "dir")
public List<String> classPathDirectories = Lists.newArrayList(".");
public List<String> classPathDirectories = null;
public static class CheckPackagePrivateArgument {
@Parameter(names = {"--check-package-private-access", "--package-private", "--checkpp", "--pp"},
@ -77,9 +82,37 @@ public class AnalysisArguments {
public boolean checkPackagePrivateAccess = false;
}
@Nonnull
public ClassPath loadClassPathForDexFile(@Nonnull DexFile dexFile, boolean checkPackagePrivateAccess)
throws IOException {
return ClassPath.loadClassPath(classPathDirectories, bootClassPath, classPath, dexFile, apiLevel,
checkPackagePrivateAccess);
ClassPathResolver resolver;
List<String> filteredClassPathDirectories = Lists.newArrayList();
if (classPathDirectories != null) {
for (String dir: classPathDirectories) {
File file = new File(dir);
if (!file.exists()) {
System.err.println(String.format("Warning: directory %s does not exist. Ignoring.", dir));
} else if (!file.isDirectory()) {
System.err.println(String.format("Warning: %s is not a directory. Ignoring.", dir));
} else {
filteredClassPathDirectories.add(dir);
}
}
}
if (bootClassPath == null) {
// TODO: we should be able to get the api from the Opcodes object associated with the dexFile..
// except that the oat version -> api mapping doesn't fully work yet
resolver = new ClassPathResolver(filteredClassPathDirectories, classPath, dexFile, apiLevel);
} else {
resolver = new ClassPathResolver(filteredClassPathDirectories, bootClassPath, classPath, dexFile);
}
int oatVersion = NOT_ART;
if (dexFile instanceof OatDexFile) {
oatVersion = ((OatDexFile)dexFile).getOatFile().getOatVersion();
}
return new ClassPath(resolver.getResolvedClassProviders(), checkPackagePrivateAccess, oatVersion);
}
}

View File

@ -108,6 +108,67 @@ public class HelpCommand extends Command {
"around this, you can add double quotes around the entry name to specify an exact entry " +
"name. E.g. blah.oat/\"/blah/blah.dex\" or blah.oat/\"blah/blah.dex\" respectively.";
Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
ConsoleUtil.getConsoleWidth());
for (String line : lines) {
System.out.println(line);
}
} else if (cmd.equals("classpath")) {
printedHelp = true;
String registerInfoHelp = "When deodexing odex/oat files or when using the --register-info " +
"option, baksmali needs to load all classes from the framework files on the device " +
"in order to fully understand the class hierarchy. There are several options that " +
"control how baksmali finds and loads the classpath entries.\n" +
"\n"+
"L+ devices (ART):\n" +
"When deodexing or disassembling a file from an L+ device using ART, you generally " +
"just need to specify the path to the boot.oat file via the --bootclasspath/-b " +
"parameter. On pre-N devices, the boot.oat file is self-contained and no other files are " +
"needed. In N, boot.oat was split into multiple files. In this case, the other " +
"files should be in the same directory as the boot.oat file, but you still only need to " +
"specify the boot.oat file in the --bootclasspath/-b option. The other files will be " +
"automatically loaded from the same directory.\n" +
"\n" +
"Pre-L devices (dalvik):\n" +
"When deodexing odex files from a pre-L device using dalvik, you " +
"generally just need to specify the path to a directory containing the framework files " +
"from the device via the --classpath-dir/-d option. odex files contain a list of " +
"framework files they depend on and baksmali will search for these dependencies in the " +
"directory that you specify.\n" +
"\n" +
"Dex files don't contain a list of dependencies like odex files, so when disassembling a " +
"dex file using the --register-info option, and using the framework files from a " +
"pre-L device, baksmali will attempt to use a reasonable default list of classpath files " +
"based on the api level set via the -a option. If this default list is incorrect, you " +
"can override the classpath using the --bootclasspath/-b option. This option accepts a " +
"colon separated list of classpath entries. Each entry can be specified in a few " +
"different ways.\n" +
" - A simple filename like \"framework.jar\"\n" +
" - A device path like \"/system/framework/framework.jar\"\n" +
" - A local relative or absolute path like \"/tmp/framework/framework.jar\"\n" +
"When using the first or second formats, you should also specify the directory " +
"containing the framework files via the --classpath-dir/-d option. When using the third " +
"format, this option is not needed.\n" +
"It's worth noting that the second format matches the format used by Android for the " +
"BOOTCLASSPATH environment variable, so you can simply grab the value of that variable " +
"from the device and use it as-is.\n" +
"\n" +
"Examples:\n" +
" For an M device:\n" +
" adb pull /system/framework/arm/boot.oat /tmp/boot.oat\n" +
" baksmali deodex blah.oat -b /tmp/boot.oat\n" +
" For an N+ device:\n" +
" adb pull /system/framework/arm /tmp/framework\n" +
" baksmali deodex blah.oat -b /tmp/framework/boot.oat\n" +
" For a pre-L device:\n" +
" adb pull /system/framework /tmp/framework\n" +
" baksmali deodex blah.odex -d /tmp/framework\n" +
" Using the BOOTCLASSPATH on a pre-L device:\n" +
" adb pull /system/framework /tmp/framework\n" +
" export BOOTCLASSPATH=`adb shell \"echo \\\\$BOOTCLASPATH\"`\n" +
" baksmali disassemble --register-info ARGS,DEST blah.apk -b $BOOTCLASSPATH -d " +
"/tmp/framework";
Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
ConsoleUtil.getConsoleWidth());
for (String line : lines) {

View File

@ -36,6 +36,9 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
@ -85,7 +88,9 @@ public class ListDexCommand extends Command {
List<String> entries;
try {
entries = DexFileFactory.getAllDexEntries(file);
MultiDexContainer<? extends DexBackedDexFile> container =
DexFileFactory.loadDexContainer(file, Opcodes.forApi(15));
entries = container.getDexEntryNames();
} catch (IOException ex) {
throw new RuntimeException(ex);
}

View File

@ -32,7 +32,7 @@
package org.jf.dexlib2;
import com.google.common.base.Joiner;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
@ -40,18 +40,16 @@ import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.dexbacked.ZipDexContainer;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.dexlib2.writer.pool.DexPool;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class DexFileFactory {
@ -100,10 +98,12 @@ public final class DexFileFactory {
}
if (zipFile != null) {
ZipDexContainer container = new ZipDexContainer(zipFile, opcodes);
try {
return new ZipDexEntryFinder(zipFile, opcodes).findEntry("classes.dex", true);
return new DexEntryFinder(file.getPath(), container)
.findEntry("classes.dex", true);
} finally {
zipFile.close();
container.close();
}
}
@ -205,10 +205,11 @@ public final class DexFileFactory {
}
if (zipFile != null) {
ZipDexContainer container = new ZipDexContainer(zipFile, opcodes);
try {
return new ZipDexEntryFinder(zipFile, opcodes).findEntry(dexEntry, exactMatch);
return new DexEntryFinder(file.getPath(), container).findEntry(dexEntry, exactMatch);
} finally {
zipFile.close();
container.close();
}
}
@ -232,7 +233,7 @@ public final class DexFileFactory {
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
}
return new OatDexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
return new DexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
}
} finally {
inputStream.close();
@ -242,20 +243,18 @@ public final class DexFileFactory {
}
/**
* Loads all dex files from the given file.
* Loads a file containing 1 or more dex files
*
* If the given file is a dex or odex file, it will return an iterable with one element. If the given file is
* an oat file, it will return all dex files within the oat file. If the given file is a zip file, it will return
* all dex files matching "classes[0-9]*.dex"
* If the given file is a dex or odex file, it will return a MultiDexContainer containing that single entry.
* Otherwise, for an oat or zip file, it will return an OatFile or ZipDexContainer respectively.
*
* @param file The file to open
* @param opcodes The set of opcodes to use
* @return An iterable of DexBackedDexFiles
* @throws IOException
* @return A MultiDexContainer
* @throws DexFileNotFoundException If the given file does not exist
* @throws UnsupportedFileTypeException If the given file is not a zip or oat file
* @throws UnsupportedFileTypeException If the given file is not a valid dex/zip/odex/oat file
*/
public static Iterable<? extends DexBackedDexFile> loadAllDexFiles(
public static MultiDexContainer<? extends DexBackedDexFile> loadDexContainer(
@Nonnull File file, @Nonnull final Opcodes opcodes) throws IOException {
if (!file.exists()) {
throw new DexFileNotFoundException("%s does not exist", file.getName());
@ -269,45 +268,21 @@ public final class DexFileFactory {
}
if (zipFile != null) {
final Pattern dexPattern = Pattern.compile("classes[0-9]*.dex");
final ZipFile finalZipFile = zipFile;
return new Iterable<DexBackedDexFile>() {
@Override public Iterator<DexBackedDexFile> iterator() {
final Enumeration<? extends ZipEntry> entries = finalZipFile.entries();
return new AbstractIterator<DexBackedDexFile>() {
@Override protected DexBackedDexFile computeNext() {
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (dexPattern.matcher(zipEntry.getName()).matches()) {
try {
return loadDexFromZip(finalZipFile, zipEntry, opcodes);
} catch (IOException ex) {
throw new ExceptionWithContext(ex, "Error while reading %s from %s",
zipEntry.getName(), finalZipFile.getName());
} catch (NotADexFile ex) {
// ignore and continue
}
}
}
endOfData();
return null;
}
};
}
};
return new ZipDexContainer(zipFile, opcodes);
}
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
try {
try {
return Lists.newArrayList(DexBackedDexFile.fromInputStream(opcodes, inputStream));
DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(opcodes, inputStream);
return new SingletonMultiDexContainer(file.getPath(), dexFile);
} catch (DexBackedDexFile.NotADexFile ex) {
// just eat it
}
try {
return Lists.newArrayList(DexBackedOdexFile.fromInputStream(opcodes, inputStream));
DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(opcodes, inputStream);
return new SingletonMultiDexContainer(file.getPath(), odexFile);
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it
}
@ -323,11 +298,11 @@ public final class DexFileFactory {
}
if (oatFile != null) {
// TODO: we should support loading earlier oat files, just not deodexing them
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
throw new UnsupportedOatVersionException(oatFile);
}
return oatFile.getDexFiles();
return oatFile;
}
} finally {
inputStream.close();
@ -336,86 +311,11 @@ public final class DexFileFactory {
throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
}
/**
* Gets all dex entries from an oat/zip file.
*
* For zip files, only entries that match classes[0-9]*.dex will be returned.
*
* @param file The file to get dex entries from
* @return A list of strings contains the dex entry names
* @throws IOException
* @throws DexFileNotFoundException If the given file does not exist
* @throws UnsupportedFileTypeException If the given file is not a zip or oat file
*/
public static List<String> getAllDexEntries(@Nonnull File file) throws IOException {
if (!file.exists()) {
throw new DexFileNotFoundException("%s does not exist", file.getName());
}
List<String> entries = Lists.newArrayList();
Opcodes opcodes = Opcodes.forApi(15);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(file);
} catch (IOException ex) {
// ignore and continue
}
if (zipFile != null) {
Pattern dexPattern = Pattern.compile("classes[0-9]*.dex");
Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = zipEntries.nextElement();
if (dexPattern.matcher(zipEntry.getName()).matches()) {
try {
loadDexFromZip(zipFile, zipEntry, opcodes);
entries.add(zipEntry.getName());
} catch (IOException ex) {
throw new IOException(String.format("Error while reading %s from %s",
zipEntry.getName(), zipFile.getName()), ex);
}catch (NotADexFile ex) {
// ignore and continue
}
}
}
return entries;
}
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);
}
for (OatDexFile oatDexFile: oatFile.getDexFiles()) {
entries.add(oatDexFile.filename);
}
return entries;
}
} finally {
inputStream.close();
}
throw new UnsupportedFileTypeException("%s is not an apk, 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
* @param dexFile a DexFile to write
*/
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
DexPool.writeTo(path, dexFile);
@ -496,29 +396,28 @@ public final class DexFileFactory {
return firstTargetChar == ':' || firstTargetChar == '/' || precedingChar == ':' || precedingChar == '/';
}
protected abstract static class DexEntryFinder {
@Nullable
protected abstract DexBackedDexFile getEntry(@Nonnull String entry) throws IOException;
protected static class DexEntryFinder {
private final String filename;
private final MultiDexContainer<? extends DexBackedDexFile> dexContainer;
@Nonnull
protected abstract List<String> getEntryNames();
@Nonnull
protected abstract String getFilename();
public DexEntryFinder(@Nonnull String filename,
@Nonnull MultiDexContainer<? extends DexBackedDexFile> dexContainer) {
this.filename = filename;
this.dexContainer = dexContainer;
}
@Nonnull
public DexBackedDexFile findEntry(@Nonnull String targetEntry, boolean exactMatch) throws IOException {
if (exactMatch) {
DexBackedDexFile dexFile = getEntry(targetEntry);
try {
DexBackedDexFile dexFile = dexContainer.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());
}
throw new DexFileNotFoundException("Could not find entry %s in %s.", targetEntry, filename);
}
return dexFile;
} catch (NotADexFile ex) {
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry, filename);
}
}
// find all full and partial matches
@ -526,130 +425,67 @@ public final class DexFileFactory {
List<DexBackedDexFile> fullEntries = Lists.newArrayList();
List<String> partialMatches = Lists.newArrayList();
List<DexBackedDexFile> partialEntries = Lists.newArrayList();
for (String entry: getEntryNames()) {
for (String entry: dexContainer.getDexEntryNames()) {
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));
fullEntries.add(dexContainer.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);
}
partialEntries.add(dexContainer.getEntry(entry));
}
}
// full matches always take priority
if (fullEntries.size() == 1) {
try {
DexBackedDexFile dexFile = fullEntries.get(0);
if (dexFile == null) {
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
fullMatches.get(0), getFilename());
}
assert dexFile != null;
return dexFile;
} catch (NotADexFile ex) {
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
fullMatches.get(0), filename);
}
}
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,
"Multiple entries in %s match %s: %s", filename, targetEntry,
Joiner.on(", ").join(fullMatches)));
}
if (partialEntries.size() == 0) {
throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s",
getFilename(), targetEntry);
filename, targetEntry);
}
if (partialEntries.size() > 1) {
throw new MultipleMatchingDexEntriesException(String.format(
"Multiple dex entries in %s match %s: %s", getFilename(), targetEntry,
"Multiple dex entries in %s match %s: %s", filename, targetEntry,
Joiner.on(", ").join(partialMatches)));
}
return partialEntries.get(0);
}
}
@Nonnull
private static DexBackedDexFile loadDexFromZip(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry,
@Nonnull Opcodes opcodes) throws IOException {
InputStream stream;
stream = zipFile.getInputStream(zipEntry);
try {
return DexBackedDexFile.fromInputStream(opcodes, new BufferedInputStream(stream));
} finally {
if (stream != null) {
stream.close();
}
}
private static class SingletonMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
private final String entryName;
private final DexBackedDexFile dexFile;
public SingletonMultiDexContainer(@Nonnull String entryName, @Nonnull DexBackedDexFile dexFile) {
this.entryName = entryName;
this.dexFile = dexFile;
}
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;
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
return ImmutableList.of(entryName);
}
@Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException {
ZipEntry zipEntry = zipFile.getEntry(entry);
if (zipEntry == null) {
return null;
}
return loadDexFromZip(zipFile, zipEntry, opcodes);
}
@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)) {
@Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
if (entryName.equals(this.entryName)) {
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;
}
}
}

View File

@ -36,29 +36,18 @@ import com.google.common.base.Suppliers;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.*;
import com.google.common.io.Files;
import com.google.common.primitives.Ints;
import org.jf.dexlib2.DexFileFactory;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.jf.util.ExceptionWithContext;
import org.jf.util.PathUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Arrays;
import java.util.List;
public class ClassPath {
@Nonnull private final TypeProto unknownClass;
@ -165,173 +154,6 @@ public class ClassPath {
return checkPackagePrivateAccess;
}
/**
* Creates a ClassPath given a set of user inputs
*
* This performs all the magic in finding the right defaults based on the values provided and what type of dex
* file we have. E.g. choosing the right default bootclasspath if needed, actually locating the files on the
* filesystem, etc.
*
* This is meant to be as forgiving as possible and to generally "do the right thing" based on the given inputs.
*
* @param classPathDirs A list of directories to search for class path entries in. Be sure to include "." to search
* the current working directory, if appropriate.
* @param bootClassPathEntries A list of boot class path entries to load. This can be just the bare filenames,
* relative paths, absolute paths based on the local directory structure, absolute paths
* based on the device directory structure, etc. It can contain paths to
* jar/dex/oat/odex files, or just bare filenames with no extension, etc.
* If non-null and blank, then no entries will be loaded other than dexFile
* If null, it will attempt to use the correct defaults based on the inputs.
* @param extraClassPathEntries Additional class path entries. The same sorts of naming mechanisms as for
* bootClassPathEntries are allowed
* @param dexFile The dex file that will be analyzed. It can be a dex, odex or oat file.
* @param api The api level of the device that these dex files come from.
* @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
* default
*
* @return A ClassPath object
*/
@Nonnull
public static ClassPath loadClassPath(@Nonnull Iterable<String> classPathDirs,
@Nullable Iterable<String> bootClassPathEntries,
@Nonnull Iterable<String> extraClassPathEntries, @Nonnull DexFile dexFile,
int api, boolean checkPackagePrivateAccess)
throws IOException {
List<ClassProvider> classProviders = Lists.newArrayList();
if (bootClassPathEntries == null) {
bootClassPathEntries = getDefaultDeviceBootClassPath(dexFile, api);
}
for (String entry: Iterables.concat(bootClassPathEntries, extraClassPathEntries)) {
List<File> files = Lists.newArrayList();
for (String extension: new String[] { null, ".apk", ".jar", ".odex", ".oat", ".dex" }) {
String searchEntry = entry;
if (Files.getFileExtension(entry).equals(extension)) {
continue;
}
if (extension != null) {
searchEntry = Files.getNameWithoutExtension(entry) + extension;
}
for (String dir: classPathDirs) {
files.addAll(findFiles(new File(dir), new File(searchEntry).getName(), 100));
}
if (files.size() > 0) {
break;
}
}
if (files.size() == 0) {
throw new FileNotFoundException(String.format("Classpath entry %s could not be found", entry));
}
File bestMatch = Collections.max(files, new ClassPathEntryComparator(entry));
Iterable<? extends DexBackedDexFile> dexFiles =
DexFileFactory.loadAllDexFiles(bestMatch, Opcodes.forApi(api));
for (DexFile loadedDexFile: dexFiles) {
classProviders.add(new DexClassProvider(loadedDexFile));
}
}
int oatVersion = -1;
if (dexFile instanceof OatDexFile) {
oatVersion = ((OatDexFile)dexFile).getOatVersion();
}
classProviders.add(new DexClassProvider(dexFile));
return new ClassPath(classProviders, checkPackagePrivateAccess, oatVersion);
}
private static class ClassPathEntryComparator implements Comparator<File> {
@Nonnull private List<String> reversePathComponents;
public ClassPathEntryComparator(@Nonnull String entry) {
// TODO: will PathUtil.getPathComponents work for unix-style paths while on windows?
this.reversePathComponents = Lists.reverse(PathUtil.getPathComponents(new File(entry)));
}
@Override public int compare(File file1, File file2) {
int comparison = Ints.compare(countMatchingComponents(file1), countMatchingComponents(file2));
if (comparison != 0) {
// the path that matches the entry being searched for wins
return comparison;
}
comparison = Ints.compare(PathUtil.getPathComponents(file1).size(),
PathUtil.getPathComponents(file2).size());
if (comparison != 0) {
// the path "higher up" (with fewer directories) wins
return comparison * -1;
}
// otherwise.. just return the first one alphabetically.
return file1.compareTo(file2);
}
private int countMatchingComponents(File file) {
for (int i=0; i<reversePathComponents.size(); i++) {
if (file == null) {
return i;
}
if (!file.getName().equals(reversePathComponents.get(i))) {
return i;
}
file = file.getParentFile();
}
return reversePathComponents.size();
}
}
/**
* Ye olde recursive file search.
*
* Searches for all files (not directories!) named "name".
*
* It attempts to detect filesystem loops via File.getCanonicalPath, and will not recurse path maxDepth directories.
*/
@Nonnull
private static List<File> findFiles(@Nonnull File dir, @Nonnull String name, int maxDepth) throws IOException {
List<File> files = Lists.newArrayList();
Set<String> visitedPaths = Sets.newHashSet();
if (!dir.exists()) {
throw new IllegalArgumentException(String.format("Directory %s does not exist", dir.getPath()));
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException(String.format("%s is not a directory", dir.getPath()));
}
findFiles(files, visitedPaths, dir, name, maxDepth);
return files;
}
private static void findFiles(@Nonnull List<File> result, @Nonnull Set<String> visitedPaths, @Nonnull File dir,
@Nonnull String name, int maxDepth) throws IOException {
if (maxDepth < 0 || !visitedPaths.add(dir.getCanonicalPath())) {
return;
}
File[] children = dir.listFiles();
if (children == null) {
return;
}
for (File child: children) {
if (child.isDirectory()) {
findFiles(result, visitedPaths, child, name, maxDepth-1);
} else {
if (name.equals(child.getName())) {
try {
DexFileFactory.loadDexFile(child);
} catch (ExceptionWithContext ex) {
continue;
}
result.add(child);
}
}
}
}
private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
new Supplier<OdexedFieldInstructionMapper>() {
@Override public OdexedFieldInstructionMapper get() {
@ -343,133 +165,4 @@ public class ClassPath {
public OdexedFieldInstructionMapper getFieldInstructionMapper() {
return fieldInstructionMapperSupplier.get();
}
/**
* Returns the default boot class path for the given api. This is boot class path that is used for "stock"
* (i.e nexus) images for the given api level, but may not be correct for devices with heavily modified firmware.
*/
@Nonnull
private static List<String> getDefaultDeviceBootClassPath(DexFile dexFile, int apiLevel) {
if (dexFile instanceof OatFile.OatDexFile) {
if (((OatFile.OatDexFile) dexFile).getOatVersion() >= 74) {
return ((OatFile.OatDexFile) dexFile).getOatFile().getBootClassPath();
} else {
return Lists.newArrayList("boot.oat");
}
}
if (dexFile instanceof DexBackedOdexFile) {
return ((DexBackedOdexFile)dexFile).getDependencies();
}
if (apiLevel <= 8) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel <= 11) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 13) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 15) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel <= 17) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 18) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 19) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/conscrypt.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/framework2.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/webviewchromium.jar");
} else if (apiLevel <= 22) {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
} else /*if (apiLevel <= 23)*/ {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/apache-xml.jar",
"/system/framework/org.apache.http.legacy.boot.jar");
}
}
}

View File

@ -0,0 +1,432 @@
/*
* 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.analysis;
import com.beust.jcommander.internal.Sets;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.dexlib2.iface.MultiDexContainer.MultiDexFile;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public class ClassPathResolver {
private final Iterable<String> classPathDirs;
private final Opcodes opcodes;
private final Set<File> loadedFiles = Sets.newHashSet();
private final List<ClassProvider> classProviders = Lists.newArrayList();
/**
* Constructs a new ClassPathResolver using a specified list of bootclasspath entries
*
* @param bootClassPathDirs A list of directories to search for boot classpath entries. Can be empty if all boot
* classpath entries are specified as local paths
* @param bootClassPathEntries A list of boot classpath entries to load. These can either be local paths, or
* device paths (e.g. "/system/framework/framework.jar"). The entry will be interpreted
* first as a local path. If not found as a local path, it will be interpreted as a
* partial or absolute device path, and will be searched for in bootClassPathDirs
* @param extraClassPathEntries A list of additional classpath entries to load. Can be empty. All entries must be
* local paths. Device paths are not supported.
* @param dexFile The dex file that the classpath will be used to analyze
* @throws IOException If any IOException occurs
* @throws ResolveException If any classpath entries cannot be loaded for some reason
*
* If null, a default bootclasspath is used,
* depending on the the file type of dexFile and the api level. If empty, no boot
* classpath entries will be loaded
*/
public ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nonnull List<String> bootClassPathEntries,
@Nonnull List<String> extraClassPathEntries, @Nonnull DexFile dexFile)
throws IOException {
this(bootClassPathDirs, bootClassPathEntries, extraClassPathEntries, dexFile, dexFile.getOpcodes().api);
}
/**
* Constructs a new ClassPathResolver using a default list of bootclasspath entries
*
* @param bootClassPathDirs A list of directories to search for boot classpath entries
* @param extraClassPathEntries A list of additional classpath entries to load. Can be empty. All entries must be
* local paths. Device paths are not supported.
* @param dexFile The dex file that the classpath will be used to analyze
* @param apiLevel The api level of the device. This is used to select an appropriate set of boot classpath entries.
* @throws IOException If any IOException occurs
* @throws ResolveException If any classpath entries cannot be loaded for some reason
*
* If null, a default bootclasspath is used,
* depending on the the file type of dexFile and the api level. If empty, no boot
* classpath entries will be loaded
*/
public ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nonnull List<String> extraClassPathEntries,
@Nonnull DexFile dexFile, int apiLevel)
throws IOException {
this(bootClassPathDirs, null, extraClassPathEntries, dexFile, apiLevel);
}
private ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nullable List<String> bootClassPathEntries,
@Nonnull List<String> extraClassPathEntries,
@Nonnull DexFile dexFile, int apiLevel)
throws IOException {
this.classPathDirs = bootClassPathDirs;
opcodes = dexFile.getOpcodes();
if (bootClassPathEntries == null) {
bootClassPathEntries = getDefaultBootClassPath(dexFile, apiLevel);
}
for (String entry: bootClassPathEntries) {
try {
loadLocalOrDeviceBootClassPathEntry(entry);
} catch (NoDexException ex) {
if (entry.endsWith(".jar")) {
String odexEntry = entry.substring(0, entry.length() - 4) + ".odex";
try {
loadLocalOrDeviceBootClassPathEntry(odexEntry);
} catch (NoDexException ex2) {
throw new ResolveException("Neither %s nor %s contain a dex file", entry, odexEntry);
} catch (NotFoundException ex2) {
throw new ResolveException(ex);
}
} else {
throw new ResolveException(ex);
}
} catch (NotFoundException ex) {
throw new ResolveException(ex);
}
}
for (String entry: extraClassPathEntries) {
// extra classpath entries must be specified using a local path, so we don't need to do the search through
// bootClassPathDirs
try {
loadLocalClassPathEntry(entry);
} catch (NoDexException ex) {
throw new ResolveException(ex);
}
}
if (dexFile instanceof MultiDexContainer.MultiDexFile) {
MultiDexContainer<? extends MultiDexFile> container = ((MultiDexFile)dexFile).getContainer();
for (String entry: container.getDexEntryNames()) {
classProviders.add(new DexClassProvider(container.getEntry(entry)));
}
} else {
classProviders.add(new DexClassProvider(dexFile));
}
}
@Nonnull
public List<ClassProvider> getResolvedClassProviders() {
return classProviders;
}
private boolean loadLocalClassPathEntry(@Nonnull String entry) throws NoDexException, IOException {
File entryFile = new File(entry);
if (entryFile.exists() && entryFile.isFile()) {
try {
loadEntry(entryFile, true);
return true;
} catch (UnsupportedFileTypeException ex) {
throw new ResolveException(ex, "Couldn't load classpath entry %s", entry);
}
}
return false;
}
private void loadLocalOrDeviceBootClassPathEntry(@Nonnull String entry)
throws IOException, NoDexException, NotFoundException {
// first, see if the entry is a valid local path
if (loadLocalClassPathEntry(entry)) {
return;
}
// It's not a local path, so let's try to resolve it as a device path, relative to one of the provided
// directories
List<String> pathComponents = splitDevicePath(entry);
Joiner pathJoiner = Joiner.on(File.pathSeparatorChar);
for (String directory: classPathDirs) {
File directoryFile = new File(directory);
if (!directoryFile.exists()) {
// TODO: print a warning in the baksmali frontend before we get here
continue;
}
for (int i=0; i<pathComponents.size(); i++) {
String partialPath = pathJoiner.join(pathComponents.subList(i, pathComponents.size()));
File entryFile = new File(directoryFile, partialPath);
if (entryFile.exists() && entryFile.isFile()) {
loadEntry(entryFile, true);
return;
}
}
}
throw new NotFoundException("Could not find classpath entry %s", entry);
}
private void loadEntry(@Nonnull File entryFile, boolean loadOatDependencies)
throws IOException, NoDexException {
if (loadedFiles.contains(entryFile)) {
return;
}
MultiDexContainer<? extends DexBackedDexFile> container;
try {
container = DexFileFactory.loadDexContainer(entryFile, opcodes);
} catch (UnsupportedFileTypeException ex) {
throw new ResolveException(ex);
}
List<String> entryNames = container.getDexEntryNames();
if (entryNames.size() == 0) {
throw new NoDexException("%s contains no dex file");
}
loadedFiles.add(entryFile);
for (String entryName: entryNames) {
classProviders.add(new DexClassProvider(container.getEntry(entryName)));
}
if (loadOatDependencies && container instanceof OatFile) {
List<String> oatDependencies = ((OatFile)container).getBootClassPath();
if (!oatDependencies.isEmpty()) {
try {
loadOatDependencies(entryFile.getParentFile(), oatDependencies);
} catch (NotFoundException ex) {
throw new ResolveException(ex, "Error while loading oat file %s", entryFile);
} catch (NoDexException ex) {
throw new ResolveException(ex, "Error while loading dependencies for oat file %s", entryFile);
}
}
}
}
@Nonnull
private static List<String> splitDevicePath(@Nonnull String path) {
return Lists.newArrayList(Splitter.on('/').split(path));
}
private void loadOatDependencies(@Nonnull File directory, @Nonnull List<String> oatDependencies)
throws IOException, NoDexException, NotFoundException {
// We assume that all oat dependencies are located in the same directory as the oat file
for (String oatDependency: oatDependencies) {
String oatDependencyName = getFilenameForOatDependency(oatDependency);
File file = new File(directory, oatDependencyName);
if (!file.exists()) {
throw new NotFoundException("Cannot find dependency %s in %s", oatDependencyName, directory);
}
loadEntry(file, false);
}
}
@Nonnull
private String getFilenameForOatDependency(String oatDependency) {
int index = oatDependency.lastIndexOf('/');
String dependencyLeaf = oatDependency.substring(index+1);
if (dependencyLeaf.endsWith(".art")) {
return dependencyLeaf.substring(0, dependencyLeaf.length() - 4) + ".oat";
}
return dependencyLeaf;
}
private static class NotFoundException extends Exception {
public NotFoundException(String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
}
private static class NoDexException extends Exception {
public NoDexException(String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
}
/**
* An error that occurred while resolving the classpath
*/
public static class ResolveException extends RuntimeException {
public ResolveException (String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
public ResolveException (Throwable cause) {
super(cause);
}
public ResolveException (Throwable cause, String message, Object... formatArgs) {
super(String.format(message, formatArgs), cause);
}
}
/**
* Returns the default boot class path for the given dex file and api level.
*/
@Nonnull
private static List<String> getDefaultBootClassPath(@Nonnull DexFile dexFile, int apiLevel) {
if (dexFile instanceof OatFile.OatDexFile) {
return Lists.newArrayList("boot.oat");
}
if (dexFile instanceof DexBackedOdexFile) {
return ((DexBackedOdexFile)dexFile).getDependencies();
}
if (apiLevel <= 8) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel <= 11) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 13) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 15) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel <= 17) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 18) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 19) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/conscrypt.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/framework2.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/webviewchromium.jar");
} else if (apiLevel <= 22) {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
} else /*if (apiLevel <= 23)*/ {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/apache-xml.jar",
"/system/framework/org.apache.http.legacy.boot.jar");
}
// TODO: update for N
}
}

View File

@ -69,7 +69,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
private final int classCount;
private final int classStartOffset;
private DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
protected DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
super(buf, offset);
this.opcodes = opcodes;
@ -157,7 +157,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
};
}
private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
protected static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
if (!HeaderItem.verifyMagic(buf, offset)) {
StringBuilder sb = new StringBuilder("Invalid magic value:");
for (int i=0; i<8; i++) {

View File

@ -31,11 +31,15 @@
package org.jf.dexlib2.dexbacked;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol;
import org.jf.dexlib2.dexbacked.raw.HeaderItem;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.util.AbstractForwardSequentialList;
import javax.annotation.Nonnull;
@ -49,7 +53,7 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class OatFile extends BaseDexBuffer {
public class OatFile extends BaseDexBuffer implements MultiDexContainer<OatDexFile> {
private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
private static final int MIN_ELF_HEADER_SIZE = 52;
@ -171,54 +175,44 @@ public class OatFile extends BaseDexBuffer {
}
@Nonnull @Override public Iterator<OatDexFile> iterator() {
return new Iterator<OatDexFile>() {
int index = 0;
int offset = oatHeader.getDexListStart();
@Override public boolean hasNext() {
return index < size();
return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
@Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
return dexEntry.getDexFile();
}
@Override public OatDexFile next() {
int filenameLength = readSmallUint(offset);
offset += 4;
// TODO: what is the correct character encoding?
String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
offset += filenameLength;
offset += 4; // checksum
int dexOffset = readSmallUint(offset) + oatHeader.headerOffset;
offset += 4;
if (getOatVersion() >= 75) {
offset += 4; // offset to class offsets table
}
if (getOatVersion() >= 73) {
offset += 4; // lookup table offset
}
if (getOatVersion() < 75) {
// prior to 75, the class offsets are included here directly
int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
offset += 4 * classCount;
}
index++;
return new OatDexFile(dexOffset, filename);
}
@Override public void remove() {
throw new UnsupportedOperationException();
});
}
};
}
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
return new AbstractForwardSequentialList<String>() {
@Override public int size() {
return oatHeader.getDexFileCount();
}
@Nonnull @Override public Iterator<String> iterator() {
return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() {
@Nullable @Override public String apply(DexEntry dexEntry) {
return dexEntry.entryName;
}
});
}
};
}
public class OatDexFile extends DexBackedDexFile {
@Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException {
DexEntryIterator iterator = new DexEntryIterator();
while (iterator.hasNext()) {
DexEntry entry = iterator.next();
if (entry.entryName.equals(entryName)) {
return entry.getDexFile();
}
}
return null;
}
public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
@Nonnull public final String filename;
public OatDexFile(int offset, @Nonnull String filename) {
@ -226,8 +220,12 @@ public class OatFile extends BaseDexBuffer {
this.filename = filename;
}
public int getOatVersion() {
return OatFile.this.getOatVersion();
@Nonnull @Override public String getEntryName() {
return filename;
}
@Nonnull @Override public MultiDexContainer getContainer() {
return OatFile.this;
}
public OatFile getOatFile() {
@ -540,7 +538,64 @@ public class OatFile extends BaseDexBuffer {
return new String(buf, start, end-start, Charset.forName("US-ASCII"));
}
}
private class DexEntry {
public final String entryName;
public final int dexOffset;
public DexEntry(String entryName, int dexOffset) {
this.entryName = entryName;
this.dexOffset = dexOffset;
}
public OatDexFile getDexFile() {
return new OatDexFile(dexOffset, entryName);
}
}
private class DexEntryIterator implements Iterator<DexEntry> {
int index = 0;
int offset = oatHeader.getDexListStart();
@Override public boolean hasNext() {
return index < oatHeader.getDexFileCount();
}
@Override public DexEntry next() {
int filenameLength = readSmallUint(offset);
offset += 4;
// TODO: what is the correct character encoding?
String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
offset += filenameLength;
offset += 4; // checksum
int dexOffset = readSmallUint(offset) + oatHeader.headerOffset;
offset += 4;
if (getOatVersion() >= 75) {
offset += 4; // offset to class offsets table
}
if (getOatVersion() >= 73) {
offset += 4; // lookup table offset
}
if (getOatVersion() < 75) {
// prior to 75, the class offsets are included here directly
int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
offset += 4 * classCount;
}
index++;
return new DexEntry(filename, dexOffset);
}
@Override public void remove() {
throw new UnsupportedOperationException();
}
}
public static class InvalidOatFileException extends RuntimeException {
@ -552,4 +607,5 @@ public class OatFile extends BaseDexBuffer {
public static class NotAnOatFileException extends RuntimeException {
public NotAnOatFileException() {}
}
}

View File

@ -0,0 +1,166 @@
/*
* 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.dexbacked;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.dexbacked.ZipDexContainer.ZipDexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.jf.dexlib2.dexbacked.DexBackedDexFile.verifyMagicAndByteOrder;
/**
* Represents a zip file that contains dex files (i.e. an apk or jar file)
*/
public class ZipDexContainer implements MultiDexContainer<ZipDexFile>, Closeable {
private final ZipFile zipFile;
private final Opcodes opcodes;
/**
* Constructs a new ZipDexContainer for the given zip file
*
* @param zipFile A Zip
* @param opcodes The Opcodes instance to use when loading dex files from this container
*/
public ZipDexContainer(@Nonnull ZipFile zipFile, @Nonnull Opcodes opcodes) {
this.zipFile = zipFile;
this.opcodes = opcodes;
}
/**
* Gets a list of the names of dex files in this zip file.
*
* @return A list of the names of dex files in this zip file
*/
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
List<String> entryNames = Lists.newArrayList();
Enumeration<? extends ZipEntry> entriesEnumeration = zipFile.entries();
while (entriesEnumeration.hasMoreElements()) {
ZipEntry entry = entriesEnumeration.nextElement();
if (!isDex(entry)) {
continue;
}
entryNames.add(entry.getName());
}
return entryNames;
}
/**
* Loads a dex file from a specific named entry.
*
* @param entryName The name of the entry
* @return A ZipDexFile, or null if there is no entry with the given name
* @throws NotADexFile If the entry isn't a dex file
*/
@Nullable @Override public ZipDexFile getEntry(@Nonnull String entryName) throws IOException {
ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) {
return null;
}
return loadEntry(entry);
}
@Override public void close() throws IOException {
zipFile.close();
}
public class ZipDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
private final String entryName;
protected ZipDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, @Nonnull String entryName) {
super(opcodes, buf, 0);
this.entryName = entryName;
}
@Nonnull @Override public String getEntryName() {
return entryName;
}
@Nonnull @Override public MultiDexContainer getContainer() {
return ZipDexContainer.this;
}
}
private boolean isDex(@Nonnull ZipEntry zipEntry) throws IOException {
InputStream inputStream = zipFile.getInputStream(zipEntry);
try {
inputStream.mark(44);
byte[] partialHeader = new byte[44];
try {
ByteStreams.readFully(inputStream, partialHeader);
} catch (EOFException ex) {
throw new NotADexFile("File is too short");
}
try {
verifyMagicAndByteOrder(partialHeader, 0);
} catch (NotADexFile ex) {
return false;
}
return true;
} finally {
inputStream.close();
}
}
@Nonnull
private ZipDexFile loadEntry(@Nonnull ZipEntry zipEntry)
throws IOException {
InputStream inputStream = zipFile.getInputStream(zipEntry);
try {
byte[] buf = ByteStreams.toByteArray(inputStream);
return new ZipDexFile(opcodes, buf, zipEntry.getName());
} finally {
inputStream.close();
}
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.iface;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
/**
* This class represents a dex container that can contain multiple, named dex files
*/
public interface MultiDexContainer<T extends DexFile> {
/**
* @return A list of the names of dex entries in this container
*/
@Nonnull List<String> getDexEntryNames() throws IOException;
/**
* Gets the dex entry with the given name
*
* @param entryName The name of the entry
* @return A DexFile, or null if no entry with that name is found
*/
@Nullable T getEntry(@Nonnull String entryName) throws IOException;
/**
* This class represents a dex file that is contained in a MultiDexContainer
*/
interface MultiDexFile extends DexFile {
/**
* @return The name of this entry within its container
*/
@Nonnull String getEntryName();
/**
* @return The MultiDexContainer that contains this dex file
*/
@Nonnull MultiDexContainer<? extends MultiDexFile> getContainer();
}
}

View File

@ -33,12 +33,13 @@ 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.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.junit.Assert;
import org.junit.Test;
@ -47,12 +48,12 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static org.mockito.Mockito.mock;
public class DexEntryFinderTest {
@Test
public void testNormalStuff() throws Exception {
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
@ -60,7 +61,7 @@ public class DexEntryFinderTest {
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);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
@ -109,7 +110,7 @@ public class DexEntryFinderTest {
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);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar", true));
@ -130,7 +131,7 @@ public class DexEntryFinderTest {
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);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", true));
@ -148,13 +149,13 @@ public class DexEntryFinderTest {
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
entries.put("classes.dex", dexFile1);
entries.put("/blah/classes.dex", null);
TestDexEntryFinder testFinder = new TestDexEntryFinder("blah.oat", entries);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(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);
assertDexFileNotFound(testFinder, "/blah/classes.dex", false);
}
private void assertEntryNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
@ -184,27 +185,43 @@ public class DexEntryFinderTest {
}
}
public static class TestDexFileFactory {
public static class TestDexEntryFinder extends DexEntryFinder {
@Nonnull private final String fileName;
private void assertDexFileNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
try {
finder.findEntry(entry, exactMatch);
Assert.fail();
} catch (DexFileNotFoundException ex) {
// expected exception
}
}
public static class TestMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
@Nonnull private final Map<String, DexBackedDexFile> entries;
public TestDexEntryFinder(@Nonnull String fileName, @Nonnull Map<String, DexBackedDexFile> entries) {
this.fileName = fileName;
public TestMultiDexContainer(@Nonnull Map<String, DexBackedDexFile> entries) {
this.entries = entries;
}
@Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException {
return entries.get(entry);
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
List<String> entryNames = Lists.newArrayList();
for (Entry<String, DexBackedDexFile> entry: entries.entrySet()) {
if (entry.getValue() != null) {
entryNames.add(entry.getKey());
}
}
@Nonnull @Override protected List<String> getEntryNames() {
return Lists.newArrayList(entries.keySet());
return entryNames;
}
@Nonnull @Override protected String getFilename() {
return fileName;
}
@Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
if (entries.containsKey(entryName)) {
DexBackedDexFile entry = entries.get(entryName);
if (entry == null) {
throw new NotADexFile();
}
return entry;
}
return null;
}
}
}

View File

@ -70,8 +70,9 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
ClassPath classPath = ClassPath.loadClassPath(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, 15, false);
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -98,8 +99,10 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
ClassPath classPath = ClassPath.loadClassPath(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, 15, false);
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -125,8 +128,10 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
ClassPath classPath = ClassPath.loadClassPath(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, 15, false);
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);

View File

@ -47,16 +47,6 @@ public class PathUtil {
return new File(getRelativeFileInternal(baseFile.getCanonicalFile(), fileToRelativize.getCanonicalFile()));
}
public static String getRelativePath(String basePath, String pathToRelativize) throws IOException {
File baseFile = new File(basePath);
if (baseFile.isFile()) {
baseFile = baseFile.getParentFile();
}
return getRelativeFileInternal(baseFile.getCanonicalFile(),
new File(pathToRelativize).getCanonicalFile());
}
static String getRelativeFileInternal(File canonicalBaseFile, File canonicalFileToRelativize) {
List<String> basePath = getPathComponents(canonicalBaseFile);
List<String> pathToRelativize = getPathComponents(canonicalFileToRelativize);
@ -108,7 +98,7 @@ public class PathUtil {
return sb.toString();
}
public static List<String> getPathComponents(File file) {
private static List<String> getPathComponents(File file) {
ArrayList<String> path = new ArrayList<String>();
while (file != null) {