• 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

55.31
/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics.CodeAnalysis;
4
using System.Linq;
5
using System.Reflection;
6
using Cpp2IL.Core.Analysis;
7
using Cpp2IL.Core.Graphs;
8
using Cpp2IL.Core.ISIL;
9
using Cpp2IL.Core.Logging;
10
using Cpp2IL.Core.Utils;
11
using LibCpp2IL;
12
using LibCpp2IL.Metadata;
13
using StableNameDotNet.Providers;
14

15
namespace Cpp2IL.Core.Model.Contexts;
16

17
/// <summary>
18
/// Represents one method within the application. Can be analyzed to attempt to reconstruct the function body.
19
/// </summary>
20
public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider
21
{
22
    /// <summary>
23
    /// The underlying metadata for the method.
24
    ///
25
    /// Nullable iff this is a subclass.
26
    /// </summary>
27
    public readonly Il2CppMethodDefinition? Definition;
28

29
    /// <summary>
30
    /// The analysis context for the declaring type of this method.
31
    /// </summary>
32
    public readonly TypeAnalysisContext? DeclaringType;
33

34
    /// <summary>
35
    /// The address of this method as defined in the underlying metadata.
36
    /// </summary>
37
    public virtual ulong UnderlyingPointer => Definition?.MethodPointer ?? throw new("Subclasses of MethodAnalysisContext should override UnderlyingPointer");
1,125,470!
38

39
    public ulong Rva => UnderlyingPointer == 0 ? 0 : AppContext.Binary.GetRva(UnderlyingPointer);
×
40

41
    /// <summary>
42
    /// The raw method body as machine code in the active instruction set.
43
    /// </summary>
44
    public Memory<byte> RawBytes => rawMethodBody ??= InitRawBytes();
×
45

46
    /// <summary>
47
    /// The first-stage-analyzed Instruction-Set-Independent Language Instructions.
48
    /// </summary>
49
    public List<Instruction>? ConvertedIsil;
50

51
    /// <summary>
52
    /// All ISIL local variables.
53
    /// </summary>
54
    public List<LocalVariable> Locals = [];
1,054,811✔
55

56
    /// <summary>
57
    /// Operands used as parameters.
58
    /// </summary>
59
    public List<object> ParameterOperands = [];
1,054,811✔
60

61
    /// <summary>
62
    /// The control flow graph for this method, if one is built.
63
    /// </summary>
64
    public ISILControlFlowGraph? ControlFlowGraph;
65

66
    /// <summary>
67
    /// Dominance info for the control flow graph.
68
    /// </summary>
69
    public DominatorInfo? DominatorInfo;
70

71
    public List<string> AnalysisWarnings = [];
1,054,811✔
72

73
    private const int MaxMethodSizeBytes = 18000; // 18KB
74

75
    public List<ParameterAnalysisContext> Parameters = [];
1,054,811✔
76

77
    public List<LocalVariable> ParameterLocals = [];
1,054,811✔
78

79
    /// <summary>
80
    /// Does this method return void?
81
    /// </summary>
82
    public bool IsVoid => ReturnType == AppContext.SystemTypes.SystemVoidType;
×
83

84
    public bool IsStatic => (Attributes & MethodAttributes.Static) != 0;
87,687✔
85

86
    public bool IsVirtual => (Attributes & MethodAttributes.Virtual) != 0;
332✔
87

88
    public bool IsAbstract => (Attributes & MethodAttributes.Abstract) != 0;
×
89

90
    public bool IsNewSlot => (Attributes & MethodAttributes.NewSlot) != 0;
332✔
91

92
    protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeIndex if they have custom attributes");
435,150!
93

94
    public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType?.DeclaringAssembly ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeAssembly if they have custom attributes");
1,297,444!
95

96
    public override string DefaultName => Definition?.Name ?? throw new("Subclasses of MethodAnalysisContext should override DefaultName");
87,704!
97

98
    public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";
39!
99

100
    public string FullNameWithSignature => $"{ReturnType.FullName} {FullName}({string.Join(", ", Parameters.Select(p => p.HumanReadableSignature))})";
×
101

102
    public virtual MethodAttributes DefaultAttributes => Definition?.Attributes ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultAttributes)}");
262,411!
103

104
    public virtual MethodAttributes? OverrideAttributes { get; set; }
262,411✔
105

106
    public MethodAttributes Attributes
107
    {
108
        get => OverrideAttributes ?? DefaultAttributes;
262,411!
109
        set => OverrideAttributes = value;
×
110
    }
111

112
    public virtual MethodImplAttributes DefaultImplAttributes => Definition?.MethodImplAttributes ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultImplAttributes)}");
87,030!
113

114
    public virtual MethodImplAttributes? OverrideImplAttributes { get; set; }
87,030✔
115

116
    public MethodImplAttributes ImplAttributes
117
    {
118
        get => OverrideImplAttributes ?? DefaultImplAttributes;
87,030!
119
        set => OverrideImplAttributes = value;
×
120
    }
121

122
    public MethodAttributes Visibility
123
    {
124
        get
125
        {
126
            return Attributes & MethodAttributes.MemberAccessMask;
×
127
        }
128
        set
129
        {
130
            Attributes = (Attributes & ~MethodAttributes.MemberAccessMask) | (value & MethodAttributes.MemberAccessMask);
×
131
        }
×
132
    }
133

134
    private List<GenericParameterTypeAnalysisContext>? _genericParameters;
135
    public override List<GenericParameterTypeAnalysisContext> GenericParameters
136
    {
137
        get
138
        {
139
            // Lazy load the generic parameters
140
            _genericParameters ??= Definition?.GenericContainer?.GenericParameters.Select(p => new GenericParameterTypeAnalysisContext(p, this)).ToList() ?? [];
663,065!
141
            return _genericParameters;
655,195✔
142
        }
143
    }
144

145
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
25,514!
146

147
    public virtual TypeAnalysisContext DefaultReturnType => DeclaringType?.DeclaringAssembly.ResolveIl2CppType(Definition?.RawReturnType) ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultReturnType)}");
570,029!
148

149
    public TypeAnalysisContext? OverrideReturnType { get; set; }
570,032✔
150

151
    //TODO Support custom attributes on return types (v31 feature)
152
    public TypeAnalysisContext ReturnType
153
    {
154
        get => OverrideReturnType ?? DefaultReturnType;
570,032✔
155
        set => OverrideReturnType = value;
×
156
    }
157
    
158
    protected Memory<byte>? rawMethodBody;
159

160
    public MethodAnalysisContext? BaseMethod
161
    {
162
        get
163
        {
164
            if (Definition == null)
335!
165
                return null;
×
166

167
            var vtable = DeclaringType?.Definition?.VTable;
335!
168
            if (vtable == null)
335!
169
                return null;
×
170

171
            for (var i = 0; i < vtable.Length; ++i)
744✔
172
            {
173
                var vtableEntry = vtable[i];
39✔
174
                if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef } || vtableEntry.AsMethod() != Definition)
39!
175
                    continue;
176

177
                var baseType = DeclaringType?.DefaultBaseType;
5!
178
                while (baseType is not null)
9✔
179
                {
180
                    if (TryGetMethodForSlot(baseType, i, out var method))
6✔
181
                    {
182
                        return method;
2✔
183
                    }
184
                    baseType = baseType.DefaultBaseType;
4✔
185
                }
186
            }
187
            return null;
333✔
188
        }
189
    }
190

191
    private List<MethodAnalysisContext>? _overrides;
192

193
    /// <summary>
194
    /// The set of interface methods which this method explicitly overrides.
195
    /// </summary>
196
    public List<MethodAnalysisContext> Overrides
197
    {
198
        get
199
        {
200
            // Lazy load the overrides
201
            return _overrides ??= GetOverrides().ToList();
18,376✔
202
        }
203
    }
204

205
    private IEnumerable<MethodAnalysisContext> GetOverrides()
206
    {
207
        if (Definition == null)
18,376!
208
            return [];
×
209

210
        var declaringTypeDefinition = DeclaringType?.Definition;
18,376!
211
        if (declaringTypeDefinition == null)
18,376!
212
            return [];
×
213

214
        var vtable = declaringTypeDefinition.VTable;
18,376✔
215
        if (vtable == null)
18,376!
216
            return [];
×
217

218
        return GetOverriddenMethods(declaringTypeDefinition, vtable);
18,376✔
219

220
        IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
221
        {
222
            for (var i = 0; i < vtable.Length; ++i)
684,280✔
223
            {
224
                var vtableEntry = vtable[i];
323,764✔
225
                if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef })
323,764✔
226
                    continue;
227

228
                if (vtableEntry.AsMethod() != Definition)
318,154✔
229
                    continue;
230

231
                // Interface inheritance
232
                foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
34,510✔
233
                {
234
                    if (i >= interfaceOffset.offset)
14,135✔
235
                    {
236
                        var interfaceTypeContext = interfaceOffset.Type.ToContext(CustomAttributeAssembly);
8,738✔
237
                        if (interfaceTypeContext != null && TryGetMethodForSlot(interfaceTypeContext, i - interfaceOffset.offset, out var method))
8,738✔
238
                        {
239
                            yield return method;
3,119✔
240
                        }
241
                    }
242
                }
243
            }
244
        }
18,376✔
245
    }
246

247
    private static bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method)
248
    {
249
        if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
8,744✔
250
        {
251
            var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
6,253✔
252
            if (genericMethod is not null)
1,727✔
253
            {
254
                method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments, []);
328✔
255
                return true;
328✔
256
            }
257
        }
258
        else
259
        {
260
            var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot);
28,005✔
261
            if (baseMethod is not null)
7,017✔
262
            {
263
                method = baseMethod;
2,793✔
264
                return true;
2,793✔
265
            }
266
        }
267

268
        method = null;
5,623✔
269
        return false;
5,623✔
270
    }
271

272
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
1,054,811✔
273
    {
274
        DeclaringType = parent;
1,054,811✔
275
        Definition = definition;
1,054,811✔
276

277
        if (Definition != null)
1,054,811✔
278
        {
279
            InitCustomAttributeData();
571,885✔
280

281
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
2,464,014✔
282
            {
283
                var parameterDefinition = Definition.InternalParameterData![i];
660,122✔
284
                Parameters.Add(new(parameterDefinition, i, this));
660,122✔
285
            }
286
        }
287
        else
288
            rawMethodBody = Array.Empty<byte>();
482,926✔
289
    }
482,926✔
290

291
    [MemberNotNull(nameof(rawMethodBody))]
292
    public void EnsureRawBytes()
293
    {
294
        rawMethodBody ??= InitRawBytes();
571,885✔
295
    }
571,885✔
296

297
    private Memory<byte> InitRawBytes()
298
    {
299
        //Some abstract methods (on interfaces, no less) apparently have a body? Unity doesn't support default interface methods so idk what's going on here.
300
        //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build)
301
        if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract))
571,885✔
302
        {
303
            var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false);
553,585✔
304

305
            if (ret.Length == 0)
553,585✔
306
            {
307
                Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC");
39!
308
            }
309

310
            return ret;
553,585✔
311
        }
312
        else
313
            return Array.Empty<byte>();
18,300✔
314
    }
315

316
    protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
×
317
    {
318
        rawMethodBody = Array.Empty<byte>();
×
319
    }
×
320

321
    [MemberNotNull(nameof(ConvertedIsil))]
322
    public void Analyze()
323
    {
324
        if (RawBytes.Length > MaxMethodSizeBytes)
×
325
        {
326
            Logger.WarnNewline($"Method {FullName} is too big ({RawBytes.Length} bytes), skipping analysis.");
×
327
            ConvertedIsil = [];
×
328
            return;
×
329
        }
330

331
        if (ConvertedIsil != null)
×
332
            return;
×
333

334
        if (UnderlyingPointer == 0)
×
335
        {
336
            ConvertedIsil = [];
×
337
            return;
×
338
        }
339

340
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
341
        ParameterOperands = AppContext.InstructionSet.GetParameterOperandsFromMethod(this);
×
342

343
        if (ConvertedIsil.Count == 0)
×
344
            return; //Nothing to do, empty function
×
345

346
        ControlFlowGraph = new ISILControlFlowGraph(ConvertedIsil);
×
347

348
        // Indirect jumps/calls should probably be resolved here before stack analysis
349

350
        StackAnalyzer.Analyze(this);
×
351

352
        // Dominator info must be computed after stack analysis, which removes unreachable/empty
353
        // blocks and would otherwise leave the dominator tree out of sync with the graph SSA sees.
354
        DominatorInfo = new DominatorInfo(ControlFlowGraph);
×
355

356
        // Create locals
357
        SsaForm.Build(this);
×
358
        LocalVariables.CreateAll(this);
×
359

360
        // Fold the explicit per-comparison flag arithmetic back into single relational comparisons,
361
        // then eliminate the now-dead flag computations. Both run in SSA form, where each
362
        // flag/temporary has a single, version-stable definition.
UNCOV
363
        FlagConditionRecovery.Run(this);
×
364
        DeadCodeEliminator.Run(this);
×
365

366
        // Resolve call targets, strings and getters, then run the combined type-propagation and
367
        // field-resolution fixpoint - all while still in SSA form, so every local is
368
        // single-assignment and a type, once known, is stable for that value.
NEW
369
        MetadataResolver.ResolveAll(this);
×
NEW
370
        LocalVariables.ResolveTypesAndFields(this);
×
371

372
        SsaForm.Remove(this);
×
373

374
        // Now out of SSA: inline/simplify the copies SSA removal introduced, then drop dead locals.
UNCOV
375
        Simplifier.Simplify(this);
×
376
        LocalVariables.RemoveUnused(this);
×
377
    }
×
378

UNCOV
379
    public void AddWarning(string warning) => AnalysisWarnings.Add(warning);
×
380

381
    public void ReleaseAnalysisData()
382
    {
383
        ConvertedIsil = null;
×
384
        ControlFlowGraph = null;
×
UNCOV
385
        DominatorInfo = null;
×
UNCOV
386
    }
×
387

388
    public ConcreteGenericMethodAnalysisContext MakeGenericInstanceMethod(params IEnumerable<TypeAnalysisContext> methodGenericParameters)
389
    {
390
        if (this is ConcreteGenericMethodAnalysisContext methodOnGenericInstanceType)
×
391
        {
UNCOV
392
            return new ConcreteGenericMethodAnalysisContext(methodOnGenericInstanceType.BaseMethodContext, methodOnGenericInstanceType.TypeGenericParameters, methodGenericParameters);
×
393
        }
394
        else
395
        {
UNCOV
396
            return new ConcreteGenericMethodAnalysisContext(this, [], methodGenericParameters);
×
397
        }
398
    }
399

400
    public ConcreteGenericMethodAnalysisContext MakeConcreteGenericMethod(IEnumerable<TypeAnalysisContext> typeGenericParameters, IEnumerable<TypeAnalysisContext> methodGenericParameters)
401
    {
402
        if (this is ConcreteGenericMethodAnalysisContext)
×
403
        {
UNCOV
404
            throw new InvalidOperationException($"Attempted to make a {nameof(ConcreteGenericMethodAnalysisContext)} concrete: {this}");
×
405
        }
406
        else
407
        {
UNCOV
408
            return new ConcreteGenericMethodAnalysisContext(this, typeGenericParameters, methodGenericParameters);
×
409
        }
410
    }
411

412
    public override string ToString() => $"Method: {FullName}";
39✔
413

414
    #region StableNameDot implementation
415

416
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
417
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
UNCOV
418
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
419
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
420

421
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
422

423
    string IMethodInfoProvider.MethodName => Name;
×
424

UNCOV
425
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
426

427
    MethodSemantics IMethodInfoProvider.MethodSemantics
428
    {
429
        get
430
        {
UNCOV
431
            if (DeclaringType != null)
×
432
            {
433
                //This one is a bit trickier, as il2cpp doesn't use semantics.
434
                foreach (var prop in DeclaringType.Properties)
×
435
                {
436
                    if (prop.Getter == this)
×
437
                        return MethodSemantics.Getter;
×
UNCOV
438
                    if (prop.Setter == this)
×
UNCOV
439
                        return MethodSemantics.Setter;
×
440
                }
441

442
                foreach (var evt in DeclaringType.Events)
×
443
                {
444
                    if (evt.Adder == this)
×
445
                        return MethodSemantics.AddOn;
×
446
                    if (evt.Remover == this)
×
447
                        return MethodSemantics.RemoveOn;
×
UNCOV
448
                    if (evt.Invoker == this)
×
UNCOV
449
                        return MethodSemantics.Fire;
×
450
                }
451
            }
452

UNCOV
453
            return 0;
×
UNCOV
454
        }
×
455
    }
456

457
    #endregion
458
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc