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

SamboyCoding / Cpp2IL / 15052278841

15 May 2025 06:16PM UTC coverage: 34.038% (-0.4%) from 34.453%
15052278841

push

github

SamboyCoding
Support injecting anything
* Nested types
* Events
* Properties
* Assemblies

Support additional metadata in injected assemblies

Make setter for TypeAnalysisContext::OverrideBaseType public

TypeAnalysisContext::InterfaceContexts as list rather than array

Fix sign bug

Support generic parameters on type contexts and method contexts

Make GenericParameterTypeAnalysisContext instances unique

Revert change to Il2CppGenericParameter::Index

Use attributes to determine if injected methods are static
* Also add hide by sig attribute for injected constructors

Support overrides on injected methods

In ControlFlowGraphOutputFormat, exclude injected assemblies

Ensure injected assemblies can't delete existing assemblies

Backing field on .NET Standard for injected method overrides

Implement requested change

1774 of 6622 branches covered (26.79%)

Branch coverage included in aggregate %.

147 of 196 new or added lines in 27 files covered. (75.0%)

53 existing lines in 2 files now uncovered.

4155 of 10797 relevant lines covered (38.48%)

188267.61 hits per line

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

0.0
/Cpp2IL.Core/Utils/CsFileUtils.cs
1
using System;
2
using System.Reflection;
3
using System.Text;
4
using Cpp2IL.Core.Logging;
5
using Cpp2IL.Core.Model.Contexts;
6
using LibCpp2IL;
7

8
namespace Cpp2IL.Core.Utils;
9

10
public static class CsFileUtils
11
{
12
    /// <summary>
13
    /// Returns the parameters of the given method as they would likely appear in a C# method signature.
14
    /// That is to say, joined with a comma and a space, and with each parameter expressed as its type, a space, then its name, and optionally a default value if one is set.
15
    /// Note this does not include the method name, the return type, or the parentheses around the parameters.
16
    /// </summary>
17
    /// <param name="method">The method to generate the parameter string for</param>
18
    /// <returns>A properly-formatted parameter string as described above.</returns>
19
    public static string GetMethodParameterString(MethodAnalysisContext method)
20
    {
21
        var sb = new StringBuilder();
×
22
        var first = true;
×
23
        foreach (var paramData in method!.Parameters!)
×
24
        {
25
            if (!first)
×
26
                sb.Append(", ");
×
27

28
            first = false;
×
29

30
            sb.Append(paramData); //ToString on the ParameterData will do the right thing.
×
31
        }
32

33
        return sb.ToString();
×
34
    }
35

36
    /// <summary>
37
    /// Returns all the keywords that would be present in the c# source file to generate this type, i.e. access modifiers, static/sealed/etc, and the type of type (class, enum, interface).
38
    /// Does not include the name of the type.
39
    /// </summary>
40
    /// <param name="type">The type to generate the keywords for</param>
41
    public static string GetKeyWordsForType(TypeAnalysisContext type)
42
    {
43
        var sb = new StringBuilder();
×
44
        var attributes = type.Definition!.Attributes;
×
45

46
        if (attributes.HasFlag(TypeAttributes.NestedPrivate))
×
47
            sb.Append("private ");
×
48
        else if (attributes.HasFlag(TypeAttributes.Public))
×
49
            sb.Append("public ");
×
50
        else
51
            sb.Append("internal "); //private top-level classes don't exist, for obvious reasons
×
52

53
        if (type.IsEnumType)
×
54
            sb.Append("enum ");
×
55
        else if (type.IsValueType)
×
56
            sb.Append("struct ");
×
57
        else if (attributes.HasFlag(TypeAttributes.Interface))
×
58
            sb.Append("interface ");
×
59
        else
60
        {
61
            if (attributes.HasFlag(TypeAttributes.Abstract) && attributes.HasFlag(TypeAttributes.Sealed))
×
62
                //Abstract Sealed => Static
63
                sb.Append("static ");
×
64
            else if (attributes.HasFlag(TypeAttributes.Abstract))
×
65
                sb.Append("abstract ");
×
66
            else if (attributes.HasFlag(TypeAttributes.Sealed))
×
67
                sb.Append("sealed ");
×
68

69
            sb.Append("class ");
×
70
        }
71

72
        return sb.ToString().Trim();
×
73
    }
74

75
    /// <summary>
76
    /// Returns all the keywords that would be present in the c# source file to generate this method, i.e. access modifiers, static/const/etc.
77
    /// Does not include the type of the field or its name.
78
    /// </summary>
79
    /// <param name="field">The field to generate keywords for</param>
80
    public static string GetKeyWordsForField(FieldAnalysisContext field)
81
    {
82
        var sb = new StringBuilder();
×
83
        var attributes = field.BackingData!.Attributes;
×
84

85
        if (attributes.HasFlag(FieldAttributes.Public))
×
86
            sb.Append("public ");
×
87
        else if (attributes.HasFlag(FieldAttributes.Family))
×
88
            sb.Append("protected ");
×
89
        if (attributes.HasFlag(FieldAttributes.Assembly))
×
90
            sb.Append("internal ");
×
91
        else if (attributes.HasFlag(FieldAttributes.Private))
×
92
            sb.Append("private ");
×
93

94
        if (attributes.HasFlag(FieldAttributes.Literal))
×
95
            sb.Append("const ");
×
96
        else
97
        {
98
            if (attributes.HasFlag(FieldAttributes.Static))
×
99
                sb.Append("static ");
×
100

101
            if (attributes.HasFlag(FieldAttributes.InitOnly))
×
102
                sb.Append("readonly ");
×
103
        }
104

105
        return sb.ToString().Trim();
×
106
    }
107

108
    /// <summary>
109
    /// Returns all the keywords that would be present in the c# source file to generate this method, i.e. access modifiers, static/abstract/etc.
110
    /// Does not include the return type, name, or parameters.
111
    /// </summary>
112
    /// <param name="method">The method to generate keywords for</param>
113
    /// <param name="skipSlotRelated">Skip slot-related modifiers like abstract, virtual, override</param>
114
    /// <param name="skipKeywordsInvalidForAccessors">Skip the public and static keywords, as those aren't valid for property accessors</param>
115
    public static string GetKeyWordsForMethod(MethodAnalysisContext method, bool skipSlotRelated = false, bool skipKeywordsInvalidForAccessors = false)
116
    {
117
        var sb = new StringBuilder();
×
118
        var attributes = method.Definition!.Attributes;
×
119

120
        if (!skipKeywordsInvalidForAccessors)
×
121
        {
122
            if (attributes.HasFlag(MethodAttributes.Public))
×
123
                sb.Append("public ");
×
124
            else if (attributes.HasFlag(MethodAttributes.Family))
×
125
                sb.Append("protected ");
×
126
        }
127

128
        if (attributes.HasFlag(MethodAttributes.Assembly))
×
129
            sb.Append("internal ");
×
130
        else if (attributes.HasFlag(MethodAttributes.Private))
×
131
            sb.Append("private ");
×
132

133
        if (!skipKeywordsInvalidForAccessors && attributes.HasFlag(MethodAttributes.Static))
×
134
            sb.Append("static ");
×
135

136
        if (method.DeclaringType!.Definition!.Attributes.HasFlag(TypeAttributes.Interface) || skipSlotRelated)
×
137
        {
138
            //Deliberate no-op to avoid unnecessarily marking interface methods as abstract
139
        }
140
        else if (attributes.HasFlag(MethodAttributes.Abstract))
×
141
            sb.Append("abstract ");
×
142
        else if (attributes.HasFlag(MethodAttributes.NewSlot))
×
143
            sb.Append("override ");
×
144
        else if (attributes.HasFlag(MethodAttributes.Virtual))
×
145
            sb.Append("virtual ");
×
146

147

148
        return sb.ToString().Trim();
×
149
    }
150

151
    /// <summary>
152
    /// Returns all the keywords that would be present in the c# source file to generate this event, i.e. access modifiers, static/abstract/etc.
153
    /// Does not include the event type or name
154
    /// </summary>
155
    /// <param name="evt">The event to generate keywords for</param>
156
    public static string GetKeyWordsForEvent(EventAnalysisContext evt)
157
    {
158
        var sb = new StringBuilder();
×
159

160
        var addAttrs = evt.Adder?.Attributes ?? 0;
×
161
        var removeAttrs = evt.Remover?.Attributes ?? 0;
×
162
        var raiseAttrs = evt.Invoker?.Attributes ?? 0;
×
163

164
        var all = addAttrs | removeAttrs | raiseAttrs;
×
165

166
        //Accessibility must be that of the most accessible method
167
        if (addAttrs.HasFlag(MethodAttributes.Public) || removeAttrs.HasFlag(MethodAttributes.Public) || raiseAttrs.HasFlag(MethodAttributes.Public))
×
168
            sb.Append("public ");
×
169
        else if (all.HasFlag(MethodAttributes.Family)) //Family is only one bit so we can use the OR'd attributes
×
170
            sb.Append("protected ");
×
171
        if (addAttrs.HasFlag(MethodAttributes.Assembly) || removeAttrs.HasFlag(MethodAttributes.Assembly) || raiseAttrs.HasFlag(MethodAttributes.Assembly))
×
172
            sb.Append("internal ");
×
173
        else if (all.HasFlag(MethodAttributes.Private))
×
174
            sb.Append("private ");
×
175

176
        if (all.HasFlag(MethodAttributes.Static))
×
177
            sb.Append("static ");
×
178

179
        if (evt.DeclaringType!.Definition!.Attributes.HasFlag(TypeAttributes.Interface))
×
180
        {
181
            //Deliberate no-op to avoid unnecessarily marking interface methods as abstract
182
        }
183
        else if (all.HasFlag(MethodAttributes.Abstract))
×
184
            sb.Append("abstract ");
×
185
        else if (all.HasFlag(MethodAttributes.NewSlot))
×
186
            sb.Append("override ");
×
187
        else if (all.HasFlag(MethodAttributes.Virtual))
×
188
            sb.Append("virtual ");
×
189

190
        sb.Append("event ");
×
191

192
        return sb.ToString().Trim();
×
193
    }
194

195
    /// <summary>
196
    /// Returns all the keywords that would be present in the c# source file to generate this event, i.e. access modifiers, static/abstract/etc.
197
    /// Does not include the event type or name
198
    /// </summary>
199
    /// <param name="prop">The event to generate keywords for</param>
200
    public static string GetKeyWordsForProperty(PropertyAnalysisContext prop)
201
    {
202
        var sb = new StringBuilder();
×
203

204
        var getterAttributes = prop.Getter?.Attributes ?? 0;
×
205
        var setterAttributes = prop.Setter?.Attributes ?? 0;
×
206

207
        var all = getterAttributes | setterAttributes;
×
208

209
        //Accessibility must be that of the most accessible method
210
        if (getterAttributes.HasFlag(MethodAttributes.Public) || setterAttributes.HasFlag(MethodAttributes.Public))
×
211
            sb.Append("public ");
×
212
        else if (all.HasFlag(MethodAttributes.Family)) //Family is only one bit so we can use the OR'd attributes
×
213
            sb.Append("protected ");
×
214
        if (getterAttributes.HasFlag(MethodAttributes.Assembly) || setterAttributes.HasFlag(MethodAttributes.Assembly))
×
215
            sb.Append("internal ");
×
216
        else if (all.HasFlag(MethodAttributes.Private))
×
217
            sb.Append("private ");
×
218

219
        if (all.HasFlag(MethodAttributes.Static))
×
220
            sb.Append("static ");
×
221

222
        if (prop.DeclaringType!.Definition!.Attributes.HasFlag(TypeAttributes.Interface))
×
223
        {
224
            //Deliberate no-op to avoid unnecessarily marking interface methods as abstract
225
        }
226
        else if (all.HasFlag(MethodAttributes.Abstract))
×
227
            sb.Append("abstract ");
×
228
        else if (all.HasFlag(MethodAttributes.NewSlot))
×
229
            sb.Append("override ");
×
230
        else if (all.HasFlag(MethodAttributes.Virtual))
×
231
            sb.Append("virtual ");
×
232

233
        return sb.ToString().Trim();
×
234
    }
235

236
    /// <summary>
237
    /// Returns all the custom attributes for the given entity, as they would appear in a C# source file (i.e. properly wrapped in square brackets, with params if known)
238
    /// </summary>
239
    /// <param name="context">The entity to generate custom attribute strings for</param>
240
    /// <param name="indentCount">The number of tab characters to emit at the start of each line</param>
241
    /// <param name="analyze">True to call <see cref="HasCustomAttributes.AnalyzeCustomAttributeData"/> before generating.</param>
242
    /// <param name="includeIncomplete">True to emit custom attributes even if they have required parameters that aren't known</param>
243
    public static string GetCustomAttributeStrings(HasCustomAttributes context, int indentCount, bool analyze = true, bool includeIncomplete = true)
244
    {
245
        var sb = new StringBuilder();
×
246

247
        if (analyze)
×
248
            context.AnalyzeCustomAttributeData();
×
249

250
        //Sort alphabetically by type name
251
        context.CustomAttributes!.SortByExtractedKey(a => a.Constructor.DeclaringType!.Name);
×
252

253
        foreach (var analyzedCustomAttribute in context.CustomAttributes!)
×
254
        {
255
            if (!includeIncomplete && !analyzedCustomAttribute.IsSuitableForEmission)
×
256
                continue;
257

258
            if (indentCount > 0)
×
259
                sb.Append('\t', indentCount);
×
260

261
            try
262
            {
263
                sb.AppendLine(analyzedCustomAttribute.ToString());
×
264
            }
×
265
            catch (Exception e)
×
266
            {
267
                Logger.WarnNewline("Exception printing/formatting custom attribute: " + e, "C# Generator");
×
268
                sb.Append("/*Cpp2IL: Exception outputting custom attribute of type ").Append(analyzedCustomAttribute.Constructor.DeclaringType?.Name ?? "<unknown type?>").AppendLine("*/");
×
269
            }
×
270
        }
271

272
        return sb.ToString();
×
273
    }
274

275
    /// <summary>
276
    /// Returns the name of the given type, as it would appear in a C# source file.
277
    /// This mainly involves stripping the backtick section from generic type names, and replacing certain system types with their primitive name.
278
    /// </summary>
279
    /// <param name="originalName">The original name of the type</param>
280
    public static string GetTypeName(string originalName)
281
    {
282
        if (originalName.Contains("`"))
×
283
            //Generics - remove `1 etc
284
            return originalName.Remove(originalName.IndexOf('`'), 2);
×
285

286
        if (originalName[^1] == '&')
×
287
            originalName = originalName[..^1]; //Remove trailing & for ref params
×
288

289
        return originalName switch
×
290
        {
×
291
            "Void" => "void",
×
292
            "Boolean" => "bool",
×
293
            "Byte" => "byte",
×
294
            "SByte" => "sbyte",
×
295
            "Char" => "char",
×
296
            "Decimal" => "decimal",
×
297
            "Single" => "float",
×
298
            "Double" => "double",
×
299
            "Int32" => "int",
×
300
            "UInt32" => "uint",
×
301
            "Int64" => "long",
×
302
            "UInt64" => "ulong",
×
303
            "Int16" => "short",
×
304
            "UInt16" => "ushort",
×
305
            "String" => "string",
×
306
            "Object" => "object",
×
307
            _ => originalName
×
308
        };
×
309
    }
310

311
    /// <summary>
312
    /// Appends inheritance data (base class and interfaces) for the given type to the given string builder.
313
    /// If the base class is System.Object, System.ValueType, or System.Enum, it will be ignored
314
    /// </summary>
315
    /// <param name="type"></param>
316
    /// <param name="sb"></param>
317
    public static void AppendInheritanceInfo(TypeAnalysisContext type, StringBuilder sb)
318
    {
319
        var baseType = type.BaseType;
×
320
        var needsBaseClass = baseType is { FullName: not "System.Object" and not "System.ValueType" and not "System.Enum" };
×
321
        if (needsBaseClass)
×
322
            sb.Append(" : ").Append(GetTypeName(baseType!.Name));
×
323

324
        //Interfaces
NEW
325
        if (type.InterfaceContexts.Count <= 0)
×
326
            return;
×
327

328
        if (!needsBaseClass)
×
329
            sb.Append(" : ");
×
330

331
        var addComma = needsBaseClass;
×
332
        foreach (var iface in type.InterfaceContexts)
×
333
        {
334
            if (addComma)
×
335
                sb.Append(", ");
×
336

337
            addComma = true;
×
338

339
            sb.Append(GetTypeName(iface.Name));
×
340
        }
341
    }
×
342
}
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