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

SamboyCoding / Cpp2IL / 17216182984

25 Aug 2025 05:35PM UTC coverage: 30.38% (-4.0%) from 34.352%
17216182984

Pull #481

github

web-flow
Merge b82763a24 into d5260685f
Pull Request #481: Decompiler

1804 of 7561 branches covered (23.86%)

Branch coverage included in aggregate %.

100 of 1839 new or added lines in 29 files covered. (5.44%)

41 existing lines in 6 files now uncovered.

4093 of 11850 relevant lines covered (34.54%)

165575.01 hits per line

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

0.36
/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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

259
        (FieldAnalysisContext, object)? returnTypeField;
260
        TypeAnalysisContext? returnType;
261
        if (targetMethod is NativeMethodAnalysisContext)
×
262
        {
263
            returnType = null; //Native methods don't have identifiable return types.
×
264
        }
265
        else
266
        {
267
            returnType = targetMethod.ReturnType;
×
268
        }
269

270
        if (returnType is not null)
×
271
        {
272
            if (returnType.IsAccessibleTo(annotatedMethod.DeclaringType!) && !returnType.HasAnyGenericParameters())
×
273
            {
274
                returnTypeField = (callsAttributeInfo.Item2[4], returnType);
×
275
            }
276
            else
277
            {
278
                returnTypeField = (callsAttributeInfo.Item2[4], returnType.FullName);
×
279
            }
280
        }
281
        else
282
        {
283
            returnTypeField = null;
×
284
        }
285

286
        AttributeInjectionUtils.AddAttribute(
×
287
            annotatedMethod,
×
288
            callsAttributeInfo.Item1,
×
289
            ((IEnumerable<(FieldAnalysisContext, object)>) [typeField, memberField])
×
290
            .MaybeAppend(typeParametersField)
×
291
            .MaybeAppend(parametersField)
×
292
            .MaybeAppend(returnTypeField));
×
293
    }
×
294

295
    private static Dictionary<AssemblyAnalysisContext, (InjectedMethodAnalysisContext, InjectedFieldAnalysisContext[])> CreateCallAttributes(ApplicationAnalysisContext appContext, string Namespace, string methodName)
296
    {
297
        return AttributeInjectionUtils.InjectAttribute(
×
298
            appContext,
×
299
            Namespace,
×
300
            methodName,
×
301
            AttributeTargets.Method,
×
302
            true,
×
303
            (appContext.SystemTypes.SystemObjectType, "Type"),
×
304
            (appContext.SystemTypes.SystemStringType, "Member"),
×
305
            (appContext.SystemTypes.SystemObjectType.MakeSzArrayType(), "MemberTypeParameters"),
×
306
            (appContext.SystemTypes.SystemObjectType.MakeSzArrayType(), "MemberParameters"),
×
307
            (appContext.SystemTypes.SystemObjectType, "ReturnType"));
×
308
    }
309

310
    private static bool TryGetCommonMethodFromList(List<MethodAnalysisContext> methods, [NotNullWhen(true)] out MethodAnalysisContext? commonMethod)
311
    {
312
        if (methods.Count < 1)
×
313
        {
314
            throw new ArgumentException("Count cannot be 0.", nameof(methods));
×
315
        }
316

317
        if (methods.Count == 1)
×
318
        {
319
            commonMethod = methods[0];
×
320
            return true;
×
321
        }
322

323
        // We attempt to unify multiple concrete generic methods into a common base method.
324

325
        var firstMethod = GetBaseMethodIfConcrete(methods[0]);
×
326

327
        for (var i = 1; i < methods.Count; i++)
×
328
        {
329
            var method = GetBaseMethodIfConcrete(methods[i]);
×
330
            if (firstMethod != method)
×
331
            {
332
                commonMethod = null;
×
333
                return false;
×
334
            }
335
        }
336

337
        commonMethod = firstMethod;
×
338
        return true;
×
339

340
        static MethodAnalysisContext GetBaseMethodIfConcrete(MethodAnalysisContext method)
341
        {
342
            return method is ConcreteGenericMethodAnalysisContext genericMethod ? genericMethod.BaseMethodContext : method;
×
343
        }
344
    }
345

346
    private static bool TryGetDeclaringTypeForMethod(MethodAnalysisContext annotedMethod, MethodAnalysisContext targetMethod, [NotNullWhen(true)] out TypeAnalysisContext? targetDeclaringType, [NotNullWhen(false)] out string? targetDeclaringTypeFullName)
347
    {
348
        var declaringType = targetMethod.DeclaringType;
×
349
        if (declaringType == null)
×
350
        {
351
            targetDeclaringType = null;
×
352
            targetDeclaringTypeFullName = "";
×
353
            return false;
×
354
        }
355
        else if (annotedMethod.DeclaringType != null && declaringType.IsAccessibleTo(annotedMethod.DeclaringType))
×
356
        {
357
            targetDeclaringType = declaringType;
×
358
            targetDeclaringTypeFullName = null;
×
359
            return true;
×
360
        }
361
        else
362
        {
363
            targetDeclaringType = null;
×
364
            targetDeclaringTypeFullName = declaringType.FullName;
×
365
            return false;
×
366
        }
367
    }
368

369
    private static void Add<T>(Dictionary<T, HashSet<T>> dictionary, T key, T value) where T : notnull
370
    {
371
        if (!dictionary.TryGetValue(key, out var list))
×
372
        {
373
            list = [];
×
374
            dictionary.Add(key, list);
×
375
        }
376

377
        list.Add(value);
×
378
    }
×
379
}
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