Skip to content

Commit a516024

Browse files
committed
take a different approach to computing consumers
1 parent 75cc093 commit a516024

File tree

12 files changed

+350
-82
lines changed

12 files changed

+350
-82
lines changed

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

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,7 @@ public OriginalSourceValue newOperation(final AbstractInsnNode insn) {
131131
@Override
132132
public OriginalSourceValue copyOperation(final AbstractInsnNode insn, final OriginalSourceValue value) {
133133
// Narumii start - Track the original value
134-
OriginalSourceValue sourceValue = new OriginalSourceValue(insn, value);
135-
value.getConsumers().addAll(sourceValue.insns);
136-
return sourceValue;
134+
return new OriginalSourceValue(insn, value);
137135
// Narumii end
138136
}
139137

@@ -160,20 +158,17 @@ public OriginalSourceValue unaryOperation(final AbstractInsnNode insn, final Ori
160158
}
161159

162160
// Narumii start - Predict constant
163-
OriginalSourceValue sourceValue = new OriginalSourceValue(size, insn);
164161
if (AsmMathHelper.isMathUnaryOperation(insn.getOpcode())) {
165162
OriginalSourceValue.ConstantValue constant = value.getConstantValue();
166163

167164
if (constant != null && constant.get() instanceof Number constNum) {
168165
Number result = AsmMathHelper.mathUnaryOperation(constNum, insn.getOpcode());
169-
sourceValue = new OriginalSourceValue(size, insn, null, OriginalSourceValue.ConstantValue.of(result));
166+
return new OriginalSourceValue(size, insn, null, OriginalSourceValue.ConstantValue.of(result));
170167
}
171168
}
172169
// Narumii end
173170

174-
value.getConsumers().addAll(sourceValue.insns);
175-
176-
return sourceValue;
171+
return new OriginalSourceValue(size, insn);
177172
}
178173

179174
@Override
@@ -207,25 +202,21 @@ public OriginalSourceValue binaryOperation(
207202
}
208203

209204
// Narumii start - Predict constant
210-
OriginalSourceValue sourceValue = new OriginalSourceValue(size, insn);
211205
if (AsmMathHelper.isMathBinaryOperation(insn.getOpcode())) {
212206
OriginalSourceValue.ConstantValue constant1 = value1.getConstantValue();
213207
OriginalSourceValue.ConstantValue constant2 = value2.getConstantValue();
214208

215209
if (constant1 != null && constant2 != null && constant1.get() instanceof Number constNum1 && constant2.get() instanceof Number constNum2) {
216210
try {
217211
Number result = AsmMathHelper.mathBinaryOperation(constNum1, constNum2, insn.getOpcode());
218-
sourceValue = new OriginalSourceValue(size, insn, null, OriginalSourceValue.ConstantValue.of(result));
212+
return new OriginalSourceValue(size, insn, null, OriginalSourceValue.ConstantValue.of(result));
219213
} catch (ArithmeticException ignored) {
220214
}
221215
}
222216
}
223217
// Narumii end
224218

225-
value1.getConsumers().addAll(sourceValue.insns);
226-
value2.getConsumers().addAll(sourceValue.insns);
227-
228-
return sourceValue;
219+
return new OriginalSourceValue(size, insn);
229220
}
230221

231222
@Override
@@ -234,11 +225,7 @@ public OriginalSourceValue ternaryOperation(
234225
final OriginalSourceValue value1,
235226
final OriginalSourceValue value2,
236227
final OriginalSourceValue value3) {
237-
OriginalSourceValue sourceValue = new OriginalSourceValue(1, insn);
238-
value1.getConsumers().addAll(sourceValue.insns);
239-
value2.getConsumers().addAll(sourceValue.insns);
240-
value3.getConsumers().addAll(sourceValue.insns);
241-
return sourceValue;
228+
return new OriginalSourceValue(1, insn);
242229
}
243230

244231
@Override
@@ -255,25 +242,20 @@ public OriginalSourceValue naryOperation(
255242
}
256243

257244
// Narumii start - Predict constant
258-
OriginalSourceValue sourceValue = new OriginalSourceValue(size, insn);
259245
MethodlessInsnContext insnContext = new MethodlessInsnContext(insn, null);
260246

