From 8f3ecc318c39f0270aff53efdee7a1c8d82af421 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 2 Jan 2023 07:09:58 +0100 Subject: [PATCH 1/4] feat: add missing setter to `MutableMethod` --- .../util/proxy/mutableTypes/MutableMethod.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/kotlin/app/revanced/patcher/util/proxy/mutableTypes/MutableMethod.kt b/src/main/kotlin/app/revanced/patcher/util/proxy/mutableTypes/MutableMethod.kt index 9e17e15..9792b23 100644 --- a/src/main/kotlin/app/revanced/patcher/util/proxy/mutableTypes/MutableMethod.kt +++ b/src/main/kotlin/app/revanced/patcher/util/proxy/mutableTypes/MutableMethod.kt @@ -20,6 +20,22 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() { private val _parameterTypes by lazy { method.parameterTypes.toMutableList() } private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions } + fun setDefiningClass(definingClass: String) { + this.definingClass = definingClass + } + + fun setName(name: String) { + this.name = name + } + + fun setAccessFlags(accessFlags: Int) { + this.accessFlags = accessFlags + } + + fun setReturnType(returnType: String) { + this.returnType = returnType + } + override fun getDefiningClass(): String { return definingClass } From 3e0bf8c86369fc6d71a19f9718470381b59b0375 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 2 Jan 2023 07:10:24 +0100 Subject: [PATCH 2/4] refactor: move merging classes code to own class --- .../kotlin/app/revanced/patcher/Patcher.kt | 100 +------------ .../app/revanced/patcher/util/ClassMerger.kt | 139 ++++++++++++++++++ 2 files changed, 143 insertions(+), 96 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index eeaa556..d9f114a 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -7,12 +7,8 @@ import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.nullOutputStream import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve import app.revanced.patcher.patch.* -import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy +import app.revanced.patcher.util.ClassMerger.merge import app.revanced.patcher.util.VersionReader -import app.revanced.patcher.util.proxy.mutableTypes.MutableClass -import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable -import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import brut.androlib.Androlib import brut.androlib.meta.UsesFramework import brut.androlib.options.BuildOptions @@ -26,11 +22,8 @@ import brut.directory.ExtFile import lanchon.multidexlib2.BasicDexFileNamer import lanchon.multidexlib2.DexIO import lanchon.multidexlib2.MultiDexIO -import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.Opcodes -import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.DexFile -import org.jf.dexlib2.util.MethodUtil import org.jf.dexlib2.writer.io.MemoryDataStore import java.io.File import java.nio.file.Files @@ -94,95 +87,10 @@ class Patcher(private val options: PatcherOptions) { logger.trace("Type $type exists. Adding missing methods and fields.") - /** - * Add missing fields and methods from [from]. - * - * @param from The class to add methods and fields from. - */ - fun ClassDef.addMissingFrom(from: ClassDef) { - var changed = false - fun ClassDef.transformClass(transform: (MutableClass) -> T): T { - fun toMutableClass() = - if (this@transformClass is MutableClass) this else this.toMutable() - return transform(toMutableClass()) - } - - /** - * Check if the [AccessFlags.PUBLIC] flag is set. - * - * @return True, if the flag is set. - */ - fun Int.isPublic() = AccessFlags.PUBLIC.isSet(this) - - /** - * Make a class and its super class public recursively. - */ - fun MutableClass.publicize() { - context.bytecodeContext.traverseClassHierarchy(this) { - if (accessFlags.isPublic()) return@traverseClassHierarchy - - accessFlags = accessFlags.or(AccessFlags.PUBLIC.value) - } - } - - /** - * Add missing methods to the class, considering to publicise the [ClassDef] if necessary. - */ - fun ClassDef.addMissingMethods(): ClassDef { - fun getMissingMethods() = from.methods.filterNot { - this@addMissingMethods.methods.any { original -> - MethodUtil.methodSignaturesMatch(original, it) - } - } - - return getMissingMethods() - .apply { - if (isEmpty()) return@addMissingMethods this@addMissingMethods else changed = - true - } - .map { it.toMutable() } - .let { missingMethods -> - this@addMissingMethods.transformClass { classDef -> - classDef.apply { - // make sure the class is public, if the class contains public methods - if (missingMethods.any { it.accessFlags.isPublic() }) - classDef.publicize() - - methods.addAll(missingMethods) - } - } - } - } - - /** - * Add missing fields to the class, considering to publicise the [ClassDef] if necessary. - */ - fun ClassDef.addMissingFields(): ClassDef { - fun getMissingFields() = from.fields.filterNot { - this@addMissingFields.fields.any { original -> original.name == it.name } - } - - return getMissingFields() - .apply { - if (isEmpty()) return@addMissingFields this@addMissingFields else changed = true - } - .map { it.toMutable() } - .let { missingFields -> - this@addMissingFields.transformClass { classDef -> - // make sure the class is public, if the class contains public fields - if (missingFields.any { it.accessFlags.isPublic() }) - classDef.publicize() - - classDef.apply { fields.addAll(missingFields) } - } - } - } - - classes[existingClassIndex] = addMissingMethods().addMissingFields() - .apply { if (!changed) return } + existingClass.merge(classDef, context, logger).let { mergedClass -> + if (mergedClass !== existingClass) // referential equality check + classes[existingClassIndex] = mergedClass } - - existingClass.addMissingFrom(classDef) } } } diff --git a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt new file mode 100644 index 0000000..a3f41e4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt @@ -0,0 +1,139 @@ +package app.revanced.patcher.util + +import app.revanced.patcher.PatcherContext +import app.revanced.patcher.extensions.or +import app.revanced.patcher.logging.Logger +import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass +import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny +import app.revanced.patcher.util.ClassMerger.Utils.isPublic +import app.revanced.patcher.util.ClassMerger.Utils.toPublic +import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy +import app.revanced.patcher.util.proxy.mutableTypes.MutableClass +import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.util.MethodUtil +import kotlin.reflect.KFunction2 + +/** + * Experimental class to merge a [ClassDef] with another. + * Note: This will not consider method implementations or if the class is missing a superclass or interfaces. + */ +internal object ClassMerger { + /** + * Merge a class with [otherClass]. + * + * @param otherClass The class to merge with + * @param context The context to traverse the class hierarchy in. + * @param logger A logger. + */ + fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this + .addMissingFields(otherClass, logger) + .addMissingMethods(otherClass, logger) + .publicize(otherClass, context, logger) + + /** + * Add methods which are missing but existing in [fromClass]. + * + * @param fromClass The class to add missing methods from. + * @param logger A logger. + */ + private fun ClassDef.addMissingMethods(fromClass: ClassDef, logger: Logger? = null): ClassDef { + val missingMethods = fromClass.methods.let { fromMethods -> + methods.filterNot { method -> + fromMethods.any { fromMethod -> + MethodUtil.methodSignaturesMatch(fromMethod, method) + } + } + } + + if (missingMethods.isEmpty()) return this + + logger?.trace("Found ${missingMethods.size} missing methods") + + return asMutableClass().apply { + methods.addAll(missingMethods.map { it.toMutable() }) + } + } + + /** + * Add fields which are missing but existing in [fromClass]. + * + * @param fromClass The class to add missing fields from. + * @param logger A logger. + */ + private fun ClassDef.addMissingFields(fromClass: ClassDef, logger: Logger? = null): ClassDef { + val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField -> + fromField.name == field.name + } + + if (missingFields.isEmpty()) return this + + logger?.trace("Found ${missingFields.size} missing fields") + + return asMutableClass().apply { + fields.addAll(missingFields.map { it.toMutable() }) + } + } + + /** + * Make a class and its super class public recursively. + * @param reference The class to check the [AccessFlags] of. + * @param context The context to traverse the class hierarchy in. + * @param logger A logger. + */ + private fun ClassDef.publicize(reference: ClassDef, context: PatcherContext, logger: Logger? = null) = + if (reference.accessFlags.isPublic() && !accessFlags.isPublic()) + this.asMutableClass().apply { + context.bytecodeContext.traverseClassHierarchy(this) { + if (accessFlags.isPublic()) return@traverseClassHierarchy + + logger?.trace("Publicizing ${this.type}") + + accessFlags = accessFlags.toPublic() + } + } + else this + + private object Utils { + fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable() + + /** + * Check if the [AccessFlags.PUBLIC] flag is set. + * + * @return True, if the flag is set. + */ + fun Int.isPublic() = AccessFlags.PUBLIC.isSet(this) + + /** + * Make [AccessFlags] public. + * + * @return The new [AccessFlags]. + */ + fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv()) + + /** + * Filter [this] on [needles] not matching the given [predicate]. + * + * @param this The hay to filter for [needles]. + * @param needles The needles to filter [this] with. + * @param predicate The filter. + * @return The [this] filtered on [needles] not matching the given [predicate]. + */ + fun Iterable.filterNotAny( + needles: Iterable, predicate: (HayType, NeedleType) -> Boolean + ) = Iterable::filterNot.any(this, needles, predicate) + + fun KFunction2, (HayType) -> Boolean, List>.any( + haystack: Iterable, + needles: Iterable, + predicate: (HayType, NeedleType) -> Boolean + ) = this(haystack) { hay -> + needles.any { needle -> + predicate(hay, needle) + } + } + } +} \ No newline at end of file From 5c09ef7837f9b731e137b66c19da77f63c007595 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 2 Jan 2023 07:10:51 +0100 Subject: [PATCH 3/4] feat: fix method and field access when merging classes --- .../kotlin/app/revanced/patcher/Patcher.kt | 15 ++-- .../app/revanced/patcher/util/ClassMerger.kt | 75 +++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index d9f114a..cd33ee7 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -82,15 +82,16 @@ class Patcher(private val options: PatcherOptions) { if (result == null) { logger.trace("Merging type $type") classes.add(classDef) - } else { - val (existingClass, existingClassIndex) = result + continue + } - logger.trace("Type $type exists. Adding missing methods and fields.") + val (existingClass, existingClassIndex) = result - existingClass.merge(classDef, context, logger).let { mergedClass -> - if (mergedClass !== existingClass) // referential equality check - classes[existingClassIndex] = mergedClass - } + logger.trace("Type $type exists. Adding missing methods and fields.") + + existingClass.merge(classDef, context, logger).let { mergedClass -> + if (mergedClass !== existingClass) // referential equality check + classes[existingClassIndex] = mergedClass } } } diff --git a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt index a3f41e4..c1ffd3c 100644 --- a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt +++ b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt @@ -4,13 +4,16 @@ import app.revanced.patcher.PatcherContext import app.revanced.patcher.extensions.or import app.revanced.patcher.logging.Logger import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass +import app.revanced.patcher.util.ClassMerger.Utils.filterAny import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny import app.revanced.patcher.util.ClassMerger.Utils.isPublic import app.revanced.patcher.util.ClassMerger.Utils.toPublic import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableField import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.iface.ClassDef @@ -30,6 +33,8 @@ internal object ClassMerger { * @param logger A logger. */ fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this + .fixFieldAccess(otherClass, logger) + .fixMethodAccess(otherClass, logger) .addMissingFields(otherClass, logger) .addMissingMethods(otherClass, logger) .publicize(otherClass, context, logger) @@ -97,6 +102,64 @@ internal object ClassMerger { } else this + /** + * Publicize fields if they are public in [reference]. + * + * @param reference The class to check the [AccessFlags] of the fields in. + * @param logger A logger. + */ + private fun ClassDef.fixFieldAccess(reference: ClassDef, logger: Logger? = null): ClassDef { + val brokenFields = fields.filterAny(reference.fields) { field, referenceField -> + if (field.name != referenceField.name) return@filterAny false + + referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic() + } + + if (brokenFields.isEmpty()) return this + + logger?.trace("Found ${brokenFields.size} broken fields") + + /** + * Make a field public. + */ + fun MutableField.publicize() { + accessFlags = accessFlags.toPublic() + } + + return asMutableClass().apply { + fields.filter { brokenFields.contains(it) }.forEach(MutableField::publicize) + } + } + + /** + * Publicize methods if they are public in [reference]. + * + * @param reference The class to check the [AccessFlags] of the methods in. + * @param logger A logger. + */ + private fun ClassDef.fixMethodAccess(reference: ClassDef, logger: Logger? = null): ClassDef { + val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod -> + if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false + + referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic() + } + + if (brokenMethods.isEmpty()) return this + + logger?.trace("Found ${brokenMethods.size} methods") + + /** + * Make a method public. + */ + fun MutableMethod.publicize() { + accessFlags = accessFlags.toPublic() + } + + return asMutableClass().apply { + methods.filter { brokenMethods.contains(it) }.forEach(MutableMethod::publicize) + } + } + private object Utils { fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable() @@ -114,6 +177,18 @@ internal object ClassMerger { */ fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv()) + /** + * Filter [this] on [needles] matching the given [predicate]. + * + * @param this The hay to filter for [needles]. + * @param needles The needles to filter [this] with. + * @param predicate The filter. + * @return The [this] filtered on [needles] matching the given [predicate]. + */ + fun Iterable.filterAny( + needles: Iterable, predicate: (HayType, NeedleType) -> Boolean + ) = Iterable::filter.any(this, needles, predicate) + /** * Filter [this] on [needles] not matching the given [predicate]. * From 4102f43b8a9473fd0ee96c5d4fb8f6e9b4e30e70 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 2 Jan 2023 08:50:08 +0100 Subject: [PATCH 4/4] feat: do not fix methods or methods in class merger --- src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt index c1ffd3c..abac334 100644 --- a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt +++ b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt @@ -33,8 +33,8 @@ internal object ClassMerger { * @param logger A logger. */ fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this - .fixFieldAccess(otherClass, logger) - .fixMethodAccess(otherClass, logger) + //.fixFieldAccess(otherClass, logger) + //.fixMethodAccess(otherClass, logger) .addMissingFields(otherClass, logger) .addMissingMethods(otherClass, logger) .publicize(otherClass, context, logger)