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

SamboyCoding / Cpp2IL / 15159531087

21 May 2025 10:13AM UTC coverage: 34.289% (+0.2%) from 34.047%
15159531087

Pull #462

github

web-flow
Merge c760cddbe into 5807d2b6c
Pull Request #462: Support overriding member types

1801 of 6650 branches covered (27.08%)

Branch coverage included in aggregate %.

121 of 213 new or added lines in 33 files covered. (56.81%)

22 existing lines in 6 files now uncovered.

4201 of 10854 relevant lines covered (38.7%)

186268.63 hits per line

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

56.82
/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");
865,848!
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 = [];
738,870✔
57

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

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

65
    protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeIndex if they have custom attributes");
365,526!
66

67
    public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType?.DeclaringAssembly ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeAssembly if they have custom attributes");
968,522!
68

69
    public override string DefaultName => Definition?.Name ?? throw new("Subclasses of MethodAnalysisContext should override DefaultName");
87,698!
70

71
    public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";
33!
72

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

75
    public virtual MethodAttributes DefaultAttributes => Definition?.Attributes ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultAttributes)}");
261,415!
76

77
    public virtual MethodAttributes? OverrideAttributes { get; set; }
261,415✔
78

79
    public MethodAttributes Attributes => OverrideAttributes ?? DefaultAttributes;
261,415!
80

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

83
    public virtual MethodImplAttributes? OverrideImplAttributes { get; set; }
87,030✔
84

85
    public MethodImplAttributes ImplAttributes => OverrideImplAttributes ?? DefaultImplAttributes;
87,030!
86

87
    public MethodAttributes Visibility
88
    {
89
        get
90
        {
NEW
91
            return Attributes & MethodAttributes.MemberAccessMask;
×
92
        }
93
        set
94
        {
NEW
95
            OverrideAttributes = (Attributes & ~MethodAttributes.MemberAccessMask) | (value & MethodAttributes.MemberAccessMask);
×
NEW
96
        }
×
97
    }
98

99
    public int ParameterCount => Parameters.Count;
10✔
100

101
    private List<GenericParameterTypeAnalysisContext>? _genericParameters;
102
    public override List<GenericParameterTypeAnalysisContext> GenericParameters
103
    {
104
        get
105
        {
106
            // Lazy load the generic parameters
107
            _genericParameters ??= Definition?.GenericContainer?.GenericParameters.Select(p => new GenericParameterTypeAnalysisContext(p, this)).ToList() ?? [];
485,803!
108
            return _genericParameters;
480,782✔
109
        }
110
    }
111

112
    public int GenericParameterCount => GenericParameters.Count;
338,335✔
113

114
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
75,705!
115

116
    public virtual TypeAnalysisContext DefaultReturnType => DeclaringType?.DeclaringAssembly.ResolveIl2CppType(Definition?.RawReturnType) ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultReturnType)}");
385,993!
117

118
    public TypeAnalysisContext? OverrideReturnType { get; set; }
385,996✔
119

120
    //TODO Support custom attributes on return types (v31 feature)
121
    public TypeAnalysisContext ReturnType => OverrideReturnType ?? DefaultReturnType;
385,996✔
122
    
123
    protected Memory<byte>? rawMethodBody;
124

125
    public MethodAnalysisContext? BaseMethod => Overrides.FirstOrDefault(m => m.DeclaringType?.IsInterface is false);
8!
126

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

137
            var declaringTypeDefinition = DeclaringType?.Definition;
18,047!
138
            if (declaringTypeDefinition == null)
18,047!
139
                return [];
×
140

141
            var vtable = declaringTypeDefinition.VTable;
18,047✔
142
            if (vtable == null)
18,047!
143
                return [];
×
144

145
            return GetOverriddenMethods(declaringTypeDefinition, vtable);
18,047✔
146

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

168
                method = null;
10,360✔
169
                return false;
10,360✔
170
            }
171

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

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

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

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

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

218
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
738,870✔
219
    {
220
        DeclaringType = parent;
738,870✔
221
        Definition = definition;
738,870✔
222

223
        if (Definition != null)
738,870✔
224
        {
225
            InitCustomAttributeData();
439,980✔
226

227
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
1,890,450✔
228
            {
229
                var parameterDefinition = Definition.InternalParameterData![i];
505,245✔
230
                Parameters.Add(new(parameterDefinition, i, this));
505,245✔
231
            }
232
        }
233
        else
234
            rawMethodBody = Array.Empty<byte>();
298,890✔
235
    }
298,890✔
236

237
    [MemberNotNull(nameof(rawMethodBody))]
238
    public void EnsureRawBytes()
239
    {
240
        rawMethodBody ??= InitRawBytes();
439,980✔
241
    }
439,980✔
242

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

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

256
            return ret;
425,868✔
257
        }
258
        else
259
            return Array.Empty<byte>();
14,112✔
260
    }
261

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

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

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

279
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
280

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

284
        ControlFlowGraph = new ISILControlFlowGraph();
×
285
        ControlFlowGraph.Build(ConvertedIsil);
×
286

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

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

303
    public override string ToString() => $"Method: {FullName}";
33✔
304

305
    #region StableNameDot implementation
306

307
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
308
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
309
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
310
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
311

312
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
313

314
    string IMethodInfoProvider.MethodName => Name;
×
315

316
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
317

318
    MethodSemantics IMethodInfoProvider.MethodSemantics
319
    {
320
        get
321
        {
322
            if (DeclaringType != null)
×
323
            {
324
                //This one is a bit trickier, as il2cpp doesn't use semantics.
325
                foreach (var prop in DeclaringType.Properties)
×
326
                {
327
                    if (prop.Getter == this)
×
328
                        return MethodSemantics.Getter;
×
329
                    if (prop.Setter == this)
×
330
                        return MethodSemantics.Setter;
×
331
                }
332

333
                foreach (var evt in DeclaringType.Events)
×
334
                {
335
                    if (evt.Adder == this)
×
336
                        return MethodSemantics.AddOn;
×
337
                    if (evt.Remover == this)
×
338
                        return MethodSemantics.RemoveOn;
×
339
                    if (evt.Invoker == this)
×
340
                        return MethodSemantics.Fire;
×
341
                }
342
            }
343

344
            return 0;
×
345
        }
×
346
    }
347

348
    #endregion
349
}
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