Skip to content

Commit 1fa52fb

Browse files
committed
improve local variable inlining
1 parent d9de32d commit 1fa52fb

File tree

5 files changed

+175
-93
lines changed

5 files changed

+175
-93
lines changed

deobfuscator-api/src/main/java/org/objectweb/asm/tree/AbstractInsnNode.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.List;
3434
import java.util.Map;
3535
import java.util.function.Consumer;
36+
import java.util.function.Function;
3637
import java.util.function.Predicate;
3738

3839
import org.jetbrains.annotations.Nullable;
@@ -633,6 +634,53 @@ public AbstractInsnNode walkNextUntil(
633634
return current;
634635
}
635636

637+
/**
638+
* Follows jumps until a condition is met. Follows only GOTO
639+
*
640+
* @param until Condition to stop walking
641+
* @return The instruction that matches the condition or {@code null} if no such instruction is found
642+
*/
643+
@Nullable
644+
public AbstractInsnNode followJumpsUntil(
645+
Function<AbstractInsnNode, PredicateResult> until,
646+
Predicate<AbstractInsnNode> filter,
647+
Consumer<AbstractInsnNode> consumer
648+
) {
649+
AbstractInsnNode current = this;
650+
PredicateResult result = null;
651+
while (current != null && (result = until.apply(current)) == PredicateResult.CONTINUE) {
652+
if (current instanceof JumpInsnNode jumpInsn) {
653+
if (current.getOpcode() == GOTO) {
654+
current = jumpInsn.label;
655+
continue;
656+
} else {
657+
// If it's not a GOTO, we stop here
658+
return null;
659+
}
660+
}
661+
662+
if (filter.test(current)) {
663+
consumer.accept(current);
664+
}
665+
666+
current = current.getNext();
667+
}
668+
669+
if (result == PredicateResult.SUCCESS) {
670+
return current;
671+
} else if (result == PredicateResult.FAILED) {
672+
return null; // Failed to find a matching instruction
673+
} else {
674+
return null;
675+
}
676+
}
677+
678+
public enum PredicateResult {
679+
FAILED,
680+
CONTINUE,
681+
SUCCESS
682+
}
683+
636684
public String namedOpcode() {
637685
return NamedOpcodes.map(this.getOpcode());
638686
}

deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
import org.objectweb.asm.Opcodes;
88
import org.objectweb.asm.Type;
99
import org.objectweb.asm.tree.*;
10+
import org.objectweb.asm.tree.analysis.Frame;
11+
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
1012
import org.objectweb.asm.tree.analysis.Value;
1113
import uwu.narumi.deobfuscator.api.asm.ClassWrapper;
14+
import uwu.narumi.deobfuscator.api.asm.InsnContext;
1215
import uwu.narumi.deobfuscator.api.asm.MethodContext;
1316
import uwu.narumi.deobfuscator.api.asm.MethodRef;
1417
import uwu.narumi.deobfuscator.api.context.Context;
@@ -271,4 +274,40 @@ public static List<AbstractInsnNode> getInstructionsBetween(
271274

272275
return instructions;
273276
}
277+
278+
/**
279+
* Counts variable usages in the method.
280+
*
281+
* @param methodContext Method context to analyze
282+
* @return A map of var store instructions -> usage count (load, iinc) in the method.
283+
*/
284+
public static Map<VarInsnNode, Integer> getVarUsages(MethodContext methodContext) {
285+
Map<VarInsnNode, Integer> varUsages = new HashMap<>();
286+
287+
for (AbstractInsnNode insn : methodContext.methodNode().instructions.toArray()) {
288+
if ((insn instanceof VarInsnNode && !insn.isVarStore()) || insn instanceof IincInsnNode) {
289+
InsnContext insnContext = methodContext.at(insn);
290+
291+
Frame<OriginalSourceValue> frame = insnContext.frame();
292+
if (frame == null) continue;
293+
294+
int varIndex;
295+
if (insn instanceof VarInsnNode varInsnNode) {
296+
varIndex = varInsnNode.var;
297+
} else {
298+
varIndex = ((IincInsnNode) insn).var;
299+
}
300+
301+
OriginalSourceValue localVariableSourceValue = frame.getLocal(varIndex);
302+
for (AbstractInsnNode sourceInsn : localVariableSourceValue.insns) {
303+
// Save var stores in use
304+
if (sourceInsn.isVarStore()) {
305+
varUsages.merge((VarInsnNode) sourceInsn, 1, Integer::sum);
306+
}
307+
}
308+
}
309+
}
310+
311+
return varUsages;
312+
}
274313
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/clean/peephole/PopUnUsedLocalVariablesTransformer.java

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package uwu.narumi.deobfuscator.core.other.impl.clean.peephole;
22

33
import org.objectweb.asm.tree.AbstractInsnNode;
4-
import org.objectweb.asm.tree.IincInsnNode;
54
import org.objectweb.asm.tree.VarInsnNode;
6-
import org.objectweb.asm.tree.analysis.Frame;
7-
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
85
import uwu.narumi.deobfuscator.api.asm.InsnContext;
96
import uwu.narumi.deobfuscator.api.asm.MethodContext;
7+
import uwu.narumi.deobfuscator.api.helper.AsmHelper;
108
import uwu.narumi.deobfuscator.api.transformer.Transformer;
119

12-
import java.util.HashSet;
1310
import java.util.Set;
1411

1512
public class PopUnUsedLocalVariablesTransformer extends Transformer {
@@ -19,32 +16,7 @@ protected void transform() throws Exception {
1916
scopedClasses().parallelStream().forEach(classWrapper -> classWrapper.methods().parallelStream().forEach(methodNode -> {
2017
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
2118

22-
Set<VarInsnNode> varStoresInUse = new HashSet<>();
23-
24-
// Find all local variables in use
25-
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
26-
if ((insn instanceof VarInsnNode && !insn.isVarStore()) || insn instanceof IincInsnNode) {
27-
InsnContext insnContext = methodContext.at(insn);
28-
29-
Frame<OriginalSourceValue> frame = insnContext.frame();
30-
if (frame == null) return;
31-
32-
int varIndex;
33-
if (insn instanceof VarInsnNode varInsnNode) {
34-
varIndex = varInsnNode.var;
35-
} else {
36-
varIndex = ((IincInsnNode) insn).var;
37-
}
38-
39-
OriginalSourceValue localVariableSourceValue = frame.getLocal(varIndex);
40-
for (AbstractInsnNode sourceInsn : localVariableSourceValue.insns) {
41-
// Save var stores in use
42-
if (sourceInsn.isVarStore()) {
43-
varStoresInUse.add((VarInsnNode) sourceInsn);
44-
}
45-
}
46-
}
47-
}
19+
Set<VarInsnNode> varStoresInUse = AsmHelper.getVarUsages(methodContext).keySet();
4820

4921
// Remove all local variables that are not in use
5022
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
package uwu.narumi.deobfuscator.core.other.impl.pool;
22

33
import org.objectweb.asm.tree.AbstractInsnNode;
4-
import org.objectweb.asm.tree.IincInsnNode;
54
import org.objectweb.asm.tree.VarInsnNode;
65
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
6+
import uwu.narumi.deobfuscator.api.asm.InsnContext;
77
import uwu.narumi.deobfuscator.api.asm.MethodContext;
8-
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
9-
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
10-
import uwu.narumi.deobfuscator.api.asm.matcher.impl.VarLoadMatch;
8+
import uwu.narumi.deobfuscator.api.helper.AsmHelper;
119
import uwu.narumi.deobfuscator.api.helper.FramedInstructionsStream;
1210
import uwu.narumi.deobfuscator.api.transformer.Transformer;
1311

14-
import java.util.HashMap;
12+
import java.util.ArrayList;
13+
import java.util.Collections;
14+
import java.util.List;
1515
import java.util.Map;
1616

1717
/**
18-
* Inlines constant local variables
18+
* Inlines local variables (constants and next to each other var load/store pairs).
1919
*/
2020
public class InlineLocalVariablesTransformer extends Transformer {
21-
private static final Match VAR_STORE_LOAD_PAIR = SequenceMatch.of(
22-
// Store
23-
Match.of(ctx -> ctx.insn().isVarStore()).capture("store"),
24-
// Load
25-
Match.of(ctx -> ctx.insn().isVarLoad()).capture("load")
26-
);
2721

2822
@Override
2923
protected void transform() throws Exception {
@@ -49,38 +43,68 @@ protected void transform() throws Exception {
4943
}
5044
});
5145

52-
// Find all variable store and load pairs
46+
// Inline var load/store pairs that are next to each other and used only once
5347
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
48+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
49+
5450
// Count var usages
55-
Map<Integer, Integer> varUsages = new HashMap<>(); // var index -> usage count
51+
Map<VarInsnNode, Integer> varUsages = AsmHelper.getVarUsages(methodContext);
52+
5653
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
57-
if (insn instanceof VarInsnNode && insn.isVarLoad()) {
58-
varUsages.merge(((VarInsnNode) insn).var, 1, Integer::sum);
59-
} else if (insn instanceof IincInsnNode) {
60-
varUsages.merge(((IincInsnNode) insn).var, 1, Integer::sum);
61-
}
62-
}
54+
if (insn.isVarStore()) {
55+
if (!varUsages.containsKey((VarInsnNode) insn) || varUsages.get((VarInsnNode) insn) > 1) {
56+
// If the variable is used more than once, we cannot inline
57+
continue;
58+
}
6359

64-
// Now we can find pairs
65-
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
66-
VAR_STORE_LOAD_PAIR.findAllMatches(methodContext).forEach(match -> {
67-
VarInsnNode varLoadInsn = (VarInsnNode) match.captures().get("load").insn();
68-
VarInsnNode varStoreInsn = (VarInsnNode) match.captures().get("store").insn();
69-
if (varLoadInsn.var != varStoreInsn.var) {
70-
// If they are not the same variable, we cannot inline
71-
return;
72-
}
73-
int varIndex = varLoadInsn.var;
74-
if (varUsages.getOrDefault(varIndex, 0) > 1) {
75-
// If the variable is used more than once, we cannot inline
76-
return;
77-
}
60+
List<VarInsnNode> varStoreOrder = new ArrayList<>();
61+
// Collect all variable indexes in order of their usage
62+
AbstractInsnNode end = insn.followJumpsUntil(insn2 -> {
63+
if (insn2.isVarStore()) {
64+
return AbstractInsnNode.PredicateResult.CONTINUE;
65+
}
66+
return AbstractInsnNode.PredicateResult.SUCCESS;
67+
}, AbstractInsnNode::isVarStore, insn2 -> varStoreOrder.add((VarInsnNode)insn2));
68+
if (end == null) {
69+
// If we cannot find the end of the sequence, we cannot inline
70+
continue;
71+
}
72+
73+
// Check if we have var load instructions in the same order
74+
List<VarInsnNode> varLoadOrder = new ArrayList<>();
75+
end.followJumpsUntil(insn2 -> {
76+
if (insn2.isVarLoad()) {
77+
InsnContext insnContext = methodContext.at(insn2);
78+
OriginalSourceValue localVariableSourceValue = insnContext.frame().getLocal(((VarInsnNode) insn2).var);
79+
if (!localVariableSourceValue.isOneWayProduced()) {
80+
// If the variable is not one-way produced, we cannot inline
81+
return AbstractInsnNode.PredicateResult.FAILED;
82+
}
83+
return AbstractInsnNode.PredicateResult.CONTINUE;
84+
}
85+
86+
return AbstractInsnNode.PredicateResult.SUCCESS;
87+
}, AbstractInsnNode::isVarLoad, insn2 -> varLoadOrder.add((VarInsnNode)insn2));
88+
89+
List<Integer> varStoreIndexesOrder = varStoreOrder.stream()
90+
.map(varInsn -> varInsn.var)
91+
.toList();
92+
// In reverse order to match the stack order
93+
List<Integer> varLoadIndexesOrder = varLoadOrder.stream()
94+
.map(varInsn -> varInsn.var)
95+
.sorted(Collections.reverseOrder())
96+
.toList();
7897

79-
// Remove the variable completely and use stack value instead
80-
match.removeAll();
98+
// Var load and stores are in the same order and next to each other. We can inline them
99+
if (varStoreIndexesOrder.equals(varLoadIndexesOrder)) {
100+
// Inline (use stack instead of var load/store)
101+
varStoreOrder.forEach(varInsn -> methodNode.instructions.remove(varInsn));
102+
varLoadOrder.forEach(varInsn -> methodNode.instructions.remove(varInsn));
81103

82-
markChange();
83-
});
104+
markChange();
105+
}
106+
}
107+
}
84108
}));
85109
}
86110
}