261247
// Transform method calls on literals
262248
for (SimpleInterpreter.MethodInterpreter methodInterpreter : SimpleInterpreter.METHOD_INTERPRETERS) {
263249
if (methodInterpreter.match().matches(insnContext)) {
264250
OriginalSourceValue.ConstantValue constantValue = methodInterpreter.methodComputation().computeConstant(insn, values);
265251
if (constantValue != null) {
266-
sourceValue = new OriginalSourceValue(size, insn, null, constantValue);
252+
return new OriginalSourceValue(size, insn, null, constantValue);
267253
}
268254
}
269255
}
270256
// Narumii end
271257

272-
for (OriginalSourceValue value : values) {
273-
value.getConsumers().addAll(sourceValue.insns);
274-
}
275-
276-
return sourceValue;
258+
return new OriginalSourceValue(size, insn);
277259
}
278260

279261
@Override
@@ -289,41 +271,33 @@ public OriginalSourceValue merge(final OriginalSourceValue value1, final Origina
289271
!Objects.equals(value1.getConstantValue(), value2.getConstantValue()) ||
290272
value1.isMethodParameter() != value2.isMethodParameter() ||
291273
!containsAll(value1.insns, value2.insns) ||
292-
!containsAll(value1.getConsumers(), value2.getConsumers()) ||
293274
!Objects.equals(value1.copiedFrom, value2.copiedFrom)
294275
) {
295-
Set<AbstractInsnNode> producersUnion;
276+
Set<AbstractInsnNode> setUnion;
296277
if (value1.insns instanceof SmallSet && value2.insns instanceof SmallSet) {
297278
// Use optimized merging method
298-
producersUnion =
279+
setUnion =
299280
((SmallSet<AbstractInsnNode>) value1.insns)
300281
.union((SmallSet<AbstractInsnNode>) value2.insns);
301282
} else {
302-
producersUnion = new HashSet<>();
303-
producersUnion.addAll(value1.insns);
304-
producersUnion.addAll(value2.insns);
283+
setUnion = new HashSet<>();
284+
setUnion.addAll(value1.insns);
285+
setUnion.addAll(value2.insns);
305286
}
306287

307-
// Multiple producers
308-
OriginalSourceValue sourceValue = new OriginalSourceValue(Math.min(value1.size, value2.size), producersUnion);
309-
310288
// Single producer
311-
if (producersUnion.size() == 1) {
312-
AbstractInsnNode producer = producersUnion.iterator().next();
289+
if (setUnion.size() == 1) {
290+
AbstractInsnNode producer = setUnion.iterator().next();
313291

314292
OriginalSourceValue copiedFrom = null;
315293
if (value1.copiedFrom != null && value2.copiedFrom != null) {
316294
copiedFrom = this.merge(value1.copiedFrom, value2.copiedFrom);
317295
}
318296

319-
sourceValue = new OriginalSourceValue(Math.min(value1.size, value2.size), producer, copiedFrom, null);
297+
return new OriginalSourceValue(Math.min(value1.size, value2.size), producer, copiedFrom, null);
320298
}
321-
322-
// Merge consumers
323-
sourceValue.getConsumers().addAll(value1.getConsumers());
324-
sourceValue.getConsumers().addAll(value2.getConsumers());
325-
326-
return sourceValue;
299+
// Multiple producers
300+
return new OriginalSourceValue(Math.min(value1.size, value2.size), setUnion);
327301
}
328302
return value1;
329303
}

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.objectweb.asm.Type;
66
import org.objectweb.asm.tree.AbstractInsnNode;
77

