• 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.35
/Cpp2IL.Core/ProcessingLayers/CallAnalysisProcessingLayer.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics.CodeAnalysis;
4
using System.Linq;
5
using System.Reflection;
6
using Cpp2IL.Core.Api;
7
using Cpp2IL.Core.Extensions;
8
using Cpp2IL.Core.ISIL;
9
using Cpp2IL.Core.Model.Contexts;
10
using Cpp2IL.Core.Utils;
11
using LibCpp2IL;
12

13
namespace Cpp2IL.Core.ProcessingLayers;
14

15
public class CallAnalysisProcessingLayer : Cpp2IlProcessingLayer
16
{
17
    public override string Name => "Call Analyzer";
×
18
    public override string Id => "callanalyzer";
1✔
19

20
    /// <summary>
21
    /// We don't want 1000 attributes on a single method
22
    /// </summary>
23
    const int MaximumCalledByAttributes = 20;
24

25
    public override void Process(ApplicationAnalysisContext appContext, Action<int, int>? progressCallback = null)
26
    {
27
        InjectAttribute(appContext);
×
28
    }
×
29

30
    private static void InjectAttribute(ApplicationAnalysisContext appContext)
31
    {
32
        const string Namespace = "Cpp2ILInjected.CallAnalysis";
33

34
        var deduplicatedMethodAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "DeduplicatedMethodAttribute", AttributeTargets.Method, false);
×
35
        var invalidInstructionsAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "ContainsInvalidInstructionsAttribute", AttributeTargets.Method, false);
×
36
        var unimplementedInstructionsAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "ContainsUnimplementedInstructionsAttribute", AttributeTargets.Method, false);
×
37
        var analysisNotSupportedAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "CallAnalysisNotSupportedAttribute", AttributeTargets.Method, false);
×
38

39
        var callsDeduplicatedMethodsAttributes = AttributeInjectionUtils.InjectOneParameterAttribute(appContext, Namespace, "CallsDeduplicatedMethodsAttribute", AttributeTargets.Method, false, appContext.SystemTypes.SystemInt32Type, "Count");
×
40
        var callsUnknownMethodsAttributes = AttributeInjectionUtils.InjectOneParameterAttribute(appContext, Namespace, "CallsUnknownMethodsAttribute", AttributeTargets.Method, false, appContext.SystemTypes.SystemInt32Type, "Count");
×
41
        var callerCountAttributes = AttributeInjectionUtils.InjectOneParameterAttribute(appContext, Namespace, "CallerCountAttribute", AttributeTargets.Method, false, appContext.SystemTypes.SystemInt32Type, "Count");
×
42

43
        var callsAttributes = CreateCallAttributes(appContext, Namespace, "CallsAttribute");
×
44
        var calledByAttributes = CreateCallAttributes(appContext, Namespace, "CalledByAttribute");
×
45

46
        Dictionary<ulong, int> callCounts = new();
×
47
        Dictionary<MethodAnalysisContext, int> unknownCalls = new();
×
48
        Dictionary<MethodAnalysisContext, int> deduplicatedCalls = new();
×
49
        Dictionary<MethodAnalysisContext, HashSet<MethodAnalysisContext>> callsDictionary = new();
×
50
        Dictionary<MethodAnalysisContext, HashSet<MethodAnalysisContext>> calledByDictionary = new();
×
51

52
        var keyFunctionAddresses = appContext.GetOrCreateKeyFunctionAddresses();
×
53

54
        foreach (var assemblyAnalysisContext in appContext.Assemblies)
