forked from openrs2/openrs2
parent
4da1c5c3cf
commit
ca419eecb5
@ -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…
Reference in new issue