diff --git a/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt b/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt index 7c4a2d72..2b370594 100644 --- a/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt +++ b/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt @@ -12,6 +12,7 @@ import dev.openrs2.deob.transform.BundlerTransformer import dev.openrs2.deob.transform.CanvasTransformer import dev.openrs2.deob.transform.ClassLiteralTransformer import dev.openrs2.deob.transform.ConstantArgTransformer +import dev.openrs2.deob.transform.CopyPropagationTransformer import dev.openrs2.deob.transform.CounterTransformer import dev.openrs2.deob.transform.EmptyClassTransformer import dev.openrs2.deob.transform.ExceptionTracingTransformer @@ -61,6 +62,7 @@ object DeobfuscatorModule : AbstractModule() { binder.addBinding().to(FieldOrderTransformer::class.java) binder.addBinding().to(BitwiseOpTransformer::class.java) binder.addBinding().to(ConstantArgTransformer::class.java) + binder.addBinding().to(CopyPropagationTransformer::class.java) binder.addBinding().to(UnusedLocalTransformer::class.java) binder.addBinding().to(UnusedMethodTransformer::class.java) binder.addBinding().to(UnusedArgTransformer::class.java) diff --git a/deob/src/main/java/dev/openrs2/deob/analysis/CopyAssignment.kt b/deob/src/main/java/dev/openrs2/deob/analysis/CopyAssignment.kt new file mode 100644 index 00000000..224ab7e6 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/analysis/CopyAssignment.kt @@ -0,0 +1,3 @@ +package dev.openrs2.deob.analysis + +data class CopyAssignment(val destination: Int, val source: Int) diff --git a/deob/src/main/java/dev/openrs2/deob/analysis/CopyPropagationAnalyzer.kt b/deob/src/main/java/dev/openrs2/deob/analysis/CopyPropagationAnalyzer.kt new file mode 100644 index 00000000..8c871084 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/analysis/CopyPropagationAnalyzer.kt @@ -0,0 +1,79 @@ +package dev.openrs2.deob.analysis + +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.IincInsnNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.VarInsnNode +import java.util.Collections + +class CopyPropagationAnalyzer(owner: String, method: MethodNode) : + DataFlowAnalyzer>(owner, method) { + + private val allAssignments = mutableSetOf() + + init { + for (insn in method.instructions) { + if (insn !is VarInsnNode || !STORE_OPCODES.contains(insn.opcode)) { + continue + } + + val previous = insn.previous + if (previous !is VarInsnNode || !LOAD_OPCODES.contains(previous.opcode)) { + continue + } + + allAssignments += CopyAssignment(insn.`var`, previous.`var`) + } + } + + override fun createEntrySet(): Set { + return Collections.emptySet() + } + + override fun createInitialSet(): Set { + return allAssignments + } + + override fun join(set1: Set, set2: Set): Set { + return set1 intersect set2 + } + + override fun transfer(set: Set, insn: AbstractInsnNode): Set { + return when { + insn is VarInsnNode && STORE_OPCODES.contains(insn.opcode) -> { + val newSet = set.minusKilledByAssignmentTo(insn.`var`) + + val previous = insn.previous + if (previous is VarInsnNode && LOAD_OPCODES.contains(previous.opcode)) { + newSet.plus(CopyAssignment(insn.`var`, previous.`var`)) + } else { + newSet + } + } + insn is IincInsnNode -> set.minusKilledByAssignmentTo(insn.`var`) + else -> set + } + } + + private fun Set.minusKilledByAssignmentTo(index: Int): Set { + return filterTo(mutableSetOf()) { it.source != index && it.destination != index } + } + + private companion object { + private val LOAD_OPCODES = setOf( + Opcodes.ILOAD, + Opcodes.LSTORE, + Opcodes.FLOAD, + Opcodes.DLOAD, + Opcodes.ALOAD + ) + private val STORE_OPCODES = setOf( + Opcodes.ISTORE, + Opcodes.LSTORE, + Opcodes.FSTORE, + Opcodes.DSTORE, + Opcodes.ASTORE + ) + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/transform/CopyPropagationTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/CopyPropagationTransformer.kt new file mode 100644 index 00000000..e02f09f5 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/transform/CopyPropagationTransformer.kt @@ -0,0 +1,81 @@ +package dev.openrs2.deob.transform + +import com.github.michaelbull.logging.InlineLogger +import dev.openrs2.asm.classpath.ClassPath +import dev.openrs2.asm.classpath.Library +import dev.openrs2.asm.transform.Transformer +import dev.openrs2.deob.analysis.CopyPropagationAnalyzer +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.VarInsnNode +import javax.inject.Singleton + +/** + * A [Transformer] that performs + * [copy propagation](https://en.wikipedia.org/wiki/Copy_propagation) of + * assignments of one variable to another. + * + * This is primarily for improving the decompilation of `for` loops. Without + * copy propagation, the initializer in many `for` loops declares a different + * variable to the one in the increment expression: + * + * ``` + * Object[] array = ... + * int i = 0; + * for (Object[] array2 = array; i < n; i++) { + * // use array2[n] + * } + * ``` + * + * With copy propagation, the variables match: + * + * ``` + * Object[] array = ... + * for (int i = 0; i < n; i++) { + * // use array[n] + * } + * ``` + */ +@Singleton +class CopyPropagationTransformer : Transformer() { + private var propagatedLocals = 0 + + override fun preTransform(classPath: ClassPath) { + propagatedLocals = 0 + } + + override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { + val analyzer = CopyPropagationAnalyzer(clazz.name, method) + analyzer.analyze() + + for (insn in method.instructions) { + if (insn !is VarInsnNode || !LOAD_OPCODES.contains(insn.opcode)) { + continue + } + + val set = analyzer.getInSet(insn) ?: continue + val assignment = set.singleOrNull { it.destination == insn.`var` } ?: continue + insn.`var` = assignment.source + propagatedLocals++ + } + + return false + } + + override fun postTransform(classPath: ClassPath) { + logger.info { "Propagated $propagatedLocals copies" } + } + + private companion object { + private val logger = InlineLogger() + + private val LOAD_OPCODES = setOf( + Opcodes.ILOAD, + Opcodes.LLOAD, + Opcodes.FLOAD, + Opcodes.DLOAD, + Opcodes.ALOAD + ) + } +}