From cf57726bbb6965bf35a1a368ae7cc9b362e8209a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 19 Nov 2025 20:08:49 +0100 Subject: [PATCH] feat: More APIs/adjustments --- .../kotlin/app/revanced/patcher/Matching.kt | 482 ++++++++++++------ .../patcher/patch/BytecodePatchContext.kt | 113 ++-- 2 files changed, 373 insertions(+), 222 deletions(-) diff --git a/src/main/kotlin/app/revanced/patcher/Matching.kt b/src/main/kotlin/app/revanced/patcher/Matching.kt index 7daa222..84163c6 100644 --- a/src/main/kotlin/app/revanced/patcher/Matching.kt +++ b/src/main/kotlin/app/revanced/patcher/Matching.kt @@ -1,109 +1,101 @@ -@file:Suppress("unused", "MemberVisibilityCanBePrivate") +@file:Suppress("unused", "MemberVisibilityCanBePrivate", "CONTEXT_RECEIVERS_DEPRECATED") package app.revanced.patcher -import app.revanced.patcher.dex.mutable.MutableClassDef.Companion.toMutable +import app.revanced.patcher.Matcher.MatchContext import app.revanced.patcher.dex.mutable.MutableMethod -import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable -import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.patch.BytecodePatchContext -import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.patch.gettingBytecodePatch import com.android.tools.smali.dexlib2.HiddenApiRestriction -import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.* import com.android.tools.smali.dexlib2.iface.Annotation -import com.android.tools.smali.dexlib2.iface.ClassDef -import com.android.tools.smali.dexlib2.iface.ExceptionHandler -import com.android.tools.smali.dexlib2.iface.Field -import com.android.tools.smali.dexlib2.iface.Method -import com.android.tools.smali.dexlib2.iface.MethodImplementation -import com.android.tools.smali.dexlib2.iface.MethodParameter -import com.android.tools.smali.dexlib2.iface.TryBlock import com.android.tools.smali.dexlib2.iface.instruction.Instruction -import com.android.tools.smali.dexlib2.util.InstructionUtil +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.util.MethodUtil -import kotlin.collections.any import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty -fun Iterable.anyClassDef(predicate: ClassDef.() -> Boolean) = - any(predicate) +fun Iterable.anyClassDef(predicate: ClassDef.() -> Boolean) = any(predicate) -fun ClassDef.anyMethod(predicate: Method.() -> Boolean) = - methods.any(predicate) +fun ClassDef.anyMethod(predicate: Method.() -> Boolean) = methods.any(predicate) -fun ClassDef.anyDirectMethod(predicate: Method.() -> Boolean) = - directMethods.any(predicate) +fun ClassDef.anyDirectMethod(predicate: Method.() -> Boolean) = directMethods.any(predicate) -fun ClassDef.anyVirtualMethod(predicate: Method.() -> Boolean) = - virtualMethods.any(predicate) +fun ClassDef.anyVirtualMethod(predicate: Method.() -> Boolean) = virtualMethods.any(predicate) -fun ClassDef.anyField(predicate: Field.() -> Boolean) = - fields.any(predicate) +fun ClassDef.anyField(predicate: Field.() -> Boolean) = fields.any(predicate) -fun ClassDef.anyInstanceField(predicate: Field.() -> Boolean) = - instanceFields.any(predicate) +fun ClassDef.anyInstanceField(predicate: Field.() -> Boolean) = instanceFields.any(predicate) -fun ClassDef.anyStaticField(predicate: Field.() -> Boolean) = - staticFields.any(predicate) +fun ClassDef.anyStaticField(predicate: Field.() -> Boolean) = staticFields.any(predicate) -fun ClassDef.anyInterface(predicate: String.() -> Boolean) = - interfaces.any(predicate) +fun ClassDef.anyInterface(predicate: String.() -> Boolean) = interfaces.any(predicate) -fun ClassDef.anyAnnotation(predicate: Annotation.() -> Boolean) = - annotations.any(predicate) +fun ClassDef.anyAnnotation(predicate: Annotation.() -> Boolean) = annotations.any(predicate) -fun Method.implementation(predicate: MethodImplementation.() -> Boolean) = - implementation?.predicate() ?: false +fun Method.implementation(predicate: MethodImplementation.() -> Boolean) = implementation?.predicate() ?: false -fun Method.anyParameter(predicate: MethodParameter.() -> Boolean) = - parameters.any(predicate) +fun Method.anyParameter(predicate: MethodParameter.() -> Boolean) = parameters.any(predicate) -fun Method.anyParameterType(predicate: CharSequence.() -> Boolean) = - parameterTypes.any(predicate) +fun Method.anyParameterType(predicate: CharSequence.() -> Boolean) = parameterTypes.any(predicate) -fun Method.anyAnnotation(predicate: Annotation.() -> Boolean) = - annotations.any(predicate) +fun Method.anyAnnotation(predicate: Annotation.() -> Boolean) = annotations.any(predicate) -fun Method.anyHiddenApiRestriction(predicate: HiddenApiRestriction.() -> Boolean) = - hiddenApiRestrictions.any(predicate) +fun Method.anyHiddenApiRestriction(predicate: HiddenApiRestriction.() -> Boolean) = hiddenApiRestrictions.any(predicate) -fun MethodImplementation.anyInstruction(predicate: Instruction.() -> Boolean) = - instructions.any(predicate) +fun MethodImplementation.anyInstruction(predicate: Instruction.() -> Boolean) = instructions.any(predicate) -fun MethodImplementation.anyTryBlock(predicate: TryBlock.() -> Boolean) = - tryBlocks.any(predicate) +fun MethodImplementation.anyTryBlock(predicate: TryBlock.() -> Boolean) = tryBlocks.any(predicate) -fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) = - debugItems.any(predicate) +fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) = debugItems.any(predicate) -fun Iterable.anyInstruction(predicate: Instruction.() -> Boolean) = - any(predicate) +fun Iterable.anyInstruction(predicate: Instruction.() -> Boolean) = any(predicate) +fun BytecodePatchContext.firstClassDefOrNull(predicate: MatchPredicate) = + with(predicate) { with(MatchContext()) { classDefs.firstOrNull { it.match() } } } -fun BytecodePatchContext.firstClassDefOrNull(predicate: ClassDef.() -> Boolean) = - classDefs.firstOrNull { predicate(it) } - -fun BytecodePatchContext.firstClassDef(predicate: ClassDef.() -> Boolean) = +fun BytecodePatchContext.firstClassDef(predicate: MatchPredicate) = requireNotNull(firstClassDefOrNull(predicate)) -fun BytecodePatchContext.firstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) = +fun BytecodePatchContext.firstClassDefMutableOrNull(predicate: MatchPredicate) = firstClassDefOrNull(predicate)?.mutable() -fun BytecodePatchContext.firstMutableClassDef(predicate: ClassDef.() -> Boolean) = - requireNotNull(firstMutableClassDefOrNull(predicate)) +fun BytecodePatchContext.firstClassDefMutable(predicate: MatchPredicate) = + requireNotNull(firstClassDefMutableOrNull(predicate)) -fun BytecodePatchContext.firstMethodOrNull(predicate: Method.() -> Boolean) = - classDefs.asSequence().flatMap { it.methods.asSequence() } - .firstOrNull { predicate(it) } +fun BytecodePatchContext.firstClassDefOrNull( + type: String, predicate: (MatchPredicate)? = null +) = lookupMaps.classesByType[type]?.takeIf { + predicate == null || with(predicate) { with(MatchContext()) { it.match() } } +} -fun BytecodePatchContext.firstMethod(predicate: Method.() -> Boolean) = - requireNotNull(firstMethodOrNull(predicate)) +fun BytecodePatchContext.firstClassDef( + type: String, predicate: (MatchPredicate)? = null +) = requireNotNull(firstClassDefOrNull(type, predicate)) -fun BytecodePatchContext.firstMutableMethodOrNull(predicate: Method.() -> Boolean): MutableMethod? { - classDefs.forEach { classDef -> - classDef.methods.forEach { method -> - if (predicate(method)) return classDef.mutable().methods.first { - MethodUtil.methodSignaturesMatch(it, method) +fun BytecodePatchContext.firstClassDefMutableOrNull( + type: String, predicate: (MatchPredicate)? = null +) = firstClassDefOrNull(type, predicate)?.mutable() + +fun BytecodePatchContext.firstClassDefMutable( + type: String, predicate: (MatchPredicate)? = null +) = requireNotNull(firstClassDefMutableOrNull(type, predicate)) + +fun BytecodePatchContext.firstMethodOrNull(predicate: MatchPredicate) = with(predicate) { + with(MatchContext()) { + classDefs.asSequence().flatMap { it.methods.asSequence() }.firstOrNull { it.match() } + } +} + +fun BytecodePatchContext.firstMethod(predicate: MatchPredicate) = requireNotNull(firstMethodOrNull(predicate)) + +fun BytecodePatchContext.firstMethodMutableOrNull(predicate: MatchPredicate): MutableMethod? { + with(predicate) { + with(MatchContext()) { + classDefs.forEach { classDef -> + classDef.methods.firstOrNull { it.match() }?.let { method -> + return classDef.mutable().methods.first { MethodUtil.methodSignaturesMatch(it, method) } + } } } } @@ -111,109 +103,301 @@ fun BytecodePatchContext.firstMutableMethodOrNull(predicate: Method.() -> Boolea return null } -fun BytecodePatchContext.firstMutableMethod(predicate: Method.() -> Boolean) = - requireNotNull(firstMutableMethodOrNull(predicate)) +fun BytecodePatchContext.firstMethodMutable(predicate: MatchPredicate) = + requireNotNull(firstMethodMutableOrNull(predicate)) -fun gettingFirstClassDefOrNull(predicate: ClassDef.() -> Boolean) = ReadOnlyProperty { thisRef, _ -> - require(thisRef is BytecodePatchContext) - - thisRef.firstClassDefOrNull(predicate) -} - -fun gettingFirstClassDef(predicate: ClassDef.() -> Boolean) = requireNotNull(gettingFirstClassDefOrNull(predicate)) - -fun gettingFirstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) = - ReadOnlyProperty { thisRef, _ -> - require(thisRef is BytecodePatchContext) - - thisRef.firstMutableClassDefOrNull(predicate) +fun BytecodePatchContext.firstMethodOrNull( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = with(predicate) { + with(MatchContext()) { + strings.mapNotNull { lookupMaps.methodsByStrings[it] }.minByOrNull { it.size }?.firstOrNull { it.match() } } - -fun gettingFirstMutableClassDef(predicate: ClassDef.() -> Boolean) = - requireNotNull(gettingFirstMutableClassDefOrNull(predicate)) - -fun gettingFirstMethodOrNull(predicate: Method.() -> Boolean) = ReadOnlyProperty { thisRef, _ -> - require(thisRef is BytecodePatchContext) - - thisRef.firstMethodOrNull(predicate) } -fun gettingFirstMethod(predicate: Method.() -> Boolean) = requireNotNull(gettingFirstMethodOrNull(predicate)) +fun BytecodePatchContext.firstMethod( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = requireNotNull(firstMethodOrNull(*strings, predicate = predicate)) -fun gettingFirstMutableMethodOrNull(predicate: Method.() -> Boolean) = ReadOnlyProperty { thisRef, _ -> - require(thisRef is BytecodePatchContext) - - thisRef.firstMutableMethodOrNull(predicate) -} - -fun gettingFirstMutableMethod(predicate: Method.() -> Boolean) = - requireNotNull(gettingFirstMutableMethodOrNull(predicate)) - -val classDefOrNull by gettingFirstClassDefOrNull { true } -val classDef by gettingFirstClassDef { true } -val mutableClassDefOrNull by gettingFirstMutableClassDefOrNull { true } -val mutableClassDef by gettingFirstMutableClassDef { true } -val methodOrNull by gettingFirstMethodOrNull { true } -val methodDef by gettingFirstMethod { true } -val mutableMethodOrNull by gettingFirstMutableMethodOrNull { true } -val mutableMethodDef by gettingFirstMutableMethod { true } - -val `My Patch` by gettingBytecodePatch { - execute { - val classDefOrNull = firstClassDefOrNull { true } - val classDef = firstClassDef { true } - val mutableClassDefOrNull = firstMutableClassDefOrNull { true } - val mutableClassDef = firstMutableClassDef { true } - val methodOrNull = firstMethodOrNull { true } - val method = firstMethod { true } - val mutableMethodOrNull = firstMutableMethodOrNull { true } - val mutableMethod = firstMutableMethod { true } - val apiTest = firstMethod { - implementation { - instructions.matchSequentially { - add { opcode == Opcode.RETURN_VOID } - add { opcode == Opcode.NOP } +fun BytecodePatchContext.firstMethodMutableOrNull( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = with(predicate) { + with(MatchContext()) { + strings.mapNotNull { lookupMaps.methodsByStrings[it] }.minByOrNull { it.size }?.let { methods -> + methods.firstOrNull { it.match() }?.let { method -> + firstClassDefMutable(method.definingClass).methods.first { + MethodUtil.methodSignaturesMatch( + method, it + ) } } } - - fun Method.matchSequentiallyInstructions( - builder: MutableList Boolean>.() -> Unit - ) = implementation?.instructions?.matchSequentially(builder) ?: false - - firstMutableMethod { - matchSequentiallyInstructions { - add { opcode == Opcode.RETURN_VOID } - add { opcode == Opcode.NOP } - } - }.addInstructions("apiTest2") } } +fun BytecodePatchContext.firstMethodMutable( + vararg strings: String, predicate: MatchPredicate = MatchPredicate { true } +) = requireNotNull(firstMethodMutableOrNull(*strings, predicate = predicate)) -abstract class Matcher : MutableList Boolean> by mutableListOf() { +inline fun ReadOnlyProperty(crossinline block: C.(KProperty<*>) -> T) = + ReadOnlyProperty { thisRef, property -> + require(thisRef is C) + + thisRef.block(property) + } + +fun gettingFirstClassDefOrNull(predicate: MatchPredicate) = + ReadOnlyProperty { firstClassDefOrNull(predicate) } + +fun gettingFirstClassDef(predicate: MatchPredicate) = requireNotNull(gettingFirstClassDefOrNull(predicate)) + +fun gettingFirstClassDefMutableOrNull(predicate: MatchPredicate) = + ReadOnlyProperty { firstClassDefMutableOrNull(predicate) } + +fun gettingFirstClassDefMutable(predicate: MatchPredicate) = + requireNotNull(gettingFirstClassDefMutableOrNull(predicate)) + +fun gettingFirstClassDefOrNull( + type: String, predicate: (MatchPredicate)? = null +) = ReadOnlyProperty { firstClassDefOrNull(type, predicate) } + +fun gettingFirstClassDef( + type: String, predicate: (MatchPredicate)? = null +) = requireNotNull(gettingFirstClassDefOrNull(type, predicate)) + +fun gettingFirstClassDefMutableOrNull( + type: String, predicate: (MatchPredicate)? = null +) = ReadOnlyProperty { firstClassDefMutableOrNull(type, predicate) } + +fun gettingFirstClassDefMutable( + type: String, predicate: (MatchPredicate)? = null +) = requireNotNull(gettingFirstClassDefMutableOrNull(type, predicate)) + +fun gettingFirstMethodOrNull(predicate: MatchPredicate) = + ReadOnlyProperty { firstMethodOrNull(predicate) } + +fun gettingFirstMethod(predicate: MatchPredicate) = requireNotNull(gettingFirstMethodOrNull(predicate)) + +fun gettingFirstMethodMutableOrNull(predicate: MatchPredicate) = + ReadOnlyProperty { firstMethodMutableOrNull(predicate) } + +fun gettingFirstMethodMutable(predicate: MatchPredicate) = + requireNotNull(gettingFirstMethodMutableOrNull(predicate)) + +fun gettingFirstMethodOrNull( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = ReadOnlyProperty { firstMethodOrNull(*strings, predicate = predicate) } + +fun gettingFirstMethod( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = requireNotNull(gettingFirstMethodOrNull(*strings, predicate = predicate)) + +fun gettingFirstMethodMutableOrNull( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = ReadOnlyProperty { firstMethodMutableOrNull(*strings, predicate = predicate) } + +fun gettingFirstMethodMutable( + vararg strings: String, + predicate: MatchPredicate = MatchPredicate { true }, +) = requireNotNull(gettingFirstMethodMutableOrNull(*strings, predicate = predicate)) + +fun interface MatchPredicate { + context(MatchContext) fun T.match(): Boolean +} + +abstract class Matcher : MutableList by mutableListOf() { var matchIndex = -1 protected set abstract operator fun invoke(haystack: Iterable): Boolean + + class MatchContext internal constructor() : MutableMap by mutableMapOf() } -open class SequentialMatcher internal constructor() : Matcher() { - override operator fun invoke(haystack: Iterable) = true -} +fun slidingWindowMatcher(builder: MutableList Boolean>.() -> Unit) = + SlidingWindowMatcher().apply(builder) -class CaptureStringIndices internal constructor() : Matcher() { - val capturedStrings = mutableMapOf() +context(MatchContext) +fun Iterable.matchSlidingWindow(key: String, builder: MutableList Boolean>.() -> Unit) = + (getOrPut(key) { slidingWindowMatcher(builder) } as Matcher Boolean>)(this) - override operator fun invoke(haystack: Iterable) { +fun Iterable.matchSlidingWindow(builder: MutableList Boolean>.() -> Unit) = + slidingWindowMatcher(builder)(this) +class SlidingWindowMatcher() : Matcher Boolean>() { + override operator fun invoke(haystack: Iterable): Boolean { + val haystackCount = haystack.count() + val needleSize = size + if (needleSize == 0) return false + + for (i in 0..(haystackCount - needleSize)) { + var matched = true + for (j in 0 until needleSize) { + if (!this[j].invoke(haystack.elementAt(i + j))) { + matched = false + break + } + } + if (matched) { + matchIndex = i + return true + } + } + + matchIndex = -1 + return false } } -fun matchSequentially() = SequentialMatcher() -fun sequentialMatcher(builder: MutableList Boolean>.() -> Unit) = - SequentialMatcher().apply(builder) +fun findStringsMatcher(builder: MutableList.() -> Unit) = + FindStringsMatcher().apply(builder) -fun Iterable.matchSequentially(builder: MutableList Boolean>.() -> Unit) = - sequentialMatcher(builder)(this) +class FindStringsMatcher() : Matcher() { + val matchedStrings = mutableMapOf() + var needles = toMutableSet() // Reduce O(n²) to O(log n) by removing from the set + + override fun invoke(haystack: Iterable): Boolean { + needles = toMutableSet() // Reset needles for each invocation + // (or do not use the set if set is too small for performance) + + val foundStrings = mutableMapOf() + + haystack.forEachIndexed { index, instruction -> + if (instruction !is ReferenceInstruction) return@forEachIndexed + val reference = instruction.reference + if (reference !is StringReference) return@forEachIndexed + val string = reference.string + + if (needles.removeIf { it in string }) { + foundStrings[string] = index + } + } + + return if (foundStrings.size == size) { + matchedStrings += foundStrings + + true + } else { + false + } + } +} + +fun BytecodePatchContext.findStringIndices() { + val match = findStringsMatcher { + add("fullstring1") + add("fullstring2") + add("partialString") + } + + firstMethod("fullstring", "fullstring") { + implementation { + match(instructions) + } + } + + match.matchedStrings.forEach { (key, value) -> + println("Found string '$key' at index $value") + } + + firstMethod { + implementation { + // Uncached usage + instructions.matchSlidingWindow { + + } || instructions.matchSlidingWindow("cached usage") { + + } + } + } +} + +fun BytecodePatchContext.anotherExample() { + val desiredStringIndices = listOf("fullstring1", "fullstring2", "partialString") + val matchedIndices = mutableMapOf() + + firstMethod("fullstring", "fullstring") { + val remaining = desiredStringIndices.toMutableSet() + val foundMap = mutableMapOf() + + implementation { + + instructions.withIndex().forEach { (index, instruction) -> + val string = (instruction as? ReferenceInstruction)?.reference + .let { it as? StringReference }?.string + ?: return@forEach + + val iterator = remaining.iterator() + while (iterator.hasNext()) { + val desired = iterator.next() + if (desired in string) { + foundMap[desired] = index + iterator.remove() + } + } + + if (remaining.isEmpty()) return@forEach + } + + if (remaining.isEmpty()) { + matchedIndices.putAll(foundMap) + true + } else { + false + } + } + } +} + +fun BytecodePatchContext.wrapperExample() { + fun Method.captureStrings( + desiredStringIndices: Set, + out: MutableMap + ): Boolean { + val remaining = desiredStringIndices.toMutableSet() + val foundMap = mutableMapOf() + + return implementation { + instructions.withIndex().forEach { (index, instruction) -> + val string = (instruction as? ReferenceInstruction)?.reference + .let { it as? StringReference }?.string + ?: return@forEach + + val iterator = remaining.iterator() + while (iterator.hasNext()) { + val desired = iterator.next() + if (desired in string) { + foundMap[desired] = index + iterator.remove() + } + } + + if (remaining.isEmpty()) return@forEach + } + + if (remaining.isEmpty()) { + out += foundMap + true + } else { + false + } + } + } + + val desiredStringIndices = setOf("fullstring1", "fullstring2", "partialString") + val matchedIndices = mutableMapOf() + + val method = firstMethod { + name == "desiredMethodName" && captureStrings(desiredStringIndices, matchedIndices) + } + + for ((key, value) in matchedIndices) { + println("Found string '$key' at index $value in method '$method'") + } +} diff --git a/src/main/kotlin/app/revanced/patcher/patch/BytecodePatchContext.kt b/src/main/kotlin/app/revanced/patcher/patch/BytecodePatchContext.kt index 86dc42e..39c8b47 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/BytecodePatchContext.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/BytecodePatchContext.kt @@ -1,6 +1,7 @@ package app.revanced.patcher.patch import app.revanced.patcher.InternalApi +import app.revanced.patcher.Matcher import app.revanced.patcher.PatcherConfig import app.revanced.patcher.PatcherResult import app.revanced.patcher.dex.mutable.MutableClassDef @@ -8,7 +9,6 @@ import app.revanced.patcher.dex.mutable.MutableClassDef.Companion.toMutable import app.revanced.patcher.extensions.instructionsOrNull import app.revanced.patcher.util.ClassMerger.merge import app.revanced.patcher.util.MethodNavigator -import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcodes import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.DexFile @@ -21,9 +21,10 @@ import lanchon.multidexlib2.DexIO import lanchon.multidexlib2.MultiDexIO import lanchon.multidexlib2.RawDexIO import java.io.Closeable -import java.util.* +import java.io.IOException import java.util.logging.Logger + /** * A context for patches containing the current state of the bytecode. * @@ -54,7 +55,8 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi /** * The lookup maps for methods and the class they are a member of from the [classDefs]. */ - internal val lookupMaps by lazy { LookupMaps(classDefs) } + internal val lookupMaps by lazy { _lookupMaps ?: LookupMaps().also { _lookupMaps = it } } + private var _lookupMaps: LookupMaps? = null // For freeing up memory when compiling. /** * Merge the extension of [bytecodePatch] into the [BytecodePatchContext]. @@ -69,7 +71,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi logger.fine { "Adding class \"$classDef\"" } classDefs += classDef - lookupMaps.classesByType[classDef.type] = classDef + lookupMaps += classDef return@forEach } @@ -117,7 +119,8 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi logger.info("Compiling patched dex files") // Free up memory before compiling the dex files. - lookupMaps.close() + close() + System.gc() val patchedDexFileResults = config.patchedFiles.resolve("dex").also { @@ -146,75 +149,39 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi return patchedDexFileResults } - /** - * A lookup map for methods and the class they are a member of and classes. - * - * @param classDefs The list of classes to create the lookup maps from. - */ - internal class LookupMaps internal constructor(classDefs: Set) : Closeable { - /** - * Methods associated by strings referenced in it. - */ - internal val methodsByStrings = MethodClassPairsLookupMap() - - // Lookup map for fast checking if a class exists by its type. - val classesByType = mutableMapOf().apply { - classDefs.forEach { classDef -> put(classDef.type, classDef) } - } - - init { - classDefs.forEach { classDef -> - classDef.methods.forEach { method -> - val methodClassPair: MethodClassPair = method to classDef - - // Add strings contained in the method as the key. - method.instructionsOrNull?.forEach instructions@{ instruction -> - if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) { - return@instructions - } - - val string = ((instruction as ReferenceInstruction).reference as StringReference).string - - methodsByStrings[string] = methodClassPair - } - - // In the future, the class type could be added to the lookup map. - // This would require MethodFingerprint to be changed to include the class type. - } - } - } - - override fun close() { - methodsByStrings.clear() - classesByType.clear() - } - } + internal val matchers =Map> override fun close() { - lookupMaps.close() - classDefs.clear() + try { + classDefs.clear() + _lookupMaps = null + } catch (e: IOException) { + logger.warning("Failed to clear BytecodePatchContext: ${e.message}") + } + } + + internal inner class LookupMaps { + private val _classesByType = mutableMapOf() + val classesByType: Map = _classesByType + + private val _methodsByStrings = mutableMapOf>() + val methodsByStrings: Map> = _methodsByStrings + + init { + classDefs.forEach(::plusAssign) + } + + operator fun plusAssign(classDef: ClassDef) { + classDef.methods.asSequence().forEach { method -> + method.instructionsOrNull?.asSequence() + ?.filterIsInstance() + ?.map { it.reference } + ?.filterIsInstance() + ?.map { it.string } + ?.forEach { string -> _methodsByStrings.getOrPut(string) { mutableListOf() } += method } + } + + _classesByType[classDef.type] = classDef + } } } - -/** - * A pair of a [Method] and the [ClassDef] it is a member of. - */ -internal typealias MethodClassPair = Pair - -/** - * A list of [MethodClassPair]s. - */ -internal typealias MethodClassPairs = LinkedList - -/** - * A lookup map for [MethodClassPairs]s. - * The key is a string and the value is a list of [MethodClassPair]s. - */ -internal class MethodClassPairsLookupMap : MutableMap by mutableMapOf() { - /** - * Add a [MethodClassPair] associated by any key. - * If the key does not exist, a new list is created and the [MethodClassPair] is added to it. - */ - internal operator fun set(key: String, methodClassPair: MethodClassPair) = - apply { getOrPut(key) { MethodClassPairs() }.add(methodClassPair) } -}