forked from openrs2/openrs2
parent
c10134c1f6
commit
0823a0253f
@ -1,200 +0,0 @@ |
||||
package dev.openrs2.deob.transform; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import com.google.common.collect.ImmutableSet; |
||||
import dev.openrs2.asm.MemberRef; |
||||
import dev.openrs2.asm.MethodNodeUtilsKt; |
||||
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.deob.ArgRef; |
||||
import dev.openrs2.deob.analysis.ConstSourceInterpreter; |
||||
import dev.openrs2.deob.remap.TypedRemapper; |
||||
import org.objectweb.asm.Opcodes; |
||||
import org.objectweb.asm.Type; |
||||
import org.objectweb.asm.tree.AbstractInsnNode; |
||||
import org.objectweb.asm.tree.ClassNode; |
||||
import org.objectweb.asm.tree.MethodInsnNode; |
||||
import org.objectweb.asm.tree.MethodNode; |
||||
import org.objectweb.asm.tree.VarInsnNode; |
||||
import org.objectweb.asm.tree.analysis.Analyzer; |
||||
import org.objectweb.asm.tree.analysis.AnalyzerException; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public final class UnusedArgTransformer extends Transformer { |
||||
private static final Logger logger = LoggerFactory.getLogger(UnusedArgTransformer.class); |
||||
|
||||
private static final ImmutableSet<Integer> INT_SORTS = ImmutableSet.of(Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.INT, Type.CHAR); |
||||
|
||||
private static Map<Integer, Integer> createLocalToArgMap(MethodNode method) { |
||||
var type = Type.getType(method.desc); |
||||
var argumentTypes = type.getArgumentTypes(); |
||||
|
||||
var map = new HashMap<Integer, Integer>(); |
||||
var argIndex = 0; |
||||
var localIndex = 0; |
||||
|
||||
if ((method.access & Opcodes.ACC_STATIC) == 0) { |
||||
localIndex++; |
||||
} |
||||
|
||||
for (var t : argumentTypes) { |
||||
map.put(localIndex, argIndex++); |
||||
localIndex += t.getSize(); |
||||
} |
||||
|
||||
return map; |
||||
} |
||||
|
||||
private final Set<ArgRef> retainedArgs = new HashSet<>(); |
||||
private DisjointSet<MemberRef> inheritedMethodSets; |
||||
private int deletedArgs; |
||||
|
||||
@Override |
||||
protected void preTransform(ClassPath classPath) throws AnalyzerException { |
||||
retainedArgs.clear(); |
||||
inheritedMethodSets = classPath.createInheritedMethodSets(); |
||||
deletedArgs = 0; |
||||
|
||||
for (var library : classPath.getLibraries()) { |
||||
for (var clazz : library) { |
||||
for (var method : clazz.methods) { |
||||
if (MethodNodeUtilsKt.hasCode(method)) { |
||||
populateRetainedArgs(classPath, clazz, method); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void populateRetainedArgs(ClassPath classPath, ClassNode clazz, MethodNode method) throws AnalyzerException { |
||||
var partition = inheritedMethodSets.get(new MemberRef(clazz, method)); |
||||
var localToArgMap = createLocalToArgMap(method); |
||||
|
||||
var analyzer = new Analyzer<>(new ConstSourceInterpreter()); |
||||
var frames = analyzer.analyze(clazz.name, method); |
||||
|
||||
for (var i = 0; i < frames.length; i++) { |
||||
var frame = frames[i]; |
||||
if (frame == null) { |
||||
continue; |
||||
} |
||||
|
||||
var stackSize = frame.getStackSize(); |
||||
|
||||
var insn = method.instructions.get(i); |
||||
switch (insn.getOpcode()) { |
||||
case Opcodes.ILOAD: |
||||
var iload = (VarInsnNode) insn; |
||||
var arg = localToArgMap.get(iload.var); |
||||
if (arg != null) { |
||||
retainedArgs.add(new ArgRef(partition, arg)); |
||||
} |
||||
break; |
||||
case Opcodes.INVOKEVIRTUAL: |
||||
case Opcodes.INVOKESPECIAL: |
||||
case Opcodes.INVOKESTATIC: |
||||
case Opcodes.INVOKEINTERFACE: |
||||
var invoke = (MethodInsnNode) insn; |
||||
var invokePartition = inheritedMethodSets.get(new MemberRef(invoke)); |
||||
if (invokePartition == null || TypedRemapper.isMethodImmutable(classPath, invokePartition)) { |
||||
continue; |
||||
} |
||||
|
||||
var args = Type.getArgumentTypes(invoke.desc).length; |
||||
for (var j = 0; j < args; j++) { |
||||
var source = frame.getStack(stackSize - args + j); |
||||
if (!source.isSingleSourceConstant()) { |
||||
retainedArgs.add(new ArgRef(invokePartition, j)); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected boolean preTransformMethod(ClassPath classPath, Library library, ClassNode clazz, MethodNode method) throws AnalyzerException { |
||||
/* delete unused int args from call sites */ |
||||
if (MethodNodeUtilsKt.hasCode(method)) { |
||||
var analyzer = new Analyzer<>(new ConstSourceInterpreter()); |
||||
var frames = analyzer.analyze(clazz.name, method); |
||||
var deadInsns = new ArrayList<AbstractInsnNode>(); |
||||
|
||||
for (var i = 0; i < frames.length; i++) { |
||||
var frame = frames[i]; |
||||
if (frame == null) { |
||||
continue; |
||||
} |
||||
|
||||
var stackSize = frame.getStackSize(); |
||||
|
||||
var insn = method.instructions.get(i); |
||||
if (insn.getType() != AbstractInsnNode.METHOD_INSN) { |
||||
continue; |
||||
} |
||||
|
||||
var methodInsn = (MethodInsnNode) insn; |
||||
var partition = inheritedMethodSets.get(new MemberRef(methodInsn)); |
||||
if (partition == null || TypedRemapper.isMethodImmutable(classPath, partition)) { |
||||
continue; |
||||
} |
||||
|
||||
var type = Type.getType(methodInsn.desc); |
||||
var argTypes = type.getArgumentTypes(); |
||||
|
||||
var newArgTypes = new ArrayList<Type>(); |
||||
|
||||
for (var j = 0; j < argTypes.length; j++) { |
||||
var argType = argTypes[j]; |
||||
if (INT_SORTS.contains(argType.getSort()) && !retainedArgs.contains(new ArgRef(partition, j))) { |
||||
var source = frame.getStack(stackSize - argTypes.length + j).getSource(); |
||||
deadInsns.add(source); |
||||
} else { |
||||
newArgTypes.add(argType); |
||||
} |
||||
} |
||||
|
||||
methodInsn.desc = Type.getMethodDescriptor(type.getReturnType(), newArgTypes.toArray(Type[]::new)); |
||||
} |
||||
|
||||
deadInsns.forEach(method.instructions::remove); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean postTransformMethod(ClassPath classPath, Library library, ClassNode clazz, MethodNode method) { |
||||
/* delete unused int args from the method itself */ |
||||
var partition = inheritedMethodSets.get(new MemberRef(clazz, method)); |
||||
if (TypedRemapper.isMethodImmutable(classPath, partition)) { |
||||
return false; |
||||
} |
||||
|
||||
var type = Type.getType(method.desc); |
||||
var argTypes = type.getArgumentTypes(); |
||||
|
||||
for (var i = argTypes.length - 1; i >= 0; i--) { |
||||
var argType = argTypes[i]; |
||||
if (INT_SORTS.contains(argType.getSort()) && !retainedArgs.contains(new ArgRef(partition, i))) { |
||||
MethodNodeUtilsKt.removeArgument(method, i); |
||||
deletedArgs++; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
protected void postTransform(ClassPath classPath) { |
||||
logger.info("Removed {} dummy arguments", deletedArgs); |
||||
} |
||||
} |
@ -0,0 +1,196 @@ |
||||
package dev.openrs2.deob.transform |
||||
|
||||
import com.github.michaelbull.logging.InlineLogger |
||||
import dev.openrs2.asm.MemberRef |
||||
import dev.openrs2.asm.classpath.ClassPath |
||||
import dev.openrs2.asm.classpath.Library |
||||
import dev.openrs2.asm.hasCode |
||||
import dev.openrs2.asm.removeArgument |
||||
import dev.openrs2.asm.transform.Transformer |
||||
import dev.openrs2.common.collect.DisjointSet |
||||
import dev.openrs2.deob.ArgRef |
||||
import dev.openrs2.deob.analysis.ConstSourceInterpreter |
||||
import dev.openrs2.deob.remap.TypedRemapper |
||||
import org.objectweb.asm.Opcodes |
||||
import org.objectweb.asm.Type |
||||
import org.objectweb.asm.tree.* |
||||
import org.objectweb.asm.tree.analysis.Analyzer |
||||
import org.objectweb.asm.tree.analysis.AnalyzerException |
||||
|
||||
class UnusedArgTransformer : Transformer() { |
||||
private val retainedArgs = mutableSetOf<ArgRef>() |
||||
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> |
||||
private var deletedArgs = 0 |
||||
|
||||
@Throws(AnalyzerException::class) |
||||
override fun preTransform(classPath: ClassPath) { |
||||
retainedArgs.clear() |
||||
inheritedMethodSets = classPath.createInheritedMethodSets() |
||||
deletedArgs = 0 |
||||
|
||||
for (library in classPath.libraries) { |
||||
for (clazz in library) { |
||||
for (method in clazz.methods) { |
||||
if (method.hasCode()) { |
||||
populateRetainedArgs(classPath, clazz, method) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Throws(AnalyzerException::class) |
||||
private fun populateRetainedArgs(classPath: ClassPath, clazz: ClassNode, method: MethodNode) { |
||||
val partition = inheritedMethodSets[MemberRef(clazz, method)]!! |
||||
val localToArgMap = createLocalToArgMap(method) |
||||
|
||||
val analyzer = Analyzer(ConstSourceInterpreter()) |
||||
val frames = analyzer.analyze(clazz.name, method) |
||||
|
||||
frame@ for ((i, frame) in frames.withIndex()) { |
||||
if (frame == null) { |
||||
continue |
||||
} |
||||
|
||||
val stackSize = frame.stackSize |
||||
|
||||
val insn = method.instructions[i] |
||||
when (insn) { |
||||
is VarInsnNode -> { |
||||
if (insn.opcode != Opcodes.ILOAD) { |
||||
continue@frame |
||||
} |
||||
|
||||
val arg = localToArgMap[insn.`var`] |
||||
if (arg != null) { |
||||
retainedArgs.add(ArgRef(partition, arg)) |
||||
} |
||||
} |
||||
is MethodInsnNode -> { |
||||
val invokePartition = inheritedMethodSets[MemberRef(insn)] |
||||
if (invokePartition == null || TypedRemapper.isMethodImmutable(classPath, invokePartition)) { |
||||
continue@frame |
||||
} |
||||
|
||||
val args = Type.getArgumentTypes(insn.desc).size |
||||
for (j in 0 until args) { |
||||
val source = frame.getStack(stackSize - args + j) |
||||
if (!source.isSingleSourceConstant) { |
||||
retainedArgs.add(ArgRef(invokePartition, j)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Throws(AnalyzerException::class) |
||||
override fun preTransformMethod( |
||||
classPath: ClassPath, |
||||
library: Library, |
||||
clazz: ClassNode, |
||||
method: MethodNode |
||||
): Boolean { |
||||
// delete unused int args from call sites |
||||
if (!method.hasCode()) { |
||||
return false |
||||
} |
||||
|
||||
val analyzer = Analyzer(ConstSourceInterpreter()) |
||||
val frames = analyzer.analyze(clazz.name, method) |
||||
val deadInsns = mutableListOf<AbstractInsnNode>() |
||||
|
||||
for ((i, frame) in frames.withIndex()) { |
||||
if (frame == null) { |
||||
continue |
||||
} |
||||
|
||||
val stackSize = frame.stackSize |
||||
|
||||
val insn = method.instructions[i] |
||||
if (insn !is MethodInsnNode) { |
||||
continue |
||||
} |
||||
|
||||
val partition = inheritedMethodSets[MemberRef(insn)] |
||||
if (partition == null || TypedRemapper.isMethodImmutable(classPath, partition)) { |
||||
continue |
||||
} |
||||
|
||||
val type = Type.getType(insn.desc) |
||||
val argTypes = type.argumentTypes |
||||
val newArgTypes = mutableListOf<Type>() |
||||
|
||||
for ((j, argType) in argTypes.withIndex()) { |
||||
if (INT_SORTS.contains(argType.sort) && !retainedArgs.contains(ArgRef(partition, j))) { |
||||
val source = frame.getStack(stackSize - argTypes.size + j).source |
||||
deadInsns.add(source) |
||||
} else { |
||||
newArgTypes.add(argType) |
||||
} |
||||
} |
||||
|
||||
insn.desc = Type.getMethodDescriptor(type.returnType, *newArgTypes.toTypedArray()) |
||||
} |
||||
|
||||
deadInsns.forEach(method.instructions::remove) |
||||
|
||||
return false |
||||
} |
||||
|
||||
override fun postTransformMethod( |
||||
classPath: ClassPath, |
||||
library: Library, |
||||
clazz: ClassNode, |
||||
method: MethodNode |
||||
): Boolean { |
||||
// delete unused int args from the method itself |
||||
val partition = inheritedMethodSets[MemberRef(clazz, method)]!! |
||||
if (TypedRemapper.isMethodImmutable(classPath, partition)) { |
||||
return false |
||||
} |
||||
|
||||
val argTypes = Type.getType(method.desc).argumentTypes |
||||
for ((i, argType) in argTypes.withIndex().reversed()) { |
||||
if (INT_SORTS.contains(argType.sort) && !retainedArgs.contains(ArgRef(partition, i))) { |
||||
method.removeArgument(i) |
||||
deletedArgs++ |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
override fun postTransform(classPath: ClassPath) { |
||||
logger.info { "Removed $deletedArgs dummy arguments" } |
||||
} |
||||
|
||||
companion object { |
||||
private val logger = InlineLogger() |
||||
private val INT_SORTS = setOf( |
||||
Type.BOOLEAN, |
||||
Type.BYTE, |
||||
Type.SHORT, |
||||
Type.INT, |
||||
Type.CHAR |
||||
) |
||||
|
||||
private fun createLocalToArgMap(method: MethodNode): Map<Int, Int> { |
||||
val argTypes = Type.getType(method.desc).argumentTypes |
||||
|
||||
val map = mutableMapOf<Int, Int>() |
||||
var localIndex = 0 |
||||
|
||||
if (method.access and Opcodes.ACC_STATIC == 0) { |
||||
localIndex++ |
||||
} |
||||
|
||||
for ((argIndex, t) in argTypes.withIndex()) { |
||||
map[localIndex] = argIndex |
||||
localIndex += t.size |
||||
} |
||||
|
||||
return map |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue