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

SamboyCoding / Cpp2IL / 13494977167

24 Feb 2025 09:38AM UTC coverage: 27.383% (+0.04%) from 27.341%
13494977167

Pull #414

github

web-flow
Merge 2fd947674 into 16c3a7581
Pull Request #414: Fix explicit interface method overrides

1261 of 6406 branches covered (19.68%)

Branch coverage included in aggregate %.

17 of 87 new or added lines in 10 files covered. (19.54%)

1 existing line in 1 file now uncovered.

3375 of 10524 relevant lines covered (32.07%)

123786.14 hits per line

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

24.14
/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");
488,988!
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 = [];
394,026✔
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");
×
65

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

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

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

72
    public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";
22!
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");
×
77

78
    public TypeAnalysisContext? InjectedReturnType { get; set; }
290,901✔
79

80
    public int ParameterCount => Parameters.Count;
×
81

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

NEW
84
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
×
85

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

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

NEW
101
            var declaringTypeDefinition = DeclaringType?.Definition;
×
NEW
102
            if (declaringTypeDefinition == null)
×
NEW
103
                return [];
×
104

NEW
105
            var vtable = declaringTypeDefinition.VTable;
×
NEW
106
            if (vtable == null)
×
NEW
107
                return [];
×
108

NEW
109
            return GetOverriddenMethods(declaringTypeDefinition, vtable);
×
110

111
            static void GetParentTypeAndSlot(Il2CppTypeDefinition declaringTypeDefinition, int vtableIndex, out Il2CppTypeReflectionData? parentType, out int slot)
112
            {
NEW
113
                var interfaceOffsets = declaringTypeDefinition.InterfaceOffsets;
×
NEW
114
                for (var i = interfaceOffsets.Length - 1; i >= 0; i--)
×
115
                {
NEW
116
                    var interfaceOffset = interfaceOffsets[i];
×
NEW
117
                    if (vtableIndex >= interfaceOffset.offset)
×
118
                    {
NEW
119
                        slot = vtableIndex - interfaceOffsets[i].offset;
×
NEW
120
                        parentType = interfaceOffset.Type;
×
NEW
121
                        return;
×
122
                    }
123
                }
124

NEW
125
                parentType = declaringTypeDefinition.BaseType;
×
NEW
126
                slot = vtableIndex;
×
NEW
127
            }
×
128

129
            IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
130
            {
NEW
131
                for (var i = 0; i < vtable.Length; ++i)
×
132
                {
NEW
133
                    var vtableEntry = vtable[i];
×
NEW
134
                    if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef })
×
135
                        continue;
136

NEW
137
                    if (vtableEntry.AsMethod() != Definition)
×
138
                        continue;
139

NEW
140
                    GetParentTypeAndSlot(declaringTypeDefinition, i, out var parentType, out var slot);
×
141

NEW
142
                    var parentTypeContext = parentType?.ToContext(CustomAttributeAssembly);
×
NEW
143
                    if (parentTypeContext == null)
×
144
                        continue;
145

NEW
146
                    if (parentTypeContext is GenericInstanceTypeAnalysisContext genericInstanceType)
×
147
                    {
NEW
148
                        var parentMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
×
NEW
149
                        if (parentMethod is not null)
×
NEW
150
                            yield return new ConcreteGenericMethodAnalysisContext(parentMethod, genericInstanceType.GenericArguments.ToArray(), []);
×
151
                    }
152
                    else
153
                    {
NEW
154
                        var parentMethod = parentTypeContext.Methods.FirstOrDefault(m => m.Slot == slot);
×
NEW
155
                        if (parentMethod is not null)
×
NEW
156
                            yield return parentMethod;
×
157
                    }
158
                }
NEW
159
            }
×
160
        }
161
    }
162

163
    private static readonly List<IBlockProcessor> blockProcessors =
×
164
    [
×
165
        new MetadataProcessor(),
×
166
        new CallProcessor()
×
167
    ];
×
168

169
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
394,026✔
170
    {
171
        DeclaringType = parent;
394,026✔
172
        Definition = definition;
394,026✔
173

174
        if (Definition != null)
394,026✔
175
        {
176
            InitCustomAttributeData();
248,514✔
177

178
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
1,075,284✔
179
            {
180
                var parameterDefinition = Definition.InternalParameterData![i];
289,128✔
181
                Parameters.Add(new(parameterDefinition, i, this));
289,128✔
182
            }
183
        }
184
        else
185
            rawMethodBody = Array.Empty<byte>();
145,512✔
186
    }
145,512✔
187

188
    [MemberNotNull(nameof(rawMethodBody))]
189
    public void EnsureRawBytes()
190
    {
191
        rawMethodBody ??= InitRawBytes();
248,514✔
192
    }
248,514✔
193

194
    private Memory<byte> InitRawBytes()
195
    {
196
        //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.
197
        //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build)
198
        if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract))
248,514✔
199
        {
200
            var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false);
240,474✔
201

202
            if (ret.Length == 0)
240,474✔
203
            {
204
                Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC");
22!
205
            }
206

207
            return ret;
240,474✔
208
        }
209
        else
210
            return Array.Empty<byte>();
8,040✔
211
    }
212

213
    protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
×
214
    {
215
        rawMethodBody = Array.Empty<byte>();
×
216
    }
×
217

218
    [MemberNotNull(nameof(ConvertedIsil))]
219
    public void Analyze()
220
    {
221
        if (ConvertedIsil != null)
×
222
            return;
×
223

224
        if (UnderlyingPointer == 0)
×
225
        {
226
            ConvertedIsil = [];
×
227
            return;
×
228
        }
229

230
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
231

232
        if (ConvertedIsil.Count == 0)
×
233
            return; //Nothing to do, empty function
×
234

235
        ControlFlowGraph = new ISILControlFlowGraph();
×
236
        ControlFlowGraph.Build(ConvertedIsil);
×
237

238
        // Post step to convert metadata usage. Ldstr Opcodes etc.
239
        foreach (var block in ControlFlowGraph.Blocks)
×
240
        {
241
            foreach (var converter in blockProcessors)
×
242
            {
243
                converter.Process(this, block);
×
244
            }
245
        }
246
    }
×
247

248
    public void ReleaseAnalysisData()
249
    {
250
        ConvertedIsil = null;
×
251
        ControlFlowGraph = null;
×
252
    }
×
253

254
    public override string ToString() => $"Method: {FullName}";
22✔
255

256
    #region StableNameDot implementation
257

258
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
259
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
260
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
261
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
262

263
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
264

265
    string IMethodInfoProvider.MethodName => Name;
×
266

267
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
268

269
    MethodSemantics IMethodInfoProvider.MethodSemantics
270
    {
271
        get
272
        {
273
            if (DeclaringType != null)
×
274
            {
275
                //This one is a bit trickier, as il2cpp doesn't use semantics.
276
                foreach (var prop in DeclaringType.Properties)
×
277
                {
278
                    if (prop.Getter == this)
×
279
                        return MethodSemantics.Getter;
×
280
                    if (prop.Setter == this)
×
281
                        return MethodSemantics.Setter;
×
282
                }
283

284
                foreach (var evt in DeclaringType.Events)
×
285
                {
286
                    if (evt.Adder == this)
×
287
                        return MethodSemantics.AddOn;
×
288
                    if (evt.Remover == this)
×
289
                        return MethodSemantics.RemoveOn;
×
290
                    if (evt.Invoker == this)
×
291
                        return MethodSemantics.Fire;
×
292
                }
293
            }
294

295
            return 0;
×
296
        }
×
297
    }
298

299
    #endregion
300
}
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

© 2025 Coveralls, Inc