From 488e8ef8c39f930c8016741b2123f21be4796012 Mon Sep 17 00:00:00 2001 From: Major Date: Sun, 8 Mar 2020 07:03:01 +0000 Subject: [PATCH] Add UnusedMethodTransformer The transformer does _not_ check for use via reflection. The only cases in the 550 and OSRS clients where methods are accessed via reflection are either 1) JRE classes, 2) when the method name is sent from the server. PSVM and methods declared in TypedRemapper.EXCLUDED_METHODS are never removed. Signed-off-by: Major --- .../openrs2/common/collect/IterableUtils.kt | 13 +++ .../java/dev/openrs2/deob/Deobfuscator.kt | 2 + .../deob/transform/UnusedMethodTransformer.kt | 86 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 common/src/main/java/dev/openrs2/common/collect/IterableUtils.kt create mode 100644 deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt diff --git a/common/src/main/java/dev/openrs2/common/collect/IterableUtils.kt b/common/src/main/java/dev/openrs2/common/collect/IterableUtils.kt new file mode 100644 index 0000000000..687a8d8905 --- /dev/null +++ b/common/src/main/java/dev/openrs2/common/collect/IterableUtils.kt @@ -0,0 +1,13 @@ +package dev.openrs2.common.collect + +inline fun MutableIterable.removeFirst(predicate: (T) -> Boolean): Boolean { + val iterator = iterator() + for (element in iterator) { + if (predicate(element)) { + iterator.remove() + return true + } + } + + return false +} diff --git a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt index d5e37bf3ac..b48eb44311 100644 --- a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt +++ b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt @@ -32,6 +32,7 @@ import dev.openrs2.deob.transform.RemapTransformer import dev.openrs2.deob.transform.ResetTransformer import dev.openrs2.deob.transform.StaticScramblingTransformer import dev.openrs2.deob.transform.UnusedArgTransformer +import dev.openrs2.deob.transform.UnusedMethodTransformer import dev.openrs2.deob.transform.VisibilityTransformer import java.nio.file.Files import java.nio.file.Path @@ -168,6 +169,7 @@ class Deobfuscator(private val input: Path, private val output: Path) { DummyArgTransformer(), DummyLocalTransformer(), UnusedArgTransformer(), + UnusedMethodTransformer(), CounterTransformer(), ResetTransformer(), ClassLiteralTransformer(), diff --git a/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt new file mode 100644 index 0000000000..aa32056e61 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt @@ -0,0 +1,86 @@ +package dev.openrs2.deob.transform + +import com.github.michaelbull.logging.InlineLogger +import com.google.common.collect.HashMultimap +import com.google.common.collect.Multimap +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.common.collect.DisjointSet +import dev.openrs2.common.collect.removeFirst +import dev.openrs2.deob.remap.TypedRemapper +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode + +class UnusedMethodTransformer : Transformer() { + private lateinit var inheritedMethodSets: DisjointSet + private val methodReferences = HashMultimap.create, MemberRef>() + + override fun preTransform(classPath: ClassPath) { + inheritedMethodSets = classPath.createInheritedMethodSets() + methodReferences.clear() + } + + override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { + for (insn in method.instructions) { + if (insn is MethodInsnNode) { + addReference(methodReferences, inheritedMethodSets, MemberRef(insn), MemberRef(clazz, method)) + } + } + + return false + } + + override fun postTransform(classPath: ClassPath) { + var methodsRemoved = 0 + + for (library in classPath.libraries) { + for (clazz in library) { + val methods = clazz.methods.iterator() + + for (method in methods) { + if (method.access and Opcodes.ACC_NATIVE != 0 || method.name in TypedRemapper.EXCLUDED_METHODS) { + continue + } + + val member = MemberRef(clazz, method) + val partition = inheritedMethodSets[member]!! + + if (partition.any { classPath[it.owner]!!.dependency }) { + continue + } + + val references = methodReferences[partition] + if (references.isEmpty() || references.size == 1 && references.first() == member) { + methods.remove() + methodsRemoved++ + + for (ref in partition) { + val owner = library[ref.owner]!! + owner.methods.removeFirst { it.name == ref.name && it.desc == ref.desc } + } + } + } + } + } + + logger.info { "Removed $methodsRemoved unused methods" } + } + + private companion object { + private val logger = InlineLogger() + + private fun addReference( + references: Multimap, MemberRef>, + disjointSet: DisjointSet, + member: MemberRef, + className: MemberRef + ) { + val partition = disjointSet[member] ?: return + references.put(partition, className) + } + } +}