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

SamboyCoding / Cpp2IL / 30857682569

03 Aug 2026 10:11PM UTC coverage: 32.944% (-0.002%) from 32.946%
30857682569

push

github

web-flow
Fix pre-v29 attributes (#596)

2939 of 10659 branches covered (27.57%)

Branch coverage included in aggregate %.

2 of 4 new or added lines in 2 files covered. (50.0%)

5375 of 14578 relevant lines covered (36.87%)

151746.72 hits per line

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

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

13
namespace Cpp2IL.Core.Model.Contexts;
14

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

24
    /// <summary>
25
    /// On V29, stores the custom attribute blob. Pre-29, stores the bytes for the custom attribute generator function.
26
    /// </summary>
27
    public BinarySlice RawIl2CppCustomAttributeData = BinarySlice.Empty;
4,306,392✔
28

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

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

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

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

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

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

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

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

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

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

84

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

88
    protected void InitCustomAttributeData()
89
    {
90
        if (IsInjected)
2,311,494!
91
            return;
×
92
        
93
        _hasInitCustomAttributeData = true;
2,311,494✔
94
        if (AppContext.MetadataVersion >= 29)
2,311,494✔
95
        {
96
            var offsets = GetV29BlobOffsets();
773,481✔
97

98
            if (!offsets.HasValue)
773,481✔
99
                return;
705,047✔
100

101
            var (blobStart, blobEnd) = offsets.Value;
68,434✔
102
            RawIl2CppCustomAttributeData = new BinarySlice(AppContext.Metadata.ReadByteArrayAtRawAddress(blobStart, (int)(blobEnd - blobStart)));
68,434✔
103

104
            return;
68,434✔
105
        }
106

107
        if (CustomAttributeAssembly.Definition is null)
1,538,013✔
108
            return;
1,177✔
109

110
        AttributeTypeRange = AppContext.Metadata.GetCustomAttributeData(CustomAttributeAssembly.Definition.Image, CustomAttributeIndex, Token, out Pre29RangeIndex);
1,536,836✔
111

112
        if (AttributeTypeRange == null || AttributeTypeRange.count == 0)
1,536,836✔
113
        {
114
            AttributeTypes = [];
1,417,556✔
115
            return; //No attributes
1,417,556✔
116
        }
117

118
        AttributeTypes = Enumerable.Range(AttributeTypeRange.start, AttributeTypeRange.count)
119,280✔
119
            .Select(attrIdx => AppContext.Metadata.attributeTypes![attrIdx]) //Not null because we've checked we're not on v29
268,520✔
120
            .Select(typeIdx => AppContext.Binary.GetType(Il2CppVariableWidthIndex<Il2CppType>.MakeTemporaryForFixedWidthUsage(typeIdx)))
268,520✔
121
            .ToList();
119,280✔
122
    }
119,280✔
123

124
    private (long blobStart, long blobEnd)? GetV29BlobOffsets()
125
    {
126
        if (CustomAttributeAssembly.Definition is null)
773,481✔
127
            return null;
575✔
128

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

138
        if (caIndex < 0)
772,906✔
139
        {
140
            return null;
704,472✔
141
        }
142

143
        var attributeDataRange = AppContext.Metadata.AttributeDataRanges[caIndex];
68,434✔
144
        var next = AppContext.Metadata.AttributeDataRanges[caIndex + 1];
68,434✔
145

146
        var blobStart = AppContext.Metadata.metadataHeader.attributeData.Offset + attributeDataRange.startOffset;
68,434✔
147
        var blobEnd = AppContext.Metadata.metadataHeader.attributeData.Offset + next.startOffset;
68,434✔
148
        return (blobStart, blobEnd);
68,434✔
149
    }
150

151
    private void InitPre29AttributeGeneratorAnalysis(int rangeIndex)
152
    {
153
        ulong generatorPtr;
154
        if (AppContext.MetadataVersion < 27)
×
155
        {
156
            if (rangeIndex < 0)
×
157
            {
158
                return;
×
159
            }
160

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

184
        if (generatorPtr == 0 || !AppContext.Binary.TryMapVirtualAddressToRaw(generatorPtr, out _))
×
185
        {
186
            Logger.WarnNewline($"Supposedly had custom attributes ({string.Join(", ", AttributeTypes ?? [])}), but generator was null for " + this, "CA Restore");
×
187
            return;
×
188
        }
189

190
        CaCacheGeneratorAnalysis = new(generatorPtr, AppContext, this);
×
NEW
191
        CaCacheGeneratorAnalysis.EnsureRawBytes();
×
192
        RawIl2CppCustomAttributeData = CaCacheGeneratorAnalysis.RawBytes;
×
193
    }
×
194

195
    /// <summary>
196
    /// Attempt to parse the Il2CppCustomAttributeData blob into custom attributes.
197
    /// </summary>
198
    public void AnalyzeCustomAttributeData(bool allowAnalysis = true)
199
    {
200
        if (_hasAnalyzedCustomAttributeData)
72,321✔
201
            return;
6,264✔
202
        
203
        if(IsInjected)
66,057!
204
            return;
×
205
        
206
        if(!_hasInitCustomAttributeData)
66,057!
207
            throw new($"Must call InitCustomAttributeData before AnalyzeCustomAttributeData on {this}");
×
208

209
        _hasAnalyzedCustomAttributeData = true;
66,057✔
210

211
        CustomAttributes = [];
66,057✔
212

213
        if (AppContext.MetadataVersion >= 29)
66,057!
214
        {
215
            AnalyzeCustomAttributeDataV29();
66,057✔
216
            return;
66,057✔
217
        }
218
        
219
        InitPre29AttributeGeneratorAnalysis(Pre29RangeIndex);
×
220

221
        if (RawIl2CppCustomAttributeData.Length == 0)
×
222
            return;
×
223

224
        if (allowAnalysis)
×
225
        {
226
            try
227
            {
228
                CaCacheGeneratorAnalysis!.Analyze();
×
229
            }
×
230
            catch (Exception e)
×
231
            {
232
                Logger.WarnNewline("Failed to analyze custom attribute cache generator for " + this + " because " + e.Message, "CA Restore");
×
233
                return;
×
234
            }
235
        }
236

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

239
        foreach (var il2CppType in AttributeTypes ?? []) //Can be null for injected objects
×
240
        {
241
            var typeDef = il2CppType.AsClass();
×
242
            var attributeTypeContext = AppContext.ResolveContextForType(typeDef) ?? throw new("Unable to find type " + typeDef.FullName);
×
243

244
            AnalyzedCustomAttribute attribute;
NEW
245
            if (attributeTypeContext.Methods.FirstOrDefault(c => c.Name == ".ctor" && c.Parameters.Count == 0) is { } constructor)
×
246
            {
247
                attribute = new(constructor);
×
248
            }
249
            else if (attributeTypeContext.Methods.FirstOrDefault(c => c.Name == ".ctor") is { } anyConstructor)
×
250
            {
251
                //TODO change this to actual constructor w/ params once anaylsis is available
252
                attribute = new(anyConstructor);
×
253
            }
254
            else
255
                //No constructor - shouldn't happen?
256
                continue;
257

258
            //Add the attribute, even if we don't have constructor params, so it can be read regardless
259
            CustomAttributes.Add(attribute);
×
260
        }
261
    }
×
262

263
    /// <summary>
264
    /// Parses the Il2CppCustomAttributeData blob as a v29 metadata attribute blob into custom attributes.
265
    /// </summary>
266
    private void AnalyzeCustomAttributeDataV29()
267
    {
268
        if (RawIl2CppCustomAttributeData.Length == 0)
66,057✔
269
            return;
57,540✔
270

271
        using var blobStream = new MemoryStream(RawIl2CppCustomAttributeData.ToArray());
8,517✔
272
        var attributeCount = blobStream.ReadUnityCompressedUint();
8,517✔
273
        var constructors = V29AttributeUtils.ReadConstructors(blobStream, attributeCount, AppContext);
8,517✔
274

275
        //Diagnostic data
276
        var startOfData = blobStream.Position;
8,517✔
277
        var perAttributeStartOffsets = new Dictionary<MethodAnalysisContext, long>();
8,517✔
278

279
        CustomAttributes = [];
8,517✔
280
        foreach (var constructor in constructors)
40,644✔
281
        {
282
            perAttributeStartOffsets[constructor] = blobStream.Position;
11,805✔
283

284
            try
285
            {
286
                CustomAttributes.Add(V29AttributeUtils.ReadAttribute(blobStream, constructor, AppContext));
11,805✔
287
            }
11,805✔
288
            catch (Exception e)
×
289
            {
290
                Logger.ErrorNewline($"Failed to read attribute data for {constructor}, which has parameters {string.Join(", ", constructor.Parameters.Select(p => p.ParameterType))}", "CA Restore");
×
291
                Logger.ErrorNewline($"This member ({ToString()}) has {RawIl2CppCustomAttributeData.Length} bytes of data starting at 0x{GetV29BlobOffsets()!.Value.blobStart:X}", "CA Restore");
×
292
                Logger.ErrorNewline($"The post-constructor data started at 0x{startOfData:X} bytes into our blob", "CA Restore");
×
293
                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");
×
294
                Logger.ErrorNewline($"The exception message was {e.Message}", "CA Restore");
×
295

296
                throw;
×
297
            }
298
        }
299
    }
17,034✔
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc