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

SamboyCoding / Cpp2IL / 19779760238

29 Nov 2025 05:47AM UTC coverage: 34.306% (-0.004%) from 34.31%
19779760238

Pull #503

github

web-flow
Merge d8f31b3c5 into e66a74bb3
Pull Request #503: Fix warnings

1791 of 6590 branches covered (27.18%)

Branch coverage included in aggregate %.

3 of 10 new or added lines in 5 files covered. (30.0%)

4191 of 10847 relevant lines covered (38.64%)

180612.93 hits per line

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

42.29
/Cpp2IL.Core/Model/Contexts/HasCustomAttributes.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics;
4
using System.IO;
5
using System.Linq;
6
using Cpp2IL.Core.Extensions;
7
using Cpp2IL.Core.Logging;
8
using Cpp2IL.Core.Model.CustomAttributes;
9
using Cpp2IL.Core.Utils;
10
using LibCpp2IL;
11
using LibCpp2IL.BinaryStructures;
12
using LibCpp2IL.Metadata;
13

14
namespace Cpp2IL.Core.Model.Contexts;
15

16
/// <summary>
17
/// A base class to represent any type which has, or can have, custom attributes.
18
/// </summary>
19
public abstract class HasCustomAttributes(uint token, ApplicationAnalysisContext appContext)
20
    : HasToken(token, appContext)
2,553,721✔
21
{
22
    private bool _hasAnalyzedCustomAttributeData;
23
    private bool _hasInitCustomAttributeData;
24

25
    /// <summary>
26
    /// On V29, stores the custom attribute blob. Pre-29, stores the bytes for the custom attribute generator function.
27
    /// </summary>
28
    public Memory<byte> RawIl2CppCustomAttributeData = Memory<byte>.Empty;
2,553,721✔
29

30
    /// <summary>
31
    /// Stores the analyzed custom attribute data once analysis has actually run.
32
    /// </summary>
33
    public List<AnalyzedCustomAttribute>? CustomAttributes;
34

35
    /// <summary>
36
    /// Stores the attribute type range for this member, which references which custom attributes are present.
37
    ///
38
    /// Null on v29+, nonnull prior to that
39
    /// </summary>
40
    public Il2CppCustomAttributeTypeRange? AttributeTypeRange;
41

42
    /// <summary>
43
    /// Stores the raw types of the custom attributes on this member.
44
    ///
45
    /// Null on v29+ (constructors are in the blob), nonnull prior to that
46
    /// </summary>
47
    public List<Il2CppType>? AttributeTypes;
48

49
    /// <summary>
50
    /// Prior to v29, stores the analysis context for custom attribute cache generator function.
51
    ///
52
    /// On v29, is null because there is no method, the attribute blob is stored instead, in the metadata file.
53
    /// </summary>
54
    public AttributeGeneratorMethodAnalysisContext? CaCacheGeneratorAnalysis;
55

56
    /// <summary>
57
    /// Returns this member's custom attribute index, or -1 if it has no custom attributes.
58
    /// </summary>
59
    protected abstract int CustomAttributeIndex { get; }
60

61
    /// <summary>
62
    /// Returns this member's assembly context for use in custom attribute reconstruction.
63
    /// </summary>
64
    public abstract AssemblyAnalysisContext CustomAttributeAssembly { get; }
65

66
    /// <summary>
67
    /// Returns true if this member is injected by Cpp2IL (and thus should not be analyzed for custom attributes).
68
    /// </summary>
69
    protected virtual bool IsInjected => false;
1,398,123✔
70

71
    /// <summary>
72
    /// Pre-v29, stores the index of the custom attribute range for this member. Post-v29, always -1.
73
    /// </summary>
74
    private int Pre29RangeIndex = -1;
2,553,721✔
75

76
    public bool IsCompilerGeneratedBasedOnCustomAttributes => HasCustomAttributeWithFullName("System.Runtime.CompilerServices.CompilerGeneratedAttribute");
×
77

78
    public bool HasCustomAttributeWithFullName(string fullName)
79
    {
80
        return CustomAttributes?.Any(a => a.Constructor.DeclaringType!.FullName == fullName)
×
81
            ?? AttributeTypes?.Any(t => t.Type == Il2CppTypeEnum.IL2CPP_TYPE_CLASS && t.AsClass().FullName == fullName)
×
82
            ?? false;
×
83
    }
84

85

86
#pragma warning disable CS8618 //Non-null member is not initialized.
87
#pragma warning restore CS8618
88

89
    protected void InitCustomAttributeData()
90
    {
91
        if(IsInjected)
1,399,215!
92
            return;
×
93
        
94
        _hasInitCustomAttributeData = true;
1,399,215✔
95
        if (AppContext.MetadataVersion >= 29)
1,399,215✔
96
        {
97
            var offsets = GetV29BlobOffsets();
246,588✔
98

99
            if (!offsets.HasValue)
246,588✔
100
                return;
228,024✔
101

102
            var (blobStart, blobEnd) = offsets.Value;
18,564✔
103
            RawIl2CppCustomAttributeData = AppContext.Metadata.ReadByteArrayAtRawAddress(blobStart, (int)(blobEnd - blobStart));
18,564✔
104

105
            return;
18,564✔
106
        }
107

108
        if (CustomAttributeAssembly.Definition is null)
1,152,627!
109
            return;
×
110

111
        AttributeTypeRange = AppContext.Metadata.GetCustomAttributeData(CustomAttributeAssembly.Definition.Image, CustomAttributeIndex, Token, out Pre29RangeIndex);
1,152,627✔
112

113
        if (AttributeTypeRange == null || AttributeTypeRange.count == 0)
1,152,627✔
114
        {
115
            RawIl2CppCustomAttributeData = Array.Empty<byte>();
1,063,167✔
116
            AttributeTypes = [];
1,063,167✔
117
            return; //No attributes
1,063,167✔
118
        }
119

120
        AttributeTypes = Enumerable.Range(AttributeTypeRange.start, AttributeTypeRange.count)
89,460✔
121
            .Select(attrIdx => AppContext.Metadata!.attributeTypes![attrIdx]) //Not null because we've checked we're not on v29
201,390✔
122
            .Select(typeIdx => AppContext.Binary!.GetType(typeIdx))
201,390✔
123
            .ToList();
89,460✔
124
    }
89,460✔
125

126
    private (long blobStart, long blobEnd)? GetV29BlobOffsets()
127
    {
128
        if (CustomAttributeAssembly.Definition is null)
246,588!
129
            return null;
×
130

131
        var target = new Il2CppCustomAttributeDataRange() { token = Token };
246,588✔
132
        var caIndex = AppContext.Metadata.AttributeDataRanges!.BinarySearch
246,588✔
133
        (
246,588✔
134
            CustomAttributeAssembly.Definition.Image.customAttributeStart,
246,588✔
135
            (int)CustomAttributeAssembly.Definition.Image.customAttributeCount,
246,588✔
136
            target,
246,588✔
137
            new TokenComparer()
246,588✔
138
        );
246,588✔
139

140
        if (caIndex < 0)
246,588✔
141
        {
142
            RawIl2CppCustomAttributeData = Array.Empty<byte>();
228,024✔
143
            return null;
228,024✔
144
        }
145

146
        var attributeDataRange = AppContext.Metadata.AttributeDataRanges[caIndex];
18,564✔
147
        var next = AppContext.Metadata.AttributeDataRanges[caIndex + 1];
18,564✔
148

149
        var blobStart = AppContext.Metadata.metadataHeader.attributeDataOffset + attributeDataRange.startOffset;
18,564✔
150
        var blobEnd = AppContext.Metadata.metadataHeader.attributeDataOffset + next.startOffset;
18,564✔
151
        return (blobStart, blobEnd);
18,564✔
152
    }
153

154
    private void InitPre29AttributeGeneratorAnalysis(int rangeIndex)
155
    {
156
        ulong generatorPtr;
157
        if (AppContext.MetadataVersion < 27)
×
158
        {
159
            if (rangeIndex < 0)
×
160
            {
161
                RawIl2CppCustomAttributeData = Array.Empty<byte>();
×
162
                return;
×
163
            }
164

165
            try
166
            {
167
                generatorPtr = AppContext.Binary.GetCustomAttributeGenerator(rangeIndex);
×
168
            }
×
169
            catch (IndexOutOfRangeException)
×
170
            {
171
                Logger.WarnNewline("Custom attribute generator out of range for " + this, "CA Restore");
×
172
                RawIl2CppCustomAttributeData = Array.Empty<byte>();
×
173
                return;
×
174
            }
175
        }
176
        else
177
        {
178
            if(AttributeTypeRange == null || AttributeTypeRange.count == 0 || CustomAttributeAssembly.Definition is null)
×
179
            {
180
                RawIl2CppCustomAttributeData = Array.Empty<byte>();
×
181
                return;
×
182
            }
183
            
184
            var baseAddress = CustomAttributeAssembly.CodeGenModule!.customAttributeCacheGenerator;
×
185
            var relativeIndex = rangeIndex - CustomAttributeAssembly.Definition.Image.customAttributeStart;
×
186
            var ptrToAddress = baseAddress + (ulong)relativeIndex * AppContext.Binary.PointerSize;
×
187
            generatorPtr = AppContext.Binary.ReadPointerAtVirtualAddress(ptrToAddress);
×
188
        }
189

190
        if (generatorPtr == 0 || !AppContext.Binary.TryMapVirtualAddressToRaw(generatorPtr, out _))
×
191
        {
NEW
192
            Logger.WarnNewline($"Supposedly had custom attributes ({string.Join(", ", AttributeTypes ?? [])}), but generator was null for " + this, "CA Restore");
×
193
            RawIl2CppCustomAttributeData = Memory<byte>.Empty;
×
194
            return;
×
195
        }
196

197
        CaCacheGeneratorAnalysis = new(generatorPtr, AppContext, this);
×
198
        RawIl2CppCustomAttributeData = CaCacheGeneratorAnalysis.RawBytes;
×
199
    }
×
200

201
    /// <summary>
202
    /// Attempt to parse the Il2CppCustomAttributeData blob into custom attributes.
203
    /// </summary>
204
    public void AnalyzeCustomAttributeData(bool allowAnalysis = true)
205
    {
206
        if (_hasAnalyzedCustomAttributeData)
6!
207
            return;
×
208
        
209
        if(IsInjected)
6!
210
            return;
×
211
        
212
        if(!_hasInitCustomAttributeData)
6!
213
            throw new($"Must call InitCustomAttributeData before AnalyzeCustomAttributeData on {this}");
×
214

215
        _hasAnalyzedCustomAttributeData = true;
6✔
216

217
        CustomAttributes = [];
6✔
218

219
        if (AppContext.MetadataVersion >= 29)
6!
220
        {
221
            AnalyzeCustomAttributeDataV29();
6✔
222
            return;
6✔
223
        }
224
        
225
        InitPre29AttributeGeneratorAnalysis(Pre29RangeIndex);
×
226

227
        if (RawIl2CppCustomAttributeData.Length == 0)
×
228
            return;
×
229

230
        if (allowAnalysis)
×
231
        {
232
            try
233
            {
234
                CaCacheGeneratorAnalysis!.Analyze();
×
235
            }
×
236
            catch (Exception e)
×
237
            {
238
                Logger.WarnNewline("Failed to analyze custom attribute cache generator for " + this + " because " + e.Message, "CA Restore");
×
239
                return;
×
240
            }
241
        }
242

243
        //Basically, extract actions from the analysis, and compare with the type list we have to resolve parameters and populate the CustomAttributes list.
244

245
        foreach (var il2CppType in AttributeTypes ?? []) //Can be null for injected objects
×
246
        {
247
            var typeDef = il2CppType.AsClass();
×
248
            var attributeTypeContext = AppContext.ResolveContextForType(typeDef) ?? throw new("Unable to find type " + typeDef.FullName);
×
249

250
            AnalyzedCustomAttribute attribute;
251
            if (attributeTypeContext.Methods.FirstOrDefault(c => c.Name == ".ctor" && c.Definition!.parameterCount == 0) is { } constructor)
×
252
            {
253
                attribute = new(constructor);
×
254
            }
255
            else if (attributeTypeContext.Methods.FirstOrDefault(c => c.Name == ".ctor") is { } anyConstructor)
×
256
            {
257
                //TODO change this to actual constructor w/ params once anaylsis is available
258
                attribute = new(anyConstructor);
×
259
            }
260
            else
261
                //No constructor - shouldn't happen?
262
                continue;
263

264
            //Add the attribute, even if we don't have constructor params, so it can be read regardless
265
            CustomAttributes.Add(attribute);
×
266
        }
267
    }
×
268

269
    /// <summary>
270
    /// Parses the Il2CppCustomAttributeData blob as a v29 metadata attribute blob into custom attributes.
271
    /// </summary>
272
    private void AnalyzeCustomAttributeDataV29()
273
    {
274
        if (RawIl2CppCustomAttributeData.Length == 0)
6!
275
            return;
×
276

277
        using var blobStream = new MemoryStream(RawIl2CppCustomAttributeData.ToArray());
6✔
278
        var attributeCount = blobStream.ReadUnityCompressedUint();
6✔
279
        var constructors = V29AttributeUtils.ReadConstructors(blobStream, attributeCount, AppContext);
6✔
280

281
        //Diagnostic data
282
        var startOfData = blobStream.Position;
6✔
283
        var perAttributeStartOffsets = new Dictionary<Il2CppMethodDefinition, long>();
6✔
284

285
        CustomAttributes = [];
6✔
286
        foreach (var constructor in constructors)
36✔
287
        {
288
            perAttributeStartOffsets[constructor] = blobStream.Position;
12✔
289

290
            var attributeTypeContext = AppContext.ResolveContextForType(constructor.DeclaringType!) ?? throw new($"Unable to find type {constructor.DeclaringType!.FullName}");
12!
291
            var attributeMethodContext = attributeTypeContext.GetMethod(constructor) ?? throw new($"Unable to find method {constructor.Name} in type {attributeTypeContext.Definition?.FullName}");
12!
292

293
            try
294
            {
295
                CustomAttributes.Add(V29AttributeUtils.ReadAttribute(blobStream, attributeMethodContext, AppContext));
12✔
296
            }
12✔
297
            catch (Exception e)
×
298
            {
299
                Logger.ErrorNewline($"Failed to read attribute data for {constructor}, which has parameters {string.Join(", ", constructor.Parameters!.Select(p => p.Type))}", "CA Restore");
×
300
                Logger.ErrorNewline($"This member ({ToString()}) has {RawIl2CppCustomAttributeData.Length} bytes of data starting at 0x{GetV29BlobOffsets()!.Value.blobStart:X}", "CA Restore");
×
301
                Logger.ErrorNewline($"The post-constructor data started at 0x{startOfData:X} bytes into our blob", "CA Restore");
×
302
                Logger.ErrorNewline($"Data for this constructor started at 0x{perAttributeStartOffsets[constructor]:X} bytes into our blob, we are now 0x{blobStream.Position:X} bytes into the blob", "CA Restore");
×
303
                Logger.ErrorNewline($"The exception message was {e.Message}", "CA Restore");
×
304

305
                throw;
×
306
            }
307
        }
308
    }
12✔
309
}
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