Open-source multiplayer game server compatible with the RuneScape client
https://www.openrs2.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
4.0 KiB
119 lines
4.0 KiB
package org.openrs2.deob.bytecode.transform
|
|
|
|
import com.github.michaelbull.logging.InlineLogger
|
|
import org.objectweb.asm.Opcodes
|
|
import org.objectweb.asm.tree.MethodInsnNode
|
|
import org.openrs2.asm.MemberRef
|
|
import org.openrs2.asm.classpath.ClassPath
|
|
import org.openrs2.asm.filter.MemberFilter
|
|
import org.openrs2.asm.filter.UnionMemberFilter
|
|
import org.openrs2.asm.hasCode
|
|
import org.openrs2.asm.removeDeadCode
|
|
import org.openrs2.asm.transform.Transformer
|
|
import org.openrs2.deob.bytecode.Profile
|
|
import org.openrs2.deob.bytecode.filter.ReflectedConstructorFilter
|
|
import org.openrs2.util.collect.DisjointSet
|
|
import org.openrs2.util.collect.UniqueQueue
|
|
import javax.inject.Inject
|
|
import javax.inject.Singleton
|
|
|
|
@Singleton
|
|
public class UnusedMethodTransformer @Inject constructor(private val profile: Profile) : Transformer() {
|
|
private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
|
|
private lateinit var entryPoints: MemberFilter
|
|
private val pendingMethods = UniqueQueue<MemberRef>()
|
|
private val usedMethods = mutableSetOf<DisjointSet.Partition<MemberRef>>()
|
|
|
|
override fun preTransform(classPath: ClassPath) {
|
|
inheritedMethodSets = classPath.createInheritedMethodSets()
|
|
entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
|
|
|
|
queueEntryPoints(classPath)
|
|
|
|
while (true) {
|
|
val method = pendingMethods.poll() ?: break
|
|
analyzeMethod(classPath, method)
|
|
}
|
|
}
|
|
|
|
private fun analyzeMethod(classPath: ClassPath, ref: MemberRef) {
|
|
// find ClassNode/MethodNode
|
|
val owner = classPath.getClassNode(ref.owner) ?: return
|
|
val method = owner.methods.singleOrNull { it.name == ref.name && it.desc == ref.desc } ?: return
|
|
if (!method.hasCode) {
|
|
return
|
|
}
|
|
|
|
// iterate over non-dead call instructions
|
|
method.removeDeadCode(owner.name)
|
|
|
|
for (insn in method.instructions) {
|
|
if (insn !is MethodInsnNode) {
|
|
continue
|
|
}
|
|
|
|
val invokedRef = MemberRef(insn.owner, insn.name, insn.desc)
|
|
val partition = inheritedMethodSets[invokedRef] ?: continue
|
|
if (usedMethods.add(partition)) {
|
|
pendingMethods += partition
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun queueEntryPoints(classPath: ClassPath) {
|
|
for (partition in inheritedMethodSets) {
|
|
if (isEntryPoint(classPath, partition)) {
|
|
pendingMethods.addAll(partition)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun isEntryPoint(classPath: ClassPath, partition: DisjointSet.Partition<MemberRef>): Boolean {
|
|
for (method in partition) {
|
|
val clazz = classPath[method.owner]!!
|
|
|
|
if (entryPoints.matches(method) || clazz.dependency) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
override fun postTransform(classPath: ClassPath) {
|
|
var methodsRemoved = 0
|
|
|
|
for (library in classPath.libraries) {
|
|
for (clazz in library) {
|
|
val it = clazz.methods.iterator()
|
|
|
|
while (it.hasNext()) {
|
|
val method = it.next()
|
|
if (method.access and Opcodes.ACC_NATIVE != 0) {
|
|
continue
|
|
} else if (entryPoints.matches(clazz.name, method.name, method.desc)) {
|
|
continue
|
|
}
|
|
|
|
val member = MemberRef(clazz, method)
|
|
val partition = inheritedMethodSets[member]!!
|
|
|
|
if (partition.any { classPath[it.owner]!!.dependency }) {
|
|
continue
|
|
} else if (usedMethods.contains(partition)) {
|
|
continue
|
|
}
|
|
|
|
it.remove()
|
|
methodsRemoved++
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.info { "Removed $methodsRemoved unused methods" }
|
|
}
|
|
|
|
private companion object {
|
|
private val logger = InlineLogger()
|
|
}
|
|
}
|
|
|