Skip to content

Commit 73c00b6

Browse files
committed
Decompiler: Fix simplifier replacing incorrect constants, add methodInfo resolution
1 parent c96f1ff commit 73c00b6

7 files changed

Lines changed: 153 additions & 31 deletions

File tree

Cpp2IL.Core.Tests/Analysis/SimplifierTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,69 @@ public void DoesNotInlineAcrossJoinWhenLocalHasMultipleDefinitions()
6363
Assert.That(ReferenceEquals(writeLineCall.Operands[1], selected), Is.True,
6464
"join-point call must keep the selected local, not a branch-specific constant");
6565
}
66+
67+
[Test]
68+
public void DoesNotCorruptUnrelatedConstantMemoryAddends()
69+
{
70+
var aLocal = new LocalVariable("a", new Register(null, "a"));
71+
var bLocal = new LocalVariable("b", new Register(null, "b"));
72+
73+
// a := [0xAAAA]; f(a); b := [0xBBBB]; g(b). Inlining a's constant load must not touch the
74+
// unrelated constant address [0xBBBB] - they are distinct absolute addresses.
75+
var instructions = new List<Instruction>
76+
{
77+
new(0, OpCode.Move, aLocal, new MemoryOperand(null, null, 0xAAAA, 0)),
78+
new(1, OpCode.CallVoid, "f", aLocal, 0),
79+
new(2, OpCode.Move, bLocal, new MemoryOperand(null, null, 0xBBBB, 0)),
80+
new(3, OpCode.CallVoid, "g", bLocal, 0),
81+
new(4, OpCode.Return),
82+
};
83+
84+
var graph = new ISILControlFlowGraph(instructions);
85+
var method = CreateMethod(graph, aLocal, bLocal);
86+
87+
Simplifier.Simplify(method);
88+
89+
var live = graph.Blocks.SelectMany(b => b.Instructions).ToList();
90+
91+
var gCall = live.Single(i => i.OpCode == OpCode.CallVoid && i.Operands[0] is "g");
92+
Assert.That(gCall.Operands[1], Is.InstanceOf<MemoryOperand>());
93+
Assert.That(((MemoryOperand)gCall.Operands[1]).Addend, Is.EqualTo(0xBBBBL),
94+
"an unrelated constant address must not be rewritten by another inline");
95+
96+
// The intended inline still happens.
97+
var fCall = live.Single(i => i.OpCode == OpCode.CallVoid && i.Operands[0] is "f");
98+
Assert.That(fCall.Operands[1], Is.InstanceOf<MemoryOperand>());
99+
Assert.That(((MemoryOperand)fCall.Operands[1]).Addend, Is.EqualTo(0xAAAAL));
100+
}
101+
102+
[Test]
103+
public void PropagatesCopyThroughMemoryBase()
104+
{
105+
var x = new LocalVariable("x", new Register(null, "x"));
106+
var y = new LocalVariable("y", new Register(null, "y"));
107+
var z = new LocalVariable("z", new Register(null, "z"));
108+
109+
// x := y; z := [x]; f(z). Copy propagation must rewrite the load's base x -> y (a base must
110+
// stay a local), so the surviving load reads [y].
111+
var instructions = new List<Instruction>
112+
{
113+
new(0, OpCode.Move, x, y),
114+
new(1, OpCode.Move, z, new MemoryOperand(x, null, 0, 0)),
115+
new(2, OpCode.CallVoid, "f", z, 0),
116+
new(3, OpCode.Return),
117+
};
118+
119+
var graph = new ISILControlFlowGraph(instructions);
120+
var method = CreateMethod(graph, x, y, z);
121+
122+
Simplifier.Simplify(method);
123+
124+
var live = graph.Blocks.SelectMany(b => b.Instructions).ToList();
125+
var load = live.Single(i => i.Operands.Any(o => o is MemoryOperand));
126+
var memory = (MemoryOperand)load.Operands.First(o => o is MemoryOperand);
127+
Assert.That(ReferenceEquals(memory.Base, y), Is.True, "the copy's source must be propagated into the memory base");
128+
Assert.That(live.Any(i => i.OpCode == OpCode.Move && ReferenceEquals(i.Operands[0], x)), Is.False,
129+
"the now-dead copy is removed");
130+
}
66131
}

Cpp2IL.Core/Analysis/LocalVariables.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ public static void ResolveTypesAndFields(MethodAnalysisContext method)
207207
PropagateFromReturn(method);
208208
PropagateFromParameters(method);
209209
SeedRuntimeClassTypes(method);
210+
SeedMethodInfoTypes(method);
210211
SeedComparisonResults(method);
211212

