refactor: move merging classes code to own class

This commit is contained in:
oSumAtrIX 2023-01-02 07:10:24 +01:00
parent 8f3ecc318c
commit 3e0bf8c863
No known key found for this signature in database
GPG key ID: A9B3094ACDB604B4
2 changed files with 143 additions and 96 deletions

View file

@ -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 <T> 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)
}
}
}

View file

@ -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 <HayType, NeedleType> Iterable<HayType>.filterNotAny(
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
) = Iterable<HayType>::filterNot.any(this, needles, predicate)
fun <HayType, NeedleType> KFunction2<Iterable<HayType>, (HayType) -> Boolean, List<HayType>>.any(
haystack: Iterable<HayType>,
needles: Iterable<NeedleType>,
predicate: (HayType, NeedleType) -> Boolean
) = this(haystack) { hay ->
needles.any { needle ->
predicate(hay, needle)
}
}
}
}