Add CopyPropagationTransformer

Signed-off-by: Graham <gpe@openrs2.dev>
Graham 4 years ago
parent 4da1c5c3cf
commit ca419eecb5
  1. 2
      deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt
  2. 3
      deob/src/main/java/dev/openrs2/deob/analysis/CopyAssignment.kt
  3. 79
      deob/src/main/java/dev/openrs2/deob/analysis/CopyPropagationAnalyzer.kt
  4. 81
      deob/src/main/java/dev/openrs2/deob/transform/CopyPropagationTransformer.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)

@ -0,0 +1,3 @@
package dev.openrs2.deob.analysis
data class CopyAssignment(val destination: Int, val source: Int)

@ -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<Set<CopyAssignment>>(owner, method) {
private val allAssignments = mutableSetOf<CopyAssignment>()
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<CopyAssignment> {
return Collections.emptySet()
}
override fun createInitialSet(): Set<CopyAssignment> {
return allAssignments
}
override fun join(set1: Set<CopyAssignment>, set2: Set<CopyAssignment>): Set<CopyAssignment> {
return set1 intersect set2
}
override fun transfer(set: Set<CopyAssignment>, insn: AbstractInsnNode): Set<CopyAssignment> {
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<CopyAssignment>.minusKilledByAssignmentTo(index: Int): Set<CopyAssignment> {
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
)
}
}

@ -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
)
}
}
Loading…
Cancel
Save