mirror of
https://github.com/revanced/smali.git
synced 2025-05-03 16:14:29 +02:00
Add support for the buggy post-default method vtable generation
This replicates the buggy vtable generation logic for Android 7.0
This commit is contained in:
parent
7cb0937324
commit
0de5ef0ce7
@ -456,6 +456,19 @@ public class ClassProto implements TypeProto {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int findMethodIndexInVtableReverse(@Nonnull List<Method> 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<FieldReference> getInstanceFields() {
|
@Nonnull public SparseArray<FieldReference> getInstanceFields() {
|
||||||
if (classPath.isArt()) {
|
if (classPath.isArt()) {
|
||||||
return artInstanceFieldsSupplier.get();
|
return artInstanceFieldsSupplier.get();
|
||||||
@ -841,6 +854,8 @@ public class ClassProto implements TypeProto {
|
|||||||
@Nonnull public List<Method> getVtable() {
|
@Nonnull public List<Method> getVtable() {
|
||||||
if (!classPath.isArt() || classPath.oatVersion < 72) {
|
if (!classPath.isArt() || classPath.oatVersion < 72) {
|
||||||
return preDefaultMethodVtableSupplier.get();
|
return preDefaultMethodVtableSupplier.get();
|
||||||
|
} else if (classPath.oatVersion < 79) {
|
||||||
|
return buggyPostDefaultMethodVtableSupplier.get();
|
||||||
} else {
|
} else {
|
||||||
return postDefaultMethodVtableSupplier.get();
|
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<List<Method>> buggyPostDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||||
|
@Override public List<Method> get() {
|
||||||
|
List<Method> 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<String> interfaces = Lists.newArrayList(getInterfaces().keySet());
|
||||||
|
|
||||||
|
List<Method> defaultMethods = Lists.newArrayList();
|
||||||
|
List<Method> defaultConflictMethods = Lists.newArrayList();
|
||||||
|
List<Method> mirandaMethods = Lists.newArrayList();
|
||||||
|
|
||||||
|
final HashMap<MethodReference, Integer> 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<vtable.size(); j++) {
|
||||||
|
Method candidate = vtable.get(j);
|
||||||
|
if (MethodUtil.methodSignaturesMatch(candidate, interfaceMethod)) {
|
||||||
|
if (!classPath.shouldCheckPackagePrivateAccess() ||
|
||||||
|
AnalyzedMethodUtil.canAccess(ClassProto.this, candidate, true, false, false)) {
|
||||||
|
if (interfaceMethodOverrides(interfaceMethod, candidate)) {
|
||||||
|
vtable.set(j, interfaceMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vtableIndex >= 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<MethodReference> comparator = new Comparator<MethodReference>() {
|
||||||
|
@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<List<Method>> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
@Nonnull private final Supplier<List<Method>> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||||
@Override public List<Method> get() {
|
@Override public List<Method> get() {
|
||||||
List<Method> vtable = Lists.newArrayList();
|
List<Method> vtable = Lists.newArrayList();
|
||||||
@ -1012,8 +1184,8 @@ public class ClassProto implements TypeProto {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
|
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable,
|
||||||
@Nonnull List<Method> vtable, boolean replaceExisting, boolean sort) {
|
boolean replaceExisting, boolean sort) {
|
||||||
if (sort) {
|
if (sort) {
|
||||||
ArrayList<Method> methods = Lists.newArrayList(localMethods);
|
ArrayList<Method> methods = Lists.newArrayList(localMethods);
|
||||||
Collections.sort(methods);
|
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
|
* Checks if the interface method overrides the virtual or interface method2
|
||||||
* @param method A Method from an interface
|
* @param method A Method from an interface
|
||||||
@ -1058,10 +1235,7 @@ public class ClassProto implements TypeProto {
|
|||||||
|
|
||||||
if (classProto.isInterface()) {
|
if (classProto.isInterface()) {
|
||||||
ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass());
|
ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass());
|
||||||
if (targetClassProto.implementsInterface(method2.getDefiningClass())) {
|
return targetClassProto.implementsInterface(method2.getDefiningClass());
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user