×
55
        {
56
            var invalidInstructionsConstructor = invalidInstructionsAttributes[assemblyAnalysisContext];
×
57
            var unimplementedInstructionsConstructor = unimplementedInstructionsAttributes[assemblyAnalysisContext];
×
58
            var analysisNotSupportedConstructor = analysisNotSupportedAttributes[assemblyAnalysisContext];
×
59
            var deduplicatedMethodConstructor = deduplicatedMethodAttributes[assemblyAnalysisContext];
×
60

61
            foreach (var m in assemblyAnalysisContext.Types.SelectMany(t => t.Methods))
×
62
            {
63
                m.AnalyzeCustomAttributeData();
×
64
                if (m.CustomAttributes == null || m.UnderlyingPointer == 0)
×
65
                    continue;
66

67
                if (appContext.MethodsByAddress.TryGetValue(m.UnderlyingPointer, out var methodsWithThatAddress) && methodsWithThatAddress.Count > 1)
×
68
                {
69
                    AttributeInjectionUtils.AddZeroParameterAttribute(m, deduplicatedMethodConstructor);
×
70
                }
71

72
                var convertedIsil = appContext.InstructionSet.GetIsilFromMethod(m);
×
73

74
                if (convertedIsil is { Count: 0 })
×
75
                {
76
                    if ((m.Attributes & MethodAttributes.Abstract) == 0)
×
77
                    {
78
                        AttributeInjectionUtils.AddZeroParameterAttribute(m, analysisNotSupportedConstructor);
×
79
                    }
80

81
                    continue;
×
82
                }
83

84
                if (convertedIsil.Any(i => i.OpCode == InstructionSetIndependentOpCode.Invalid))
×
85
                {
86
                    AttributeInjectionUtils.AddZeroParameterAttribute(m, invalidInstructionsConstructor);
×
87
                }
88

89
                if (convertedIsil.Any(i => i.OpCode == InstructionSetIndependentOpCode.NotImplemented))
×
90
                {
91
                    AttributeInjectionUtils.AddZeroParameterAttribute(m, unimplementedInstructionsConstructor);
×
92
                }
93

94
                foreach (var instruction in convertedIsil)
×
95
                {
96
                    if (instruction.OpCode != InstructionSetIndependentOpCode.Call && instruction.OpCode != InstructionSetIndependentOpCode.CallNoReturn)
×
97
                    {
98
                        continue;
99
                    }
100

101
                    if (instruction.Operands.Length > 0 && instruction.Operands[0].Data is IsilImmediateOperand operand && operand.Value is not string)
×
102
                    {
103
                        var address = operand.Value.ToUInt64(null);
×
104
                        if (appContext.MethodsByAddress.TryGetValue(address, out var list))
×
105
                        {
106
                            callCounts[address] = callCounts.GetOrDefault(address, 0) + 1;
×
107
                            if (list.Count == 0)
×
108
                            {
109
                                unknownCalls[m] = unknownCalls.GetOrDefault(m, 0) + 1;
×
110
                            }
111
                            else if (TryGetCommonMethodFromList(list, out var calledMethod))
×
112
                            {
113
                                Add(callsDictionary, m, calledMethod);
×
114
                                Add(calledByDictionary, calledMethod, m);
×
115
                            }
116
                            else
117
                            {
118
                                deduplicatedCalls[m] = deduplicatedCalls.GetOrDefault(m, 0) + 1;
×
119
                            }
120
                        }
121
                        else if (!keyFunctionAddresses.IsKeyFunctionAddress(address) && !appContext.Binary.IsExportedFunction(address))
×
122
                        {
123
                            unknownCalls[m] = unknownCalls.GetOrDefault(m, 0) + 1;
×
124
                        }
125
                    }
126
                    else
127
                    {
128
                        unknownCalls[m] = unknownCalls.GetOrDefault(m, 0) + 1;
×
129
                    }
130
                }
131
            }
132

133
            if (Cpp2IlApi.LowMemoryMode)
×
134
                GC.Collect();
×
135
        }
136

137
        foreach (var assemblyAnalysisContext in appContext.Assemblies)
×
138
        {
139
            var callerCountAttributeInfo = callerCountAttributes[assemblyAnalysisContext];
×
140
            var callsUnknownMethodsAttributeInfo = callsUnknownMethodsAttributes[assemblyAnalysisContext];
×
141
            var callsDeduplicatedMethodsAttributeInfo = callsDeduplicatedMethodsAttributes[assemblyAnalysisContext];
×
142
            var callsAttributeInfo = callsAttributes[assemblyAnalysisContext];
×
143
            var calledByAttributeInfo = calledByAttributes[assemblyAnalysisContext];
×
144

145
            foreach (var m in assemblyAnalysisContext.Types.SelectMany(t => t.Methods))
×
146
            {
147
                if (m.CustomAttributes == null || m.UnderlyingPointer == 0)
×
148
                    continue;
149

150
                var unknownCallCount = unknownCalls.GetOrDefault(m, 0);
×
151
                if (calledByDictionary.TryGetValue(m, out var calledByList) && calledByList.Count < MaximumCalledByAttributes)
×
152
                {
153
                    foreach (var callingMethod in calledByList)
×
154
                    {
155
                        AddAttribute(calledByAttributeInfo, m, callingMethod);
×
156
                    }
157
                }
158

159
                AttributeInjectionUtils.AddOneParameterAttribute(m, callerCountAttributeInfo, callCounts.GetOrDefault(m.UnderlyingPointer, 0));
×
160
                if (callsDictionary.TryGetValue(m, out var callsList))
×
161
                {
162
                    foreach (var calledMethod in callsList)
×
163
                    {
164
                        AddAttribute(callsAttributeInfo, m, calledMethod);
×
165
                    }
166
                }
167

168
                if (deduplicatedCalls.TryGetValue(m, out var deduplicatedCallCount))
×
169
                {
170
                    AttributeInjectionUtils.AddOneParameterAttribute(m, callsDeduplicatedMethodsAttributeInfo, deduplicatedCallCount);
×
171
                }
172

173
                if (unknownCallCount > 0)
×
174
                {
175
                    AttributeInjectionUtils.AddOneParameterAttribute(m, callsUnknownMethodsAttributeInfo, unknownCallCount);
×
176
                }
177
            }
178

179
            if (Cpp2IlApi.LowMemoryMode)
×
180
                GC.Collect();
×
181
        }