8-
import java.util.HashSet;
98
import java.util.Objects;
109
import java.util.Optional;
1110
import java.util.Set;
@@ -89,11 +88,6 @@ public class OriginalSourceValue extends SourceValue {
8988
*/
9089
private final boolean isMethodParameter;
9190

92-
/**
93-
* Set of instructions that consume this value. Due to the nature of OW2 ASM, it does not contain POP instructions
94-
*/
95-
private final Set<AbstractInsnNode> consumers = new HashSet<>();
96-
9791
public OriginalSourceValue(int size, boolean isMethodParameter) {
9892
super(size, new SmallSet<>());
9993
this.isMethodParameter = isMethodParameter;
@@ -187,10 +181,6 @@ public boolean isMethodParameter() {
187181
return isMethodParameter;
188182
}
189183

190-
public Set<AbstractInsnNode> getConsumers() {
191-
return consumers;
192-
}
193-
194184
/**
195185
* Walk to the last parent value until the predicate returns true.
196186
*
@@ -213,12 +203,12 @@ public boolean equals(Object o) {
213203
if (o == null || getClass() != o.getClass()) return false;
214204
if (!super.equals(o)) return false;
215205
OriginalSourceValue that = (OriginalSourceValue) o;
216-
return Objects.equals(constantValue, that.constantValue) && isMethodParameter == that.isMethodParameter && Objects.equals(consumers, that.consumers) && Objects.equals(copiedFrom, that.copiedFrom);
206+
return Objects.equals(constantValue, that.constantValue) && isMethodParameter == that.isMethodParameter && Objects.equals(copiedFrom, that.copiedFrom);
217207
}
218208

219209
@Override
220210
public int hashCode() {
221-
return Objects.hash(super.hashCode(), constantValue, isMethodParameter, consumers, copiedFrom);
211+
return Objects.hash(super.hashCode(), constantValue, isMethodParameter, copiedFrom);
222212
}
223213

224214
/**

deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/MethodContext.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import uwu.narumi.deobfuscator.api.helper.MethodHelper;
1010

1111
import java.util.Map;
12+
import java.util.Set;
1213

1314
/**
1415
* Method context
@@ -17,6 +18,7 @@ public class MethodContext {
1718
private final ClassWrapper classWrapper;
1819
private final MethodNode methodNode;
1920
private final @Nullable @Unmodifiable Map<AbstractInsnNode, Frame<OriginalSourceValue>> frames;
21+
private Map<AbstractInsnNode, Set<AbstractInsnNode>> consumersMap = null;
2022

2123
private MethodContext(
2224
ClassWrapper classWrapper,
@@ -49,6 +51,17 @@ public MethodNode methodNode() {
4951
return frames;
5052
}
5153

54+
public synchronized Map<AbstractInsnNode, Set<AbstractInsnNode>> getConsumersMap() {
55+
if (this.frames == null) {
56+
throw new IllegalStateException("Got frameless method context");
57+
}
58+
if (consumersMap == null) {
59+
// Lazy initialization
60+
this.consumersMap = MethodHelper.computeConsumersMap(this.frames);
61+
}
62+
return consumersMap;
63+
}
64+
5265
public InsnContext newInsnContext(AbstractInsnNode insn) {
5366
return new InsnContext(insn, this);
5467
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.objectweb.asm.tree.analysis.Analyzer;
66
import org.objectweb.asm.tree.analysis.BasicInterpreter;
77
import org.objectweb.asm.tree.analysis.BasicValue;
8-
import org.objectweb.asm.tree.analysis.Value;
98
import uwu.narumi.deobfuscator.api.asm.NamedOpcodes;
109
import org.objectweb.asm.Opcodes;
1110
import org.objectweb.asm.tree.AbstractInsnNode;
@@ -25,8 +24,10 @@
2524
import java.util.Collections;
2625
import java.util.Comparator;
2726
import java.util.HashMap;
27+
import java.util.HashSet;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Set;
3031

3132
public class MethodHelper implements Opcodes {
3233
/**
@@ -105,6 +106,29 @@ public static Map<AbstractInsnNode, Frame<BasicValue>> analyzeBasic(
105106
return Collections.unmodifiableMap(frames);
106107
}
107108

109+
/**
110+
* Computes a map which corresponds to: source value producer -> consumers
111+
*
112+
* @param frames Frames of the method
113+
*/
114+
public static Map<AbstractInsnNode, Set<AbstractInsnNode>> computeConsumersMap(Map<AbstractInsnNode, Frame<OriginalSourceValue>> frames) {
115+
Map<AbstractInsnNode, Set<AbstractInsnNode>> consumers = new HashMap<>();
116+
for (var entry : frames.entrySet()) {
117+
AbstractInsnNode consumer = entry.getKey();
118+
Frame<OriginalSourceValue> frame = entry.getValue();
119+
if (frame == null) continue;
120+
121+
// Loop through stack values and add consumer to them
122+
for (int i = 0; i < consumer.getRequiredStackValuesCount(); i++) {
123+
OriginalSourceValue sourceValue = frame.getStack(frame.getStackSize() - (i + 1));
124+
for (AbstractInsnNode producer : sourceValue.insns) {
125+
consumers.computeIfAbsent(producer, k -> new HashSet<>()).add(consumer);
126+
}
127+
}
128+
}
129+
return consumers;
130+
}
131+
108132
public static List<String> prettyInsnList(InsnList insnList) {
109133
return prettyInsnList(Arrays.asList(insnList.toArray()));
110134
}

deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,15 @@ protected void registerAll() {
161161
.transformers(UselessPopCleanTransformer::new)
162162
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "KotlinSample.class")
163163
.register();
164+
165+
test("Kotlin Sample 2")
166+
.transformers(UselessPopCleanTransformer::new)
167+
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "KotlinSample2.class")
168+
.register();
169+
170+
test("Kotlin Sample 3")
171+
.transformers(UselessPopCleanTransformer::new)
172+
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "KotlinSample3.class")
173+
.register();
164174
}
165175
}

deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/base/TestDeobfuscationBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public abstract class TestDeobfuscationBase {
4242
@BeforeAll
4343
public static void setup() {
4444
// Don't spam logs
45-
Configurator.setRootLevel(Level.WARN);
45+
Configurator.setRootLevel(Level.ERROR);
4646
}
4747

4848
@TestFactory

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.objectweb.asm.tree.MethodNode;
55
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
66
import uwu.narumi.deobfuscator.api.asm.InsnContext;
7+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
78
import uwu.narumi.deobfuscator.api.helper.FramedInstructionsStream;
89
import uwu.narumi.deobfuscator.api.transformer.Transformer;
910

@@ -42,7 +43,7 @@ private boolean tryRemovePop(InsnContext insnContext) {
4243
AbstractInsnNode insn = insnContext.insn();
4344
OriginalSourceValue firstValue = insnContext.frame().getStack(insnContext.frame().getStackSize() - 1);
4445

45-
if (!canPop(firstValue)) return false;
46+
if (!canPop(firstValue, insnContext.methodContext())) return false;
4647

4748
if (insn.getOpcode() == POP) {
4849
// Pop the value from the stack
@@ -64,7 +65,7 @@ private boolean tryRemovePop(InsnContext insnContext) {
6465
}
6566

6667
// Return if we can't remove the source value
67-
if (!canPop(secondValue)) return false;
68+
if (!canPop(secondValue, insnContext.methodContext())) return false;
6869

6970
// Pop
7071
popSourceValue(firstValue, insnContext.methodNode());
@@ -79,22 +80,23 @@ private boolean tryRemovePop(InsnContext insnContext) {
7980
/**
8081
* Checks if source value can be popped
8182
*/
82-
private boolean canPop(OriginalSourceValue sourceValue) {
83+
private boolean canPop(OriginalSourceValue sourceValue, MethodContext methodContext) {
8384
if (sourceValue.insns.isEmpty()) {
8485
// Nothing to remove. Probably a local variable
8586
return false;
8687
}
8788

88-
if (!sourceValue.getConsumers().isEmpty()) {
89-
// If the value is consumed by another instruction, we can't remove it
90-
return false;
91-
}
92-
9389
// Check if all producers of the source value are constants
9490
for (AbstractInsnNode producer : sourceValue.insns) {
9591
// Prevent popping instructions twice (especially DUPs)
9692
if (poppedInsns.contains(producer)) return false;
9793

94+
Set<AbstractInsnNode> consumers = methodContext.getConsumersMap().get(producer);
95+
if (consumers.stream().anyMatch(insn -> insn.getOpcode() != POP && insn.getOpcode() != POP2)) {
96+
// If the value is consumed by another instruction, we can't remove it
97+
return false;
98+
}
99+
98100
// Can be popped if the value is constant
99101
if (producer.isConstant()) continue;
100102
// Can be popped if the value is DUP or DUP2
12 KB
Binary file not shown.
5.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)