Revert "Simplify static field scrambling"

This reverts commit b6bba95435.

Although this code is more complicated, it allows us to control the
destination class for fields individually (rather than an entire set at
a time). This is a requirement for name mapping.
Graham 5 years ago
parent d61411571b
commit 524a894df2
  1. 162
      deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt

@ -2,20 +2,22 @@ package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.ClassVersionUtils import dev.openrs2.asm.ClassVersionUtils
import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.getExpression
import dev.openrs2.asm.isSequential
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.Profile import dev.openrs2.deob.Profile
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldNode import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.InsnList import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.InsnNode import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject import javax.inject.Inject
@ -24,16 +26,17 @@ import kotlin.math.max
@Singleton @Singleton
class StaticScramblingTransformer @Inject constructor(private val profile: Profile) : Transformer() { class StaticScramblingTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private class FieldSet(val owner: ClassNode, val fields: List<FieldNode>, val clinit: MethodNode?) { private data class Field(val node: FieldNode, val initializer: InsnList, val version: Int, val maxStack: Int) {
val dependencies = clinit?.instructions val dependencies = initializer.asSequence()
?.filterIsInstance<FieldInsnNode>() .filterIsInstance<FieldInsnNode>()
?.filter { it.opcode == Opcodes.GETSTATIC && it.owner != owner.name } .filter { it.opcode == Opcodes.GETSTATIC }
?.mapTo(mutableSetOf(), ::MemberRef) ?: emptySet<MemberRef>() .map(::MemberRef)
.toSet()
} }
private lateinit var inheritedFieldSets: DisjointSet<MemberRef> private lateinit var inheritedFieldSets: DisjointSet<MemberRef>
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private val fieldSets = mutableMapOf<MemberRef, FieldSet>() private val fields = mutableMapOf<DisjointSet.Partition<MemberRef>, Field>()
private val fieldClasses = mutableMapOf<DisjointSet.Partition<MemberRef>, String>() private val fieldClasses = mutableMapOf<DisjointSet.Partition<MemberRef>, String>()
private val methodClasses = mutableMapOf<DisjointSet.Partition<MemberRef>, String>() private val methodClasses = mutableMapOf<DisjointSet.Partition<MemberRef>, String>()
private var nextStaticClass: ClassNode? = null private var nextStaticClass: ClassNode? = null
@ -73,63 +76,103 @@ class StaticScramblingTransformer @Inject constructor(private val profile: Profi
return Pair(clazz, clinit) return Pair(clazz, clinit)
} }
private fun spliceFields() { private fun MethodNode.extractEntryExitBlocks(): List<AbstractInsnNode> {
val done = mutableSetOf<FieldSet>() /*
for (fieldSet in fieldSets.values) { * Most (or all?) of the <clinit> methods have "simple" initializers
spliceFields(done, fieldSet) * that we're capable of moving in the first and last basic blocks of
} * the method. The last basic block is always at the end of the code
* and ends in a RETURN. This allows us to avoid worrying about making
* a full basic block control flow graph here.
*/
val entry = instructions.takeWhile { it.isSequential }
val last = instructions.lastOrNull()
if (last == null || last.opcode != Opcodes.RETURN) {
return entry
} }
private fun spliceFields(done: MutableSet<FieldSet>, fieldSet: FieldSet) { val exit = instructions.toList()
if (!done.add(fieldSet)) { .dropLast(1)
return .takeLastWhile { it.isSequential }
return entry.plus(exit)
} }
for (dependency in fieldSet.dependencies) { private fun MethodNode.extractInitializers(owner: String): Pair<Map<MemberDesc, InsnList>, Set<MemberDesc>> {
val dependencyFieldSet = fieldSets[dependency] ?: continue val entryExitBlocks = extractEntryExitBlocks()
spliceFields(done, dependencyFieldSet)
val simpleInitializers = mutableMapOf<MemberDesc, InsnList>()
val complexInitializers = instructions.asSequence()
.filter { !entryExitBlocks.contains(it) }
.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.GETSTATIC && it.owner == owner }
.filter { !profile.excludedFields.matches(it.owner, it.name, it.desc) }
.map(::MemberDesc)
.toSet()
val putstatics = entryExitBlocks
.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.PUTSTATIC && it.owner == owner }
.filter { !profile.excludedFields.matches(it.owner, it.name, it.desc) }
for (putstatic in putstatics) {
val desc = MemberDesc(putstatic)
if (simpleInitializers.containsKey(desc) || complexInitializers.contains(desc)) {
continue
} }
val (staticClass, staticClinit) = nextClass() // TODO(gpe): use a filter here (pure with no *LOADs?)
staticClass.fields.addAll(fieldSet.fields) val expr = getExpression(putstatic) ?: continue
staticClass.version = ClassVersionUtils.max(staticClass.version, fieldSet.owner.version)
val initializer = InsnList()
for (insn in expr) {
instructions.remove(insn)
initializer.add(insn)
}
instructions.remove(putstatic)
initializer.add(putstatic)
if (fieldSet.clinit != null) { simpleInitializers[desc] = initializer
// remove tail RETURN
val insns = fieldSet.clinit.instructions
val last = insns.lastOrNull()
if (last != null && last.opcode == Opcodes.RETURN) {
insns.remove(last)
} }
// replace any remaining RETURNs with a GOTO to the end of the method return Pair(simpleInitializers, complexInitializers)
val end = LabelNode() }
insns.add(end)
for (insn in insns) { private fun spliceInitializers() {
if (insn.opcode == Opcodes.RETURN) { val done = mutableSetOf<DisjointSet.Partition<MemberRef>>()
insns.set(insn, JumpInsnNode(Opcodes.GOTO, end)) for ((partition, field) in fields) {
spliceInitializers(done, partition, field)
} }
} }
// append just before the end of the static <clinit> RETURN private fun spliceInitializers(
staticClinit.instructions.insertBefore(staticClinit.instructions.last, insns) done: MutableSet<DisjointSet.Partition<MemberRef>>,
partition: DisjointSet.Partition<MemberRef>,
field: Field
) {
if (!done.add(partition)) {
return
}
staticClinit.tryCatchBlocks.addAll(fieldSet.clinit.tryCatchBlocks) for (dependency in field.dependencies) {
staticClinit.maxStack = max(staticClinit.maxStack, fieldSet.clinit.maxStack) val dependencyPartition = inheritedFieldSets[dependency]!!
staticClinit.maxLocals = max(staticClinit.maxLocals, fieldSet.clinit.maxLocals) val dependencyField = fields[dependencyPartition] ?: continue
spliceInitializers(done, partition, dependencyField)
} }
for (field in fieldSet.fields) { val (staticClass, clinit) = nextClass()
val partition = inheritedFieldSets[MemberRef(fieldSet.owner, field)]!! staticClass.fields.add(field.node)
staticClass.version = ClassVersionUtils.max(staticClass.version, field.version)
clinit.instructions.insertBefore(clinit.instructions.last, field.initializer)
clinit.maxStack = max(clinit.maxStack, field.maxStack)
fieldClasses[partition] = staticClass.name fieldClasses[partition] = staticClass.name
} }
}
override fun preTransform(classPath: ClassPath) { override fun preTransform(classPath: ClassPath) {
inheritedFieldSets = classPath.createInheritedFieldSets() inheritedFieldSets = classPath.createInheritedFieldSets()
inheritedMethodSets = classPath.createInheritedMethodSets() inheritedMethodSets = classPath.createInheritedMethodSets()
fieldSets.clear() fields.clear()
fieldClasses.clear() fieldClasses.clear()
methodClasses.clear() methodClasses.clear()
nextStaticClass = null nextStaticClass = null
@ -143,22 +186,29 @@ class StaticScramblingTransformer @Inject constructor(private val profile: Profi
for (clazz in library) { for (clazz in library) {
// TODO(gpe): exclude the JSObject class // TODO(gpe): exclude the JSObject class
if (clazz.name == "jagex3/jagmisc/jagmisc") {
continue
}
val fields = clazz.fields.filter { it.access and Opcodes.ACC_STATIC != 0 }
clazz.fields.removeAll(fields)
val clinit = clazz.methods.find { it.name == "<clinit>" } val clinit = clazz.methods.find { it.name == "<clinit>" }
if (clinit != null) { val (simpleInitializers, complexInitializers) = clinit?.extractInitializers(clazz.name)
clazz.methods.remove(clinit) ?: Pair(emptyMap(), emptySet())
clazz.fields.removeIf { field ->
if (field.access and Opcodes.ACC_STATIC == 0) {
return@removeIf false
} else if (profile.excludedFields.matches(clazz.name, field.name, field.desc)) {
return@removeIf false
}
val desc = MemberDesc(field)
if (complexInitializers.contains(desc)) {
return@removeIf false
} }
val fieldSet = FieldSet(clazz, fields, clinit) val initializer = simpleInitializers[desc] ?: InsnList()
for (field in fields) { val maxStack = clinit?.maxStack ?: 0
val ref = MemberRef(clazz, field)
fieldSets[ref] = fieldSet val partition = inheritedFieldSets[MemberRef(clazz, field)]!!
fields[partition] = Field(field, initializer, clazz.version, maxStack)
return@removeIf true
} }
clazz.methods.removeIf { method -> clazz.methods.removeIf { method ->
@ -180,7 +230,7 @@ class StaticScramblingTransformer @Inject constructor(private val profile: Profi
} }
} }
spliceFields() spliceInitializers()
for (clazz in staticClasses) { for (clazz in staticClasses) {
library.add(clazz) library.add(clazz)

Loading…
Cancel
Save