diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index 6705d067..360d5f23 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -456,6 +456,19 @@ public class ClassProto implements TypeProto { return -1; } + private int findMethodIndexInVtableReverse(@Nonnull List vtable, MethodReference method) { + for (int i=vtable.size() - 1; i>=0; i--) { + Method candidate = vtable.get(i); + if (MethodUtil.methodSignaturesMatch(candidate, method)) { + if (!classPath.shouldCheckPackagePrivateAccess() || + AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) { + return i; + } + } + } + return -1; + } + @Nonnull public SparseArray getInstanceFields() { if (classPath.isArt()) { return artInstanceFieldsSupplier.get(); @@ -841,6 +854,8 @@ public class ClassProto implements TypeProto { @Nonnull public List getVtable() { if (!classPath.isArt() || classPath.oatVersion < 72) { return preDefaultMethodVtableSupplier.get(); + } else if (classPath.oatVersion < 79) { + return buggyPostDefaultMethodVtableSupplier.get(); } else { return postDefaultMethodVtableSupplier.get(); } @@ -893,6 +908,163 @@ public class ClassProto implements TypeProto { } }); + /** + * This is the vtable supplier for a version of art that had buggy vtable calculation logic. In some cases it can + * produce multiple vtable entries for a given virtual method. This supplier duplicates this buggy logic in order to + * generate an identical vtable + */ + @Nonnull private final Supplier> buggyPostDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier>() { + @Override public List get() { + List 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 vtable; + } + + 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; + } + } + + //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, true); + + List interfaces = Lists.newArrayList(getInterfaces().keySet()); + + List defaultMethods = Lists.newArrayList(); + List defaultConflictMethods = Lists.newArrayList(); + List mirandaMethods = Lists.newArrayList(); + + final HashMap methodOrder = Maps.newHashMap(); + + + + for (int i=interfaces.size()-1; i>=0; i--) { + String interfaceType = interfaces.get(i); + ClassDef interfaceDef = classPath.getClassDef(interfaceType); + + + + for (Method interfaceMethod : interfaceDef.getVirtualMethods()) { + + int vtableIndex = findMethodIndexInVtableReverse(vtable, interfaceMethod); + Method oldVtableMethod = null; + if (vtableIndex >= 0) { + oldVtableMethod = vtable.get(vtableIndex); + } + + for (int j=0; j= 0) { + if (!isOverridableByDefaultMethod(vtable.get(vtableIndex))) { + continue; + } + } + + int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod); + + if (defaultMethodIndex >= 0) { + if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) { + ClassProto existingInterface = (ClassProto)classPath.getClass( + defaultMethods.get(defaultMethodIndex).getDefiningClass()); + if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) { + Method removedMethod = defaultMethods.remove(defaultMethodIndex); + defaultConflictMethods.add(removedMethod); + } + } + continue; + } + + int defaultConflictMethodIndex = findMethodIndexInVtable( + defaultConflictMethods, interfaceMethod); + if (defaultConflictMethodIndex >= 0) { + // There's already a matching method in the conflict list, we don't need to do + // anything else + continue; + } + + int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod); + + if (mirandaMethodIndex >= 0) { + if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) { + + ClassProto existingInterface = (ClassProto)classPath.getClass( + mirandaMethods.get(mirandaMethodIndex).getDefiningClass()); + if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) { + Method oldMethod = mirandaMethods.remove(mirandaMethodIndex); + int methodOrderValue = methodOrder.get(oldMethod); + methodOrder.put(interfaceMethod, methodOrderValue); + defaultMethods.add(interfaceMethod); + } + } + continue; + } + + if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) { + if (oldVtableMethod != null) { + if (!interfaceMethodOverrides(interfaceMethod, oldVtableMethod)) { + continue; + } + } + defaultMethods.add(interfaceMethod); + methodOrder.put(interfaceMethod, methodOrder.size()); + } else { + // TODO: do we need to check interfaceMethodOverrides here? + if (oldVtableMethod == null) { + mirandaMethods.add(interfaceMethod); + methodOrder.put(interfaceMethod, methodOrder.size()); + } + } + } + } + + Comparator comparator = new Comparator() { + @Override public int compare(MethodReference o1, MethodReference o2) { + return Ints.compare(methodOrder.get(o1), methodOrder.get(o2)); + } + }; + + // The methods should be in the same order within each list as they were iterated over. + // They can be misordered if, e.g. a method was originally added to the default list, but then moved + // to the conflict list. + Collections.sort(mirandaMethods, comparator); + Collections.sort(defaultMethods, comparator); + Collections.sort(defaultConflictMethods, comparator); + + // TODO: need to reparent these interface methods at some point. Can we reparent them when adding to the vtable? + vtable.addAll(mirandaMethods); + vtable.addAll(defaultMethods); + vtable.addAll(defaultConflictMethods); + } + return vtable; + } + }); + @Nonnull private final Supplier> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier>() { @Override public List get() { List vtable = Lists.newArrayList(); @@ -1012,8 +1184,8 @@ public class ClassProto implements TypeProto { } }); - private void addToVtable(@Nonnull Iterable localMethods, - @Nonnull List vtable, boolean replaceExisting, boolean sort) { + private void addToVtable(@Nonnull Iterable localMethods, @Nonnull List vtable, + boolean replaceExisting, boolean sort) { if (sort) { ArrayList methods = Lists.newArrayList(localMethods); Collections.sort(methods); @@ -1047,6 +1219,11 @@ public class ClassProto implements TypeProto { } } + private boolean isOverridableByDefaultMethod(@Nonnull Method method) { + ClassProto classProto = (ClassProto)classPath.getClass(method.getDefiningClass()); + return classProto.isInterface(); + } + /** * Checks if the interface method overrides the virtual or interface method2 * @param method A Method from an interface @@ -1058,10 +1235,7 @@ public class ClassProto implements TypeProto { if (classProto.isInterface()) { ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass()); - if (targetClassProto.implementsInterface(method2.getDefiningClass())) { - return true; - } - return false; + return targetClassProto.implementsInterface(method2.getDefiningClass()); } else { return false; }