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

SamboyCoding / Cpp2IL / 14150525278

29 Mar 2025 11:47PM UTC coverage: 34.818% (+0.08%) from 34.736%
14150525278

Pull #436

github

web-flow
Merge 7fbf05fff into 0fc947d77
Pull Request #436: Improvements to method overrides

1802 of 6468 branches covered (27.86%)

Branch coverage included in aggregate %.

24 of 24 new or added lines in 1 file covered. (100.0%)

24 existing lines in 1 file now uncovered.

4154 of 10638 relevant lines covered (39.05%)

161309.83 hits per line

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

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

16
namespace Cpp2IL.Core.Model.Contexts;
17

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

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

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

40
    public ulong Rva => UnderlyingPointer == 0 || LibCpp2IlMain.Binary == null ? 0 : LibCpp2IlMain.Binary.GetRva(UnderlyingPointer);
×
41

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

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

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

57
    public List<ParameterAnalysisContext> Parameters = [];
582,203✔
58

59
    /// <summary>
60
    /// Does this method return void?
61
    /// </summary>
62
    public virtual bool IsVoid => (Definition?.ReturnType?.ToString() ?? throw new("Subclasses of MethodAnalysisContext should override IsVoid")) == "System.Void";
×
63

64
    public virtual bool IsStatic => Definition?.IsStatic ?? throw new("Subclasses of MethodAnalysisContext should override IsStatic");
87,355!
65

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

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

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

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

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

76
    public virtual MethodAttributes Attributes => Definition?.Attributes ?? throw new("Subclasses of MethodAnalysisContext should override Attributes");
174,060!
77

78
    public TypeAnalysisContext? InjectedReturnType { get; set; }
545,738✔
79

80
    public int ParameterCount => Parameters.Count;
8✔
81

82
    public int GenericParameterCount => Definition?.GenericContainer?.genericParameterCount ?? 0;
279,909!
83

84
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
75,670!
85

86
    //TODO Support custom attributes on return types (v31 feature)
87
    public TypeAnalysisContext ReturnTypeContext => InjectedReturnType ?? DeclaringType!.DeclaringAssembly.ResolveIl2CppType(Definition!.RawReturnType!);
316,485✔
88
    
89
    protected Memory<byte>? rawMethodBody;
90

91
    public MethodAnalysisContext? BaseMethod => Overrides.FirstOrDefault(m => m.DeclaringType?.IsInterface is false);
6!
92

93
    /// <summary>
94
    /// The set of methods which this method overrides.
95
    /// </summary>
96
    public virtual IEnumerable<MethodAnalysisContext> Overrides
97
    {
98
        get
99
        {
100
            if (Definition == null)
18,034!
101
                return [];
×
102

103
            var declaringTypeDefinition = DeclaringType?.Definition;
18,034!
104
            if (declaringTypeDefinition == null)
18,034!
105
                return [];
×
106

107
            var vtable = declaringTypeDefinition.VTable;
18,034✔
108
            if (vtable == null)
18,034!
109
                return [];
×
110

111
            return GetOverriddenMethods(declaringTypeDefinition, vtable);
18,034✔
112

113
            bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method)
114
            {
115
                if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
13,515✔
116
                {
117
                    var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
7,093✔
118
                    if (genericMethod is not null)
1,800✔
119
                    {
120
                        method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments.ToArray(), []);
329✔
121
                        return true;
329✔
122
                    }
123
                }
124
                else
125
                {
126
                    var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot);
82,092✔
127
                    if (baseMethod is not null)
11,715✔
128
                    {
129
                        method = baseMethod;
2,829✔
130
                        return true;
2,829✔
131
                    }
132
                }
133

134
                method = null;
10,357✔
135
                return false;
10,357✔
136
            }
137

138
            IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
139
            {
140
                for (var i = 0; i < vtable.Length; ++i)
683,538✔
141
                {
142
                    var vtableEntry = vtable[i];
323,736✔
143
                    if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef })
323,736✔
144
                        continue;
145

146
                    if (vtableEntry.AsMethod() != Definition)
318,126✔
147
                        continue;
148

149
                    // Normal inheritance
150
                    var baseType = DeclaringType?.BaseType;
3,123!
151
                    while (baseType is not null)
7,849✔
152
                    {
153
                        if (TryGetMethodForSlot(baseType, i, out var method))
4,763✔
154
                        {
155
                            yield return method;
37✔
156
                            break; // We only want direct overrides, not the entire inheritance chain.
36✔
157
                        }
158
                        baseType = baseType.BaseType;
4,726✔
159
                    }
160

161
                    // Interface inheritance
162
                    foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
20,633✔
163
                    {
164
                        if (i >= interfaceOffset.offset)
8,755✔
165
                        {
166
                            var interfaceTypeContext = interfaceOffset.Type.ToContext(CustomAttributeAssembly);
8,752✔
167
                            if (interfaceTypeContext != null && TryGetMethodForSlot(interfaceTypeContext, i - interfaceOffset.offset, out var method))
8,752✔
168
                            {
169
                                yield return method;
3,121✔
170
                                break; // A vtable entry can only point to one method.
3,121✔
171
                            }
172
                        }
173
                    }
174
                }
175
            }
18,033✔
176
        }
177
    }
178

179
    private static readonly List<IBlockProcessor> blockProcessors =
×
180
    [
×
181
        new MetadataProcessor(),
×
182
        new CallProcessor()
×
UNCOV
183
    ];
×
184

185
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
582,203✔
186
    {
187
        DeclaringType = parent;
582,203✔
188
        Definition = definition;
582,203✔
189

190
        if (Definition != null)
582,203✔
191
        {
192
            InitCustomAttributeData();
352,950✔
193

194
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
1,519,920✔
195
            {
196
                var parameterDefinition = Definition.InternalParameterData![i];
407,010✔
197
                Parameters.Add(new(parameterDefinition, i, this));
407,010✔
198
            }
199
        }
200
        else
201
            rawMethodBody = Array.Empty<byte>();
229,253✔
202
    }
229,253✔
203

204
    [MemberNotNull(nameof(rawMethodBody))]
205
    public void EnsureRawBytes()
206
    {
207
        rawMethodBody ??= InitRawBytes();
352,950✔
208
    }
352,950✔
209

210
    private Memory<byte> InitRawBytes()
211
    {
212
        //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.
213
        //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build)
214
        if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract))
352,950✔
215
        {
216
            var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false);
341,598✔
217

218
            if (ret.Length == 0)
341,598✔
219
            {
220
                Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC");
28!
221
            }
222

223
            return ret;
341,598✔
224
        }
225
        else
226
            return Array.Empty<byte>();
11,352✔
227
    }
228

UNCOV
229
    protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
×
230
    {
231
        rawMethodBody = Array.Empty<byte>();
×
UNCOV
232
    }
×
233

234
    [MemberNotNull(nameof(ConvertedIsil))]
235
    public void Analyze()
236
    {
237
        if (ConvertedIsil != null)
×
UNCOV
238
            return;
×
239

UNCOV
240
        if (UnderlyingPointer == 0)
×
241
        {
242
            ConvertedIsil = [];
×
UNCOV
243
            return;
×
244
        }
245

UNCOV
246
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
247

248
        if (ConvertedIsil.Count == 0)
×
UNCOV
249
            return; //Nothing to do, empty function
×
250

251
        ControlFlowGraph = new ISILControlFlowGraph();
×
UNCOV
252
        ControlFlowGraph.Build(ConvertedIsil);
×
253

254
        // Post step to convert metadata usage. Ldstr Opcodes etc.
UNCOV
255
        foreach (var block in ControlFlowGraph.Blocks)
×
256
        {
UNCOV
257
            foreach (var converter in blockProcessors)
×
258
            {
UNCOV
259
                converter.Process(this, block);
×
260
            }
261
        }
UNCOV
262
    }
×
263

264
    public void ReleaseAnalysisData()
265
    {
266
        ConvertedIsil = null;
×
267
        ControlFlowGraph = null;
×
UNCOV
268
    }
×
269

270
    public override string ToString() => $"Method: {FullName}";
28✔
271

272
    #region StableNameDot implementation
273

274
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
275
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
276
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
UNCOV
277
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
278

UNCOV
279
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
280

UNCOV
281
    string IMethodInfoProvider.MethodName => Name;
×
282

UNCOV
283
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
284

285
    MethodSemantics IMethodInfoProvider.MethodSemantics
286
    {
287
        get
288
        {
UNCOV
289
            if (DeclaringType != null)
×
290
            {
291
                //This one is a bit trickier, as il2cpp doesn't use semantics.
UNCOV
292
                foreach (var prop in DeclaringType.Properties)
×
293
                {
294
                    if (prop.Getter == this)
×
295
                        return MethodSemantics.Getter;
×
296
                    if (prop.Setter == this)
×
UNCOV
297
                        return MethodSemantics.Setter;
×
298
                }
299

UNCOV
300
                foreach (var evt in DeclaringType.Events)
×
301
                {
302
                    if (evt.Adder == this)
×
303
                        return MethodSemantics.AddOn;
×
304
                    if (evt.Remover == this)
×
305
                        return MethodSemantics.RemoveOn;
×
306
                    if (evt.Invoker == this)
×
UNCOV
307
                        return MethodSemantics.Fire;
×
308
                }
309
            }
310

311
            return 0;
×
UNCOV
312
        }
×
313
    }
314

315
    #endregion
316
}
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