Allow vtable lookups of Object methods for a class that can't be fully resolved

This commit is contained in:
Ben Gruver 2013-05-07 19:38:38 -07:00
parent 4ee6056b23
commit ec1348b46d
2 changed files with 48 additions and 43 deletions

View File

@ -59,8 +59,9 @@ public class ClassProto implements TypeProto {
@Nonnull protected final String type; @Nonnull protected final String type;
@Nullable protected ClassDef classDef; @Nullable protected ClassDef classDef;
@Nullable protected LinkedHashMap<String, ClassDef> interfaces; @Nullable protected LinkedHashMap<String, ClassDef> interfaces;
@Nullable protected Method[] vtable; @Nullable protected List<Method> vtable;
@Nullable protected SparseArray<FieldReference> instanceFields; @Nullable protected SparseArray<FieldReference> instanceFields;
protected boolean vtableFullyResolved = true;
protected boolean interfacesFullyResolved = true; protected boolean interfacesFullyResolved = true;
public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) { public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
@ -84,9 +85,9 @@ public class ClassProto implements TypeProto {
} }
@Nonnull @Nonnull
Method[] getVtable() { List<Method> getVtable() {
if (vtable == null) { if (vtable == null) {
vtable = loadVtable(); loadVtable();
} }
return vtable; return vtable;
} }
@ -346,11 +347,11 @@ public class ClassProto implements TypeProto {
@Override @Override
@Nullable @Nullable
public MethodReference getMethodByVtableIndex(int vtableIndex) { public MethodReference getMethodByVtableIndex(int vtableIndex) {
Method[] vtable = getVtable(); List<Method> vtable = getVtable();
if (vtableIndex < 0 || vtableIndex >= vtable.length) { if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
return null; return null;
} }
return vtable[vtableIndex]; return vtable.get(vtableIndex);
} }
@Nonnull @Nonnull
@ -552,66 +553,68 @@ public class ClassProto implements TypeProto {
} }
//TODO: check the case when we have a package private method that overrides an interface method //TODO: check the case when we have a package private method that overrides an interface method
@Nonnull private void loadVtable() {
private Method[] loadVtable() { vtable = Lists.newArrayList();
List<Method> virtualMethodList = Lists.newLinkedList();
//copy the virtual methods from the superclass //copy the virtual methods from the superclass
String superclassType = getSuperclass(); String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return;
}
if (superclassType != null) { if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType); ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
for (int i=0; i<superclass.getVtable().length; i++) { vtable.addAll(superclass.getVtable());
virtualMethodList.add(superclass.getVtable()[i]);
// 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 //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) //method (i.e. if it was implemented by the superclass)
if (!isInterface()) { if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), virtualMethodList, true); addToVtable(getClassDef().getVirtualMethods(), vtable, true);
for (ClassDef interfaceDef: getDirectInterfaces()) { for (ClassDef interfaceDef: getDirectInterfaces()) {
addToVtable(interfaceDef.getVirtualMethods(), virtualMethodList, false); addToVtable(interfaceDef.getVirtualMethods(), vtable, false);
} }
} }
Method[] vtable = new Method[virtualMethodList.size()];
for (int i=0; i<virtualMethodList.size(); i++) {
vtable[i] = virtualMethodList.get(i);
}
return vtable;
} }
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable, private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
boolean replaceExisting) { @Nonnull List<Method> vtable, boolean replaceExisting) {
List<? extends Method> methods = Lists.newArrayList(localMethods); List<? extends Method> methods = Lists.newArrayList(localMethods);
Collections.sort(methods); Collections.sort(methods);
for (Method virtualMethod: methods) { outer: for (Method virtualMethod: methods) {
boolean found = false;
for (int i=0; i<vtable.size(); i++) { for (int i=0; i<vtable.size(); i++) {
Method superMethod = vtable.get(i); Method superMethod = vtable.get(i);
if (methodSignaturesMatch(superMethod, virtualMethod)) { if (methodSignaturesMatch(superMethod, virtualMethod)) {
if (classPath.getApi() < 17 || canAccess(superMethod)) { if (classPath.getApi() < 17 || canAccess(superMethod)) {
found = true;
if (replaceExisting) { if (replaceExisting) {
vtable.set(i, virtualMethod); vtable.set(i, virtualMethod);
} }
break; continue outer;
} }
} }
} }
if (!found) { // we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod); vtable.add(virtualMethod);
}
} }
} }
private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) { private static boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
return (a.getName().equals(b.getName()) return (a.getName().equals(b.getName()) &&
&& a.getReturnType().equals(b.getReturnType()) a.getReturnType().equals(b.getReturnType()) &&
&& a.getParameters().equals(b.getParameters())); a.getParameters().equals(b.getParameters()));
} }
private boolean canAccess(@Nonnull Method virtualMethod) { private boolean canAccess(@Nonnull Method virtualMethod) {
@ -625,7 +628,7 @@ public class ClassProto implements TypeProto {
} }
@Nonnull @Nonnull
private String getPackage(@Nonnull String classType) { private static String getPackage(@Nonnull String classType) {
int lastSlash = classType.lastIndexOf('/'); int lastSlash = classType.lastIndexOf('/');
if (lastSlash < 0) { if (lastSlash < 0) {
return ""; return "";

View File

@ -112,16 +112,18 @@ public class DumpVtables {
for (ClassDef classDef: dexFile.getClasses()) { for (ClassDef classDef: dexFile.getClasses()) {
ClassProto classProto = (ClassProto) classPath.getClass(classDef); ClassProto classProto = (ClassProto) classPath.getClass(classDef);
Method[] methods = classProto.getVtable(); List<Method> methods = classProto.getVtable();
String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.length + " methods\n"; String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n";
outStream.write(className.getBytes()); outStream.write(className.getBytes());
for (int i=0;i<methods.length;i++) { for (int i=0;i<methods.size();i++) {
String method = i + ":" + methods[i].getDefiningClass() + "->" + methods[i].getName() + "("; Method method = methods.get(i);
for (CharSequence parameter: methods[i].getParameterTypes()) {
method += parameter; String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
for (CharSequence parameter: method.getParameterTypes()) {
methodString += parameter;
} }
method += ")" + methods[i].getReturnType() + "\n"; methodString += ")" + method.getReturnType() + "\n";
outStream.write(method.getBytes()); outStream.write(methodString.getBytes());
} }
outStream.write("\n".getBytes()); outStream.write("\n".getBytes());
} }