From 3e0bf8c86369fc6d71a19f9718470381b59b0375 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 2 Jan 2023 07:10:24 +0100 Subject: [PATCH] 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