feat: More APIs/adjustments

This commit is contained in:
oSumAtrIX
2025-11-19 20:08:49 +01:00
parent 79d3640186
commit cf57726bbb
2 changed files with 373 additions and 222 deletions

View File

@@ -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<ClassDef>.anyClassDef(predicate: ClassDef.() -> Boolean) =
any(predicate)
fun Iterable<ClassDef>.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<out ExceptionHandler>.() -> Boolean) =
tryBlocks.any(predicate)
fun MethodImplementation.anyTryBlock(predicate: TryBlock<out ExceptionHandler>.() -> Boolean) = tryBlocks.any(predicate)
fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) =
debugItems.any(predicate)
fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) = debugItems.any(predicate)
fun Iterable<Instruction>.anyInstruction(predicate: Instruction.() -> Boolean) =
any(predicate)
fun Iterable<Instruction>.anyInstruction(predicate: Instruction.() -> Boolean) = any(predicate)
fun BytecodePatchContext.firstClassDefOrNull(predicate: MatchPredicate<ClassDef>) =
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<ClassDef>) =
requireNotNull(firstClassDefOrNull(predicate))
fun BytecodePatchContext.firstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) =
fun BytecodePatchContext.firstClassDefMutableOrNull(predicate: MatchPredicate<ClassDef>) =
firstClassDefOrNull(predicate)?.mutable()
fun BytecodePatchContext.firstMutableClassDef(predicate: ClassDef.() -> Boolean) =
requireNotNull(firstMutableClassDefOrNull(predicate))
fun BytecodePatchContext.firstClassDefMutable(predicate: MatchPredicate<ClassDef>) =
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<ClassDef>)? = 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<ClassDef>)? = null
) = requireNotNull(firstClassDefOrNull(type, predicate))
fun BytecodePatchContext.firstMutableMethodOrNull(predicate: Method.() -> Boolean): MutableMethod? {
fun BytecodePatchContext.firstClassDefMutableOrNull(
type: String, predicate: (MatchPredicate<ClassDef>)? = null
) = firstClassDefOrNull(type, predicate)?.mutable()
fun BytecodePatchContext.firstClassDefMutable(
type: String, predicate: (MatchPredicate<ClassDef>)? = null
) = requireNotNull(firstClassDefMutableOrNull(type, predicate))
fun BytecodePatchContext.firstMethodOrNull(predicate: MatchPredicate<Method>) = with(predicate) {
with(MatchContext()) {
classDefs.asSequence().flatMap { it.methods.asSequence() }.firstOrNull { it.match() }
}
}
fun BytecodePatchContext.firstMethod(predicate: MatchPredicate<Method>) = requireNotNull(firstMethodOrNull(predicate))
fun BytecodePatchContext.firstMethodMutableOrNull(predicate: MatchPredicate<Method>): MutableMethod? {
with(predicate) {
with(MatchContext()) {
classDefs.forEach { classDef ->
classDef.methods.forEach { method ->
if (predicate(method)) return classDef.mutable().methods.first {
MethodUtil.methodSignaturesMatch(it, method)
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<Method>) =
requireNotNull(firstMethodMutableOrNull(predicate))
fun gettingFirstClassDefOrNull(predicate: ClassDef.() -> Boolean) = ReadOnlyProperty<Any?, ClassDef?> { thisRef, _ ->
require(thisRef is BytecodePatchContext)
thisRef.firstClassDefOrNull(predicate)
}
fun gettingFirstClassDef(predicate: ClassDef.() -> Boolean) = requireNotNull(gettingFirstClassDefOrNull(predicate))
fun gettingFirstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) =
ReadOnlyProperty<Any?, ClassDef?> { thisRef, _ ->
require(thisRef is BytecodePatchContext)
thisRef.firstMutableClassDefOrNull(predicate)
}
fun gettingFirstMutableClassDef(predicate: ClassDef.() -> Boolean) =
requireNotNull(gettingFirstMutableClassDefOrNull(predicate))
fun gettingFirstMethodOrNull(predicate: Method.() -> Boolean) = ReadOnlyProperty<Any?, Method?> { thisRef, _ ->
require(thisRef is BytecodePatchContext)
thisRef.firstMethodOrNull(predicate)
}
fun gettingFirstMethod(predicate: Method.() -> Boolean) = requireNotNull(gettingFirstMethodOrNull(predicate))
fun gettingFirstMutableMethodOrNull(predicate: Method.() -> Boolean) = ReadOnlyProperty<Any?, Method?> { 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<Instruction> {
add { opcode == Opcode.RETURN_VOID }
add { opcode == Opcode.NOP }
}
}
}
fun Method.matchSequentiallyInstructions(
builder: MutableList<Instruction.() -> Boolean>.() -> Unit
) = implementation?.instructions?.matchSequentially(builder) ?: false
firstMutableMethod {
matchSequentiallyInstructions {
add { opcode == Opcode.RETURN_VOID }
add { opcode == Opcode.NOP }
}
}.addInstructions("apiTest2")
fun BytecodePatchContext.firstMethodOrNull(
vararg strings: String,
predicate: MatchPredicate<Method> = MatchPredicate { true },
) = with(predicate) {
with(MatchContext()) {
strings.mapNotNull { lookupMaps.methodsByStrings[it] }.minByOrNull { it.size }?.firstOrNull { it.match() }
}
}
fun BytecodePatchContext.firstMethod(
vararg strings: String,
predicate: MatchPredicate<Method> = MatchPredicate { true },
) = requireNotNull(firstMethodOrNull(*strings, predicate = predicate))
abstract class Matcher<T> : MutableList<T.() -> Boolean> by mutableListOf() {
fun BytecodePatchContext.firstMethodMutableOrNull(
vararg strings: String,
predicate: MatchPredicate<Method> = 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 BytecodePatchContext.firstMethodMutable(
vararg strings: String, predicate: MatchPredicate<Method> = MatchPredicate { true }
) = requireNotNull(firstMethodMutableOrNull(*strings, predicate = predicate))
inline fun <reified C, T> ReadOnlyProperty(crossinline block: C.(KProperty<*>) -> T) =
ReadOnlyProperty<Any?, T> { thisRef, property ->
require(thisRef is C)
thisRef.block(property)
}
fun gettingFirstClassDefOrNull(predicate: MatchPredicate<ClassDef>) =
ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefOrNull(predicate) }
fun gettingFirstClassDef(predicate: MatchPredicate<ClassDef>) = requireNotNull(gettingFirstClassDefOrNull(predicate))
fun gettingFirstClassDefMutableOrNull(predicate: MatchPredicate<ClassDef>) =
ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefMutableOrNull(predicate) }
fun gettingFirstClassDefMutable(predicate: MatchPredicate<ClassDef>) =
requireNotNull(gettingFirstClassDefMutableOrNull(predicate))
fun gettingFirstClassDefOrNull(
type: String, predicate: (MatchPredicate<ClassDef>)? = null
) = ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefOrNull(type, predicate) }
fun gettingFirstClassDef(
type: String, predicate: (MatchPredicate<ClassDef>)? = null
) = requireNotNull(gettingFirstClassDefOrNull(type, predicate))
fun gettingFirstClassDefMutableOrNull(
type: String, predicate: (MatchPredicate<ClassDef>)? = null
) = ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefMutableOrNull(type, predicate) }
fun gettingFirstClassDefMutable(
type: String, predicate: (MatchPredicate<ClassDef>)? = null
) = requireNotNull(gettingFirstClassDefMutableOrNull(type, predicate))
fun gettingFirstMethodOrNull(predicate: MatchPredicate<Method>) =
ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodOrNull(predicate) }
fun gettingFirstMethod(predicate: MatchPredicate<Method>) = requireNotNull(gettingFirstMethodOrNull(predicate))
fun gettingFirstMethodMutableOrNull(predicate: MatchPredicate<Method>) =
ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodMutableOrNull(predicate) }
fun gettingFirstMethodMutable(predicate: MatchPredicate<Method>) =
requireNotNull(gettingFirstMethodMutableOrNull(predicate))
fun gettingFirstMethodOrNull(
vararg strings: String,
predicate: MatchPredicate<Method> = MatchPredicate { true },
) = ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodOrNull(*strings, predicate = predicate) }
fun gettingFirstMethod(
vararg strings: String,
predicate: MatchPredicate<Method> = MatchPredicate { true },
) = requireNotNull(gettingFirstMethodOrNull(*strings, predicate = predicate))
fun gettingFirstMethodMutableOrNull(
vararg strings: String,
predicate: MatchPredicate<Method> = MatchPredicate { true },
) = ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodMutableOrNull(*strings, predicate = predicate) }
fun gettingFirstMethodMutable(
vararg strings: String,
predicate: MatchPredicate<Method> = MatchPredicate { true },
) = requireNotNull(gettingFirstMethodMutableOrNull(*strings, predicate = predicate))
fun interface MatchPredicate<T> {
context(MatchContext) fun T.match(): Boolean
}
abstract class Matcher<T, U> : MutableList<U> by mutableListOf() {
var matchIndex = -1
protected set
abstract operator fun invoke(haystack: Iterable<T>): Boolean
class MatchContext internal constructor() : MutableMap<String, Any> by mutableMapOf()
}
open class SequentialMatcher<T> internal constructor() : Matcher<T>() {
override operator fun invoke(haystack: Iterable<T>) = true
fun <T> slidingWindowMatcher(builder: MutableList<T.() -> Boolean>.() -> Unit) =
SlidingWindowMatcher<T>().apply(builder)
context(MatchContext)
fun <T> Iterable<T>.matchSlidingWindow(key: String, builder: MutableList<T.() -> Boolean>.() -> Unit) =
(getOrPut(key) { slidingWindowMatcher(builder) } as Matcher<T, T.() -> Boolean>)(this)
fun <T> Iterable<T>.matchSlidingWindow(builder: MutableList<T.() -> Boolean>.() -> Unit) =
slidingWindowMatcher(builder)(this)
class SlidingWindowMatcher<T>() : Matcher<T, T.() -> Boolean>() {
override operator fun invoke(haystack: Iterable<T>): 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
}
class CaptureStringIndices<T> internal constructor() : Matcher<T>() {
val capturedStrings = mutableMapOf<String>()
override operator fun invoke(haystack: Iterable<T>) {
}
if (matched) {
matchIndex = i
return true
}
}
fun <T> matchSequentially() = SequentialMatcher<T>()
fun <T> sequentialMatcher(builder: MutableList<T.() -> Boolean>.() -> Unit) =
SequentialMatcher<T>().apply(builder)
matchIndex = -1
return false
}
}
fun <T> Iterable<T>.matchSequentially(builder: MutableList<T.() -> Boolean>.() -> Unit) =
sequentialMatcher(builder)(this)
fun findStringsMatcher(builder: MutableList<String>.() -> Unit) =
FindStringsMatcher().apply(builder)
class FindStringsMatcher() : Matcher<Instruction, String>() {
val matchedStrings = mutableMapOf<String, Int>()
var needles = toMutableSet() // Reduce O(n²) to O(log n) by removing from the set
override fun invoke(haystack: Iterable<Instruction>): Boolean {
needles = toMutableSet() // Reset needles for each invocation
// (or do not use the set if set is too small for performance)
val foundStrings = mutableMapOf<String, Int>()
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<String, Int>()
firstMethod("fullstring", "fullstring") {
val remaining = desiredStringIndices.toMutableSet()
val foundMap = mutableMapOf<String, Int>()
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<String>,
out: MutableMap<String, Int>
): Boolean {
val remaining = desiredStringIndices.toMutableSet()
val foundMap = mutableMapOf<String, Int>()
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<String, Int>()
val method = firstMethod {
name == "desiredMethodName" && captureStrings(desiredStringIndices, matchedIndices)
}
for ((key, value) in matchedIndices) {
println("Found string '$key' at index $value in method '$method'")
}
}

View File

@@ -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<ClassDef>) : Closeable {
/**
* Methods associated by strings referenced in it.
*/
internal val methodsByStrings = MethodClassPairsLookupMap()
internal val matchers =Map<String, Matcher<*>>
// Lookup map for fast checking if a class exists by its type.
val classesByType = mutableMapOf<String, ClassDef>().apply {
classDefs.forEach { classDef -> put(classDef.type, classDef) }
override fun close() {
try {
classDefs.clear()
_lookupMaps = null
} catch (e: IOException) {
logger.warning("Failed to clear BytecodePatchContext: ${e.message}")
}
}
internal inner class LookupMaps {
private val _classesByType = mutableMapOf<String, ClassDef>()
val classesByType: Map<String, ClassDef> = _classesByType
private val _methodsByStrings = mutableMapOf<String, MutableList<Method>>()
val methodsByStrings: Map<String, List<Method>> = _methodsByStrings
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
classDefs.forEach(::plusAssign)
}
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
methodsByStrings[string] = methodClassPair
operator fun plusAssign(classDef: ClassDef) {
classDef.methods.asSequence().forEach { method ->
method.instructionsOrNull?.asSequence()
?.filterIsInstance<ReferenceInstruction>()
?.map { it.reference }
?.filterIsInstance<StringReference>()
?.map { it.string }
?.forEach { string -> _methodsByStrings.getOrPut(string) { mutableListOf() } += method }
}
// 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.
_classesByType[classDef.type] = classDef
}
}
}
override fun close() {
methodsByStrings.clear()
classesByType.clear()
}
}
override fun close() {
lookupMaps.close()
classDefs.clear()
}
}
/**
* A pair of a [Method] and the [ClassDef] it is a member of.
*/
internal typealias MethodClassPair = Pair<Method, ClassDef>
/**
* A list of [MethodClassPair]s.
*/
internal typealias MethodClassPairs = LinkedList<MethodClassPair>
/**
* A lookup map for [MethodClassPairs]s.
* The key is a string and the value is a list of [MethodClassPair]s.
*/
internal class MethodClassPairsLookupMap : MutableMap<String, MethodClassPairs> 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) }
}