From aacf9007647b1cc11bc40053625802573efda6ef Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 7 Jun 2023 03:13:51 +0200 Subject: [PATCH] fix!: implement extension functions consistently BREAKING CHANGE: This changes the name of functions --- .../revanced/patcher/extensions/Extensions.kt | 232 +------------ .../extensions/InstructionExtensions.kt | 328 ++++++++++++++++++ .../method/impl/MethodFingerprint.kt | 12 +- .../extensions/InstructionExtensionsTest.kt | 225 ++++++++++++ .../usage/bytecode/ExampleBytecodePatch.kt | 6 +- .../util/smali/InlineSmaliCompilerTest.kt | 15 +- 6 files changed, 589 insertions(+), 229 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt create mode 100644 src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt diff --git a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt index 5531b9b..211670e 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt @@ -1,236 +1,32 @@ package app.revanced.patcher.extensions import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patcher.util.smali.ExternalLabel -import app.revanced.patcher.util.smali.toInstruction -import app.revanced.patcher.util.smali.toInstructions import org.jf.dexlib2.AccessFlags -import org.jf.dexlib2.builder.BuilderInstruction -import org.jf.dexlib2.builder.BuilderOffsetInstruction -import org.jf.dexlib2.builder.Label -import org.jf.dexlib2.builder.MutableMethodImplementation -import org.jf.dexlib2.builder.instruction.* -import org.jf.dexlib2.iface.Method -import org.jf.dexlib2.iface.instruction.Instruction -import org.jf.dexlib2.immutable.ImmutableMethod -import org.jf.dexlib2.immutable.ImmutableMethodImplementation -import java.io.OutputStream - -infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value -infix fun Int.or(other: AccessFlags) = this or other.value - -fun MutableMethodImplementation.addInstructions(index: Int, instructions: List) { - for (i in instructions.lastIndex downTo 0) { - this.addInstruction(index, instructions[i]) - } -} - -fun MutableMethodImplementation.addInstructions(instructions: List) { - for (instruction in instructions) { - this.addInstruction(instruction) - } -} - -fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List) { - for (i in instructions.lastIndex downTo 0) { - this.replaceInstruction(index + i, instructions[i]) - } -} - -fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) { - for (i in count - 1 downTo 0) { - this.removeInstruction(index + i) - } -} /** - * Clones the method. - * @param registerCount This parameter allows you to change the register count of the method. - * This may be a positive or negative number. - * @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy. - */ -internal fun Method.clone(registerCount: Int = 0): ImmutableMethod { - val clonedImplementation = implementation?.let { - ImmutableMethodImplementation( - it.registerCount + registerCount, - it.instructions, - it.tryBlocks, - it.debugItems, - ) - } - return ImmutableMethod( - returnType, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation - ) -} - -/** - * Add a smali instruction to the method. - * @param instruction The smali instruction to add. - */ -fun MutableMethod.addInstruction(instruction: String) = - this.implementation!!.addInstruction(instruction.toInstruction(this)) - -/** - * Add a smali instruction to the method. - * @param index The index to insert the instruction at. - * @param instruction The smali instruction to add. - */ -fun MutableMethod.addInstruction(index: Int, instruction: String) = - this.implementation!!.addInstruction(index, instruction.toInstruction(this)) - -/** - * Replace a smali instruction within the method. - * @param index The index to replace the instruction at. - * @param instruction The smali instruction to place. - */ -fun MutableMethod.replaceInstruction(index: Int, instruction: String) = - this.implementation!!.replaceInstruction(index, instruction.toInstruction(this)) - -/** - * Remove a smali instruction within the method. - * @param index The index to delete the instruction at. - */ -fun MutableMethod.removeInstruction(index: Int) = this.implementation!!.removeInstruction(index) - -/** - * Create a label for the instruction at given index in the method's implementation. + * Create a label for the instruction at given index. * @param index The index to create the label for the instruction at. * @return The label. */ -fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index) +fun MutableMethod.label(index: Int) = implementation!!.newLabelForIndex(index) /** - * Get an instruction at the given index in the method's implementation. - * @param index The index to get the instruction at. - * @return The instruction. + * Perform a bitwise OR operation between two [AccessFlags]. + * + * @param other The other [AccessFlags] to perform the operation with. */ -fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index] +infix fun AccessFlags.or(other: AccessFlags) = value or other.value /** - * Get an instruction at the given index in the method's implementation. - * @param index The index to get the instruction at. - * @param T The type of instruction to return. - * @return The instruction. + * Perform a bitwise OR operation between an [AccessFlags] and an [Int]. + * + * @param other The [Int] to perform the operation with. */ -fun MutableMethod.instruction(index: Int): T = instruction(index) as T +infix fun Int.or(other: AccessFlags) = this or other.value /** - * Add smali instructions to the method. - * @param index The index to insert the instructions at. - * @param smali The smali instructions to add. - * @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile. + * Perform a bitwise OR operation between an [Int] and an [AccessFlags]. + * + * @param other The [AccessFlags] to perform the operation with. */ - -fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: List = emptyList()) { - // Create reference dummy instructions for the instructions. - val nopedSmali = StringBuilder(smali).also { builder -> - externalLabels.forEach { (name, _) -> - builder.append("\n:$name\nnop") - } - }.toString() - - // Compile the instructions with the dummy labels - val compiledInstructions = nopedSmali.toInstructions(this) - - // Add the compiled list of instructions to the method. - val methodImplementation = this.implementation!! - methodImplementation.addInstructions(index, compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size)) - - val methodInstructions = methodImplementation.instructions - methodInstructions.subList(index, index + compiledInstructions.size - externalLabels.size) - .forEachIndexed { compiledInstructionIndex, compiledInstruction -> - // If the compiled instruction is not an offset instruction, skip it. - if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed - - /** - * Creates a new label for the instruction and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex]. - */ - fun Instruction.makeNewLabel() { - // Create the final label. - val label = methodImplementation.newLabelForIndex(methodInstructions.indexOf(this)) - // Create the final instruction with the new label. - val newInstruction = replaceOffset( - compiledInstruction, label - ) - // Replace the instruction pointing to the dummy label with the new instruction pointing to the real instruction. - methodImplementation.replaceInstruction(index + compiledInstructionIndex, newInstruction) - } - - // If the compiled instruction targets its own instruction, - // which means it points to some of its own, simply an offset has to be applied. - val labelIndex = compiledInstruction.target.location.index - if (labelIndex < compiledInstructions.size - externalLabels.size) { - // Get the targets index (insertion index + the index of the dummy instruction). - methodInstructions[index + labelIndex].makeNewLabel() - return@forEachIndexed - } - - // Since the compiled instruction points to a dummy instruction, - // we can find the real instruction which it was created for by calculation. - - // Get the index of the instruction in the externalLabels list which the dummy instruction was created for. - // this line works because we created the dummy instructions in the same order as the externalLabels list. - val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex] - instruction.makeNewLabel() - } -} - -/** - * Add smali instructions to the end of the method. - * @param instructions The smali instructions to add. - */ -fun MutableMethod.addInstructions(instructions: String, labels: List = emptyList()) = - this.addInstructions(this.implementation!!.instructions.size, instructions, labels) - -/** - * Replace smali instructions within the method. - * @param index The index to replace the instructions at. - * @param instructions The smali instructions to place. - */ -fun MutableMethod.replaceInstructions(index: Int, instructions: String) = - this.implementation!!.replaceInstructions(index, instructions.toInstructions(this)) - -/** - * Remove smali instructions from the method. - * @param index The index to remove the instructions at. - * @param count The amount of instructions to remove. - */ -fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count) - -private fun replaceOffset( - i: BuilderOffsetInstruction, label: Label -): BuilderOffsetInstruction { - return when (i) { - is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label) - is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label) - is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label) - is BuilderInstruction22t -> BuilderInstruction22t(i.opcode, i.registerA, i.registerB, label) - is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label) - is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label) - else -> throw IllegalStateException("A non-offset instruction was given, this should never happen!") - } -} - -/** - * Clones the method. - * @param registerCount This parameter allows you to change the register count of the method. - * This may be a positive or negative number. - * @return The **mutable** cloned method. Call [clone] to get an **immutable** copy. - */ -internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable() - -internal fun parametersEqual( - parameters1: Iterable, parameters2: Iterable -): Boolean { - if (parameters1.count() != parameters2.count()) return false - val iterator1 = parameters1.iterator() - parameters2.forEach { - if (!it.startsWith(iterator1.next())) return false - } - return true -} - -internal val nullOutputStream = object : OutputStream() { - override fun write(b: Int) {} -} \ No newline at end of file +infix fun AccessFlags.or(other: Int) = value or other \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt new file mode 100644 index 0000000..f1834d3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt @@ -0,0 +1,328 @@ +package app.revanced.patcher.extensions + +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patcher.util.smali.toInstruction +import app.revanced.patcher.util.smali.toInstructions +import org.jf.dexlib2.builder.BuilderInstruction +import org.jf.dexlib2.builder.BuilderOffsetInstruction +import org.jf.dexlib2.builder.Label +import org.jf.dexlib2.builder.MutableMethodImplementation +import org.jf.dexlib2.builder.instruction.* +import org.jf.dexlib2.iface.instruction.Instruction + +object InstructionExtensions { + + /** + * Add instructions to a method at the given index. + * + * @param index The index to add the instructions at. + * @param instructions The instructions to add. + */ + fun MutableMethodImplementation.addInstructions( + index: Int, + instructions: List + ) = + instructions.asReversed().forEach { addInstruction(index, it) } + + /** + * Add instructions to a method. + * The instructions will be added at the end of the method. + * + * @param instructions The instructions to add. + */ + fun MutableMethodImplementation.addInstructions(instructions: List) = + instructions.forEach { this.addInstruction(it) } + + /** + * Remove instructions from a method at the given index. + * + * @param index The index to remove the instructions at. + * @param count The amount of instructions to remove. + */ + fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) = repeat(count) { + removeInstruction(index) + } + + /** + * Remove the first instructions from a method. + * + * @param count The amount of instructions to remove. + */ + fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count) + + /** + * Replace instructions at the given index with the given instructions. + * The amount of instructions to replace is the amount of instructions in the given list. + * + * @param index The index to replace the instructions at. + * @param instructions The instructions to replace the instructions with. + */ + fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List) { + // Remove the instructions at the given index. + removeInstructions(index, instructions.size) + + // Add the instructions at the given index. + addInstructions(index, instructions) + } + + /** + * Add an instruction to a method at the given index. + * + * @param index The index to add the instruction at. + * @param instruction The instruction to add. + */ + fun MutableMethod.addInstruction(index: Int, instruction: BuilderInstruction) = + implementation!!.addInstruction(index, instruction) + + /** + * Add an instruction to a method. + * + * @param instruction The instructions to add. + */ + fun MutableMethod.addInstruction(instruction: BuilderInstruction) = + implementation!!.addInstruction(instruction) + + /** + * Add an instruction to a method at the given index. + * + * @param index The index to add the instruction at. + * @param smaliInstructions The instruction to add. + */ + fun MutableMethod.addInstruction(index: Int, smaliInstructions: String) = + implementation!!.addInstruction(index, smaliInstructions.toInstruction(this)) + + /** + * Add an instruction to a method. + * + * @param smaliInstructions The instruction to add. + */ + fun MutableMethod.addInstruction(smaliInstructions: String) = + implementation!!.addInstruction(smaliInstructions.toInstruction(this)) + + + /** + * Add instructions to a method at the given index. + * + * @param index The index to add the instructions at. + * @param instructions The instructions to add. + */ + fun MutableMethod.addInstructions(index: Int, instructions: List) = + implementation!!.addInstructions(index, instructions) + + /** + * Add instructions to a method. + * + * @param instructions The instructions to add. + */ + fun MutableMethod.addInstructions(instructions: List) = + implementation!!.addInstructions(instructions) + + /** + * Add instructions to a method. + * + * @param smaliInstructions The instructions to add. + */ + fun MutableMethod.addInstructions(index: Int, smaliInstructions: String) = + implementation!!.addInstructions(index, smaliInstructions.toInstructions(this)) + + /** + * Add instructions to a method. + * + * @param smaliInstructions The instructions to add. + */ + fun MutableMethod.addInstructions(smaliInstructions: String) = + implementation!!.addInstructions(smaliInstructions.toInstructions(this)) + + /** + * Add instructions to a method at the given index. + * + * @param index The index to add the instructions at. + * @param smaliInstructions The instructions to add. + * @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions]. + */ +// Special function for adding instructions with external labels. + fun MutableMethod.addInstructionsWithLabels( + index: Int, + smaliInstructions: String, + vararg externalLabels: ExternalLabel + ) { + // Create reference dummy instructions for the instructions. + val nopSmali = StringBuilder(smaliInstructions).also { builder -> + externalLabels.forEach { (name, _) -> + builder.append("\n:$name\nnop") + } + }.toString() + + // Compile the instructions with the dummy labels + val compiledInstructions = nopSmali.toInstructions(this) + + // Add the compiled list of instructions to the method. + addInstructions( + index, + compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size) + ) + + implementation!!.apply { + this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size) + .forEachIndexed { compiledInstructionIndex, compiledInstruction -> + // If the compiled instruction is not an offset instruction, skip it. + if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed + + /** + * Creates a new label for the instruction + * and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex]. + */ + fun Instruction.makeNewLabel() { + fun replaceOffset( + i: BuilderOffsetInstruction, label: Label + ): BuilderOffsetInstruction { + return when (i) { + is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label) + is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label) + is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label) + is BuilderInstruction22t -> BuilderInstruction22t( + i.opcode, + i.registerA, + i.registerB, + label + ) + is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label) + is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label) + else -> throw IllegalStateException( + "A non-offset instruction was given, this should never happen!" + ) + } + } + + // Create the final label. + val label = newLabelForIndex(this@apply.instructions.indexOf(this)) + + // Create the final instruction with the new label. + val newInstruction = replaceOffset( + compiledInstruction, label + ) + + // Replace the instruction pointing to the dummy label + // with the new instruction pointing to the real instruction. + replaceInstruction(index + compiledInstructionIndex, newInstruction) + } + + // If the compiled instruction targets its own instruction, + // which means it points to some of its own, simply an offset has to be applied. + val labelIndex = compiledInstruction.target.location.index + if (labelIndex < compiledInstructions.size - externalLabels.size) { + // Get the targets index (insertion index + the index of the dummy instruction). + this.instructions[index + labelIndex].makeNewLabel() + return@forEachIndexed + } + + // Since the compiled instruction points to a dummy instruction, + // we can find the real instruction which it was created for by calculation. + + // Get the index of the instruction in the externalLabels list + // which the dummy instruction was created for. + // This works because we created the dummy instructions in the same order as the externalLabels list. + val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex] + instruction.makeNewLabel() + } + } + } + + /** + * Remove an instruction at the given index. + * + * @param index The index to remove the instruction at. + */ + fun MutableMethod.removeInstruction(index: Int) = + implementation!!.removeInstruction(index) + + /** + * Remove instructions at the given index. + * + * @param index The index to remove the instructions at. + * @param count The amount of instructions to remove. + */ + fun MutableMethod.removeInstructions(index: Int, count: Int) = + implementation!!.removeInstructions(index, count) + + /** + * Remove instructions at the given index. + * + * @param count The amount of instructions to remove. + */ + fun MutableMethod.removeInstructions(count: Int) = + implementation!!.removeInstructions(count) + + /** + * Replace an instruction at the given index. + * + * @param index The index to replace the instruction at. + * @param instruction The instruction to replace the instruction with. + */ + fun MutableMethod.replaceInstruction(index: Int, instruction: BuilderInstruction) = + implementation!!.replaceInstruction(index, instruction) + + /** + * Replace an instruction at the given index. + * + * @param index The index to replace the instruction at. + * @param smaliInstruction The smali instruction to replace the instruction with. + */ + fun MutableMethod.replaceInstruction(index: Int, smaliInstruction: String) = + implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this)) + + /** + * Replace instructions at the given index. + * + * @param index The index to replace the instructions at. + * @param instructions The instructions to replace the instructions with. + */ + fun MutableMethod.replaceInstructions(index: Int, instructions: List) = + implementation!!.replaceInstructions(index, instructions) + + /** + * Replace instructions at the given index. + * + * @param index The index to replace the instructions at. + * @param smaliInstructions The smali instructions to replace the instructions with. + */ + fun MutableMethod.replaceInstructions(index: Int, smaliInstructions: String) = + implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this)) + +// TODO: Use proper names for functions below. + + /** + * Get an instruction at the given index. + * + * @param index The index to get the instruction at. + * @return The instruction. + */ + fun MutableMethodImplementation.instruction(index: Int): BuilderInstruction = instructions[index] + + /** + * Get an instruction at the given index. + * + * @param index The index to get the instruction at. + * @param T The type of instruction to return. + * @return The instruction. + */ + @Suppress("UNCHECKED_CAST") + fun MutableMethodImplementation.instruction(index: Int): T = instruction(index) as T + + /** + * Get an instruction at the given index. + * @param index The index to get the instruction at. + * @return The instruction. + */ + fun MutableMethod.instruction(index: Int): BuilderInstruction = implementation!!.instruction(index) + + /** + * Get an instruction at the given index. + * @param index The index to get the instruction at. + * @param T The type of instruction to return. + * @return The instruction. + */ + @Suppress("UNCHECKED_CAST") + fun MutableMethod.instruction(index: Int): T = implementation!!.instruction(index) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt index f297f95..c5fd920 100644 --- a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt +++ b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt @@ -3,7 +3,6 @@ package app.revanced.patcher.fingerprint.method.impl import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold -import app.revanced.patcher.extensions.parametersEqual import app.revanced.patcher.fingerprint.Fingerprint import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.util.proxy.ClassProxy @@ -90,6 +89,17 @@ abstract class MethodFingerprint( return false + fun parametersEqual( + parameters1: Iterable, parameters2: Iterable + ): Boolean { + if (parameters1.count() != parameters2.count()) return false + val iterator1 = parameters1.iterator() + parameters2.forEach { + if (!it.startsWith(iterator1.next())) return false + } + return true + } + if (methodFingerprint.parameters != null && !parametersEqual( methodFingerprint.parameters, // TODO: parseParameters() method.parameterTypes diff --git a/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt b/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt new file mode 100644 index 0000000..c2b227c --- /dev/null +++ b/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt @@ -0,0 +1,225 @@ +package app.revanced.patcher.extensions + +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patcher.util.smali.ExternalLabel +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.BuilderOffsetInstruction +import org.jf.dexlib2.builder.MutableMethodImplementation +import org.jf.dexlib2.builder.instruction.BuilderInstruction21s +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.immutable.ImmutableMethod +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +private object InstructionExtensionsTest { + private lateinit var testMethod: MutableMethod + private lateinit var testMethodImplementation: MutableMethodImplementation + + @BeforeEach + fun createTestMethod() = ImmutableMethod( + "TestClass;", + "testMethod", + null, + "V", + AccessFlags.PUBLIC.value, + null, + null, + MutableMethodImplementation(16).also { testMethodImplementation = it }.apply { + repeat(10) { i -> this.addInstruction(TestInstruction(i)) } + }, + ).let { testMethod = it.toMutable() } + + @Test + fun addInstructionsToImplementationIndexed() = applyOnImplementation { + addInstructions(5, getTestInstructions(5..6)).also { + assertRegisterIs(5, 5) + assertRegisterIs(6, 6) + + assertRegisterIs(5, 7) + } + } + + @Test + fun addInstructionsToImplementation() = applyOnImplementation { + addInstructions(getTestInstructions(10..11)).also { + assertRegisterIs(10, 10) + assertRegisterIs(11, 11) + } + } + + @Test + fun removeInstructionsFromImplementationIndexed() = applyOnImplementation { + removeInstructions(5, 5).also { assertRegisterIs(4, 4) } + } + + @Test + fun removeInstructionsFromImplementation() = applyOnImplementation { + removeInstructions(0).also { assertRegisterIs(9, 9) } + removeInstructions(1).also { assertRegisterIs(1, 0) } + removeInstructions(2).also { assertRegisterIs(3, 0) } + } + + @Test + fun replaceInstructionsInImplementationIndexed() = applyOnImplementation { + replaceInstructions(5, getTestInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(7, 7) + } + } + + @Test + fun addInstructionToMethodIndexed() = applyOnMethod { + addInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) } + } + + @Test + fun addInstructionToMethod() = applyOnMethod { + addInstruction(TestInstruction(0)).also { assertRegisterIs(0, 10) } + } + + @Test + fun addSmaliInstructionToMethodIndexed() = applyOnMethod { + addInstruction(5, getTestSmaliInstruction(0)).also { assertRegisterIs(0, 5) } + } + + @Test + fun addSmaliInstructionToMethod() = applyOnMethod { + addInstruction(getTestSmaliInstruction(0)).also { assertRegisterIs(0, 10) } + } + + @Test + fun addInstructionsToMethodIndexed() = applyOnMethod { + addInstructions(5, getTestInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + + assertRegisterIs(5, 7) + } + } + + @Test + fun addInstructionsToMethod() = applyOnMethod { + addInstructions(getTestInstructions(0..1)).also { + assertRegisterIs(0, 10) + assertRegisterIs(1, 11) + + assertRegisterIs(9, 9) + } + } + + @Test + fun addSmaliInstructionsToMethodIndexed() = applyOnMethod { + addInstructionsWithLabels(5, getTestSmaliInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + + assertRegisterIs(5, 7) + } + } + + @Test + fun addSmaliInstructionsToMethod() = applyOnMethod { + addInstructions(getTestSmaliInstructions(0..1)).also { + assertRegisterIs(0, 10) + assertRegisterIs(1, 11) + + assertRegisterIs(9, 9) + } + } + + @Test + fun addSmaliInstructionsWithExternalLabelToMethodIndexed() = applyOnMethod { + val label = ExternalLabel("testLabel", instruction(5)) + + addInstructionsWithLabels( + 5, + getTestSmaliInstructions(0..1).plus("\n").plus("goto :${label.name}"), + label + ).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(5, 8) + + val gotoTarget = instruction(7) + .target.location.instruction as OneRegisterInstruction + + assertEquals(5, gotoTarget.registerA) + } + } + + @Test + fun removeInstructionFromMethodIndexed() = applyOnMethod { + removeInstruction(5).also { + assertRegisterIs(4, 4) + assertRegisterIs(6, 5) + } + } + + @Test + fun removeInstructionsFromMethodIndexed() = applyOnMethod { + removeInstructions(5, 5).also { assertRegisterIs(4, 4) } + } + + @Test + fun removeInstructionsFromMethod() = applyOnMethod { + removeInstructions(0).also { assertRegisterIs(9, 9) } + removeInstructions(1).also { assertRegisterIs(1, 0) } + removeInstructions(2).also { assertRegisterIs(3, 0) } + } + + @Test + fun replaceInstructionInMethodIndexed() = applyOnMethod { + replaceInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) } + } + + @Test + fun replaceInstructionsInMethodIndexed() = applyOnMethod { + replaceInstructions(5, getTestInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(7, 7) + } + } + + @Test + fun replaceSmaliInstructionsInMethodIndexed() = applyOnMethod { + replaceInstructions(5, getTestSmaliInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(7, 7) + } + } + + // region Helper methods + + private fun applyOnImplementation(block: MutableMethodImplementation.() -> Unit) { + testMethodImplementation.apply(block) + } + + private fun applyOnMethod(block: MutableMethod.() -> Unit) { + testMethod.apply(block) + } + + private fun MutableMethodImplementation.assertRegisterIs(register: Int, atIndex: Int) = assertEquals( + register, instruction(atIndex).registerA + ) + + private fun MutableMethod.assertRegisterIs(register: Int, atIndex: Int) = + implementation!!.assertRegisterIs(register, atIndex) + + private fun getTestInstructions(range: IntRange) = range.map { TestInstruction(it) } + + private fun getTestSmaliInstruction(register: Int) = "const/16 v$register, 0" + + private fun getTestSmaliInstructions(range: IntRange) = range.joinToString("\n") { + getTestSmaliInstruction(it) + } + + // endregion + + private class TestInstruction(register: Int) : BuilderInstruction21s(Opcode.CONST_16, register, 0) +} \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index 4656b25..0add464 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -4,9 +4,9 @@ import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.or -import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.* import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch @@ -119,7 +119,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { // For this sake of example I reuse the TestClass field dummyField inside the virtual register 0. // // Control flow instructions are not supported as of now. - method.addInstructions( + method.addInstructionsWithLabels( startIndex + 2, """ invoke-static { }, LTestClass;->returnHello()Ljava/lang/String; diff --git a/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt b/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt index 9d1bbd4..137d23f 100644 --- a/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt +++ b/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt @@ -1,7 +1,8 @@ package app.revanced.patcher.util.smali -import app.revanced.patcher.extensions.addInstructions -import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.extensions.InstructionExtensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.InstructionExtensions.instruction import app.revanced.patcher.extensions.label import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import org.jf.dexlib2.AccessFlags @@ -35,7 +36,7 @@ internal class InlineSmaliCompilerTest { method.addInstructions(arrayOfNulls(insnAmount).also { Arrays.fill(it, "const/4 v0, 0x0") }.joinToString("\n")) - method.addInstructions( + method.addInstructionsWithLabels( targetIndex, """ :test @@ -63,14 +64,14 @@ internal class InlineSmaliCompilerTest { assertEquals(labelIndex, method.label(labelIndex).location.index) - method.addInstructions( + method.addInstructionsWithLabels( + method.implementation!!.instructions.size, """ const/4 v0, 0x1 if-eqz v0, :test return-void - """, listOf( - ExternalLabel("test",method.instruction(1)) - ) + """, + ExternalLabel("test", method.instruction(1)) ) val insn = method.instruction(insnIndex)