forked from openrs2/openrs2
parent
eee96d4c3e
commit
38f731f986
@ -0,0 +1,78 @@ |
||||
package dev.openrs2.deob.analysis |
||||
|
||||
import dev.openrs2.asm.MemberDesc |
||||
import dev.openrs2.asm.classpath.ClassPath |
||||
import org.objectweb.asm.Opcodes |
||||
import org.objectweb.asm.tree.AbstractInsnNode |
||||
import org.objectweb.asm.tree.FieldInsnNode |
||||
import org.objectweb.asm.tree.MethodNode |
||||
import org.objectweb.asm.tree.analysis.Frame |
||||
|
||||
class FieldWriteAnalyzer( |
||||
private val owner: String, |
||||
private val method: MethodNode, |
||||
private val classPath: ClassPath, |
||||
private val frames: Array<Frame<ThisValue>> |
||||
) : DataFlowAnalyzer<Map<MemberDesc, FieldWriteCount>>(owner, method) { |
||||
override fun createInitialSet(): Map<MemberDesc, FieldWriteCount> { |
||||
return emptyMap() |
||||
} |
||||
|
||||
override fun join( |
||||
set1: Map<MemberDesc, FieldWriteCount>, |
||||
set2: Map<MemberDesc, FieldWriteCount> |
||||
): Map<MemberDesc, FieldWriteCount> { |
||||
if (set1 == set2) { |
||||
return set1 |
||||
} |
||||
|
||||
val set = mutableMapOf<MemberDesc, FieldWriteCount>() |
||||
|
||||
for (member in set1.keys union set2.keys) { |
||||
val count1 = set1.getOrDefault(member, FieldWriteCount.NEVER) |
||||
val count2 = set2.getOrDefault(member, FieldWriteCount.NEVER) |
||||
|
||||
set[member] = if (count1 == count2) { |
||||
count1 |
||||
} else { |
||||
FieldWriteCount.UNKNOWN |
||||
} |
||||
} |
||||
|
||||
return set |
||||
} |
||||
|
||||
override fun transfer( |
||||
set: Map<MemberDesc, FieldWriteCount>, |
||||
insn: AbstractInsnNode |
||||
): Map<MemberDesc, FieldWriteCount> { |
||||
if (insn !is FieldInsnNode) { |
||||
return set |
||||
} else if (insn.opcode != Opcodes.PUTFIELD && insn.opcode != Opcodes.PUTSTATIC) { |
||||
return set |
||||
} |
||||
|
||||
val member = MemberDesc(insn) |
||||
val declaredOwner = classPath[insn.owner]!!.resolveField(member)!!.name |
||||
if (declaredOwner != owner) { |
||||
return set |
||||
} |
||||
|
||||
val isThis = if (insn.opcode == Opcodes.PUTFIELD) { |
||||
val insnIndex = method.instructions.indexOf(insn) |
||||
val frame = frames[insnIndex] |
||||
frame.getStack(frame.stackSize - 2).isThis |
||||
} else { |
||||
true |
||||
} |
||||
|
||||
val count = set.getOrDefault(member, FieldWriteCount.NEVER) |
||||
return when { |
||||
isThis && count == FieldWriteCount.NEVER -> set.plus(Pair(member, FieldWriteCount.EXACTLY_ONCE)) |
||||
isThis && count == FieldWriteCount.EXACTLY_ONCE -> set.plus(Pair(member, FieldWriteCount.UNKNOWN)) |
||||
// save an allocation if count is already set to UNKNOWN |
||||
count == FieldWriteCount.UNKNOWN -> set |
||||
else -> set.plus(Pair(member, FieldWriteCount.UNKNOWN)) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
package dev.openrs2.deob.analysis |
||||
|
||||
enum class FieldWriteCount { |
||||
NEVER, |
||||
EXACTLY_ONCE, |
||||
UNKNOWN |
||||
} |
@ -0,0 +1,80 @@ |
||||
package dev.openrs2.deob.analysis |
||||
|
||||
import org.objectweb.asm.Opcodes |
||||
import org.objectweb.asm.Type |
||||
import org.objectweb.asm.tree.AbstractInsnNode |
||||
import org.objectweb.asm.tree.analysis.BasicInterpreter |
||||
import org.objectweb.asm.tree.analysis.Interpreter |
||||
|
||||
class ThisInterpreter : Interpreter<ThisValue>(Opcodes.ASM8) { |
||||
private val basicInterpreter = BasicInterpreter() |
||||
|
||||
override fun newValue(type: Type?): ThisValue? { |
||||
val basicValue = basicInterpreter.newValue(type) ?: return null |
||||
return ThisValue(basicValue, false) |
||||
} |
||||
|
||||
override fun newParameterValue(isInstanceMethod: Boolean, local: Int, type: Type): ThisValue { |
||||
val basicValue = basicInterpreter.newParameterValue(isInstanceMethod, local, type) |
||||
return ThisValue(basicValue, isInstanceMethod && local == 0) |
||||
} |
||||
|
||||
override fun newOperation(insn: AbstractInsnNode): ThisValue { |
||||
val basicValue = basicInterpreter.newOperation(insn) |
||||
return ThisValue(basicValue, false) |
||||
} |
||||
|
||||
override fun copyOperation(insn: AbstractInsnNode, value: ThisValue): ThisValue { |
||||
val basicValue = basicInterpreter.copyOperation(insn, value.basicValue) |
||||
/* |
||||
* Only allow "this"ness to propagate from a variable to the stack, not |
||||
* vice-versa. This is compatible with javac's analysis. |
||||
*/ |
||||
return if (insn.opcode == Opcodes.ASTORE) { |
||||
ThisValue(basicValue, false) |
||||
} else { |
||||
ThisValue(basicValue, value.isThis) |
||||
} |
||||
} |
||||
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: ThisValue): ThisValue? { |
||||
val basicValue = basicInterpreter.unaryOperation(insn, value.basicValue) ?: return null |
||||
return ThisValue(basicValue, false) |
||||
} |
||||
|
||||
override fun binaryOperation(insn: AbstractInsnNode, value1: ThisValue, value2: ThisValue): ThisValue? { |
||||
val basicValue = basicInterpreter.binaryOperation(insn, value1.basicValue, value2.basicValue) |
||||
?: return null |
||||
return ThisValue(basicValue, false) |
||||
} |
||||
|
||||
override fun ternaryOperation( |
||||
insn: AbstractInsnNode, |
||||
value1: ThisValue, |
||||
value2: ThisValue, |
||||
value3: ThisValue |
||||
): ThisValue? { |
||||
val basicValue = basicInterpreter.ternaryOperation( |
||||
insn, |
||||
value1.basicValue, |
||||
value2.basicValue, |
||||
value3.basicValue |
||||
) ?: return null |
||||
return ThisValue(basicValue, false) |
||||
} |
||||
|
||||
override fun naryOperation(insn: AbstractInsnNode, values: List<ThisValue>): ThisValue? { |
||||
val args = values.map(ThisValue::basicValue) |
||||
val basicValue = basicInterpreter.naryOperation(insn, args) ?: return null |
||||
return ThisValue(basicValue, false) |
||||
} |
||||
|
||||
override fun returnOperation(insn: AbstractInsnNode, value: ThisValue, expected: ThisValue) { |
||||
basicInterpreter.returnOperation(insn, value.basicValue, expected.basicValue) |
||||
} |
||||
|
||||
override fun merge(value1: ThisValue, value2: ThisValue): ThisValue { |
||||
val basicValue = basicInterpreter.merge(value1.basicValue, value2.basicValue) |
||||
return ThisValue(basicValue, value1.isThis && value2.isThis) |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
package dev.openrs2.deob.analysis |
||||
|
||||
import org.objectweb.asm.tree.analysis.BasicValue |
||||
import org.objectweb.asm.tree.analysis.Value |
||||
|
||||
data class ThisValue(val basicValue: BasicValue, val isThis: Boolean) : Value { |
||||
override fun getSize(): Int { |
||||
return basicValue.size |
||||
} |
||||
} |
@ -0,0 +1,139 @@ |
||||
package dev.openrs2.deob.transform |
||||
|
||||
import com.github.michaelbull.logging.InlineLogger |
||||
import dev.openrs2.asm.MemberDesc |
||||
import dev.openrs2.asm.MemberRef |
||||
import dev.openrs2.asm.classpath.ClassPath |
||||
import dev.openrs2.asm.classpath.Library |
||||
import dev.openrs2.asm.transform.Transformer |
||||
import dev.openrs2.deob.analysis.FieldWriteAnalyzer |
||||
import dev.openrs2.deob.analysis.FieldWriteCount |
||||
import dev.openrs2.deob.analysis.ThisInterpreter |
||||
import dev.openrs2.util.collect.DisjointSet |
||||
import org.objectweb.asm.Opcodes |
||||
import org.objectweb.asm.tree.ClassNode |
||||
import org.objectweb.asm.tree.FieldInsnNode |
||||
import org.objectweb.asm.tree.FieldNode |
||||
import org.objectweb.asm.tree.MethodInsnNode |
||||
import org.objectweb.asm.tree.MethodNode |
||||
import org.objectweb.asm.tree.analysis.Analyzer |
||||
|
||||
class FinalFieldTransformer : Transformer() { |
||||
private lateinit var inheritedFieldSets: DisjointSet<MemberRef> |
||||
private val nonFinalFields = mutableSetOf<DisjointSet.Partition<MemberRef>>() |
||||
|
||||
override fun preTransform(classPath: ClassPath) { |
||||
inheritedFieldSets = classPath.createInheritedFieldSets() |
||||
nonFinalFields.clear() |
||||
} |
||||
|
||||
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { |
||||
val constructor = method.name == "<init>" || method.name == "<clinit>" |
||||
val thisCall = if (constructor && method.name == "<init>") { |
||||
method.instructions.filterIsInstance<MethodInsnNode>() |
||||
.any { it.opcode == Opcodes.INVOKESPECIAL && it.owner == clazz.name && it.name == "<init>" } |
||||
} else { |
||||
false |
||||
} |
||||
|
||||
val constructorWithoutThisCall = constructor && !thisCall |
||||
|
||||
val frames = if (constructorWithoutThisCall) { |
||||
Analyzer(ThisInterpreter()).analyze(clazz.name, method) |
||||
} else { |
||||
null |
||||
} |
||||
|
||||
for (insn in method.instructions) { |
||||
if (insn !is FieldInsnNode) { |
||||
continue |
||||
} else if (insn.opcode != Opcodes.PUTFIELD && insn.opcode != Opcodes.PUTSTATIC) { |
||||
continue |
||||
} |
||||
|
||||
if (constructorWithoutThisCall) { |
||||
val isThis = if (insn.opcode == Opcodes.PUTFIELD) { |
||||
val insnIndex = method.instructions.indexOf(insn) |
||||
val frame = frames!![insnIndex] ?: continue |
||||
frame.getStack(frame.stackSize - 2).isThis |
||||
} else { |
||||
true |
||||
} |
||||
|
||||
val declaredOwner = classPath[insn.owner]!!.resolveField(MemberDesc(insn))!!.name |
||||
if (isThis && declaredOwner == clazz.name) { |
||||
/* |
||||
* Writes inside constructors without a this(...) call to |
||||
* fields owned by the same class are analyzed separately - if |
||||
* there is exactly one write in the constructor the field can |
||||
* be made final. |
||||
*/ |
||||
continue |
||||
} |
||||
} |
||||
|
||||
val partition = inheritedFieldSets[MemberRef(insn)] ?: continue |
||||
nonFinalFields += partition |
||||
} |
||||
|
||||
if (constructorWithoutThisCall) { |
||||
val analyzer = FieldWriteAnalyzer(clazz.name, method, classPath, frames!!) |
||||
analyzer.analyze() |
||||
|
||||
val exits = method.instructions.filter { it.opcode == Opcodes.RETURN } |
||||
for (insn in exits) { |
||||
val counts = analyzer.getOutSet(insn) ?: emptyMap() |
||||
|
||||
for (field in clazz.fields) { |
||||
val count = counts.getOrDefault(MemberDesc(field), FieldWriteCount.NEVER) |
||||
if (count != FieldWriteCount.EXACTLY_ONCE) { |
||||
val partition = inheritedFieldSets[MemberRef(clazz, field)]!! |
||||
nonFinalFields += partition |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
override fun transformField(classPath: ClassPath, library: Library, clazz: ClassNode, field: FieldNode): Boolean { |
||||
if ((field.access and Opcodes.ACC_VOLATILE) != 0) { |
||||
val partition = inheritedFieldSets[MemberRef(clazz, field)]!! |
||||
nonFinalFields += partition |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
override fun postTransform(classPath: ClassPath) { |
||||
var fieldsChanged = 0 |
||||
|
||||
for (library in classPath.libraries) { |
||||
for (clazz in library) { |
||||
for (field in clazz.fields) { |
||||
val member = MemberRef(clazz, field) |
||||
val partition = inheritedFieldSets[member]!! |
||||
|
||||
val access = field.access |
||||
|
||||
if (nonFinalFields.contains(partition)) { |
||||
field.access = field.access and Opcodes.ACC_FINAL.inv() |
||||
} else { |
||||
field.access = field.access or Opcodes.ACC_FINAL |
||||
} |
||||
|
||||
if (field.access != access) { |
||||
fieldsChanged++ |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
logger.info { "Updated final modifier on $fieldsChanged fields" } |
||||
} |
||||
|
||||
private companion object { |
||||
private val logger = InlineLogger() |
||||
} |
||||
} |
Loading…
Reference in new issue