From 2cff30e05c5c84ed95bae521b7fac26b07984eed Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 21 Mar 2022 14:40:41 +0100 Subject: [PATCH] feat: Add `findParentMethod` utility method (#4) * feat: Add `findParentMethod` utitly method * refactor: add `resolveMethod` to `MethodResolver` added some assertions and some tests Co-authored-by: Lucaskyy --- .../net/revanced/patcher/cache/PatchData.kt | 8 +- .../patcher/resolver/MethodResolver.kt | 76 +++++++++++++------ .../revanced/patcher/signature/Signature.kt | 4 + .../net/revanced/patcher/PatcherTest.kt | 25 ++++++ 4 files changed, 88 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt b/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt index 93e94c1d..3381f8e6 100644 --- a/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt +++ b/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt @@ -1,5 +1,7 @@ package net.revanced.patcher.cache +import net.revanced.patcher.resolver.MethodResolver +import net.revanced.patcher.signature.Signature import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -7,7 +9,11 @@ data class PatchData( val declaringClass: ClassNode, val method: MethodNode, val scanData: PatternScanData -) +) { + fun findParentMethod(signature: Signature): PatchData? { + return MethodResolver.resolveMethod(declaringClass, signature) + } +} data class PatternScanData( val startIndex: Int, diff --git a/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt b/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt index 933edb29..0d1dad9d 100644 --- a/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt +++ b/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt @@ -25,7 +25,7 @@ internal class MethodResolver(private val classList: List, private va continue } logger.debug { "Resolving sig ${signature.name}: ${classNode.name} / ${method.name}" } - val (r, sr) = this.cmp(method, signature) + val (r, sr) = cmp(method, signature) if (!r || sr == null) { logger.debug { "Compare result for sig ${signature.name} has failed!" } continue @@ -52,43 +52,71 @@ internal class MethodResolver(private val classList: List, private va return methodMap } - private fun cmp(method: MethodNode, signature: Signature): Pair { - val returns = Type.getReturnType(method.desc).convertObject() - if (signature.returns != returns) { - logger.debug { - """ + // These functions do not require the constructor values, so they can be static. + companion object { + fun resolveMethod(classNode: ClassNode, signature: Signature): PatchData? { + for (method in classNode.methods) { + val (r, sr) = cmp(method, signature, true) + if (!r || sr == null) continue + return PatchData( + classNode, + method, + PatternScanData(0, 0) // opcode list is always ignored. + ) + } + return null + } + + private fun cmp(method: MethodNode, signature: Signature, search: Boolean = false): Pair { + val returns = Type.getReturnType(method.desc).convertObject() + if (signature.returns != returns) { + logger.debug { + """ Comparing sig ${signature.name}: invalid return type: expected ${signature.returns}}, got $returns """.trimIndent() + } + return false to null } - return false to null - } - if (signature.accessors != method.access) { - logger.debug { "Comparing sig ${signature.name}: invalid accessors:\nexpected ${signature.accessors},\ngot ${method.access}" } - return false to null - } + if (signature.accessors != method.access) { + logger.debug { + """ + Comparing sig ${signature.name}: invalid accessors: + expected ${signature.accessors}}, + got ${method.access} + """.trimIndent() + } + return false to null + } - val parameters = Type.getArgumentTypes(method.desc).convertObjects() - if (!signature.parameters.contentEquals(parameters)) { - logger.debug { - """ + val parameters = Type.getArgumentTypes(method.desc).convertObjects() + if (!signature.parameters.contentEquals(parameters)) { + logger.debug { + """ Comparing sig ${signature.name}: invalid parameter types: expected ${signature.parameters.joinToString()}}, got ${parameters.joinToString()} """.trimIndent() + } + return false to null } - return false to null - } - val result = method.instructions.scanFor(signature.opcodes) - if (!result.found) { - logger.debug { "Comparing sig ${signature.name}: invalid opcode pattern" } - return false to null - } + if (!search) { + if (signature.opcodes.isEmpty()) { + throw IllegalArgumentException("Opcode list for signature ${signature.name} is empty. This is not allowed for non-search signatures.") + } + val result = method.instructions.scanFor(signature.opcodes) + if (!result.found) { + logger.debug { "Comparing sig ${signature.name}: invalid opcode pattern" } + return false to null + } + return true to result + } - return true to result + return true to ScanResult(true) + } } } diff --git a/src/main/kotlin/net/revanced/patcher/signature/Signature.kt b/src/main/kotlin/net/revanced/patcher/signature/Signature.kt index a5fc2069..4c364b9c 100644 --- a/src/main/kotlin/net/revanced/patcher/signature/Signature.kt +++ b/src/main/kotlin/net/revanced/patcher/signature/Signature.kt @@ -10,10 +10,14 @@ import org.objectweb.asm.Type * If you are unable to guess a method name, doing something like "patch-name-1" is fine too. * For example: "override-codec-1". * This method name will be used to find the corresponding patch. + * Even though this is technically not needed for the `findParentMethod` method, + * it is still recommended giving the method a name, so it can be identified easily. * @param returns The return type/signature of the method. * @param accessors The accessors of the method. * @param parameters The parameter types of the method. * @param opcodes The opcode pattern of the method, used to find the method by pattern scanning. + * ***Only if*** **you are using **`findParentMethod`** are you allowed to specify an empty array.** + * This parameter will be ignored when using the `findParentMethod` method. */ @Suppress("ArrayInDataClass") data class Signature( diff --git a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt index b552e98c..9eee7020 100644 --- a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt +++ b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt @@ -9,11 +9,13 @@ import net.revanced.patcher.util.ExtraTypes import net.revanced.patcher.util.TestUtil import net.revanced.patcher.writer.ASMWriter.insertAt import net.revanced.patcher.writer.ASMWriter.setAt +import org.junit.jupiter.api.assertThrows import org.objectweb.asm.Opcodes.* import org.objectweb.asm.Type import org.objectweb.asm.tree.* import java.io.PrintStream import kotlin.test.Test +import kotlin.test.assertEquals internal class PatcherTest { companion object { @@ -145,4 +147,27 @@ internal class PatcherTest { // out.close() // testData.close() //} + + @Test() + fun `should raise an exception because opcodes is empty`() { + val sigName = "testMethod" + val e = assertThrows("Should raise an exception because opcodes is empty") { + Patcher( + PatcherTest::class.java.getResourceAsStream("/test1.jar")!!, + arrayOf( + Signature( + sigName, + Type.VOID_TYPE, + ACC_PUBLIC or ACC_STATIC, + arrayOf(ExtraTypes.ArrayAny), + emptyArray() // this is not allowed for non-search signatures! + ) + ) + ) + } + assertEquals( + "Opcode list for signature $sigName is empty. This is not allowed for non-search signatures.", + e.message + ) + } } \ No newline at end of file