testData/results/custom-classes/qprotect/sample3/fastcode/IlllIIlIIlllIIIl.dec

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -365,16 +365,15 @@ public class IlllIIlIIlllIIIl {
365365
case 254 -> 134;
366366
default -> 29907 - 29696;
367367
};
368-
short var15 = (short)var1;
369-
var14 = (~var15 | 0xFF) - ~var15;
370-
int var5 = var14 + ~var4 + 1;
368+
int var15 = (short)var1;
369+
int var5 = (~var15 | 0xFF) - ~var15 + ~var4 + 1;
371370
if (var5 < 0) {
372371
var5 += 256;
373372
}
374373

375-
int var17 = (short)var1;
376-
var17 = (~var17 | 65535) - ~var17 >>> 8;
377-
int var6 = (var17 ^ var4) - 2 * (~var17 & var4);
374+
var15 = (short)var1;
375+
var14 = (~var15 | 65535) - ~var15 >>> 8;
376+
int var6 = (var14 ^ var4) - 2 * (~var14 & var4);
378377
if (var6 < 0) {
379378
var6 += 256;
380379
}
@@ -383,27 +382,27 @@ public class IlllIIlIIlllIIIl {
383382
int var8 = var7 % 2;
384383
char var10002 = var3[var7];
385384
if (var8 == 0) {
386-
int var19 = var10002;
387-
var3[var7] = (char)((var19 | var5) & ~(var19 & var5));
388-
int var30 = var5 >>> 3;
385+
char var18 = var10002;
386+
var3[var7] = (char)((var18 | var5) & ~(var18 & var5));
387+
int var29 = var5 >>> 3;
389388
int var13 = var5 << 5;
390-
var19 = var30;
391-
var30 = (var19 & ~var13) + var13;
392-
char var27 = var3[var7];
393-
var19 = var30;
394-
var19 = var19 & ~var27 | ~var19 & var27;
395-
var5 = (~var19 | 0xFF) - ~var19;
389+
var15 = var29;
390+
var29 = (var15 & ~var13) + var13;
391+
char var26 = var3[var7];
392+
var15 = var29;
393+
var15 = var15 & ~var26 | ~var15 & var26;
394+
var5 = (~var15 | 0xFF) - ~var15;
396395
} else {
397-
int var23 = var10002;
398-
var3[var7] = (char)((var23 | var6) - (var23 & var6));
399-
int var32 = var6 >>> 3;
400-
int var28 = var6 << 5;
401-
var23 = var32;
402-
var32 = (var23 & ~var28) + var28;
403-
char var29 = var3[var7];
404-
var23 = var32;
405-
var23 = (var23 | var29) - (var23 & var29);
406-
var6 = (~var23 | 0xFF) - ~var23;
396+
int var22 = var10002;
397+
var3[var7] = (char)((var22 | var6) - (var22 & var6));
398+
int var31 = var6 >>> 3;
399+
int var27 = var6 << 5;
400+
var22 = var31;
401+
var31 = (var22 & ~var27) + var27;
402+
char var28 = var3[var7];
403+
var22 = var31;
404+
var22 = (var22 | var28) - (var22 & var28);
405+
var6 = (~var22 | 0xFF) - ~var22;
407406
}
408407
}
409408

0 commit comments

Comments
 (0)