Skip to content

Commit e9fe11f

Browse files
committed
Update ReValue model to support tracking array contents
Added an example test case showing new tracking capabilities being leveraged by constant folding transformer.
1 parent bb0c8d0 commit e9fe11f

File tree

7 files changed

+383
-19
lines changed

7 files changed

+383
-19
lines changed

recaf-core/src/main/java/software/coley/recaf/util/Types.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ public static Type array(@Nonnull Type type, int dimensions) {
142142
return Type.getType("[".repeat(dimensions) + type.getDescriptor());
143143
}
144144

145+
/**
146+
* @param arrayType
147+
* Some array type.
148+
*
149+
* @return The type when removing one dimension from the array.
150+
*/
151+
@Nonnull
152+
public static Type undimension(@Nonnull Type arrayType) {
153+
if (arrayType.getSort() != Type.ARRAY)
154+
throw new IllegalStateException("Not an array: " + arrayType);
155+
return Type.getType(arrayType.getDescriptor().substring(1));
156+
}
157+
145158
/**
146159
* @param methodType
147160
* Parsed method descriptor type.

recaf-core/src/main/java/software/coley/recaf/util/analysis/ReAnalyzer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jakarta.annotation.Nonnull;
44
import org.objectweb.asm.tree.analysis.Analyzer;
5+
import org.objectweb.asm.tree.analysis.Frame;
56
import software.coley.recaf.util.analysis.value.ReValue;
67

78
/**
@@ -28,4 +29,14 @@ public ReAnalyzer(@Nonnull ReInterpreter interpreter) {
2829
public ReInterpreter getInterpreter() {
2930
return interpreter;
3031
}
32+
33+
@Override
34+
protected Frame<ReValue> newFrame(int numLocals, int numStack) {
35+
return new ReFrame(numLocals, numStack);
36+
}
37+
38+
@Override
39+
protected Frame<ReValue> newFrame(Frame<? extends ReValue> frame) {
40+
return new ReFrame(frame);
41+
}
3142
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package software.coley.recaf.util.analysis;
2+
3+
import org.objectweb.asm.Opcodes;
4+
import org.objectweb.asm.tree.AbstractInsnNode;
5+
import org.objectweb.asm.tree.analysis.AnalyzerException;
6+
import org.objectweb.asm.tree.analysis.Frame;
7+
import org.objectweb.asm.tree.analysis.Interpreter;
8+
import software.coley.recaf.util.analysis.value.ArrayValue;
9+
import software.coley.recaf.util.analysis.value.ReValue;
10+
11+
/**
12+
* Analysis frame for some enhanced value tracking.
13+
*
14+
* @author Matt Coley
15+
*/
16+
public class ReFrame extends Frame<ReValue> {
17+
public ReFrame(int numLocals, int maxStack) {
18+
super(numLocals, maxStack);
19+
}
20+
21+
public ReFrame(Frame<? extends ReValue> frame) {
22+
super(frame);
23+
}
24+
25+
@Override
26+
public void execute(AbstractInsnNode insn, Interpreter<ReValue> interpreter) throws AnalyzerException {
27+
switch (insn.getOpcode()) {
28+
case Opcodes.IASTORE:
29+
case Opcodes.LASTORE:
30+
case Opcodes.FASTORE:
31+
case Opcodes.DASTORE:
32+
case Opcodes.AASTORE:
33+
case Opcodes.BASTORE:
34+
case Opcodes.CASTORE:
35+
case Opcodes.SASTORE:
36+
// Support updating array contents in this frame when values are stored into it.
37+
ReValue value = pop();
38+
ReValue index = pop();
39+
ReValue array = pop();
40+
ReValue updatedArray = interpreter.ternaryOperation(insn, array, index, value);
41+
if (updatedArray != array) {
42+
for (int i = 0; i < getStackSize(); i++) {
43+
ReValue stack = getStack(i);
44+
if (stack == array) {
45+
setStack(i, updatedArray);
46+
} else if (stack instanceof ArrayValue stackArray) {
47+
ArrayValue updatedStackArray = stackArray.updatedCopyIfContained(array, updatedArray);
48+
setStack(i, updatedStackArray);
49+
}
50+
}
51+
for (int i = 0; i < getLocals(); i++) {
52+
ReValue local = getLocal(i);
53+
if (local == array) {
54+
setLocal(i, updatedArray);
55+
} else if (local instanceof ArrayValue stackArray) {
56+
ArrayValue updatedStackArray = stackArray.updatedCopyIfContained(array, updatedArray);
57+
setLocal(i, updatedStackArray);
58+
}
59+
}
60+
}
61+
break;
62+
default:
63+
super.execute(insn, interpreter);
64+
}
65+
}
66+
}

recaf-core/src/main/java/software/coley/recaf/util/analysis/ReInterpreter.java

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -347,19 +347,24 @@ public ReValue binaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue
347347
case BALOAD:
348348
case CALOAD:
349349
case SALOAD:
350-
// We aren't tracking array contents, so nothing to do here.
350+
if (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())
351+
return array.getValue(index.value().getAsInt());
351352
return IntValue.UNKNOWN;
352353
case FALOAD:
353-
// We aren't tracking array contents, so nothing to do here.
354+
if (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())
355+
return array.getValue(index.value().getAsInt());
354356
return FloatValue.UNKNOWN;
355357
case LALOAD:
356-
// We aren't tracking array contents, so nothing to do here.
358+
if (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())
359+
return array.getValue(index.value().getAsInt());
357360
return LongValue.UNKNOWN;
358361
case DALOAD:
359-
// We aren't tracking array contents, so nothing to do here.
362+
if (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())
363+
return array.getValue(index.value().getAsInt());
360364
return DoubleValue.UNKNOWN;
361365
case AALOAD:
362-
// We aren't tracking array contents, so nothing to do here.
366+
if (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())
367+
return array.getValue(index.value().getAsInt());
363368
return ObjectValue.VAL_OBJECT_MAYBE_NULL;
364369
case IADD:
365370
if (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.add(i2);
@@ -492,19 +497,36 @@ public ReValue binaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue
492497
}
493498

