mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-04-30 21:34:25 +02:00
test: improve PatcherTest
This commit is contained in:
parent
0e72a6e85f
commit
ec8115f3ac
@ -7,4 +7,14 @@ object ASMWriter {
|
|||||||
fun InsnList.setAt(index: Int, node: AbstractInsnNode) {
|
fun InsnList.setAt(index: Int, node: AbstractInsnNode) {
|
||||||
this[this.get(index)] = node
|
this[this.get(index)] = node
|
||||||
}
|
}
|
||||||
|
fun InsnList.insertAt(index: Int, vararg nodes: AbstractInsnNode) {
|
||||||
|
this.insert(this.get(index), nodes.toInsnList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Sculas): Should this be public?
|
||||||
|
private fun Array<out AbstractInsnNode>.toInsnList(): InsnList {
|
||||||
|
val list = InsnList()
|
||||||
|
this.forEach { list.add(it) }
|
||||||
|
return list
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,10 +4,13 @@ import net.revanced.patcher.patch.Patch
|
|||||||
import net.revanced.patcher.patch.PatchResultSuccess
|
import net.revanced.patcher.patch.PatchResultSuccess
|
||||||
import net.revanced.patcher.signature.Signature
|
import net.revanced.patcher.signature.Signature
|
||||||
import net.revanced.patcher.util.ExtraTypes
|
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 net.revanced.patcher.writer.ASMWriter.setAt
|
||||||
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 kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
|
||||||
internal class PatcherTest {
|
internal class PatcherTest {
|
||||||
@ -46,20 +49,51 @@ internal class PatcherTest {
|
|||||||
val mainMethod = patcher.cache.methods["mainMethod"]
|
val mainMethod = patcher.cache.methods["mainMethod"]
|
||||||
// Get the instruction list
|
// Get the instruction list
|
||||||
val instructions = mainMethod.method.instructions!!
|
val instructions = mainMethod.method.instructions!!
|
||||||
// Let's modify it, so it prints "Hello, ReVanced!"
|
|
||||||
// Get the start index of our opcode pattern
|
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
||||||
// This will be the index of the LDC instruction
|
// Get the start index of our opcode pattern.
|
||||||
|
// This will be the index of the LDC instruction.
|
||||||
val startIndex = mainMethod.scanData.startIndex
|
val startIndex = mainMethod.scanData.startIndex
|
||||||
// Create a new Ldc node and replace the LDC instruction
|
TestUtil.assertNodeEqual(LdcInsnNode("Hello, world!"), instructions[startIndex]!!)
|
||||||
val stringNode = LdcInsnNode("Hello, ReVanced!");
|
// Create a new LDC node and replace the LDC instruction.
|
||||||
|
val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.")
|
||||||
instructions.setAt(startIndex, stringNode)
|
instructions.setAt(startIndex, stringNode)
|
||||||
// Now lets print our string to the console output
|
|
||||||
// First create a list of instructions
|
// Now lets print our string twice!
|
||||||
val printCode = InsnList();
|
// Insert our instructions after the second instruction by our pattern.
|
||||||
printCode.add(LdcInsnNode("Hello, ReVanced!"))
|
// This will place our instructions after the original INVOKEVIRTUAL call.
|
||||||
printCode.add(MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"))
|
// You could also copy the instructions from the list and then modify the LDC instruction again,
|
||||||
// Add the list after the second instruction by our pattern
|
// but this is to show a more advanced example of writing bytecode using the patcher and ASM.
|
||||||
instructions.insert(instructions[startIndex + 1], printCode)
|
instructions.insertAt(
|
||||||
|
startIndex + 1,
|
||||||
|
FieldInsnNode(
|
||||||
|
GETSTATIC,
|
||||||
|
Type.getInternalName(System::class.java), // "java/io/System"
|
||||||
|
"out",
|
||||||
|
Type.getInternalName(PrintStream::class.java) // "java.io.PrintStream"
|
||||||
|
),
|
||||||
|
LdcInsnNode("Hello, ReVanced! Adding bytecode."),
|
||||||
|
MethodInsnNode(
|
||||||
|
INVOKEVIRTUAL,
|
||||||
|
Type.getInternalName(PrintStream::class.java), // "java/io/PrintStream"
|
||||||
|
"println",
|
||||||
|
Type.getMethodDescriptor(
|
||||||
|
Type.VOID_TYPE,
|
||||||
|
Type.getType(String::class.java)
|
||||||
|
) // "(Ljava/lang/String;)V"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Our code now looks like this:
|
||||||
|
// public static main(java.lang.String[] arg0) { // Method signature: ([Ljava/lang/String;)V
|
||||||
|
// getstatic java/lang/System.out:java.io.PrintStream
|
||||||
|
// ldc "Hello, ReVanced! Editing bytecode." (java.lang.String) // We overwrote this instruction.
|
||||||
|
// invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
|
||||||
|
// getstatic java/lang/System.out:java.io.PrintStream // This instruction and the 2 instructions below are written manually.
|
||||||
|
// ldc "Hello, ReVanced! Adding bytecode." (java.lang.String)
|
||||||
|
// invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// Finally, tell the patcher that this patch was a success.
|
// Finally, tell the patcher that this patch was a success.
|
||||||
// You can also return PatchResultError with a message.
|
// You can also return PatchResultError with a message.
|
||||||
|
@ -1,14 +1,44 @@
|
|||||||
package net.revanced.patcher.util
|
package net.revanced.patcher.util
|
||||||
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode
|
import org.objectweb.asm.tree.AbstractInsnNode
|
||||||
import kotlin.test.assertTrue
|
import org.objectweb.asm.tree.FieldInsnNode
|
||||||
|
import org.objectweb.asm.tree.LdcInsnNode
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
object TestUtil {
|
object TestUtil {
|
||||||
fun <T: AbstractInsnNode> assertNodeEqual(a: T, b: T) {
|
fun <T: AbstractInsnNode> assertNodeEqual(expected: T, actual: T) {
|
||||||
|
val a = expected.nodeString()
|
||||||
|
val b = actual.nodeString()
|
||||||
|
if (a != b) {
|
||||||
|
fail("expected: $a,\nactual: $b\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AbstractInsnNode.nodeString(): String {
|
private fun AbstractInsnNode.nodeString(): String {
|
||||||
|
val sb = NodeStringBuilder()
|
||||||
|
when (this) {
|
||||||
|
// TODO: Add more types
|
||||||
|
is LdcInsnNode -> sb
|
||||||
|
.addType("cst", cst)
|
||||||
|
is FieldInsnNode -> sb
|
||||||
|
.addType("owner", owner)
|
||||||
|
.addType("name", name)
|
||||||
|
.addType("desc", desc)
|
||||||
|
}
|
||||||
|
return "(${this::class.simpleName}): (type = $type, opcode = $opcode, $sb)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class NodeStringBuilder {
|
||||||
|
private val sb = StringBuilder()
|
||||||
|
|
||||||
|
fun addType(name: String, value: Any): NodeStringBuilder {
|
||||||
|
sb.append("$name = \"$value\", ")
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val s = sb.toString()
|
||||||
|
return s.substring(0 until s.length - 2) // remove the last ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user