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