212213
// Everything else is mutually enabling and so runs to a fixpoint: a typed receiver lets an
@@ -241,11 +242,28 @@ private static void SeedRuntimeClassTypes(MethodAnalysisContext method)
241242
if (instruction.OpCode != OpCode.Move || instruction.Operands.Count < 2)
242243
continue;
243244

244-
if (instruction.Operands[0] is LocalVariable destination && instruction.Operands[1] is TypeAnalysisContext type)
245+
if (instruction.Operands[0] is LocalVariable destination
246+
&& instruction.Operands[1] is TypeAnalysisContext type and not RuntimeMethodInfoAnalysisContext)
245247
destination.Type = new RuntimeClassTypeAnalysisContext(type, type.DeclaringAssembly);
246248
}
247249
}
248250

251+
// A method-metadata global load (Move local, methodof(M)) puts a MethodInfo* for M into the local.
252+
// MetadataResolver already resolved the address to a RuntimeMethodInfoAnalysisContext naming the
253+
// method; that same context is the local's type (a runtime handle, recoverable via its
254+
// RepresentedMethod).
255+
private static void SeedMethodInfoTypes(MethodAnalysisContext method)
256+
{
257+
foreach (var instruction in method.ControlFlowGraph!.Instructions)
258+
{
259+
if (instruction.OpCode != OpCode.Move || instruction.Operands.Count < 2)
260+
continue;
261+
262+
if (instruction.Operands[0] is LocalVariable destination && instruction.Operands[1] is RuntimeMethodInfoAnalysisContext methodInfo)
263+
destination.Type = methodInfo;
264+
}
265+
}
266+
249267
// A comparison (CheckEqual, CheckLess, ...) writes a 0/1 result into its destination, so that local
250268
// is a System.Boolean regardless of what the compared operands are.
251269
private static void SeedComparisonResults(MethodAnalysisContext method)

Cpp2IL.Core/Analysis/MetadataResolver.cs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,57 @@ public static void ResolveAll(MethodAnalysisContext method)
1515
{
1616
ResolveCalls(method);
1717
ResolveGetter(method);
18-
ResolveStrings(method);
18+
ResolveMetadataUsages(method);
1919
}
2020

21-
private static void ResolveStrings(MethodAnalysisContext method)
21+
/// <summary>
22+
/// Resolves <c>Move local, [absoluteAddress]</c> loads of IL2CPP metadata-usage globals into a
23+
/// strongly-typed operand: a string literal, a <see cref="TypeAnalysisContext"/> (an Il2CppType*/
24+
/// Il2CppClass* usage) or, for a MethodInfo* usage, a <see cref="RuntimeMethodInfoAnalysisContext"/>
25+
/// naming the method it refers to (also used to type the local - see <see cref="LocalVariables"/>).
26+
/// </summary>
27+
private static void ResolveMetadataUsages(MethodAnalysisContext method)
2228
{
29+
var libContext = method.AppContext.LibCpp2IlContext;
30+
2331
foreach (var instruction in method.ControlFlowGraph!.Instructions)
2432
{
2533
if (instruction.OpCode != OpCode.Move)
2634
continue;
2735

28-
if ((instruction.Operands[0] is not LocalVariable) || (instruction.Operands[1] is not MemoryOperand memory))
36+
// Only an absolute-address load [addr] (no base/index/scale) can be a metadata-usage global.
37+
if (instruction.Operands[0] is not LocalVariable
38+
|| instruction.Operands[1] is not MemoryOperand { Base: null, Index: null, Scale: 0 } memory)
2939
continue;
3040

31-
if (memory.Base == null && memory.Index == null && memory.Scale == 0)
41+
var address = (ulong)memory.Addend;
42+
43+
// String literal.
44+
var stringLiteral = libContext.GetLiteralByAddress(address);
45+
if (stringLiteral != null)
3246
{
33-
var stringLiteral = method.AppContext.LibCpp2IlContext.GetLiteralByAddress((ulong)memory.Addend);
47+
instruction.Operands[1] = stringLiteral;
48+
continue;
49+
}
3450

35-
if (stringLiteral == null)
51+
// Type metadata usage (Il2CppType* / Il2CppClass*).
52+
if (method.DeclaringType is { } declaringType)
53+
{
54+
var typeContext = libContext.GetTypeGlobalByAddress(address)?.ToContext(declaringType.DeclaringAssembly);
55+
if (typeContext != null)
3656
{
37-
// Try instead check if its type metadata usage
38-
var metadataUsage = method.AppContext.LibCpp2IlContext.GetTypeGlobalByAddress((ulong)memory.Addend);
39-
if (metadataUsage != null && method.DeclaringType is not null)
40-
{
41-
var typeAnalysisContext = metadataUsage.ToContext(method.DeclaringType!.DeclaringAssembly);
42-
if (typeAnalysisContext != null)
43-
instruction.Operands[1] = typeAnalysisContext;
44-
}
45-
57+
instruction.Operands[1] = typeContext;
4658
continue;
4759
}
48-
49-
instruction.Operands[1] = stringLiteral;
5060
}
61+
62+
// Method metadata usage (MethodInfo*). On metadata v27+ GetMethodGlobalByAddress can return
63+
// any global, so confirm it is actually a method before resolving - the resolver's switch
64+
// throws on other usage kinds.
65+
var methodUsage = libContext.GetMethodGlobalByAddress(address);
66+
if (methodUsage?.Type is MetadataUsageType.MethodDef or MetadataUsageType.MethodRef
67+
&& method.AppContext.ResolveContextForMethod(methodUsage) is { DeclaringType: { } methodDeclaringType } methodContext)
68+
instruction.Operands[1] = new RuntimeMethodInfoAnalysisContext(methodContext, methodDeclaringType.DeclaringAssembly);
5169
}
5270
}
5371

Cpp2IL.Core/Analysis/Simplifier.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -188,25 +188,17 @@ void ProcessBlock(Block currentBlock, int index)
188188
if (operand is LocalVariable usedLocal && usedLocal == local)
189189
instruction.Operands[j] = replacement;
190190

191-
// [base]
192-
if (operand is MemoryOperand { Index: null, Addend: 0, Scale: 0 } memoryLocal)
191+
// A memory operand's base/index holds an address, so only a local replacement may
192+
// be substituted there (copy propagation). A constant/value replacement is left in
193+
// place - the caller sees the local is still used and keeps its defining move.
194+
if (operand is MemoryOperand memory && replacement is LocalVariable)
193195
{
194-
if (memoryLocal.Base is LocalVariable baseLocal && baseLocal == local)
195-
instruction.Operands[j] = replacement;
196-
}
197-
198-
if (operand is MemoryOperand memory)
199-
{
200-
// [addend]
201-
if (memory.IsConstant && (replacement is MemoryOperand { IsConstant: true } replacementMemory))
202-
memory.Addend = replacementMemory.Addend;
203-
204196
if (memory.Base is LocalVariable baseLocal && baseLocal == local)
205197
memory.Base = replacement;
206198

207199
if (memory.Index is LocalVariable indexLocal && indexLocal == local)
208200
memory.Index = replacement;
209-
201+
210202
instruction.Operands[j] = memory;
211203
}
212204
}

Cpp2IL.Core/ISIL/Instruction.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ private static string FormatOperand(object operand)
127127
{
128128
string text => $"\"{text}\"",
129129
MethodAnalysisContext method => $"{method.DeclaringType!.Name}.{method.Name}",
130+
RuntimeMethodInfoAnalysisContext methodInfo => $"methodof({methodInfo.RepresentedMethod.FullName})",
130131
TypeAnalysisContext type => $"typeof({type.FullName})",
131132
Instruction instruction => $"@{instruction.Index}",
132133
Block block => $"@b{block.ID}",

Cpp2IL.Core/IlGenerator.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,11 @@ private static void LoadOperand(object operand, MethodDefinition method,
416416
instructions.Add(CilOpCodes.Ldstr, "Unmanaged memory load: " + operand.ToString());
417417
instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor));
418418
break;
419+
case RuntimeMethodInfoAnalysisContext:
420+
//Not fully implemented, these basically shouldn't actually ever exist in the final IL.
421+
instructions.Add(CilOpCodes.Ldc_I4_0);
422+
instructions.Add(CilOpCodes.Conv_I);
423+
break;
419424
case TypeAnalysisContext type:
420425
if (type.Name == "T")
421426
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using LibCpp2IL.BinaryStructures;
2+
3+
namespace Cpp2IL.Core.Model.Contexts;
4+
5+
/// <summary>
6+
/// Synthetic type for a value that holds an IL2CPP runtime method pointer (<c>MethodInfo*</c>) - the
7+
/// value produced by loading a method-metadata global.
8+
/// </summary>
9+
public class RuntimeMethodInfoAnalysisContext(MethodAnalysisContext representedMethod, AssemblyAnalysisContext referencedFrom)
10+
: ReferencedTypeAnalysisContext(referencedFrom)
11+
{
12+
/// <summary>The method whose runtime MethodInfo this value points to.</summary>
13+
public MethodAnalysisContext RepresentedMethod { get; } = representedMethod;
14+
15+
// A pointer-sized runtime handle; there is no Il2CppType enum value for the Il2CppMethodInfo struct.
16+
public override Il2CppTypeEnum Type => Il2CppTypeEnum.IL2CPP_TYPE_I;
17+
18+
public override string DefaultName => "Il2CppMethodInfo";
19+
20+
public override string DefaultNamespace => "";
21+
22+
public override bool IsValueType => false;
23+
}

0 commit comments

Comments
 (0)