182
    }
×
183

184
    private static void AddAttribute((InjectedMethodAnalysisContext, InjectedFieldAnalysisContext[]) callsAttributeInfo, MethodAnalysisContext annotatedMethod, MethodAnalysisContext targetMethod)
185
    {
186
        (FieldAnalysisContext, object) typeField;
187
        if (TryGetDeclaringTypeForMethod(annotatedMethod, targetMethod, out var il2cppType, out var typeFullName))
×
188
        {
189
            typeField = (callsAttributeInfo.Item2[0], il2cppType);
×
190
        }
191
        else
192
        {
193
            typeField = (callsAttributeInfo.Item2[0], typeFullName);
×
194
        }
195

196
        var memberField = (callsAttributeInfo.Item2[1], targetMethod.Name);
×
197

198
        (FieldAnalysisContext, object)? typeParametersField;
199
        if (targetMethod is ConcreteGenericMethodAnalysisContext concreteMethod)
×
200
        {
201
            if (concreteMethod.MethodGenericParameters.Length > 0)
×
202
            {
203
                var parameters = new object?[concreteMethod.MethodGenericParameters.Length];
×
204

205
                for (var i = 0; i < parameters.Length; i++)
×
206
                {
207
                    var parameterType = concreteMethod.MethodGenericParameters[i];
×
208
                    if (parameterType.IsAccessibleTo(annotatedMethod.DeclaringType!))
×
209
                    {
210
                        parameters[i] = parameterType;
×
211
                    }
212
                    else
213
                    {
214
                        parameters[i] = parameterType.FullName;
×
215
                    }
216
                }
217

218
                typeParametersField = (callsAttributeInfo.Item2[2], parameters);
×
219
            }
220
            else
221
            {
222
                typeParametersField = null;
×
223
            }
224
        }
NEW
225
        else if (targetMethod.GenericParameters.Count > 0)
×
226
        {
NEW
227
            var parameters = targetMethod.GenericParameters.Select(p => (object?)p.Name).ToArray();
×
228
            typeParametersField = (callsAttributeInfo.Item2[2], parameters);
×
229
        }
230
        else
231
        {
232
            typeParametersField = null;
×
233
        }
234

235
        (FieldAnalysisContext, object)? parametersField;
236
        if (targetMethod.ParameterCount > 0)
×
237
        {
238
            var parameters = new object?[targetMethod.ParameterCount];
×
239

240
            for (var i = 0; i < parameters.Length; i++)
×
241
            {
242
                var parameterType = targetMethod.Parameters[i].ParameterTypeContext;
×
243
                if (parameterType.IsAccessibleTo(annotatedMethod.DeclaringType!) && !parameterType.HasAnyGenericParameters())
×
244
                {
245
                    parameters[i] = parameterType;
×
246
                }
247
                else
248
                {
249
                    parameters[i] = parameterType?.FullName;
×
250
                }
251
            }
252

253
            parametersField = (callsAttributeInfo.Item2[3], parameters);
×
254
        }
255
        else
256
        {
257
            parametersField = null;
×
258
        }
259

260
        (FieldAnalysisContext, object)? returnTypeField;
261
        TypeAnalysisContext? returnType;
262
        if (targetMethod is NativeMethodAnalysisContext)
×
263
        {
264
            returnType = null; //Native methods don't have identifiable return types.
×
265
        }
266
        else if (targetMethod.InjectedReturnType is not null)
×
267
        {
268
            returnType = targetMethod.InjectedReturnType;
×
269
        }
270
        else if (targetMethod.Definition is null)
×
271
        {
272
            returnType = null;
×
273
        }
274
        else
275
        {
276
            returnType = targetMethod.ReturnTypeContext;
×
277
        }
278

279
        if (returnType is not null)
×
280
        {
281
            if (returnType.IsAccessibleTo(annotatedMethod.DeclaringType!) && !returnType.HasAnyGenericParameters())
×
282
            {
283
                returnTypeField = (callsAttributeInfo.Item2[4], returnType);
×
284
            }
285
            else
286
            {
287
                returnTypeField = (callsAttributeInfo.Item2[4], returnType.FullName);
×
288
            }
289
        }
290
        else
291
        {
292
            returnTypeField = null;
×
293
        }
294

295
        AttributeInjectionUtils.AddAttribute(
×
296
            annotatedMethod,
×
297
            callsAttributeInfo.Item1,
×
298
            ((IEnumerable<(FieldAnalysisContext, object)>) [typeField, memberField])
×
299
            .MaybeAppend(typeParametersField)
×
300
            .MaybeAppend(parametersField)
×
301
            .MaybeAppend(returnTypeField));
×
302
    }
×
303

304
    private static Dictionary<AssemblyAnalysisContext, (InjectedMethodAnalysisContext, InjectedFieldAnalysisContext[])> CreateCallAttributes(ApplicationAnalysisContext appContext, string Namespace, string methodName)
305
    {
306
        return AttributeInjectionUtils.InjectAttribute(
×
307
            appContext,
×
308
            Namespace,
×
309
            methodName,
×
310
            AttributeTargets.Method,
×
311
            true,
×
312
            (appContext.SystemTypes.SystemObjectType, "Type"),
×
313
            (appContext.SystemTypes.SystemStringType, "Member"),
×
314
            (appContext.SystemTypes.SystemObjectType.MakeSzArrayType(), "MemberTypeParameters"),
×
315
            (appContext.SystemTypes.SystemObjectType.MakeSzArrayType(), "MemberParameters"),
×
316
            (appContext.SystemTypes.SystemObjectType, "ReturnType"));
×
317
    }
318

319
    private static bool TryGetCommonMethodFromList(List<MethodAnalysisContext> methods, [NotNullWhen(true)] out MethodAnalysisContext? commonMethod)
320
    {
321
        if (methods.Count < 1)
×
322
        {
323
            throw new ArgumentException("Count cannot be 0.", nameof(methods));
×
324
        }
325

326
        if (methods.Count == 1)
×
327
        {
328
            commonMethod = methods[0];
×
329
            return true;
×
330
        }
331

332
        // We attempt to unify multiple concrete generic methods into a common base method.
333

334
        var firstMethod = GetBaseMethodIfConcrete(methods[0]);
×
335

336
        for (var i = 1; i < methods.Count; i++)
×
337
        {
338
            var method = GetBaseMethodIfConcrete(methods[i]);
×
339
            if (firstMethod != method)
×
340
            {
341
                commonMethod = null;
×
342
                return false;
×
343
            }
344
        }
345

346
        commonMethod = firstMethod;
×
347
        return true;
×
348

349
        static MethodAnalysisContext GetBaseMethodIfConcrete(MethodAnalysisContext method)
350
        {
351
            return method is ConcreteGenericMethodAnalysisContext genericMethod ? genericMethod.BaseMethodContext : method;
×
352
        }
353
    }
354

355
    private static bool TryGetDeclaringTypeForMethod(MethodAnalysisContext annotedMethod, MethodAnalysisContext targetMethod, [NotNullWhen(true)] out TypeAnalysisContext? targetDeclaringType, [NotNullWhen(false)] out string? targetDeclaringTypeFullName)
356
    {
357
        var declaringType = targetMethod.DeclaringType;
×
358
        if (declaringType == null)
×
359
        {
360
            targetDeclaringType = null;
×
361
            targetDeclaringTypeFullName = "";
×
362
            return false;
×
363
        }
364
        else if (annotedMethod.DeclaringType != null && declaringType.IsAccessibleTo(annotedMethod.DeclaringType))
×
365
        {
366
            targetDeclaringType = declaringType;
×
367
            targetDeclaringTypeFullName = null;
×
368
            return true;
×
369
        }
370
        else
371
        {
372
            targetDeclaringType = null;
×
373
            targetDeclaringTypeFullName = declaringType.FullName;
×
374
            return false;
×
375
        }
376
    }
377

378
    private static void Add<T>(Dictionary<T, HashSet<T>> dictionary, T key, T value) where T : notnull
379
    {
380
        if (!dictionary.TryGetValue(key, out var list))
×
381
        {
382
            list = [];
×
383
            dictionary.Add(key, list);
×
384
        }
385

386
        list.Add(value);
×
387
    }
×
388
}
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