494499
@Override
495-
public ReValue ternaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value1, @Nonnull ReValue value2, ReValue value3) {
496-
// We don't track array operations, but this would cover:
500+
public ReValue ternaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value1, @Nonnull ReValue value2, @Nonnull ReValue value3) {
501+
// This method covers the following instructions:
497502
// IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE
498-
// Load operations are handled in 'binaryOperation'
499-
return null;
503+
// It will always be an array-store operation.
504+
// NOTE: Load operations are handled in 'binaryOperation'
505+
if (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())
506+
return array.setValue(index.value().getAsInt(), value3);
507+
return value1;
500508
}
501509

502510
@Override
503511
public ReValue naryOperation(@Nonnull AbstractInsnNode insn, @Nonnull List<? extends ReValue> values) {
504512
int opcode = insn.getOpcode();
505513
if (opcode == MULTIANEWARRAY) {
506514
Type type = Type.getType(((MultiANewArrayInsnNode) insn).desc);
507-
return newValue(type, Nullness.NOT_NULL);
515+
516+
// Extract dimensions from passed values.
517+
// Unknown values will be negative, which we will sanity check for.
518+
int[] dimensions = values.stream()
519+
.mapToInt(v -> v instanceof IntValue intValue && intValue.value().isPresent() ?
520+
intValue.value().getAsInt() : -1)
521+
.toArray();
522+
if (dimensions.length == 0) // Sanity check, should never occur.
523+
return newValue(type, Nullness.NOT_NULL);
524+
for (int dimension : dimensions)
525+
if (dimension < 0) // Validate dimensions are valid
526+
return newValue(type, Nullness.NOT_NULL);
527+
528+
// Make array of known dimensions.
529+
return ArrayValue.multiANewArray(type, dimensions);
508530
} else if (opcode == INVOKEDYNAMIC) {
509531
Type returnType = Type.getReturnType(((InvokeDynamicInsnNode) insn).desc);
510532
return newValue(returnType);

recaf-core/src/main/java/software/coley/recaf/util/analysis/value/ArrayValue.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package software.coley.recaf.util.analysis.value;
22

33
import jakarta.annotation.Nonnull;
4+
import jakarta.annotation.Nullable;
5+
import org.objectweb.asm.Opcodes;
46
import org.objectweb.asm.Type;
7+
import software.coley.recaf.util.Types;
58
import software.coley.recaf.util.analysis.Nullness;
69
import software.coley.recaf.util.analysis.value.impl.ArrayValueImpl;
710

11+
import java.util.Arrays;
812
import java.util.OptionalInt;
913

1014
/**
@@ -63,6 +67,46 @@ static ArrayValue of(@Nonnull Type type, @Nonnull Nullness nullness, int length)
6367
return new ArrayValueImpl(type, nullness, length);
6468
}
6569

70+
/**
71+
* @param type
72+
* Array type.
73+
* @param dimensions
74+
* Dimensions of the array to create.
75+
*
76+
* @return Array value created from a {@link Opcodes#MULTIANEWARRAY} instruction.
77+
*/
78+
@Nonnull
79+
static ReValue multiANewArray(@Nonnull Type type, @Nonnull int[] dimensions) {
80+
int length = dimensions[0];
81+
if (dimensions.length == 1)
82+
return of(type, Nullness.NOT_NULL, length);
83+
return new ArrayValueImpl(type, Nullness.NOT_NULL, length,
84+
i -> multiANewArray(Types.undimension(type), Arrays.copyOfRange(dimensions, 1, dimensions.length))
85+
);
86+
}
87+
88+
/**
89+
* @param index
90+
* Index to assign value at.
91+
* @param value
92+
* Value to assign.
93+
*
94+
* @return New array with the given value assigned at the given index.
95+
*/
96+
@Nonnull
97+
ArrayValue setValue(int index, @Nonnull ReValue value);
98+
99+
/**
100+
* @param originalValue
101+
* Some value.
102+
* @param updatedValue
103+
* Some updated version of the value.
104+
*
105+
* @return New array with the given original value replaced with the updated value.
106+
*/
107+
@Nonnull
108+
ArrayValue updatedCopyIfContained(@Nonnull ReValue originalValue, @Nonnull ReValue updatedValue);
109+
66110
@Override
67111
default boolean hasKnownValue() {
68112
return false;
@@ -116,4 +160,13 @@ default int dimensions() {
116160
*/
117161
@Nonnull
118162
OptionalInt getFirstDimensionLength();
163+
164+
/**
165+
* @param index
166+
* Index within {@link #getFirstDimensionLength()}.
167+
*
168+
* @return Value, if known, at the given index. Otherwise, a {@link ReValue} of the array's element type..
169+
*/
170+
@Nullable
171+
ReValue getValue(int index);
119172
}

0 commit comments

Comments
 (0)