Make baksmali thread safe, and add -j option

This commit is contained in:
Ben Gruver 2013-05-11 20:58:53 -07:00
parent 4b171afedb
commit 7e25c35df7
6 changed files with 419 additions and 357 deletions

View File

@ -30,6 +30,7 @@ package org.jf.baksmali;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.ClassDef;
@ -40,10 +41,11 @@ import org.jf.util.IndentingWriter;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.concurrent.*;
public class baksmali { public class baksmali {
public static void disassembleDexFile(DexFile dexFile, baksmaliOptions options) { public static void disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
if (options.registerInfo != 0 || options.deodex) { if (options.registerInfo != 0 || options.deodex) {
try { try {
Iterable<String> extraClassPathEntries; Iterable<String> extraClassPathEntries;
@ -87,11 +89,34 @@ public class baksmali {
options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs); options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs);
} }
ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
for (ClassDef classDef: classDefs) { ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
disassembleClass(classDef, fileNameHandler, options); List<Future<Void>> tasks = Lists.newArrayList();
for (final ClassDef classDef: classDefs) {
tasks.add(executor.submit(new Callable<Void>() {
@Override public Void call() throws Exception {
disassembleClass(classDef, fileNameHandler, options);
return null;
}
}));
} }
for (Future<Void> task: tasks) {
while(true) {
try {
task.get();
} catch (InterruptedException ex) {
continue;
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
break;
}
}
executor.shutdown();
} }
private static void disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, private static void disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
@ -124,8 +149,11 @@ public class baksmali {
File smaliParent = smaliFile.getParentFile(); File smaliParent = smaliFile.getParentFile();
if (!smaliParent.exists()) { if (!smaliParent.exists()) {
if (!smaliParent.mkdirs()) { if (!smaliParent.mkdirs()) {
System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); // check again, it's likely it was created in a different thread
return; if (!smaliParent.exists()) {
System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
return;
}
} }
} }

View File

@ -68,6 +68,7 @@ public class baksmaliOptions {
public InlineMethodResolver inlineResolver = null; public InlineMethodResolver inlineResolver = null;
public int registerInfo = 0; public int registerInfo = 0;
public ClassPath classPath = null; public ClassPath classPath = null;
public int jobs = -1;
public SyntheticAccessorResolver syntheticAccessorResolver = null; public SyntheticAccessorResolver syntheticAccessorResolver = null;

View File

@ -193,6 +193,9 @@ public class main {
case 'a': case 'a':
options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break; break;
case 'j':
options.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
break;
case 'N': case 'N':
disassemble = false; disassemble = false;
break; break;
@ -219,6 +222,13 @@ public class main {
return; return;
} }
if (options.jobs <= 0) {
options.jobs = Runtime.getRuntime().availableProcessors();
if (options.jobs > 6) {
options.jobs = 6;
}
}
String inputDexFileName = remainingArgs[0]; String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName); File dexFileFile = new File(inputDexFileName);
@ -375,6 +385,13 @@ public class main {
.withArgName("API_LEVEL") .withArgName("API_LEVEL")
.create("a"); .create("a");
Option jobsOption = OptionBuilder.withLongOpt("jobs")
.withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
"maximum of 6")
.hasArg()
.withArgName("NUM_THREADS")
.create("j");
Option dumpOption = OptionBuilder.withLongOpt("dump-to") Option dumpOption = OptionBuilder.withLongOpt("dump-to")
.withDescription("dumps the given dex file into a single annotated dump file named FILE" + .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
" (<dexfile>.dump by default), along with the normal disassembly") " (<dexfile>.dump by default), along with the normal disassembly")
@ -418,6 +435,7 @@ public class main {
basicOptions.addOption(codeOffsetOption); basicOptions.addOption(codeOffsetOption);
basicOptions.addOption(noAccessorCommentsOption); basicOptions.addOption(noAccessorCommentsOption);
basicOptions.addOption(apiLevelOption); basicOptions.addOption(apiLevelOption);
basicOptions.addOption(jobsOption);
debugOptions.addOption(dumpOption); debugOptions.addOption(dumpOption);
debugOptions.addOption(ignoreErrorsOption); debugOptions.addOption(ignoreErrorsOption);

View File

@ -31,6 +31,9 @@
package org.jf.dexlib2.analysis; package org.jf.dexlib2.analysis;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -53,10 +56,8 @@ import java.util.regex.Pattern;
public class ClassPath { public class ClassPath {
@Nonnull private final TypeProto unknownClass; @Nonnull private final TypeProto unknownClass;
@Nonnull private DexFile[] dexFiles;
@Nonnull private HashMap<String, TypeProto> loadedClasses = Maps.newHashMap();
@Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap(); @Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
@Nonnull private int api; private int api;
/** /**
* Creates a new ClassPath instance that can load classes from the given dex files * Creates a new ClassPath instance that can load classes from the given dex files
@ -78,6 +79,7 @@ public class ClassPath {
} }
private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray, int api) { private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray, int api) {
DexFile[] dexFiles;
if (copyArray) { if (copyArray) {
dexFiles = new DexFile[classPath.length+1]; dexFiles = new DexFile[classPath.length+1];
System.arraycopy(classPath, 0, dexFiles, 0, classPath.length); System.arraycopy(classPath, 0, dexFiles, 0, classPath.length);
@ -128,23 +130,21 @@ public class ClassPath {
@Nonnull @Nonnull
public TypeProto getClass(CharSequence type) { public TypeProto getClass(CharSequence type) {
String typeString = type.toString(); return loadedClasses.getUnchecked(type.toString());
TypeProto typeProto = loadedClasses.get(typeString);
if (typeProto != null) {
return typeProto;
}
if (type.charAt(0) == '[') {
typeProto = new ArrayProto(this, typeString);
} else {
typeProto = new ClassProto(this, typeString);
}
// All primitive types are preloaded into loadedClasses, so we don't need to check for that here
loadedClasses.put(typeString, typeProto);
return typeProto;
} }
private final CacheLoader<String, TypeProto> classLoader = new CacheLoader<String, TypeProto>() {
@Override public TypeProto load(String type) throws Exception {
if (type.charAt(0) == '[') {
return new ArrayProto(ClassPath.this, type);
} else {
return new ClassProto(ClassPath.this, type);
}
}
};
@Nonnull private LoadingCache<String, TypeProto> loadedClasses = CacheBuilder.newBuilder().build(classLoader);
@Nonnull @Nonnull
public ClassDef getClassDef(String type) { public ClassDef getClassDef(String type) {
ClassDef ret = availableClasses.get(type); ClassDef ret = availableClasses.get(type);

View File

@ -32,7 +32,12 @@
package org.jf.dexlib2.analysis; package org.jf.dexlib2.analysis;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.*; import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.analysis.util.TypeProtoUtils; import org.jf.dexlib2.analysis.util.TypeProtoUtils;
import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.ClassDef;
@ -57,10 +62,7 @@ import java.util.List;
public class ClassProto implements TypeProto { public class ClassProto implements TypeProto {
@Nonnull protected final ClassPath classPath; @Nonnull protected final ClassPath classPath;
@Nonnull protected final String type; @Nonnull protected final String type;
@Nullable protected ClassDef classDef;
@Nullable protected LinkedHashMap<String, ClassDef> interfaces;
@Nullable protected List<Method> vtable;
@Nullable protected SparseArray<FieldReference> instanceFields;
protected boolean vtableFullyResolved = true; protected boolean vtableFullyResolved = true;
protected boolean interfacesFullyResolved = true; protected boolean interfacesFullyResolved = true;
@ -78,27 +80,15 @@ public class ClassProto implements TypeProto {
@Nonnull @Nonnull
public ClassDef getClassDef() { public ClassDef getClassDef() {
if (classDef == null) { return classDefSupplier.get();
classDef = classPath.getClassDef(type);
}
return classDef;
} }
@Nonnull
List<Method> getVtable() {
if (vtable == null) {
loadVtable();
}
return vtable;
}
@Nonnull @Nonnull private final Supplier<ClassDef> classDefSupplier = Suppliers.memoize(new Supplier<ClassDef>() {
SparseArray<FieldReference> getInstanceFields() { @Override public ClassDef get() {
if (instanceFields == null) { return classPath.getClassDef(type);
instanceFields = loadFields();
} }
return instanceFields; });
}
/** /**
* Returns true if this class is an interface. * Returns true if this class is an interface.
@ -128,81 +118,88 @@ public class ClassProto implements TypeProto {
*/ */
@Nonnull @Nonnull
protected LinkedHashMap<String, ClassDef> getInterfaces() { protected LinkedHashMap<String, ClassDef> getInterfaces() {
if (interfaces != null) { return interfacesSupplier.get();
return interfaces;
}
interfaces = Maps.newLinkedHashMap();
try {
for (String interfaceType: getClassDef().getInterfaces()) {
if (!interfaces.containsKey(interfaceType)) {
ClassDef interfaceDef;
try {
interfaceDef = classPath.getClassDef(interfaceType);
interfaces.put(interfaceType, interfaceDef);
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
interfacesFullyResolved = false;
}
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superInterface)) {
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
}
}
if (!interfaceProto.interfacesFullyResolved) {
interfacesFullyResolved = false;
}
}
}
} catch (UnresolvedClassException ex) {
interfacesFullyResolved = false;
}
// now add self and super class interfaces, required for common super class lookup
// we don't really need ClassDef's for that, so let's just use null
if (isInterface() && !interfaces.containsKey(getType())) {
interfaces.put(getType(), null);
}
try {
String superclass = getSuperclass();
if (superclass != null) {
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superclassInterface)) {
interfaces.put(superclassInterface, null);
}
}
if (!superclassProto.interfacesFullyResolved) {
interfacesFullyResolved = false;
}
}
} catch (UnresolvedClassException ex) {
interfacesFullyResolved = false;
}
return interfaces;
} }
@Nonnull
private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() {
LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
try {
for (String interfaceType: getClassDef().getInterfaces()) {
if (!interfaces.containsKey(interfaceType)) {
ClassDef interfaceDef;
try {
interfaceDef = classPath.getClassDef(interfaceType);
interfaces.put(interfaceType, interfaceDef);
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
interfacesFullyResolved = false;
}
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superInterface)) {
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
}
}
if (!interfaceProto.interfacesFullyResolved) {
interfacesFullyResolved = false;
}
}
}
} catch (UnresolvedClassException ex) {
interfacesFullyResolved = false;
}
// now add self and super class interfaces, required for common super class lookup
// we don't really need ClassDef's for that, so let's just use null
if (isInterface() && !interfaces.containsKey(getType())) {
interfaces.put(getType(), null);
}
try {
String superclass = getSuperclass();
if (superclass != null) {
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superclassInterface)) {
interfaces.put(superclassInterface, null);
}
}
if (!superclassProto.interfacesFullyResolved) {
interfacesFullyResolved = false;
}
}
} catch (UnresolvedClassException ex) {
interfacesFullyResolved = false;
}
return interfaces;
}
});
/** /**
* Gets the interfaces directly implemented by this class, or the interfaces they transitively implement. * Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
* *
* This does not include any interfaces that are only implemented by a superclass * This does not include any interfaces that are only implemented by a superclass
* *
* @return An iterables of ClassDefs representing the directly or transitively implemented interfaces * @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
* @throws UnresolvedClassException if any interface could not be resolved * @throws UnresolvedClassException if interfaces could not be fully resolved
*/ */
@Nonnull @Nonnull
protected Iterable<ClassDef> getDirectInterfaces() { protected Iterable<ClassDef> getDirectInterfaces() {
Iterable<ClassDef> directInterfaces =
FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull());
if (!interfacesFullyResolved) { if (!interfacesFullyResolved) {
throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
} }
return FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull()); return directInterfaces;
} }
/** /**
@ -213,6 +210,8 @@ public class ClassProto implements TypeProto {
* *
* @param iface The interface to check for * @param iface The interface to check for
* @return true if this class implements the given interface, otherwise false * @return true if this class implements the given interface, otherwise false
* @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface
* is not one of the interfaces that were successfully resolved
*/ */
@Override @Override
public boolean implementsInterface(@Nonnull String iface) { public boolean implementsInterface(@Nonnull String iface) {
@ -354,184 +353,193 @@ public class ClassProto implements TypeProto {
return vtable.get(vtableIndex); return vtable.get(vtableIndex);
} }
@Nonnull @Nonnull SparseArray<FieldReference> getInstanceFields() {
private SparseArray<FieldReference> loadFields() { return instanceFieldsSupplier.get();
//This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
//arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
//See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
final byte REFERENCE = 0;
final byte WIDE = 1;
final byte OTHER = 2;
ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
final int fieldCount = fields.size();
//the "type" for each field in fields. 0=reference,1=wide,2=other
byte[] fieldTypes = new byte[fields.size()];
for (int i=0; i<fieldCount; i++) {
fieldTypes[i] = getFieldType(fields.get(i));
}
//The first operation is to move all of the reference fields to the front. To do this, find the first
//non-reference field, then find the last reference field, swap them and repeat
int back = fields.size() - 1;
int front;
for (front = 0; front<fieldCount; front++) {
if (fieldTypes[front] != REFERENCE) {
while (back > front) {
if (fieldTypes[back] == REFERENCE) {
swap(fieldTypes, fields, front, back--);
break;
}
back--;
}
}
if (fieldTypes[front] != REFERENCE) {
break;
}
}
int startFieldOffset = 8;
String superclassType = getSuperclass();
ClassProto superclass = null;
if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType);
if (superclass != null) {
startFieldOffset = superclass.getNextFieldOffset();
}
}
int fieldIndexMod;
if ((startFieldOffset % 8) == 0) {
fieldIndexMod = 0;
} else {
fieldIndexMod = 1;
}
//next, we need to group all the wide fields after the reference fields. But the wide fields have to be
//8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
//is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
//If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
if (front < fieldCount && (front % 2) != fieldIndexMod) {
if (fieldTypes[front] == WIDE) {
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
back = fieldCount - 1;
while (back > front) {
if (fieldTypes[back] == OTHER) {
swap(fieldTypes, fields, front++, back);
break;
}
back--;
}
} else {
//there's already a 32-bit field here that we can use
front++;
}
}
//do the swap thing for wide fields
back = fieldCount - 1;
for (; front<fieldCount; front++) {
if (fieldTypes[front] != WIDE) {
while (back > front) {
if (fieldTypes[back] == WIDE) {
swap(fieldTypes, fields, front, back--);
break;
}
back--;
}
}
if (fieldTypes[front] != WIDE) {
break;
}
}
int superFieldCount = 0;
if (superclass != null) {
superFieldCount = superclass.instanceFields.size();
}
//now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
int totalFieldCount = superFieldCount + fieldCount;
SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
int fieldOffset;
if (superclass != null && superFieldCount > 0) {
for (int i=0; i<superFieldCount; i++) {
instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i));
}
fieldOffset = instanceFields.keyAt(superFieldCount-1);
FieldReference lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
char fieldType = lastSuperField.getType().charAt(0);
if (fieldType == 'J' || fieldType == 'D') {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
} else {
//the field values start at 8 bytes into the DataObject dalvik structure
fieldOffset = 8;
}
boolean gotDouble = false;
for (int i=0; i<fieldCount; i++) {
FieldReference field = fields.get(i);
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
if (!gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
}
instanceFields.append(fieldOffset, field);
if (fieldTypes[i] == WIDE) {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
}
return instanceFields;
} }
@Nonnull @Nonnull private final Supplier<SparseArray<FieldReference>> instanceFieldsSupplier =
private static ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) { Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields()); @Override public SparseArray<FieldReference> get() {
Collections.sort(fields); //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
return fields; //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
} //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
private byte getFieldType(@Nonnull FieldReference field) { final byte REFERENCE = 0;
switch (field.getType().charAt(0)) { final byte WIDE = 1;
case '[': final byte OTHER = 2;
case 'L':
return 0; //REFERENCE
case 'J':
case 'D':
return 1; //WIDE
default:
return 2; //OTHER
}
}
private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) { ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
byte tempType = fieldTypes[position1]; final int fieldCount = fields.size();
fieldTypes[position1] = fieldTypes[position2]; //the "type" for each field in fields. 0=reference,1=wide,2=other
fieldTypes[position2] = tempType; byte[] fieldTypes = new byte[fields.size()];
for (int i=0; i<fieldCount; i++) {
fieldTypes[i] = getFieldType(fields.get(i));
}
Field tempField = fields.set(position1, fields.get(position2)); //The first operation is to move all of the reference fields to the front. To do this, find the first
fields.set(position2, tempField); //non-reference field, then find the last reference field, swap them and repeat
} int back = fields.size() - 1;
int front;
for (front = 0; front<fieldCount; front++) {
if (fieldTypes[front] != REFERENCE) {
while (back > front) {
if (fieldTypes[back] == REFERENCE) {
swap(fieldTypes, fields, front, back--);
break;
}
back--;
}
}
if (fieldTypes[front] != REFERENCE) {
break;
}
}
int startFieldOffset = 8;
String superclassType = getSuperclass();
ClassProto superclass = null;
if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType);
if (superclass != null) {
startFieldOffset = superclass.getNextFieldOffset();
}
}
int fieldIndexMod;
if ((startFieldOffset % 8) == 0) {
fieldIndexMod = 0;
} else {
fieldIndexMod = 1;
}
//next, we need to group all the wide fields after the reference fields. But the wide fields have to be
//8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
//is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
//If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
if (front < fieldCount && (front % 2) != fieldIndexMod) {
if (fieldTypes[front] == WIDE) {
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
back = fieldCount - 1;
while (back > front) {
if (fieldTypes[back] == OTHER) {
swap(fieldTypes, fields, front++, back);
break;
}
back--;
}
} else {
//there's already a 32-bit field here that we can use
front++;
}
}
//do the swap thing for wide fields
back = fieldCount - 1;
for (; front<fieldCount; front++) {
if (fieldTypes[front] != WIDE) {
while (back > front) {
if (fieldTypes[back] == WIDE) {
swap(fieldTypes, fields, front, back--);
break;
}
back--;
}
}
if (fieldTypes[front] != WIDE) {
break;
}
}
SparseArray<FieldReference> superFields;
if (superclass != null) {
superFields = superclass.getInstanceFields();
} else {
superFields = new SparseArray<FieldReference>();
}
int superFieldCount = superFields.size();
//now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
int totalFieldCount = superFieldCount + fieldCount;
SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
int fieldOffset;
if (superclass != null && superFieldCount > 0) {
for (int i=0; i<superFieldCount; i++) {
instanceFields.append(superFields.keyAt(i), superFields.valueAt(i));
}
fieldOffset = instanceFields.keyAt(superFieldCount-1);
FieldReference lastSuperField = superFields.valueAt(superFieldCount-1);
char fieldType = lastSuperField.getType().charAt(0);
if (fieldType == 'J' || fieldType == 'D') {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
} else {
//the field values start at 8 bytes into the DataObject dalvik structure
fieldOffset = 8;
}
boolean gotDouble = false;
for (int i=0; i<fieldCount; i++) {
FieldReference field = fields.get(i);
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
if (!gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
}
instanceFields.append(fieldOffset, field);
if (fieldTypes[i] == WIDE) {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
}
return instanceFields;
}
@Nonnull
private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
Collections.sort(fields);
return fields;
}
private byte getFieldType(@Nonnull FieldReference field) {
switch (field.getType().charAt(0)) {
case '[':
case 'L':
return 0; //REFERENCE
case 'J':
case 'D':
return 1; //WIDE
default:
return 2; //OTHER
}
}
private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
byte tempType = fieldTypes[position1];
fieldTypes[position1] = fieldTypes[position2];
fieldTypes[position2] = tempType;
Field tempField = fields.set(position1, fields.get(position2));
fields.set(position2, tempField);
}
});
private int getNextFieldOffset() { private int getNextFieldOffset() {
SparseArray<FieldReference> instanceFields = getInstanceFields(); SparseArray<FieldReference> instanceFields = getInstanceFields();
@ -552,93 +560,100 @@ public class ClassProto implements TypeProto {
} }
} }
//TODO: check the case when we have a package private method that overrides an interface method @Nonnull List<Method> getVtable() {
private void loadVtable() { return vtableSupplier.get();
vtable = Lists.newArrayList();
//copy the virtual methods from the superclass
String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return;
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
// class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.interfacesFullyResolved) {
vtableFullyResolved = false;
return;
}
}
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true);
for (ClassDef interfaceDef: getDirectInterfaces()) {
addToVtable(interfaceDef.getVirtualMethods(), vtable, false);
}
}
} }
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, //TODO: check the case when we have a package private method that overrides an interface method
@Nonnull List<Method> vtable, boolean replaceExisting) { @Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
List<? extends Method> methods = Lists.newArrayList(localMethods); @Override public List<Method> get() {
Collections.sort(methods); List<Method> vtable = Lists.newArrayList();
outer: for (Method virtualMethod: methods) { //copy the virtual methods from the superclass
for (int i=0; i<vtable.size(); i++) { String superclassType;
Method superMethod = vtable.get(i); try {
if (methodSignaturesMatch(superMethod, virtualMethod)) { superclassType = getSuperclass();
if (classPath.getApi() < 17 || canAccess(superMethod)) { } catch (UnresolvedClassException ex) {
if (replaceExisting) { vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtable.set(i, virtualMethod); vtableFullyResolved = false;
} return vtable;
continue outer; }
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
// class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.vtableFullyResolved) {
vtableFullyResolved = false;
return vtable;
} }
} }
// we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod);
}
}
private static boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) { //iterate over the virtual methods in the current class, and only add them when we don't already have the
return (a.getName().equals(b.getName()) && //method (i.e. if it was implemented by the superclass)
a.getReturnType().equals(b.getReturnType()) && if (!isInterface()) {
a.getParameters().equals(b.getParameters())); addToVtable(getClassDef().getVirtualMethods(), vtable, true);
}
private boolean canAccess(@Nonnull Method virtualMethod) { for (ClassDef interfaceDef: getDirectInterfaces()) {
if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) { addToVtable(interfaceDef.getVirtualMethods(), vtable, false);
return true; }
}
return vtable;
} }
String otherPackage = getPackage(virtualMethod.getDefiningClass()); private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
String ourPackage = getPackage(getClassDef().getType()); @Nonnull List<Method> vtable, boolean replaceExisting) {
return otherPackage.equals(ourPackage); List<? extends Method> methods = Lists.newArrayList(localMethods);
} Collections.sort(methods);
@Nonnull outer: for (Method virtualMethod: methods) {
private static String getPackage(@Nonnull String classType) { for (int i=0; i<vtable.size(); i++) {
int lastSlash = classType.lastIndexOf('/'); Method superMethod = vtable.get(i);
if (lastSlash < 0) { if (methodSignaturesMatch(superMethod, virtualMethod)) {
return ""; if (classPath.getApi() < 17 || canAccess(superMethod)) {
if (replaceExisting) {
vtable.set(i, virtualMethod);
}
continue outer;
}
}
}
// we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod);
}
} }
return classType.substring(1, lastSlash);
}
private static boolean methodIsPackagePrivate(int accessFlags) { private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
return (accessFlags & (AccessFlags.PRIVATE.getValue() | return (a.getName().equals(b.getName()) &&
AccessFlags.PROTECTED.getValue() | a.getReturnType().equals(b.getReturnType()) &&
AccessFlags.PUBLIC.getValue())) == 0; a.getParameters().equals(b.getParameters()));
} }
private boolean canAccess(@Nonnull Method virtualMethod) {
if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) {
return true;
}
String otherPackage = getPackage(virtualMethod.getDefiningClass());
String ourPackage = getPackage(getClassDef().getType());
return otherPackage.equals(ourPackage);
}
@Nonnull
private String getPackage(@Nonnull String classType) {
int lastSlash = classType.lastIndexOf('/');
if (lastSlash < 0) {
return "";
}
return classType.substring(1, lastSlash);
}
private boolean methodIsPackagePrivate(int accessFlags) {
return (accessFlags & (AccessFlags.PRIVATE.getValue() |
AccessFlags.PROTECTED.getValue() |
AccessFlags.PUBLIC.getValue())) == 0;
}
});
} }

View File

@ -159,7 +159,7 @@ public class ClassFileNameHandler {
} }
@Override @Override
public File addUniqueChild(String[] pathElements, int pathElementsIndex) { public synchronized File addUniqueChild(String[] pathElements, int pathElementsIndex) {
String elementName; String elementName;
String elementNameLower; String elementNameLower;