fix: More bugfixes, patches now compile

This commit is contained in:
oSumAtrIX
2025-11-26 23:12:46 +01:00
parent 14f2eb69e4
commit cdc480acea
4 changed files with 186 additions and 234 deletions

View File

@@ -5,9 +5,11 @@ package app.revanced.patcher
import app.revanced.patcher.InstructionLocation.* import app.revanced.patcher.InstructionLocation.*
import app.revanced.patcher.Match.PatternMatch import app.revanced.patcher.Match.PatternMatch
import app.revanced.patcher.Matcher.MatchContext import app.revanced.patcher.Matcher.MatchContext
import app.revanced.patcher.extensions.* import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.instructionsOrNull
import app.revanced.patcher.extensions.opcode import app.revanced.patcher.extensions.opcode
import app.revanced.patcher.extensions.string import app.revanced.patcher.extensions.string
import app.revanced.patcher.extensions.stringReference
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
@@ -15,9 +17,6 @@ import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
/** /**
@@ -69,110 +68,69 @@ class Fingerprint internal constructor(
fun matchOrNull(): Match? { fun matchOrNull(): Match? {
if (_matchOrNull != null) return _matchOrNull if (_matchOrNull != null) return _matchOrNull
val matchStrings = indexedMatcher<Instruction> { var stringMatches: List<Match.StringMatch>? = null
strings?.forEach { add { string { contains(it) } } }
}
val matchIndices = indexedMatcher<Instruction>() val matchIndices = indexedMatcher<Instruction>()
val match = context(_: MatchContext) fun Method.(): Boolean { // This line is needed, because the method must be passed by reference to "matchIndices".
// Referencing the method directly would "hardcode" it in the cached pattern by value.
// By using this variable, the reference can be updated for each method.
lateinit var currentMethod: Method
context(_: MatchContext)
fun Method.match(): Boolean {
if (this@Fingerprint.accessFlags != null && this@Fingerprint.accessFlags != accessFlags) if (this@Fingerprint.accessFlags != null && this@Fingerprint.accessFlags != accessFlags)
return false return false
if (this@Fingerprint.returnType != null && this@Fingerprint.returnType != returnType) if (this@Fingerprint.returnType != null && !returnType.startsWith(this@Fingerprint.returnType))
return false return false
if (this@Fingerprint.parameters != null && !parametersStartsWith(parameterTypes, this@Fingerprint.parameters)) if (this@Fingerprint.parameters != null && !parametersStartsWith(
parameterTypes,
this@Fingerprint.parameters
)
)
return false return false
if (custom != null && !custom(this, context.lookupMaps.classDefsByType[definingClass]!!)) if (custom != null && !custom(this, context.lookupMaps.classDefsByType[definingClass]!!))
return false return false
if (strings != null && !matchStrings(instructionsOrNull ?: return false)) stringMatches = if (strings != null) {
return false val instructions = instructionsOrNull ?: return false
var stringsList: MutableList<String>? = null
fun InstructionFilter.evaluate(instruction: Instruction): Boolean { buildList {
return when (this) { instructions.forEachIndexed { instructionIndex, instruction ->
is AnyInstruction -> filters.any { evaluate(instruction) } if (stringsList == null) stringsList = strings.toMutableList()
is CheckCastFilter -> instruction.opcode(Opcode.CHECK_CAST) && instruction.typeReference?.endsWith(
typeValue
) ?: false
is FieldAccessFilter -> { val string = instruction.stringReference?.string ?: return@forEachIndexed
val reference = instruction.fieldReference ?: return false val index = stringsList.indexOfFirst(string::contains)
if (index < 0) return@forEachIndexed
if (name != null && reference.name != name) return false add(Match.StringMatch(string, instructionIndex))
if (type != null && !reference.type.startsWith(type)) return false stringsList.removeAt(index)
definingClass == null || reference.definingClass.endsWith(definingClass) ||
(definingClass == "this" && reference.definingClass != method.definingClass)
} }
is LiteralFilter -> instruction.wideLiteral == literalValue if (stringsList == null || stringsList.isNotEmpty()) return false
&& opcodes?.contains(instruction.opcode) ?: true
is MethodCallFilter -> {
val reference = instruction.methodReference ?: return false
if (name != null && reference.name != name) return false
if (returnType != null && !reference.returnType.startsWith(returnType)) return false
if (parameters != null && !parametersStartsWith(reference.parameterTypes, parameters))
return false
if ((definingClass != null && !reference.definingClass.endsWith(definingClass)) ||
(definingClass == "this" && reference.definingClass != method.definingClass)
) return false
opcodes?.contains(instruction.opcode) ?: true
} }
is NewInstanceFilter -> { } else null
if (opcodes != null && !opcodes.contains(instruction.opcode)) return false
instruction.reference<TypeReference> { endsWith(type) }
}
is OpcodeFilter -> instruction.opcode(opcode) currentMethod = this
is StringFilter -> { return filters == null || matchIndices(instructionsOrNull ?: return false, "match") {
val string = instruction.stringReference?.string ?: return false
val filterString = stringValue
when (comparison) {
StringComparisonType.EQUALS -> string == filterString
StringComparisonType.CONTAINS -> string.contains(filterString)
StringComparisonType.STARTS_WITH -> string.startsWith(filterString)
StringComparisonType.ENDS_WITH -> string.endsWith(filterString)
}
}
is OpcodesFilter -> opcodes?.contains(instruction.opcode) == true
else -> throw IllegalStateException("Unknown InstructionFilter type: ${this::class.java}")
}
}
if (filters != null && !matchIndices(instructionsOrNull ?: return false, "match") {
filters.forEach { filter -> filters.forEach { filter ->
val filterMatches: Instruction.() -> Boolean = { filter.matches(currentMethod, this) }
when (val location = filter.location) { when (val location = filter.location) {
is MatchAfterImmediately -> after { filter.evaluate(this) } is MatchAfterImmediately -> after { filterMatches() }
is MatchAfterWithin -> after(1..location.matchDistance) { filter.evaluate(this) } is MatchAfterWithin -> after(1..location.matchDistance) { filterMatches() }
is MatchAfterAnywhere -> add { filter.evaluate(this) } is MatchAfterAnywhere -> add { filterMatches() }
is MatchAfterAtLeast -> after( is MatchAfterAtLeast -> after(location.minimumDistanceFromLastInstruction..Int.MAX_VALUE) { filterMatches() }
location.minimumDistanceFromLastInstruction..Int.MAX_VALUE is MatchAfterRange -> after(location.minimumDistanceFromLastInstruction..location.maximumDistanceFromLastInstruction) { filterMatches() }
) { filter.evaluate(this) } is MatchFirst -> head { filterMatches() }
}
is MatchAfterRange -> after(
location.minimumDistanceFromLastInstruction..
location.maximumDistanceFromLastInstruction
) { filter.evaluate(this) }
is MatchFirst -> first { filter.evaluate(this) }
} }
} }
})
return false
return true
} }
val allStrings = buildList { val allStrings = buildList {
@@ -183,30 +141,28 @@ class Fingerprint internal constructor(
) )
}.map { it.stringValue } + (strings ?: emptyList()) }.map { it.stringValue } + (strings ?: emptyList())
val method = if (allStrings.isNotEmpty()) val method = if (allStrings.isNotEmpty()) {
context.firstMethodOrNull(strings = allStrings.toTypedArray()) { match() } ?: context.firstMethodOrNull(strings = allStrings.toTypedArray()) { match() }
// Maybe a better way exists ?: context(MatchContext()) { context.lookupMaps.methodsWithString.firstOrNull { it.match() } }
context(MatchContext()) { } else {
context.lookupMaps.methodsWithString.first { it.match() } context.firstMethodOrNull { match() }
} } ?: return null
else context.firstMethodOrNull { match() } ?: return null
val instructionMatches = filters?.withIndex()?.map { (i, filter) -> val instructionMatches = filters?.withIndex()?.map { (i, filter) ->
Match.InstructionMatch(filter, matchIndices.indices[i], method.getInstruction(i)) val matchIndex = matchIndices.indices[i]
Match.InstructionMatch(filter, matchIndex, method.getInstruction(matchIndex))
} }
val stringMatches = if (strings != null) matchStrings.indices.map { i -> _matchOrNull = Match(
// TODO: Should we use the methods string or the fingerprints string
Match.StringMatch(method.getInstruction(i).stringReference!!.string, i)
} else null
return Match(
context, context,
context.lookupMaps.classDefsByType[method.definingClass]!!, context.lookupMaps.classDefsByType[method.definingClass]!!,
method, method,
instructionMatches, instructionMatches,
stringMatches, stringMatches,
) )
return _matchOrNull
} }
/** /**
@@ -265,44 +221,37 @@ class Fingerprint internal constructor(
): Match? { ): Match? {
if (_matchOrNull != null) return _matchOrNull if (_matchOrNull != null) return _matchOrNull
if (returnType != null && !method.returnType.startsWith(returnType)) { var stringMatches: List<Match.StringMatch>? = null
return null
}
if (accessFlags != null && accessFlags != method.accessFlags) { val matchIndices = indexedMatcher<Instruction>()
return null
}
// TODO: parseParameters() context(_: MatchContext)
if (parameters != null && !parametersStartsWith(method.parameterTypes, parameters)) { fun Method.match(): Boolean {
return null if (this@Fingerprint.accessFlags != null && this@Fingerprint.accessFlags != accessFlags)
} return false
if (custom != null && !custom.invoke(method, classDef)) { if (this@Fingerprint.returnType != null && !returnType.startsWith(this@Fingerprint.returnType))
return null return false
}
// Legacy string declarations. if (this@Fingerprint.parameters != null && !parametersStartsWith(
val stringMatches: List<Match.StringMatch>? = if (strings == null) { parameterTypes,
null this@Fingerprint.parameters
} else { )
buildList { )
val instructions = method.instructionsOrNull ?: return null return false
if (custom != null && !custom(this, classDef))
return false
stringMatches = if (strings != null) {
val instructions = instructionsOrNull ?: return false
var stringsList: MutableList<String>? = null var stringsList: MutableList<String>? = null
buildList {
instructions.forEachIndexed { instructionIndex, instruction -> instructions.forEachIndexed { instructionIndex, instruction ->
if ( if (stringsList == null) stringsList = strings.toMutableList()
instruction.opcode != Opcode.CONST_STRING &&
instruction.opcode != Opcode.CONST_STRING_JUMBO
) {
return@forEachIndexed
}
val string = ((instruction as ReferenceInstruction).reference as StringReference).string val string = instruction.stringReference?.string ?: return@forEachIndexed
if (stringsList == null) {
stringsList = strings.toMutableList()
}
val index = stringsList.indexOfFirst(string::contains) val index = stringsList.indexOfFirst(string::contains)
if (index < 0) return@forEachIndexed if (index < 0) return@forEachIndexed
@@ -310,79 +259,36 @@ class Fingerprint internal constructor(
stringsList.removeAt(index) stringsList.removeAt(index)
} }
if (stringsList == null || stringsList.isNotEmpty()) return null if (stringsList == null || stringsList.isNotEmpty()) return false
}
} }
val instructionMatches = if (filters == null) { } else null
null
} else {
val instructions = method.instructionsOrNull?.toList() ?: return null
fun matchFilters(): List<Match.InstructionMatch>? { return filters == null || matchIndices.apply {
val lastMethodIndex = instructions.lastIndex filters.forEach { filter ->
var instructionMatches: MutableList<Match.InstructionMatch>? = null val filterMatches: Instruction.() -> Boolean = { filter.matches(method, this) }
var firstInstructionIndex = 0 when (val location = filter.location) {
var lastMatchIndex = -1 is MatchAfterImmediately -> after { filterMatches() }
is MatchAfterWithin -> after(1..location.matchDistance) { filterMatches() }
firstFilterLoop@ while (true) { is MatchAfterAnywhere -> add { filterMatches() }
// Matched index of the first filter. is MatchAfterAtLeast -> after(location.minimumDistanceFromLastInstruction..Int.MAX_VALUE) { filterMatches() }
var firstFilterIndex = -1 is MatchAfterRange -> after(location.minimumDistanceFromLastInstruction..location.maximumDistanceFromLastInstruction) { filterMatches() }
var subIndex = firstInstructionIndex is MatchFirst -> head { filterMatches() }
for (filterIndex in filters.indices) {
val filter = filters[filterIndex]
val location = filter.location
var instructionsMatched = false
while (subIndex <= lastMethodIndex &&
location.indexIsValidForMatching(
lastMatchIndex, subIndex
)
) {
val instruction = instructions[subIndex]
if (filter.matches(method, instruction)) {
lastMatchIndex = subIndex
if (filterIndex == 0) {
firstFilterIndex = subIndex
}
if (instructionMatches == null) {
instructionMatches = ArrayList<Match.InstructionMatch>(filters.size)
}
instructionMatches += Match.InstructionMatch(filter, subIndex, instruction)
instructionsMatched = true
subIndex++
break
}
subIndex++
}
if (!instructionsMatched) {
if (filterIndex == 0) {
return null // First filter has no more matches to start from.
}
// Try again with the first filter, starting from
// the next possible first filter index.
firstInstructionIndex = firstFilterIndex + 1
instructionMatches?.clear()
continue@firstFilterLoop
} }
} }
}(instructionsOrNull ?: return false)
// All instruction filters matches.
return instructionMatches
}
} }
matchFilters() ?: return null if (!context(MatchContext()) { method.match() }) return null
val instructionMatches = filters?.withIndex()?.map { (i, filter) ->
val matchIndex = matchIndices.indices[i]
Match.InstructionMatch(filter, matchIndex, method.getInstruction(matchIndex))
} }
_matchOrNull = Match( _matchOrNull = Match(
context, context,
classDef, context.lookupMaps.classDefsByType[method.definingClass]!!,
method, method,
instructionMatches, instructionMatches,
stringMatches, stringMatches,
@@ -600,11 +506,7 @@ class Match internal constructor(
* Accessing this property allocates a new mutable instance. * Accessing this property allocates a new mutable instance.
* Use [originalClassDef] if mutable access is not required. * Use [originalClassDef] if mutable access is not required.
*/ */
val classDef by lazy { val classDef by lazy { with(context) { originalClassDef.mutable() } }
with(context) {
originalClassDef.mutable()
}
}
/** /**
* The mutable version of [originalMethod]. * The mutable version of [originalMethod].

View File

@@ -251,52 +251,101 @@ class IndexedMatcher<T>() : Matcher<T, T.(lastMatchedIndex: Int, currentIndex: I
private var lastMatchedIndex = -1 private var lastMatchedIndex = -1
private var currentIndex = -1 private var currentIndex = -1
// TODO: Hint to stop searching for performance: private var stop = false
// Also make the APIs advance indices (e.g. atLeast, atMost) for performance. private var nextIndex: Int? = null
override fun invoke(haystack: Iterable<T>): Boolean { override fun invoke(haystack: Iterable<T>): Boolean {
// Normalize to list // Normalize to list
val hayList = haystack as? List<T> ?: haystack.toList() val hay = haystack as? List<T> ?: haystack.toList()
_indices.clear() _indices.clear()
lastMatchedIndex = -1 this@IndexedMatcher.lastMatchedIndex = -1
currentIndex = -1 currentIndex = -1
for (predicate in this) { data class Frame(
var matched = false val patternIndex: Int,
val lastMatchedIndex: Int,
val previousFrame: Frame?,
var nextHayIndex: Int,
val matchedIndex: Int
)
// Continue scanning from the position after the last successful match val stack = ArrayDeque<Frame>()
for (i in (lastMatchedIndex + 1) until hayList.size) { stack.add(
Frame(
patternIndex = 0,
lastMatchedIndex = -1,
previousFrame = null,
nextHayIndex = 0,
matchedIndex = -1
)
)
while (stack.isNotEmpty()) {
val frame = stack.last()
if (frame.nextHayIndex >= hay.size || nextIndex == -1) {
stack.removeLast()
nextIndex = null
continue
}
val i = frame.nextHayIndex
currentIndex = i currentIndex = i
val element = hayList[i] lastMatchedIndex = frame.lastMatchedIndex
nextIndex = null
if (element.predicate(lastMatchedIndex, currentIndex)) { if (this[frame.patternIndex](hay[i], lastMatchedIndex, currentIndex)) {
_indices += i Frame(
lastMatchedIndex = i patternIndex = frame.patternIndex + 1,
matched = true lastMatchedIndex = i,
break previousFrame = frame,
} nextHayIndex = i + 1,
} matchedIndex = i
).also {
if (!matched) { if (it.patternIndex == size) {
return false _indices += buildList(size) {
} var f: Frame? = it
while (f != null && f.matchedIndex != -1) {
add(f.matchedIndex)
f = f.previousFrame
} }
}.asReversed()
return true return true
} }
}.let(stack::add)
}
fun first(predicate: T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean) = frame.nextHayIndex = when (val nextIndex = nextIndex) {
null -> frame.nextHayIndex + 1
-1 -> 0 // Frame will be removed next loop.
else -> nextIndex
}
}
return false
}
fun head(predicate: T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean) =
add { lastMatchedIndex, currentIndex -> add { lastMatchedIndex, currentIndex ->
currentIndex == 0 && predicate(lastMatchedIndex, currentIndex) currentIndex == 0 && predicate(lastMatchedIndex, currentIndex)
} }
fun first(predicate: T.() -> Boolean) = fun head(predicate: T.() -> Boolean) =
first { _, _ -> predicate() } head { _, _ -> predicate() }
fun after(range: IntRange = 1..1, predicate: T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean) = fun after(range: IntRange = 1..1, predicate: T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean) =
add { lastMatchedIndex, currentIndex -> add { lastMatchedIndex, currentIndex ->
currentIndex - lastMatchedIndex in range && predicate(lastMatchedIndex, currentIndex) val distance = currentIndex - lastMatchedIndex
nextIndex = when {
distance < range.first -> lastMatchedIndex + range.first
distance > range.last -> -1
else -> return@add predicate(lastMatchedIndex, currentIndex)
}
false
} }
fun after(range: IntRange = 1..1, predicate: T.() -> Boolean) = fun after(range: IntRange = 1..1, predicate: T.() -> Boolean) =

View File

@@ -156,6 +156,8 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
PatcherResult.PatchedDexFile(it.name, it.inputStream()) PatcherResult.PatchedDexFile(it.name, it.inputStream())
}.toSet() }.toSet()
// Free up more memory, although it is unclear if this is actually helpful.
classDefs.clear()
System.gc() System.gc()
return patchedDexFileResults return patchedDexFileResults
@@ -163,7 +165,6 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
override fun close() { override fun close() {
try { try {
classDefs.clear()
_lookupMaps = null _lookupMaps = null
} catch (e: IOException) { } catch (e: IOException) {
logger.warning("Failed to clear BytecodePatchContext: ${e.message}") logger.warning("Failed to clear BytecodePatchContext: ${e.message}")

View File

@@ -170,14 +170,14 @@ internal object PatcherTest {
val iterable = (1..10).toList() val iterable = (1..10).toList()
val matcher = indexedMatcher<Int>() val matcher = indexedMatcher<Int>()
matcher.apply { first { this > 5 } } matcher.apply { head { this > 5 } }
assertFalse( assertFalse(
matcher(iterable), matcher(iterable),
"Should not match at any other index than first" "Should not match at any other index than first"
) )
matcher.clear() matcher.clear()
matcher.apply { first { this == 1 } }(iterable) matcher.apply { head { this == 1 } }(iterable)
assertEquals( assertEquals(
listOf(0), listOf(0),
matcher.indices, matcher.indices,
@@ -198,7 +198,7 @@ internal object PatcherTest {
matcher.clear() matcher.clear()
matcher.apply { matcher.apply {
first { this == 1 } head { this == 1 }
add { this == 2 } add { this == 2 }
add { this == 4 } add { this == 4 }
}(iterable) }(iterable)
@@ -238,8 +238,8 @@ internal object PatcherTest {
matcher.clear() matcher.clear()
matcher.apply { matcher.apply {
first { this == 1 } head { this == 1 }
after(2..5) { this == 4} after(2..5) { this == 4 }
add { this == 8 } add { this == 8 }
add { this == 9 } add { this == 9 }
}(iterable) }(iterable)
@@ -299,7 +299,7 @@ internal object PatcherTest {
val method by gettingFirstMethod { val method by gettingFirstMethod {
implementation { implementation {
matchIndices(instructions, "match") { matchIndices(instructions, "match") {
first { opcode == Opcode.CONST_STRING } head { opcode == Opcode.CONST_STRING }
add { opcode == Opcode.IPUT_OBJECT } add { opcode == Opcode.IPUT_OBJECT }
} }
} }