fix: nullable signature members (#10)

This commit will allow "partial" signatures, basically we will be allowed to exclude members to match for the signature
This commit is contained in:
oSumAtrIX 2022-03-21 16:10:37 +01:00 committed by she11sh0cked
parent 00c6ab7faf
commit 8db8893ab1
3 changed files with 51 additions and 55 deletions

View file

@ -56,7 +56,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
companion object { companion object {
fun resolveMethod(classNode: ClassNode, signature: Signature): PatchData? { fun resolveMethod(classNode: ClassNode, signature: Signature): PatchData? {
for (method in classNode.methods) { for (method in classNode.methods) {
val (r, sr) = cmp(method, signature, true) val (r, sr) = cmp(method, signature)
if (!r || sr == null) continue if (!r || sr == null) continue
return PatchData( return PatchData(
classNode, classNode,
@ -67,52 +67,55 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
return null return null
} }
private fun cmp(method: MethodNode, signature: Signature, search: Boolean = false): Pair<Boolean, ScanResult?> { private fun cmp(method: MethodNode, signature: Signature): Pair<Boolean, ScanResult?> {
val returns = Type.getReturnType(method.desc).convertObject() signature.returns?.let { _ ->
if (signature.returns != returns) { val methodReturns = Type.getReturnType(method.desc).convertObject()
logger.debug { if (signature.returns != methodReturns) {
""" logger.debug {
Comparing sig ${signature.name}: invalid return type: """
expected ${signature.returns}}, Comparing sig ${signature.name}: invalid return type:
got $returns expected ${signature.returns},
""".trimIndent() got $methodReturns
""".trimIndent()
}
return@cmp false to null
} }
return false to null
} }
if (signature.accessors != method.access) { signature.accessors?.let { _ ->
logger.debug { if (signature.accessors != method.access) {
""" logger.debug {
Comparing sig ${signature.name}: invalid accessors: """
expected ${signature.accessors}}, Comparing sig ${signature.name}: invalid accessors:
got ${method.access} expected ${signature.accessors},
""".trimIndent() got ${method.access}
""".trimIndent()
}
return@cmp false to null
} }
return false to null
} }
val parameters = Type.getArgumentTypes(method.desc).convertObjects() signature.parameters?.let { _ ->
if (!signature.parameters.contentEquals(parameters)) { val parameters = Type.getArgumentTypes(method.desc).convertObjects()
logger.debug { if (!signature.parameters.contentEquals(parameters)) {
""" logger.debug {
Comparing sig ${signature.name}: invalid parameter types: """
expected ${signature.parameters.joinToString()}}, Comparing sig ${signature.name}: invalid parameter types:
got ${parameters.joinToString()} expected ${signature.parameters.joinToString()}},
""".trimIndent() got ${parameters.joinToString()}
""".trimIndent()
}
return@cmp false to null
} }
return false to null
} }
if (!search) { signature.opcodes?.let { _ ->
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) val result = method.instructions.scanFor(signature.opcodes)
if (!result.found) { if (!result.found) {
logger.debug { "Comparing sig ${signature.name}: invalid opcode pattern" } logger.debug { "Comparing sig ${signature.name}: invalid opcode pattern" }
return false to null return@cmp false to null
} }
return true to result return@cmp true to result
} }
return true to ScanResult(true) return true to ScanResult(true)

View file

@ -9,21 +9,19 @@ import org.objectweb.asm.Type
* Do not use the actual method name, instead try to guess what the method name originally was. * Do not use the actual method name, instead try to guess what the method name originally was.
* If you are unable to guess a method name, doing something like "patch-name-1" is fine too. * If you are unable to guess a method name, doing something like "patch-name-1" is fine too.
* For example: "override-codec-1". * For example: "override-codec-1".
* This method name will be used to find the corresponding patch. * This method name will be mapped to the method matching the signature.
* Even though this is technically not needed for the `findParentMethod` method, * 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. * 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 returns The return type/signature of the method.
* @param accessors The accessors of the method. * @param accessors The accessors of the method.
* @param parameters The parameter types 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. * @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") @Suppress("ArrayInDataClass")
data class Signature( data class Signature(
val name: String, val name: String,
val returns: Type, val returns: Type?,
val accessors: Int, val accessors: Int?,
val parameters: Array<Type>, val parameters: Array<Type>?,
val opcodes: Array<Int> val opcodes: Array<Int>?
) )

View file

@ -9,13 +9,12 @@ import net.revanced.patcher.util.ExtraTypes
import net.revanced.patcher.util.TestUtil import net.revanced.patcher.util.TestUtil
import net.revanced.patcher.writer.ASMWriter.insertAt import net.revanced.patcher.writer.ASMWriter.insertAt
import net.revanced.patcher.writer.ASMWriter.setAt import net.revanced.patcher.writer.ASMWriter.setAt
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertDoesNotThrow
import org.objectweb.asm.Opcodes.* import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type import org.objectweb.asm.Type
import org.objectweb.asm.tree.* import org.objectweb.asm.tree.*
import java.io.PrintStream import java.io.PrintStream
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
internal class PatcherTest { internal class PatcherTest {
companion object { companion object {
@ -149,25 +148,21 @@ internal class PatcherTest {
//} //}
@Test() @Test()
fun `should raise an exception because opcodes is empty`() { fun `should not raise an exception if any signature member except the name is missing`() {
val sigName = "testMethod" val sigName = "testMethod"
val e = assertThrows<IllegalArgumentException>("Should raise an exception because opcodes is empty") {
assertDoesNotThrow("Should raise an exception because opcodes is empty") {
Patcher( Patcher(
PatcherTest::class.java.getResourceAsStream("/test1.jar")!!, PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
arrayOf( arrayOf(
Signature( Signature(
sigName, sigName,
Type.VOID_TYPE, null,
ACC_PUBLIC or ACC_STATIC, null,
arrayOf(ExtraTypes.ArrayAny), null,
emptyArray() // this is not allowed for non-search signatures! null
) ))
)
) )
} }
assertEquals(
"Opcode list for signature $sigName is empty. This is not allowed for non-search signatures.",
e.message
)
} }
} }