Skip to content

Commit

Permalink
feat: Add findParentMethod utility method (#4)
Browse files Browse the repository at this point in the history
* feat: Add `findParentMethod` utitly method

* refactor: add `resolveMethod` to `MethodResolver`

added some assertions and some tests

Co-authored-by: Lucaskyy <contact@sculas.xyz>
  • Loading branch information
2 people authored and she11sh0cked committed Mar 23, 2022
1 parent 460d62a commit 00c6ab7
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 25 deletions.
8 changes: 7 additions & 1 deletion src/main/kotlin/net/revanced/patcher/cache/PatchData.kt
@@ -1,13 +1,19 @@
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

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,
Expand Down
76 changes: 52 additions & 24 deletions src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt
Expand Up @@ -25,7 +25,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, 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
Expand All @@ -52,43 +52,71 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
return methodMap
}

private fun cmp(method: MethodNode, signature: Signature): Pair<Boolean, ScanResult?> {
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<Boolean, ScanResult?> {
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)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/net/revanced/patcher/signature/Signature.kt
Expand Up @@ -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(
Expand Down
25 changes: 25 additions & 0 deletions src/test/kotlin/net/revanced/patcher/PatcherTest.kt
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<IllegalArgumentException>("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
)
}
}

0 comments on commit 00c6ab7

Please sign in to comment.