• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 27104750074

07 Jun 2026 09:03PM UTC coverage: 33.71% (+0.04%) from 33.672%
27104750074

push

github

SamboyCoding
Decompiler: First pass on type propagation

2205 of 7872 branches covered (28.01%)

Branch coverage included in aggregate %.

3 of 98 new or added lines in 6 files covered. (3.06%)

159 existing lines in 17 files now uncovered.

4630 of 12404 relevant lines covered (37.33%)

242141.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Cpp2IL.Core/Analysis/MetadataResolver.cs
1
using System.Linq;
2
using Cpp2IL.Core.Extensions;
3
using Cpp2IL.Core.Graphs;
4
using Cpp2IL.Core.Il2CppApiFunctions;
5
using Cpp2IL.Core.ISIL;
6
using Cpp2IL.Core.Model.Contexts;
7
using Cpp2IL.Core.Utils;
8
using LibCpp2IL;
9

10
namespace Cpp2IL.Core.Analysis;
11

12
public static class MetadataResolver
13
{
14
    public static void ResolveAll(MethodAnalysisContext method)
15
    {
16
        ResolveCalls(method);
×
17
        ResolveGetter(method);
×
18
        ResolveStrings(method);
×
19
    }
×
20

21
    private static void ResolveStrings(MethodAnalysisContext method)
22
    {
23
        foreach (var instruction in method.ControlFlowGraph!.Instructions)
×
24
        {
25
            if (instruction.OpCode != OpCode.Move)
×
26
                continue;
27

28
            if ((instruction.Operands[0] is not LocalVariable) || (instruction.Operands[1] is not MemoryOperand memory))
×
29
                continue;
30

31
            if (memory.Base == null && memory.Index == null && memory.Scale == 0)
×
32
            {
33
                var stringLiteral = method.AppContext.LibCpp2IlContext.GetLiteralByAddress((ulong)memory.Addend);
×
34

35
                if (stringLiteral == null)
×
36
                {
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

46
                    continue;
×
47
                }
48

49
                instruction.Operands[1] = stringLiteral;
×
50
            }
51
        }
52
    }
×
53

54
    /// <summary>
55
    /// Replaces every <c>[base + addend]</c> memory operand whose base is a typed local with a
56
    /// <see cref="FieldReference"/> to the field at that offset. Returns whether any operand was
57
    /// resolved this pass, so the type/field fixpoint can detect convergence: as more bases become
58
    /// typed (a field load types its result, which is the base of the next load), more offsets
59
    /// resolve, so this is re-run until it stops finding new fields.
60
    /// </summary>
61
    public static bool ResolveFieldOffsets(MethodAnalysisContext method)
62
    {
NEW
63
        var changed = false;
×
64

UNCOV
65
        foreach (var instruction in method.ControlFlowGraph!.Instructions)
×
66
        {
67
            for (var i = 0; i < instruction.Operands.Count; i++)
×
68
            {
69
                var operand = instruction.Operands[i];
×
70

71
                if (operand is not MemoryOperand memory)
×
72
                    continue;
73

74
                // Has to be [base (local) + addend (field offset)]
75
                if (memory.Index != null || memory.Scale != 0)
×
76
                    continue;
77

78
                if (memory.Base is not LocalVariable local || local?.Type == null)
×
79
                    continue;
80

81
                var field = local.Type.Fields.FirstOrDefault(f => f.BackingData?.FieldOffset == memory.Addend);
×
82

83
                if (field == null) // TODO: Support nested fields (Field1.Field2.Field3)
×
84
                    continue;
85

86
                instruction.Operands[i] = new FieldReference(field, local, (int)memory.Addend);
×
NEW
87
                changed = true;
×
88
            }
89
        }
90

NEW
91
        return changed;
×
92
    }
93

94
    private static void ResolveCalls(MethodAnalysisContext method)
95
    {
96
        foreach (var block in method.ControlFlowGraph!.Blocks)
×
97
        {
98
            if (block.BlockType != BlockType.Call && block.BlockType != BlockType.TailCall)
×
99
                continue;
100

101
            var callInstruction = block.Instructions[^1];
×
102
            var dest = callInstruction.Operands[0];
×
103

104
            if (!dest.IsNumeric())
×
105
                continue;
106

107
            var target = (ulong)dest;
×
108

109
            var keyFunctionAddresses = method.AppContext.GetOrCreateKeyFunctionAddresses();
×
110

111
            if (keyFunctionAddresses.IsKeyFunctionAddress(target))
×
112
            {
113
                HandleKeyFunction(method.AppContext, callInstruction, target, keyFunctionAddresses);
×
114
                continue;
×
115
            }
116

117
            //Non-key function call. Try to find a single match
118
            if (!method.AppContext.MethodsByAddress.TryGetValue(target, out var targetMethods))
×
119
                continue;
120

121
            // Several methods can share one address (identical native code merged by the linker, or
122
            // generic sharing). Those are left as a numeric target here and disambiguated later by
123
            // receiver type in ResolveAmbiguousCalls, once types are known.
UNCOV
124
            if (targetMethods is not [{ } singleTargetMethod])
×
125
                continue;
126

127
            callInstruction.Operands[0] = singleTargetMethod;
×
128
        }
129

130
        method.ControlFlowGraph.MergeCallBlocks();
×
131
    }
×
132

133
    /// <summary>
134
    /// Resolves calls whose address maps to more than one method by matching the receiver's known
135
    /// type against the candidates' declaring types. Runs inside the type/field fixpoint and so
136
    /// re-fires as receivers become typed - a resolved call types its return value, which can type
137
    /// the receiver of a further call. Returns whether any call was resolved this pass.
138
    ///
139
    /// Conservative by design: it commits only when exactly one non-static candidate's declaring
140
    /// type matches the receiver's type. Anything still untyped or ambiguous is left for a later
141
    /// pass, or left unresolved - it never guesses.
142
    /// </summary>
143
    public static bool ResolveAmbiguousCalls(MethodAnalysisContext method)
144
    {
NEW
145
        var changed = false;
×
146

NEW
147
        foreach (var instruction in method.ControlFlowGraph!.Instructions)
×
148
        {
NEW
149
            if (!instruction.IsCall)
×
150
                continue;
151

NEW
152
            var target = instruction.Operands[0];
×
153

154
            // A resolved call's target is a method/key-function name; only unresolved ones are still numeric.
NEW
155
            if (!target.IsNumeric())
×
156
                continue;
157

NEW
158
            if (!method.AppContext.MethodsByAddress.TryGetValue((ulong)target, out var candidates) || candidates.Count < 2)
×
159
                continue;
160

NEW
161
            if (GetReceiver(instruction) is not { Type: { } receiverType })
×
162
                continue;
163

NEW
164
            MethodAnalysisContext? match = null;
×
NEW
165
            var ambiguous = false;
×
166

NEW
167
            foreach (var candidate in candidates)
×
168
            {
NEW
169
                if (candidate.IsStatic || !ReferenceEquals(candidate.DeclaringType, receiverType))
×
170
                    continue;
171

NEW
172
                if (match != null)
×
173
                {
NEW
174
                    ambiguous = true;
×
NEW
175
                    break;
×
176
                }
177

NEW
178
                match = candidate;
×
179
            }
180

NEW
181
            if (ambiguous || match == null)
×
182
                continue;
183

NEW
184
            instruction.Operands[0] = match;
×
NEW
185
            changed = true;
×
186
        }
187

NEW
188
        return changed;
×
189
    }
190

191
    // The receiver ('this') of a call is the first integer-slot argument: operand 1 for CallVoid
192
    // (after the target), operand 2 for Call (after the target and the return value).
193
    private static LocalVariable? GetReceiver(Instruction call)
194
    {
NEW
195
        var index = call.OpCode == OpCode.CallVoid ? 1 : 2;
×
NEW
196
        return index < call.Operands.Count ? call.Operands[index] as LocalVariable : null;
×
197
    }
198

199
    private static void HandleKeyFunction(ApplicationAnalysisContext appContext, Instruction instruction, ulong target, BaseKeyFunctionAddresses kFA)
200
    {
201
        var method = "";
×
202
        if (target == kFA.il2cpp_codegen_initialize_method || target == kFA.il2cpp_codegen_initialize_runtime_metadata)
×
203
        {
204
            if (appContext.MetadataVersion < 27)
×
205
            {
206
                method = nameof(kFA.il2cpp_codegen_initialize_method);
×
207
            }
208
            else
209
            {
210
                method = nameof(kFA.il2cpp_codegen_initialize_runtime_metadata);
×
211
            }
212
        }
213
        else
214
        {
215
            var pairs = kFA.Pairs.ToList();
×
216
            var key = pairs.FirstOrDefault(pair => pair.Value == target).Key;
×
217
            if (key == null)
×
218
                return;
×
219
            method = key;
×
220
        }
221

222
        if (method != "")
×
223
        {
224
            instruction.Operands[0] = method;
×
225
        }
226
    }
×
227

228
    // Because of il2cpp fields (like cctor_finished_or_no_cctor) [local @ reg+offset] sometimes can't be resolved, but this works for now
229
    private static void ResolveGetter(MethodAnalysisContext method)
230
    {
231
        if (!method.Name.StartsWith("get_"))
×
232
            return;
×
233

234
        // Default get: Return [this @ reg+offset]
235
        var instructions = method.ControlFlowGraph!.Instructions;
×
236
        if (instructions.Count == 1)
×
237
        {
238
            var instr = instructions[0];
×
239

240
            if (instr.OpCode != OpCode.Return
×
241
                || instr.Operands.Count < 1
×
242
                || instr.Operands[0] is not MemoryOperand memory
×
243
                || memory.Index != null || memory.Scale != 0
×
244
                || memory.Base is not LocalVariable local)
×
245
                return;
×
246

247
            var fieldName = $"<{method.Name[4..]}>k__BackingField";
×
248

249
            var field = method.DeclaringType!.Fields.Find(f => f.Name == fieldName);
×
250
            if (field == null)
×
251
                return;
×
252

253
            instr.Operands[0] = new FieldReference(field, local, (int)memory.Addend);
×
254
        }
255
    }
×
256
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc