forked from openrs2/openrs2
parent
55646c8384
commit
3c1ec9f210
@ -0,0 +1,258 @@ |
|||||||
|
package dev.openrs2.deob.transform; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
import com.google.common.collect.HashMultimap; |
||||||
|
import com.google.common.collect.Multimap; |
||||||
|
import dev.openrs2.asm.InsnNodeUtils; |
||||||
|
import dev.openrs2.asm.MemberRef; |
||||||
|
import dev.openrs2.asm.classpath.ClassPath; |
||||||
|
import dev.openrs2.asm.transform.Transformer; |
||||||
|
import dev.openrs2.deob.analysis.IntInterpreter; |
||||||
|
import dev.openrs2.deob.analysis.IntValue; |
||||||
|
import dev.openrs2.util.collect.DisjointSet; |
||||||
|
import org.objectweb.asm.Opcodes; |
||||||
|
import org.objectweb.asm.Type; |
||||||
|
import org.objectweb.asm.tree.ClassNode; |
||||||
|
import org.objectweb.asm.tree.InsnNode; |
||||||
|
import org.objectweb.asm.tree.JumpInsnNode; |
||||||
|
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 DummyTransformer extends Transformer { |
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DummyTransformer.class); |
||||||
|
|
||||||
|
private static final class ArgRef { |
||||||
|
private final DisjointSet.Partition<MemberRef> method; |
||||||
|
private final int arg; |
||||||
|
|
||||||
|
public ArgRef(DisjointSet.Partition<MemberRef> method, int arg) { |
||||||
|
this.method = method; |
||||||
|
this.arg = arg; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object o) { |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o == null || getClass() != o.getClass()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
ArgRef argRef = (ArgRef) o; |
||||||
|
return arg == argRef.arg && |
||||||
|
method.equals(argRef.method); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return Objects.hash(method, arg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean evaluateUnaryBranch(int opcode, int value) { |
||||||
|
switch (opcode) { |
||||||
|
case Opcodes.IFEQ: |
||||||
|
return value == 0; |
||||||
|
case Opcodes.IFNE: |
||||||
|
return value != 0; |
||||||
|
default: |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean evaluateBinaryBranch(int opcode, int value1, int value2) { |
||||||
|
switch (opcode) { |
||||||
|
case Opcodes.IF_ICMPEQ: |
||||||
|
return value1 == value2; |
||||||
|
case Opcodes.IF_ICMPNE: |
||||||
|
return value1 != value2; |
||||||
|
case Opcodes.IF_ICMPLT: |
||||||
|
return value1 < value2; |
||||||
|
case Opcodes.IF_ICMPGE: |
||||||
|
return value1 >= value2; |
||||||
|
case Opcodes.IF_ICMPGT: |
||||||
|
return value1 > value2; |
||||||
|
case Opcodes.IF_ICMPLE: |
||||||
|
return value1 <= value2; |
||||||
|
default: |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final Multimap<ArgRef, IntValue> argValues = HashMultimap.create(); |
||||||
|
private final Map<DisjointSet.Partition<MemberRef>, Integer[]> constArgs = new HashMap<>(); |
||||||
|
private DisjointSet<MemberRef> inheritedMethodSets; |
||||||
|
private int loadsInlined, branchesSimplified; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void preTransform(ClassPath classPath) { |
||||||
|
inheritedMethodSets = classPath.createInheritedMethodSets(); |
||||||
|
loadsInlined = 0; |
||||||
|
branchesSimplified = 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void prePass(ClassPath classPath) { |
||||||
|
argValues.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean transformCode(ClassNode clazz, MethodNode method) throws AnalyzerException { |
||||||
|
var parentMethod = inheritedMethodSets.get(new MemberRef(clazz.name, method.name, method.desc)); |
||||||
|
var parameters = constArgs.get(parentMethod); |
||||||
|
|
||||||
|
var analyzer = new Analyzer<>(new IntInterpreter(parameters)); |
||||||
|
analyzer.analyze(clazz.name, method); |
||||||
|
|
||||||
|
var frames = analyzer.getFrames(); |
||||||
|
|
||||||
|
var changed = false; |
||||||
|
|
||||||
|
var alwaysTakenUnaryBranches = new ArrayList<JumpInsnNode>(); |
||||||
|
var neverTakenUnaryBranches = new ArrayList<JumpInsnNode>(); |
||||||
|
|
||||||
|
var alwaysTakenBinaryBranches = new ArrayList<JumpInsnNode>(); |
||||||
|
var neverTakenBinaryBranches = new ArrayList<JumpInsnNode>(); |
||||||
|
|
||||||
|
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.INVOKEVIRTUAL: |
||||||
|
case Opcodes.INVOKESPECIAL: |
||||||
|
case Opcodes.INVOKESTATIC: |
||||||
|
case Opcodes.INVOKEINTERFACE: |
||||||
|
var invoke = (MethodInsnNode) insn; |
||||||
|
var invokedMethod = inheritedMethodSets.get(new MemberRef(invoke.owner, invoke.name, invoke.desc)); |
||||||
|
if (invokedMethod == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
var args = Type.getArgumentTypes(invoke.desc).length; |
||||||
|
for (int j = 0, k = 0; j < args; j++) { |
||||||
|
var arg = frame.getStack(stackSize - args + j); |
||||||
|
argValues.put(new ArgRef(invokedMethod, k), arg); |
||||||
|
k += arg.getSize(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case Opcodes.ILOAD: |
||||||
|
var iload = (VarInsnNode) insn; |
||||||
|
var value = frame.getLocal(iload.var); |
||||||
|
if (value.isConstant()) { |
||||||
|
method.instructions.set(insn, InsnNodeUtils.createIntConstant(value.getIntValue())); |
||||||
|
loadsInlined++; |
||||||
|
changed = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
case Opcodes.IFEQ: |
||||||
|
case Opcodes.IFNE: |
||||||
|
value = frame.getStack(stackSize - 1); |
||||||
|
if (!value.isConstant()) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
var taken = evaluateUnaryBranch(insn.getOpcode(), value.getIntValue()); |
||||||
|
if (taken) { |
||||||
|
alwaysTakenUnaryBranches.add((JumpInsnNode) insn); |
||||||
|
} else { |
||||||
|
neverTakenUnaryBranches.add((JumpInsnNode) insn); |
||||||
|
} |
||||||
|
break; |
||||||
|
case Opcodes.IF_ICMPEQ: |
||||||
|
case Opcodes.IF_ICMPNE: |
||||||
|
case Opcodes.IF_ICMPLT: |
||||||
|
case Opcodes.IF_ICMPGE: |
||||||
|
case Opcodes.IF_ICMPGT: |
||||||
|
case Opcodes.IF_ICMPLE: |
||||||
|
var value1 = frame.getStack(stackSize - 2); |
||||||
|
if (!value1.isConstant()) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
var value2 = frame.getStack(stackSize - 1); |
||||||
|
if (!value2.isConstant()) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
taken = evaluateBinaryBranch(insn.getOpcode(), value1.getIntValue(), value2.getIntValue()); |
||||||
|
if (taken) { |
||||||
|
alwaysTakenBinaryBranches.add((JumpInsnNode) insn); |
||||||
|
} else { |
||||||
|
neverTakenBinaryBranches.add((JumpInsnNode) insn); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (var insn : alwaysTakenUnaryBranches) { |
||||||
|
method.instructions.insertBefore(insn, new InsnNode(Opcodes.POP)); |
||||||
|
method.instructions.set(insn, new JumpInsnNode(Opcodes.GOTO, insn.label)); |
||||||
|
branchesSimplified++; |
||||||
|
changed = true; |
||||||
|
} |
||||||
|
|
||||||
|
for (var insn : neverTakenUnaryBranches) { |
||||||
|
method.instructions.set(insn, new InsnNode(Opcodes.POP)); |
||||||
|
branchesSimplified++; |
||||||
|
changed = true; |
||||||
|
} |
||||||
|
|
||||||
|
for (var insn : alwaysTakenBinaryBranches) { |
||||||
|
method.instructions.insertBefore(insn, new InsnNode(Opcodes.POP)); |
||||||
|
method.instructions.insertBefore(insn, new InsnNode(Opcodes.POP)); |
||||||
|
method.instructions.set(insn, new JumpInsnNode(Opcodes.GOTO, insn.label)); |
||||||
|
branchesSimplified++; |
||||||
|
changed = true; |
||||||
|
} |
||||||
|
|
||||||
|
for (var insn : neverTakenBinaryBranches) { |
||||||
|
method.instructions.insertBefore(insn, new InsnNode(Opcodes.POP)); |
||||||
|
method.instructions.set(insn, new InsnNode(Opcodes.POP)); |
||||||
|
branchesSimplified++; |
||||||
|
changed = true; |
||||||
|
} |
||||||
|
|
||||||
|
return changed; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void postPass(ClassPath classPath) { |
||||||
|
for (var method : inheritedMethodSets) { |
||||||
|
var args = (Type.getArgumentsAndReturnSizes(method.iterator().next().getDesc()) >> 2) - 1; |
||||||
|
|
||||||
|
var parameters = new Integer[args]; |
||||||
|
for (int i = 0; i < args; i++) { |
||||||
|
var values = argValues.get(new ArgRef(method, i)); |
||||||
|
if (values.size() != 1) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
var value = values.iterator().next(); |
||||||
|
if (value.isConstant()) { |
||||||
|
parameters[i] = value.getIntValue(); |
||||||
|
} |
||||||
|
} |
||||||
|
constArgs.put(method, parameters); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void postTransform(ClassPath classPath) { |
||||||
|
logger.info("Inlined {} dummy loads and simplified {} dummy branches", loadsInlined, branchesSimplified); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue