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

SamboyCoding / Cpp2IL / 18693676721

21 Oct 2025 06:22PM UTC coverage: 34.331% (+0.02%) from 34.31%
18693676721

Pull #494

github

web-flow
Merge 0497b84c8 into e66a74bb3
Pull Request #494: Interface methods should not have a base method

1794 of 6590 branches covered (27.22%)

Branch coverage included in aggregate %.

2 of 3 new or added lines in 2 files covered. (66.67%)

4193 of 10849 relevant lines covered (38.65%)

196792.65 hits per line

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

54.55
/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.Graphs;
7
using Cpp2IL.Core.Graphs.Processors;
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");
968,628!
38

39
    public ulong Rva => UnderlyingPointer == 0 || LibCpp2IlMain.Binary == null ? 0 : LibCpp2IlMain.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<InstructionSetIndependentInstruction>? ConvertedIsil;
50

51
    /// <summary>
52
    /// The control flow graph for this method, if one is built.
53
    /// </summary>
54
    public ISILControlFlowGraph? ControlFlowGraph;
55

56
    public List<ParameterAnalysisContext> Parameters = [];
832,794✔
57

58
    /// <summary>
59
    /// Does this method return void?
60
    /// </summary>
61
    public bool IsVoid => ReturnType == AppContext.SystemTypes.SystemVoidType;
×
62

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

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

NEW
67
    public bool IsAbstract => (Attributes & MethodAttributes.Abstract) != 0;
×
68

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

71
    protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeIndex if they have custom attributes");
417,744!
72

73
    public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType?.DeclaringAssembly ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeAssembly if they have custom attributes");
1,073,495!
74

75
    public override string DefaultName => Definition?.Name ?? throw new("Subclasses of MethodAnalysisContext should override DefaultName");
87,701!
76

77
    public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";
36!
78

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

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

83
    public virtual MethodAttributes? OverrideAttributes { get; set; }
262,411✔
84

85
    public MethodAttributes Attributes => OverrideAttributes ?? DefaultAttributes;
262,411!
86

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

89
    public virtual MethodImplAttributes? OverrideImplAttributes { get; set; }
87,030✔
90

91
    public MethodImplAttributes ImplAttributes => OverrideImplAttributes ?? DefaultImplAttributes;
87,030!
92

93
    public MethodAttributes Visibility
94
    {
95
        get
96
        {
97
            return Attributes & MethodAttributes.MemberAccessMask;
×
98
        }
99
        set
100
        {
101
            OverrideAttributes = (Attributes & ~MethodAttributes.MemberAccessMask) | (value & MethodAttributes.MemberAccessMask);
×
102
        }
×
103
    }
104

105
    private List<GenericParameterTypeAnalysisContext>? _genericParameters;
106
    public override List<GenericParameterTypeAnalysisContext> GenericParameters
107
    {
108
        get
109
        {
110
            // Lazy load the generic parameters
111
            _genericParameters ??= Definition?.GenericContainer?.GenericParameters.Select(p => new GenericParameterTypeAnalysisContext(p, this)).ToList() ?? [];
520,504!
112
            return _genericParameters;
514,946✔
113
        }
114
    }
115

116
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
75,705!
117

118
    public virtual TypeAnalysisContext DefaultReturnType => DeclaringType?.DeclaringAssembly.ResolveIl2CppType(Definition?.RawReturnType) ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultReturnType)}");
427,699!
119

120
    public TypeAnalysisContext? OverrideReturnType { get; set; }
427,702✔
121

122
    //TODO Support custom attributes on return types (v31 feature)
123
    public TypeAnalysisContext ReturnType => OverrideReturnType ?? DefaultReturnType;
427,702✔
124
    
125
    protected Memory<byte>? rawMethodBody;
126

127
    public MethodAnalysisContext? BaseMethod => Overrides.FirstOrDefault(m => m.DeclaringType?.IsInterface is false);
340!
128

129
    /// <summary>
130
    /// The set of methods which this method overrides.
131
    /// </summary>
132
    public virtual IEnumerable<MethodAnalysisContext> Overrides
133
    {
134
        get
135
        {
136
            if (Definition == null)
18,711!
137
                return [];
×
138

139
            var declaringTypeDefinition = DeclaringType?.Definition;
18,711!
140
            if (declaringTypeDefinition == null)
18,711!
141
                return [];
×
142

143
            var vtable = declaringTypeDefinition.VTable;
18,711✔
144
            if (vtable == null)
18,711!
145
                return [];
×
146

147
            return GetOverriddenMethods(declaringTypeDefinition, vtable);
18,711✔
148

149
            bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method)
150
            {
151
                if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
13,520✔
152
                {
153
                    var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
7,102✔
154
                    if (genericMethod is not null)
1,802✔
155
                    {
156
                        method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments, []);
330✔
157
                        return true;
330✔
158
                    }
159
                }
160
                else
161
                {
162
                    var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot);
82,123✔
163
                    if (baseMethod is not null)
11,718✔
164
                    {
165
                        method = baseMethod;
2,830✔
166
                        return true;
2,830✔
167
                    }
168
                }
169

170
                method = null;
10,360✔
171
                return false;
10,360✔
172
            }
173

174
            IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
175
            {
176
                for (var i = 0; i < vtable.Length; ++i)
685,024✔
177
                {
178
                    var vtableEntry = vtable[i];
323,803✔
179
                    if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef })
323,803✔
180
                        continue;
181

182
                    if (vtableEntry.AsMethod() != Definition)
318,193✔
183
                        continue;
184

185
                    // Normal inheritance
186
                    var baseType = DeclaringType?.BaseType;
3,125!
187
                    while (baseType is not null)
7,853✔
188
                    {
189
                        if (TryGetMethodForSlot(baseType, i, out var method))
4,766✔
190
                        {
191
                            yield return method;
38✔
192
                            break; // We only want direct overrides, not the entire inheritance chain.
36✔
193
                        }
194
                        baseType = baseType.BaseType;
4,728✔
195
                    }
196

197
                    // Interface inheritance
198
                    foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
34,564✔
199
                    {
200
                        if (i >= interfaceOffset.offset)
14,159✔
201
                        {
202
                            var interfaceTypeContext = interfaceOffset.Type.ToContext(CustomAttributeAssembly);
8,754✔
203
                            if (interfaceTypeContext != null && TryGetMethodForSlot(interfaceTypeContext, i - interfaceOffset.offset, out var method))
8,754✔
204
                            {
205
                                yield return method;
3,122✔
206
                            }
207
                        }
208
                    }
209
                }
210
            }
18,709✔
211
        }
212
    }
213

214
    private static readonly List<IBlockProcessor> blockProcessors =
×
215
    [
×
216
        new MetadataProcessor(),
×
217
        new CallProcessor()
×
218
    ];
×
219

220
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
832,794✔
221
    {
222
        DeclaringType = parent;
832,794✔
223
        Definition = definition;
832,794✔
224

225
        if (Definition != null)
832,794✔
226
        {
227
            InitCustomAttributeData();
492,198✔
228

229
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
2,112,768✔
230
            {
231
                var parameterDefinition = Definition.InternalParameterData![i];
564,186✔
232
                Parameters.Add(new(parameterDefinition, i, this));
564,186✔
233
            }
234
        }
235
        else
236
            rawMethodBody = Array.Empty<byte>();
340,596✔
237
    }
340,596✔
238

239
    [MemberNotNull(nameof(rawMethodBody))]
240
    public void EnsureRawBytes()
241
    {
242
        rawMethodBody ??= InitRawBytes();
492,198✔
243
    }
492,198✔
244

245
    private Memory<byte> InitRawBytes()
246
    {
247
        //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.
248
        //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build)
249
        if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract))
492,198✔
250
        {
251
            var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false);
476,430✔
252

253
            if (ret.Length == 0)
476,430✔
254
            {
255
                Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC");
36!
256
            }
257

258
            return ret;
476,430✔
259
        }
260
        else
261
            return Array.Empty<byte>();
15,768✔
262
    }
263

264
    protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
×
265
    {
266
        rawMethodBody = Array.Empty<byte>();
×
267
    }
×
268

269
    [MemberNotNull(nameof(ConvertedIsil))]
270
    public void Analyze()
271
    {
272
        if (ConvertedIsil != null)
×
273
            return;
×
274

275
        if (UnderlyingPointer == 0)
×
276
        {
277
            ConvertedIsil = [];
×
278
            return;
×
279
        }
280

281
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
282

283
        if (ConvertedIsil.Count == 0)
×
284
            return; //Nothing to do, empty function
×
285

286
        ControlFlowGraph = new ISILControlFlowGraph();
×
287
        ControlFlowGraph.Build(ConvertedIsil);
×
288

289
        // Post step to convert metadata usage. Ldstr Opcodes etc.
290
        foreach (var block in ControlFlowGraph.Blocks)
×
291
        {
292
            foreach (var converter in blockProcessors)
×
293
            {
294
                converter.Process(this, block);
×
295
            }
296
        }
297
    }
×
298

299
    public void ReleaseAnalysisData()
300
    {
301
        ConvertedIsil = null;
×
302
        ControlFlowGraph = null;
×
303
    }
×
304

305
    public ConcreteGenericMethodAnalysisContext MakeGenericInstanceMethod(params IEnumerable<TypeAnalysisContext> methodGenericParameters)
306
    {
307
        if (this is ConcreteGenericMethodAnalysisContext methodOnGenericInstanceType)
×
308
        {
309
            return new ConcreteGenericMethodAnalysisContext(methodOnGenericInstanceType.BaseMethodContext, methodOnGenericInstanceType.TypeGenericParameters, methodGenericParameters);
×
310
        }
311
        else
312
        {
313
            return new ConcreteGenericMethodAnalysisContext(this, [], methodGenericParameters);
×
314
        }
315
    }
316

317
    public ConcreteGenericMethodAnalysisContext MakeConcreteGenericMethod(IEnumerable<TypeAnalysisContext> typeGenericParameters, IEnumerable<TypeAnalysisContext> methodGenericParameters)
318
    {
319
        if (this is ConcreteGenericMethodAnalysisContext)
×
320
        {
321
            throw new InvalidOperationException($"Attempted to make a {nameof(ConcreteGenericMethodAnalysisContext)} concrete: {this}");
×
322
        }
323
        else
324
        {
325
            return new ConcreteGenericMethodAnalysisContext(this, typeGenericParameters, methodGenericParameters);
×
326
        }
327
    }
328

329
    public override string ToString() => $"Method: {FullName}";
36✔
330

331
    #region StableNameDot implementation
332

333
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
334
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
335
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
336
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
337

338
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
339

340
    string IMethodInfoProvider.MethodName => Name;
×
341

342
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
343

344
    MethodSemantics IMethodInfoProvider.MethodSemantics
345
    {
346
        get
347
        {
348
            if (DeclaringType != null)
×
349
            {
350
                //This one is a bit trickier, as il2cpp doesn't use semantics.
351
                foreach (var prop in DeclaringType.Properties)
×
352
                {
353
                    if (prop.Getter == this)
×
354
                        return MethodSemantics.Getter;
×
355
                    if (prop.Setter == this)
×
356
                        return MethodSemantics.Setter;
×
357
                }
358

359
                foreach (var evt in DeclaringType.Events)
×
360
                {
361
                    if (evt.Adder == this)
×
362
                        return MethodSemantics.AddOn;
×
363
                    if (evt.Remover == this)
×
364
                        return MethodSemantics.RemoveOn;
×
365
                    if (evt.Invoker == this)
×
366
                        return MethodSemantics.Fire;
×
367
                }
368
            }
369

370
            return 0;
×
371
        }
×
372
    }
373

374
    #endregion
375
}
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