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