feat: Fix property delegates & add cache

This commit is contained in:
oSumAtrIX
2025-11-30 19:21:35 +01:00
parent 58ff464192
commit 2509997432
2 changed files with 112 additions and 67 deletions

View File

@@ -50,6 +50,7 @@ fun MethodImplementation.anyTryBlock(predicate: TryBlock<out ExceptionHandler>.(
fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) = debugItems.any(predicate)
fun Iterable<Instruction>.anyInstruction(predicate: Instruction.() -> Boolean) = any(predicate)
fun BytecodePatchContext.firstClassDefOrNull(predicate: context(MatchContext) ClassDef.() -> Boolean) =
with(MatchContext()) { classDefs.firstOrNull { it.predicate() } }
@@ -88,33 +89,25 @@ fun Iterable<ClassDef>.firstMethodOrNull(predicate: context(MatchContext) Method
fun Iterable<ClassDef>.firstMethod(predicate: context(MatchContext) Method.() -> Boolean) =
requireNotNull(firstMethodOrNull(predicate))
context(context: BytecodePatchContext)
fun Iterable<ClassDef>.firstMethodMutableOrNull(predicate: context(MatchContext) Method.() -> Boolean): MutableMethod? =
with(context) {
with(MatchContext()) {
this@firstMethodMutableOrNull.forEach { classDef ->
classDef.methods.firstOrNull { it.predicate() }?.let { method ->
return classDef.mutable().methods.first { MethodUtil.methodSignaturesMatch(it, method) }
}
}
null
}
}
context(_: BytecodePatchContext)
fun Iterable<ClassDef>.firstMethodMutable(predicate: context(MatchContext) Method.() -> Boolean) =
requireNotNull(firstMethodMutableOrNull(predicate))
/** Can't compile due to JVM platform declaration clash
fun Iterable<Method>.firstMethodOrNull(predicate: context(MatchContext) Method.() -> Boolean) =
with(MatchContext()) { firstOrNull { it.predicate() } }
fun Iterable<Method>.firstMethod(predicate: context(MatchContext) Method.() -> Boolean) =
with(MatchContext()) { requireNotNull(firstMethodOrNull(predicate)) }
**/
fun BytecodePatchContext.firstMethodOrNull(predicate: context(MatchContext) Method.() -> Boolean) =
classDefs.firstMethodOrNull(predicate)
fun BytecodePatchContext.firstMethod(predicate: context(MatchContext) Method.() -> Boolean) =
requireNotNull(firstMethodOrNull(predicate))
fun BytecodePatchContext.firstMethodMutableOrNull(predicate: context(MatchContext) Method.() -> Boolean) =
classDefs.firstMethodMutableOrNull(predicate)
classDefs.firstMethodOrNull(predicate)?.let { method ->
lookupMaps.classDefsByType[method.definingClass]!!.mutable().methods.first {
MethodUtil.methodSignaturesMatch(method, it)
}
}
fun BytecodePatchContext.firstMethodMutable(predicate: context(MatchContext) Method.() -> Boolean) =
requireNotNull(firstMethodMutableOrNull(predicate))
@@ -150,72 +143,81 @@ fun BytecodePatchContext.firstMethodMutable(
vararg strings: String, predicate: context(MatchContext) Method.() -> Boolean = { 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)
class CachedReadOnlyProperty<T> internal constructor(
private val block: BytecodePatchContext.(KProperty<*>) -> T
) : ReadOnlyProperty<BytecodePatchContext, T> {
private var value: T? = null
private var cached = false
thisRef.block(property)
override fun getValue(thisRef: BytecodePatchContext, property: KProperty<*>): T {
if (!cached) {
value = thisRef.block(property)
cached = true
}
return value!!
}
}
fun gettingFirstClassDefOrNull(predicate: context(MatchContext) ClassDef.() -> Boolean) =
ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefOrNull(predicate) }
CachedReadOnlyProperty { firstClassDefOrNull(predicate) }
fun gettingFirstClassDef(predicate: context(MatchContext) ClassDef.() -> Boolean) =
requireNotNull(gettingFirstClassDefOrNull(predicate))
CachedReadOnlyProperty { firstClassDef(predicate) }
fun gettingFirstClassDefMutableOrNull(predicate: context(MatchContext) ClassDef.() -> Boolean) =
ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefMutableOrNull(predicate) }
CachedReadOnlyProperty { firstClassDefMutableOrNull(predicate) }
fun gettingFirstClassDefMutable(predicate: context(MatchContext) ClassDef.() -> Boolean) =
requireNotNull(gettingFirstClassDefMutableOrNull(predicate))
CachedReadOnlyProperty { firstClassDefMutable(predicate) }
fun gettingFirstClassDefOrNull(
type: String, predicate: (context(MatchContext) ClassDef.() -> Boolean)? = null
) = ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefOrNull(type, predicate) }
) = CachedReadOnlyProperty { firstClassDefOrNull(type, predicate) }
fun gettingFirstClassDef(
type: String, predicate: (context(MatchContext) ClassDef.() -> Boolean)? = null
) = requireNotNull(gettingFirstClassDefOrNull(type, predicate))
) = CachedReadOnlyProperty { firstClassDef(type, predicate) }
fun gettingFirstClassDefMutableOrNull(
type: String, predicate: (context(MatchContext) ClassDef.() -> Boolean)? = null
) = ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefMutableOrNull(type, predicate) }
) = CachedReadOnlyProperty { firstClassDefMutableOrNull(type, predicate) }
fun gettingFirstClassDefMutable(
type: String, predicate: (context(MatchContext) ClassDef.() -> Boolean)? = null
) = requireNotNull(gettingFirstClassDefMutableOrNull(type, predicate))
) = CachedReadOnlyProperty { firstClassDefMutable(type, predicate) }
fun gettingFirstMethodOrNull(predicate: context(MatchContext) Method.() -> Boolean) =
ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodOrNull(predicate) }
CachedReadOnlyProperty { firstMethodOrNull(predicate) }
fun gettingFirstMethod(predicate: context(MatchContext) Method.() -> Boolean) =
requireNotNull(gettingFirstMethodOrNull(predicate))
CachedReadOnlyProperty { firstMethod(predicate) }
fun gettingFirstMethodMutableOrNull(predicate: context(MatchContext) Method.() -> Boolean) =
ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodMutableOrNull(predicate) }
CachedReadOnlyProperty { firstMethodMutableOrNull(predicate) }
fun gettingFirstMethodMutable(predicate: context(MatchContext) Method.() -> Boolean) =
requireNotNull(gettingFirstMethodMutableOrNull(predicate))
CachedReadOnlyProperty { firstMethodMutable(predicate) }
fun gettingFirstMethodOrNull(
vararg strings: String,
predicate: context(MatchContext) Method.() -> Boolean = { true },
) = ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodOrNull(*strings, predicate = predicate) }
) = CachedReadOnlyProperty { firstMethodOrNull(*strings, predicate = predicate) }
fun gettingFirstMethod(
vararg strings: String,
predicate: context(MatchContext) Method.() -> Boolean = { true },
) = requireNotNull(gettingFirstMethodOrNull(*strings, predicate = predicate))
) = CachedReadOnlyProperty { firstMethod(*strings, predicate = predicate) }
fun gettingFirstMethodMutableOrNull(
vararg strings: String,
predicate: context(MatchContext) Method.() -> Boolean = { true },
) = ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodMutableOrNull(*strings, predicate = predicate) }
) = CachedReadOnlyProperty { firstMethodMutableOrNull(*strings, predicate = predicate) }
fun gettingFirstMethodMutable(
vararg strings: String,
predicate: context(MatchContext) Method.() -> Boolean = { true },
) = requireNotNull(gettingFirstMethodMutableOrNull(*strings, predicate = predicate))
) = CachedReadOnlyProperty { firstMethodMutable(*strings, predicate = predicate) }
fun <T> indexedMatcher() = IndexedMatcher<T>()
@@ -406,6 +408,22 @@ fun BytecodePatchContext.firstClassDefMutableByDeclarativePredicate(
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
) = requireNotNull(firstClassDefMutableByDeclarativePredicateOrNull(type, predicate))
fun BytecodePatchContext.firstMethodByDeclarativePredicateOrNull(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = firstMethodOrNull { rememberDeclarativePredicate(predicate) }
fun BytecodePatchContext.firstMethodByDeclarativePredicate(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = requireNotNull(firstMethodByDeclarativePredicateOrNull(predicate))
fun BytecodePatchContext.firstMethodMutableByDeclarativePredicateOrNull(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = firstMethodMutableOrNull { rememberDeclarativePredicate(predicate) }
fun BytecodePatchContext.firstMethodMutableByDeclarativePredicate(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = requireNotNull(firstMethodMutableByDeclarativePredicateOrNull(predicate))
fun BytecodePatchContext.firstMethodByDeclarativePredicateOrNull(
vararg strings: String,
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
@@ -434,7 +452,7 @@ fun gettingFirstClassDefByDeclarativePredicateOrNull(
fun gettingFirstClassDefByDeclarativePredicate(
type: String,
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
) = requireNotNull(gettingFirstClassDefByDeclarativePredicateOrNull(type, predicate))
) = CachedReadOnlyProperty { firstClassDefByDeclarativePredicate(type, predicate) }
fun gettingFirstClassDefMutableByDeclarativePredicateOrNull(
type: String,
@@ -444,7 +462,7 @@ fun gettingFirstClassDefMutableByDeclarativePredicateOrNull(
fun gettingFirstClassDefMutableByDeclarativePredicate(
type: String,
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
) = requireNotNull(gettingFirstClassDefMutableByDeclarativePredicateOrNull(type, predicate))
) = CachedReadOnlyProperty { firstClassDefMutableByDeclarativePredicate(type, predicate) }
fun gettingFirstClassDefByDeclarativePredicateOrNull(
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
@@ -452,7 +470,7 @@ fun gettingFirstClassDefByDeclarativePredicateOrNull(
fun gettingFirstClassDefByDeclarativePredicate(
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
) = requireNotNull(gettingFirstClassDefByDeclarativePredicateOrNull(predicate))
) = CachedReadOnlyProperty { firstClassDefByDeclarativePredicate(predicate) }
fun gettingFirstClassDefMutableByDeclarativePredicateOrNull(
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
@@ -460,7 +478,7 @@ fun gettingFirstClassDefMutableByDeclarativePredicateOrNull(
fun gettingFirstClassDefMutableByDeclarativePredicate(
predicate: context(MatchContext, ClassDef) DeclarativePredicateBuilder<ClassDef>.() -> Unit
) = requireNotNull(gettingFirstClassDefMutableByDeclarativePredicateOrNull(predicate))
) = CachedReadOnlyProperty { firstClassDefMutableByDeclarativePredicate(predicate) }
fun gettingFirstMethodByDeclarativePredicateOrNull(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
@@ -468,7 +486,7 @@ fun gettingFirstMethodByDeclarativePredicateOrNull(
fun gettingFirstMethodByDeclarativePredicate(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = requireNotNull(gettingFirstMethodByDeclarativePredicateOrNull(predicate))
) = CachedReadOnlyProperty { firstMethodByDeclarativePredicate(predicate = predicate) }
fun gettingFirstMethodMutableByDeclarativePredicateOrNull(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
@@ -476,7 +494,7 @@ fun gettingFirstMethodMutableByDeclarativePredicateOrNull(
fun gettingFirstMethodMutableByDeclarativePredicate(
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = requireNotNull(gettingFirstMethodMutableByDeclarativePredicateOrNull(predicate))
) = CachedReadOnlyProperty { firstMethodMutableByDeclarativePredicate(predicate = predicate) }
fun gettingFirstMethodByDeclarativePredicateOrNull(
vararg strings: String,
@@ -486,7 +504,7 @@ fun gettingFirstMethodByDeclarativePredicateOrNull(
fun gettingFirstMethodByDeclarativePredicate(
vararg strings: String,
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = requireNotNull(gettingFirstMethodByDeclarativePredicateOrNull(*strings, predicate = predicate))
) = CachedReadOnlyProperty { firstMethodByDeclarativePredicate(*strings, predicate = predicate) }
fun gettingFirstMethodMutableByDeclarativePredicateOrNull(
vararg strings: String,
@@ -496,7 +514,7 @@ fun gettingFirstMethodMutableByDeclarativePredicateOrNull(
fun gettingFirstMethodMutableByDeclarativePredicate(
vararg strings: String,
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) = requireNotNull(gettingFirstMethodMutableByDeclarativePredicateOrNull(*strings, predicate = predicate))
) = CachedReadOnlyProperty { firstMethodMutableByDeclarativePredicate(*strings, predicate = predicate) }
class DeclarativePredicateBuilder<T> internal constructor() {
@@ -545,8 +563,20 @@ fun DeclarativePredicateBuilder<Method>.custom(block: context(MatchContext) Meth
class Composition internal constructor(
val indices: List<Int>,
predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
private val predicate: context(MatchContext, Method) DeclarativePredicateBuilder<Method>.() -> Unit
) {
val methodOrNull by gettingFirstMethodMutableByDeclarativePredicateOrNull(predicate)
val method = requireNotNull(methodOrNull)
private var _methodOrNull: MutableMethod? = null
context(context: BytecodePatchContext)
val methodOrNull: MutableMethod?
get() {
if (_methodOrNull == null) {
_methodOrNull = context.firstMethodMutableByDeclarativePredicateOrNull(predicate)
}
return _methodOrNull
}
context(_: BytecodePatchContext)
val method get() = requireNotNull(methodOrNull)
}

View File

@@ -3,7 +3,7 @@ package app.revanced.patcher
import app.revanced.patcher.extensions.toInstructions
import app.revanced.patcher.patch.*
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
@@ -15,8 +15,12 @@ import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.assertThrows
import java.util.logging.Logger
import kotlin.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
internal object PatcherTest {
private lateinit var patcher: Patcher
@@ -79,7 +83,11 @@ internal object PatcherTest {
infix fun Patch<*>.produces(equals: List<String>) {
val patches = setOf(this)
patches()
try {
patches()
} catch (_: PatchException) {
// Swallow expected exceptions for testing purposes.
}
assertEquals(equals, executed, "Expected patches to be executed in correct order.")
@@ -159,10 +167,7 @@ internal object PatcherTest {
}
}
assertTrue(
patch().exception != null,
"Expected an exception because the fingerprint can't match.",
)
assertThrows<PatchException>("Expected an exception because the fingerprint can't match.") { patch() }
}
@Test
@@ -290,18 +295,22 @@ internal object PatcherTest {
),
),
)
every {
with(patcher.context.bytecodeContext) {
any(ClassDef::class).mutable()
}
} answers { callOriginal() }
val a = gettingFirstMethodOrNull { true }
val fingerprint = fingerprint { returns("V") }
val fingerprint2 = fingerprint { returns("V") }
val fingerprint3 = fingerprint { returns("V") }
val matchIndices = indexedMatcher<Instruction>()
val method by gettingFirstMethod {
implementation {
matchIndices(instructions) {
head { opcode == Opcode.CONST_STRING }
add { opcode == Opcode.IPUT_OBJECT }
}
val composite = firstMethodComposite {
instructions {
head { opcode == Opcode.CONST_STRING }
add { opcode == Opcode.IPUT_OBJECT }
}
}
@@ -311,19 +320,21 @@ internal object PatcherTest {
fingerprint.match(classDefs.first().methods.first())
fingerprint2.match(classDefs.first())
fingerprint3.originalClassDef
println(method)
composite.method
}
},
)
patches()
with(patcher.context.bytecodeContext) {
with(patcher.context.bytecodeContext)
{
assertAll(
"Expected fingerprints to match.",
{ assertNotNull(fingerprint.originalClassDefOrNull) },
{ assertNotNull(fingerprint2.originalClassDefOrNull) },
{ assertNotNull(fingerprint3.originalClassDefOrNull) },
{ assertEquals("method", composite.method.name) },
)
}
}
@@ -333,7 +344,11 @@ internal object PatcherTest {
every { patcher.context.bytecodeContext.lookupMaps } returns with(patcher.context.bytecodeContext) { LookupMaps() }
every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
return runBlocking { patcher().toList() }
return runBlocking {
patcher().toList().also { results ->
results.firstOrNull { result -> result.exception != null }?.let { result -> throw result.exception!! }
}
}
}
private operator fun Patch<*>.invoke() = setOf(this)().first()