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

SamboyCoding / Cpp2IL / 15051669337

15 May 2025 05:43PM UTC coverage: 34.038% (-0.4%) from 34.453%
15051669337

Pull #451

github

web-flow
Merge c459d487b into d3bbbb38e
Pull Request #451: Support injecting anything

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