From ec8115f3ac21c60ac9f532caa37e10d2e625cea7 Mon Sep 17 00:00:00 2001 From: Lucaskyy Date: Sun, 20 Mar 2022 16:28:23 +0100 Subject: [PATCH] test: improve `PatcherTest` --- .../net/revanced/patcher/writer/ASMWriter.kt | 10 ++++ .../net/revanced/patcher/PatcherTest.kt | 58 +++++++++++++++---- .../net/revanced/patcher/util/TestUtil.kt | 40 +++++++++++-- 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/net/revanced/patcher/writer/ASMWriter.kt b/src/main/kotlin/net/revanced/patcher/writer/ASMWriter.kt index 5165cf6..cb9e83f 100644 --- a/src/main/kotlin/net/revanced/patcher/writer/ASMWriter.kt +++ b/src/main/kotlin/net/revanced/patcher/writer/ASMWriter.kt @@ -7,4 +7,14 @@ object ASMWriter { fun InsnList.setAt(index: Int, node: AbstractInsnNode) { 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.toInsnList(): InsnList { + val list = InsnList() + this.forEach { list.add(it) } + return list + } } \ No newline at end of file diff --git a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt index d199f48..d26bf87 100644 --- a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt +++ b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt @@ -4,10 +4,13 @@ import net.revanced.patcher.patch.Patch import net.revanced.patcher.patch.PatchResultSuccess import net.revanced.patcher.signature.Signature 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.objectweb.asm.Opcodes.* import org.objectweb.asm.Type import org.objectweb.asm.tree.* +import java.io.PrintStream import kotlin.test.Test internal class PatcherTest { @@ -46,20 +49,51 @@ internal class PatcherTest { val mainMethod = patcher.cache.methods["mainMethod"] // Get the instruction list val instructions = mainMethod.method.instructions!! - // Let's modify it, so it prints "Hello, ReVanced!" - // Get the start index of our opcode pattern - // This will be the index of the LDC instruction + + // Let's modify it, so it prints "Hello, ReVanced! Editing bytecode." + // Get the start index of our opcode pattern. + // This will be the index of the LDC instruction. val startIndex = mainMethod.scanData.startIndex - // Create a new Ldc node and replace the LDC instruction - val stringNode = LdcInsnNode("Hello, ReVanced!"); + TestUtil.assertNodeEqual(LdcInsnNode("Hello, world!"), instructions[startIndex]!!) + // Create a new LDC node and replace the LDC instruction. + val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.") instructions.setAt(startIndex, stringNode) - // Now lets print our string to the console output - // First create a list of instructions - val printCode = InsnList(); - printCode.add(LdcInsnNode("Hello, ReVanced!")) - printCode.add(MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")) - // Add the list after the second instruction by our pattern - instructions.insert(instructions[startIndex + 1], printCode) + + // Now lets print our string twice! + // Insert our instructions after the second instruction by our pattern. + // This will place our instructions after the original INVOKEVIRTUAL call. + // You could also copy the instructions from the list and then modify the LDC instruction again, + // but this is to show a more advanced example of writing bytecode using the patcher and ASM. + 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. // You can also return PatchResultError with a message. diff --git a/src/test/kotlin/net/revanced/patcher/util/TestUtil.kt b/src/test/kotlin/net/revanced/patcher/util/TestUtil.kt index 43cff38..55362dc 100644 --- a/src/test/kotlin/net/revanced/patcher/util/TestUtil.kt +++ b/src/test/kotlin/net/revanced/patcher/util/TestUtil.kt @@ -1,14 +1,44 @@ package net.revanced.patcher.util 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 { - fun assertNodeEqual(a: T, b: T) { - + fun 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 { - + 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)" } -} \ No newline at end of file +} + +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 ", " + } +}