forked from openrs2/openrs2
parent
d20e3997b4
commit
28369f1d75
@ -0,0 +1,202 @@ |
||||
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.MethodNodeUtils; |
||||
import dev.openrs2.asm.classpath.ClassPath; |
||||
import dev.openrs2.asm.classpath.Library; |
||||
import dev.openrs2.asm.transform.Transformer; |
||||
import dev.openrs2.deob.ArgRef; |
||||
import dev.openrs2.deob.analysis.ConstSourceInterpreter; |
||||
import dev.openrs2.deob.remap.TypedRemapper; |
||||
import dev.openrs2.util.collect.DisjointSet; |
||||
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 ((method.access & (Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT)) == 0) { |
||||
populateRetainedArgs(classPath, clazz, method); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void populateRetainedArgs(ClassPath classPath, ClassNode clazz, MethodNode method) throws AnalyzerException { |
||||
var partition = inheritedMethodSets.get(new MemberRef(clazz.name, method.name, method.desc)); |
||||
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.owner, invoke.name, invoke.desc)); |
||||
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 ((method.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0) { |
||||
var analyzer = new Analyzer<>(new ConstSourceInterpreter()); |
||||
analyzer.analyze(clazz.name, method); |
||||
|
||||
var frames = analyzer.getFrames(); |
||||
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.owner, methodInsn.name, methodInsn.desc)); |
||||
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.name, method.name, method.desc)); |
||||
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))) { |
||||
MethodNodeUtils.deleteArgument(method, i); |
||||
deletedArgs++; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
protected void postTransform(ClassPath classPath) { |
||||
logger.info("Removed {} dummy arguments", deletedArgs); |
||||
} |
||||
} |
Loading…
Reference in new issue