Skip to content

Commit

Permalink
test: improve PatcherTest
Browse files Browse the repository at this point in the history
  • Loading branch information
Sculas committed Mar 20, 2022
1 parent 0e72a6e commit ec8115f
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 16 deletions.
10 changes: 10 additions & 0 deletions src/main/kotlin/net/revanced/patcher/writer/ASMWriter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<out AbstractInsnNode>.toInsnList(): InsnList {
val list = InsnList()
this.forEach { list.add(it) }
return list
}
}
58 changes: 46 additions & 12 deletions src/test/kotlin/net/revanced/patcher/PatcherTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
38 changes: 34 additions & 4 deletions src/test/kotlin/net/revanced/patcher/util/TestUtil.kt
Original file line number Diff line number Diff line change
@@ -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 <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 {
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 ", "
}
}
}

0 comments on commit ec8115f

Please sign in to comment.