mirror of
https://github.com/AssetRipper/Cpp2IL.git
synced 2026-11-13 04:26:33 -10:00
380 lines
16 KiB
C#
380 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Cpp2IL.Core.Api;
|
|
using Cpp2IL.Core.Extensions;
|
|
using Cpp2IL.Core.ISIL;
|
|
using Cpp2IL.Core.Model.Contexts;
|
|
using Cpp2IL.Core.Utils;
|
|
using LibCpp2IL;
|
|
|
|
namespace Cpp2IL.Core.ProcessingLayers;
|
|
|
|
public class CallAnalysisProcessingLayer : Cpp2IlProcessingLayer
|
|
{
|
|
public override string Name => "Call Analyzer";
|
|
public override string Id => "callanalyzer";
|
|
|
|
/// <summary>
|
|
/// We don't want 1000 attributes on a single method
|
|
/// </summary>
|
|
const int MaximumCalledByAttributes = 20;
|
|
|
|
public override void Process(ApplicationAnalysisContext appContext, Action<int, int>? progressCallback = null)
|
|
{
|
|
InjectAttribute(appContext);
|
|
}
|
|
|
|
private static void InjectAttribute(ApplicationAnalysisContext appContext)
|
|
{
|
|
const string Namespace = "Cpp2ILInjected.CallAnalysis";
|
|
|
|
var deduplicatedMethodAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "DeduplicatedMethodAttribute", AttributeTargets.Method, false);
|
|
var invalidInstructionsAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "ContainsInvalidInstructionsAttribute", AttributeTargets.Method, false);
|
|
var unimplementedInstructionsAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "ContainsUnimplementedInstructionsAttribute", AttributeTargets.Method, false);
|
|
var analysisNotSupportedAttributes = AttributeInjectionUtils.InjectZeroParameterAttribute(appContext, Namespace, "CallAnalysisNotSupportedAttribute", AttributeTargets.Method, false);
|
|
|
|
var callsDeduplicatedMethodsAttributes = AttributeInjectionUtils.InjectOneParameterAttribute(appContext, Namespace, "CallsDeduplicatedMethodsAttribute", AttributeTargets.Method, false, appContext.SystemTypes.SystemInt32Type, "Count");
|
|
var callsUnknownMethodsAttributes = AttributeInjectionUtils.InjectOneParameterAttribute(appContext, Namespace, "CallsUnknownMethodsAttribute", AttributeTargets.Method, false, appContext.SystemTypes.SystemInt32Type, "Count");
|
|
var callerCountAttributes = AttributeInjectionUtils.InjectOneParameterAttribute(appContext, Namespace, "CallerCountAttribute", AttributeTargets.Method, false, appContext.SystemTypes.SystemInt32Type, "Count");
|
|
|
|
var callsAttributes = CreateCallAttributes(appContext, Namespace, "CallsAttribute");
|
|
var calledByAttributes = CreateCallAttributes(appContext, Namespace, "CalledByAttribute");
|
|
|
|
Dictionary<ulong, int> callCounts = new();
|
|
Dictionary<MethodAnalysisContext, int> unknownCalls = new();
|
|
Dictionary<MethodAnalysisContext, int> deduplicatedCalls = new();
|
|
Dictionary<MethodAnalysisContext, HashSet<MethodAnalysisContext>> callsDictionary = new();
|
|
Dictionary<MethodAnalysisContext, HashSet<MethodAnalysisContext>> calledByDictionary = new();
|
|
|
|
var keyFunctionAddresses = appContext.GetOrCreateKeyFunctionAddresses();
|
|
|
|
foreach (var assemblyAnalysisContext in appContext.Assemblies)
|
|
{
|
|
var invalidInstructionsConstructor = invalidInstructionsAttributes[assemblyAnalysisContext];
|
|
var unimplementedInstructionsConstructor = unimplementedInstructionsAttributes[assemblyAnalysisContext];
|
|
var analysisNotSupportedConstructor = analysisNotSupportedAttributes[assemblyAnalysisContext];
|
|
var deduplicatedMethodConstructor = deduplicatedMethodAttributes[assemblyAnalysisContext];
|
|
|
|
foreach (var m in assemblyAnalysisContext.Types.SelectMany(t => t.Methods))
|
|
{
|
|
m.AnalyzeCustomAttributeData();
|
|
if (m.CustomAttributes == null || m.UnderlyingPointer == 0)
|
|
continue;
|
|
|
|
if (appContext.MethodsByAddress.TryGetValue(m.UnderlyingPointer, out var methodsWithThatAddress) && methodsWithThatAddress.Count > 1)
|
|
{
|
|
AttributeInjectionUtils.AddZeroParameterAttribute(m, deduplicatedMethodConstructor);
|
|
}
|
|
|
|
var convertedIsil = appContext.InstructionSet.GetIsilFromMethod(m);
|
|
|
|
if (convertedIsil is { Count: 0 })
|
|
{
|
|
if ((m.Attributes & MethodAttributes.Abstract) == 0)
|
|
{
|
|
AttributeInjectionUtils.AddZeroParameterAttribute(m, analysisNotSupportedConstructor);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (convertedIsil.Any(i => i.OpCode == OpCode.Invalid))
|
|
{
|
|
AttributeInjectionUtils.AddZeroParameterAttribute(m, invalidInstructionsConstructor);
|
|
}
|
|
|
|
if (convertedIsil.Any(i => i.OpCode == OpCode.NotImplemented))
|
|
{
|
|
AttributeInjectionUtils.AddZeroParameterAttribute(m, unimplementedInstructionsConstructor);
|
|
}
|
|
|
|
foreach (var instruction in convertedIsil)
|
|
{
|
|
if (instruction.OpCode != OpCode.Call && instruction.OpCode != OpCode.CallVoid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (instruction.Operands.Count > 0 && instruction.Operands[0] is Immediate { UnsignedValue: var address })
|
|
{
|
|
if (appContext.MethodsByAddress.TryGetValue(address, out var list))
|
|
{
|
|
callCounts[address] = callCounts.GetOrDefault(address, 0) + 1;
|
|
if (list.Count == 0)
|
|
{
|
|
unknownCalls[m] = unknownCalls.GetOrDefault(m, 0) + 1;
|
|
}
|
|
else if (TryGetCommonMethodFromList(list, out var calledMethod))
|
|
{
|
|
Add(callsDictionary, m, calledMethod);
|
|
Add(calledByDictionary, calledMethod, m);
|
|
}
|
|
else
|
|
{
|
|
deduplicatedCalls[m] = deduplicatedCalls.GetOrDefault(m, 0) + 1;
|
|
}
|
|
}
|
|
else if (!keyFunctionAddresses.IsKeyFunctionAddress(address) && !appContext.Binary.IsExportedFunction(address))
|
|
{
|
|
unknownCalls[m] = unknownCalls.GetOrDefault(m, 0) + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unknownCalls[m] = unknownCalls.GetOrDefault(m, 0) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Cpp2IlApi.LowMemoryMode)
|
|
GC.Collect();
|
|
}
|
|
|
|
foreach (var assemblyAnalysisContext in appContext.Assemblies)
|
|
{
|
|
var callerCountAttributeInfo = callerCountAttributes[assemblyAnalysisContext];
|
|
var callsUnknownMethodsAttributeInfo = callsUnknownMethodsAttributes[assemblyAnalysisContext];
|
|
var callsDeduplicatedMethodsAttributeInfo = callsDeduplicatedMethodsAttributes[assemblyAnalysisContext];
|
|
var callsAttributeInfo = callsAttributes[assemblyAnalysisContext];
|
|
var calledByAttributeInfo = calledByAttributes[assemblyAnalysisContext];
|
|
|
|
foreach (var m in assemblyAnalysisContext.Types.SelectMany(t => t.Methods))
|
|
{
|
|
if (m.CustomAttributes == null || m.UnderlyingPointer == 0)
|
|
continue;
|
|
|
|
var unknownCallCount = unknownCalls.GetOrDefault(m, 0);
|
|
if (calledByDictionary.TryGetValue(m, out var calledByList) && calledByList.Count < MaximumCalledByAttributes)
|
|
{
|
|
foreach (var callingMethod in calledByList)
|
|
{
|
|
AddAttribute(calledByAttributeInfo, m, callingMethod);
|
|
}
|
|
}
|
|
|
|
AttributeInjectionUtils.AddOneParameterAttribute(m, callerCountAttributeInfo, callCounts.GetOrDefault(m.UnderlyingPointer, 0));
|
|
if (callsDictionary.TryGetValue(m, out var callsList))
|
|
{
|
|
foreach (var calledMethod in callsList)
|
|
{
|
|
AddAttribute(callsAttributeInfo, m, calledMethod);
|
|
}
|
|
}
|
|
|
|
if (deduplicatedCalls.TryGetValue(m, out var deduplicatedCallCount))
|
|
{
|
|
AttributeInjectionUtils.AddOneParameterAttribute(m, callsDeduplicatedMethodsAttributeInfo, deduplicatedCallCount);
|
|
}
|
|
|
|
if (unknownCallCount > 0)
|
|
{
|
|
AttributeInjectionUtils.AddOneParameterAttribute(m, callsUnknownMethodsAttributeInfo, unknownCallCount);
|
|
}
|
|
}
|
|
|
|
if (Cpp2IlApi.LowMemoryMode)
|
|
GC.Collect();
|
|
}
|
|
}
|
|
|
|
private static void AddAttribute((InjectedMethodAnalysisContext, InjectedFieldAnalysisContext[]) callsAttributeInfo, MethodAnalysisContext annotatedMethod, MethodAnalysisContext targetMethod)
|
|
{
|
|
(FieldAnalysisContext, object) typeField;
|
|
if (TryGetDeclaringTypeForMethod(annotatedMethod, targetMethod, out var il2cppType, out var typeFullName))
|
|
{
|
|
typeField = (callsAttributeInfo.Item2[0], il2cppType);
|
|
}
|
|
else
|
|
{
|
|
typeField = (callsAttributeInfo.Item2[0], typeFullName);
|
|
}
|
|
|
|
var memberField = (callsAttributeInfo.Item2[1], targetMethod.Name);
|
|
|
|
(FieldAnalysisContext, object)? typeParametersField;
|
|
if (targetMethod is ConcreteGenericMethodAnalysisContext concreteMethod)
|
|
{
|
|
if (concreteMethod.MethodGenericParameters.Count > 0)
|
|
{
|
|
var parameters = new object?[concreteMethod.MethodGenericParameters.Count];
|
|
|
|
for (var i = 0; i < parameters.Length; i++)
|
|
{
|
|
var parameterType = concreteMethod.MethodGenericParameters[i];
|
|
if (parameterType.IsAccessibleTo(annotatedMethod.DeclaringType!))
|
|
{
|
|
parameters[i] = parameterType;
|
|
}
|
|
else
|
|
{
|
|
parameters[i] = parameterType.FullName;
|
|
}
|
|
}
|
|
|
|
typeParametersField = (callsAttributeInfo.Item2[2], parameters);
|
|
}
|
|
else
|
|
{
|
|
typeParametersField = null;
|
|
}
|
|
}
|
|
else if (targetMethod.GenericParameters.Count > 0)
|
|
{
|
|
var parameters = targetMethod.GenericParameters.Select(p => (object?)p.Name).ToArray();
|
|
typeParametersField = (callsAttributeInfo.Item2[2], parameters);
|
|
}
|
|
else
|
|
{
|
|
typeParametersField = null;
|
|
}
|
|
|
|
(FieldAnalysisContext, object)? parametersField;
|
|
if (targetMethod.Parameters.Count > 0)
|
|
{
|
|
var parameters = new object?[targetMethod.Parameters.Count];
|
|
|
|
for (var i = 0; i < parameters.Length; i++)
|
|
{
|
|
var parameterType = targetMethod.Parameters[i].ParameterType;
|
|
if (parameterType.IsAccessibleTo(annotatedMethod.DeclaringType!) && !parameterType.HasAnyGenericParameters())
|
|
{
|
|
parameters[i] = parameterType;
|
|
}
|
|
else
|
|
{
|
|
parameters[i] = parameterType?.FullName;
|
|
}
|
|
}
|
|
|
|
parametersField = (callsAttributeInfo.Item2[3], parameters);
|
|
}
|
|
else
|
|
{
|
|
parametersField = null;
|
|
}
|
|
|
|
(FieldAnalysisContext, object)? returnTypeField;
|
|
TypeAnalysisContext? returnType;
|
|
if (targetMethod is NativeMethodAnalysisContext)
|
|
{
|
|
returnType = null; //Native methods don't have identifiable return types.
|
|
}
|
|
else
|
|
{
|
|
returnType = targetMethod.ReturnType;
|
|
}
|
|
|
|
if (returnType is not null)
|
|
{
|
|
if (returnType.IsAccessibleTo(annotatedMethod.DeclaringType!) && !returnType.HasAnyGenericParameters())
|
|
{
|
|
returnTypeField = (callsAttributeInfo.Item2[4], returnType);
|
|
}
|
|
else
|
|
{
|
|
returnTypeField = (callsAttributeInfo.Item2[4], returnType.FullName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
returnTypeField = null;
|
|
}
|
|
|
|
AttributeInjectionUtils.AddAttribute(
|
|
annotatedMethod,
|
|
callsAttributeInfo.Item1,
|
|
((IEnumerable<(FieldAnalysisContext, object)>) [typeField, memberField])
|
|
.MaybeAppend(typeParametersField)
|
|
.MaybeAppend(parametersField)
|
|
.MaybeAppend(returnTypeField));
|
|
}
|
|
|
|
private static Dictionary<AssemblyAnalysisContext, (InjectedMethodAnalysisContext, InjectedFieldAnalysisContext[])> CreateCallAttributes(ApplicationAnalysisContext appContext, string Namespace, string methodName)
|
|
{
|
|
return AttributeInjectionUtils.InjectAttribute(
|
|
appContext,
|
|
Namespace,
|
|
methodName,
|
|
AttributeTargets.Method,
|
|
true,
|
|
(appContext.SystemTypes.SystemObjectType, "Type"),
|
|
(appContext.SystemTypes.SystemStringType, "Member"),
|
|
(appContext.SystemTypes.SystemObjectType.MakeSzArrayType(), "MemberTypeParameters"),
|
|
(appContext.SystemTypes.SystemObjectType.MakeSzArrayType(), "MemberParameters"),
|
|
(appContext.SystemTypes.SystemObjectType, "ReturnType"));
|
|
}
|
|
|
|
private static bool TryGetCommonMethodFromList(List<MethodAnalysisContext> methods, [NotNullWhen(true)] out MethodAnalysisContext? commonMethod)
|
|
{
|
|
if (methods.Count < 1)
|
|
{
|
|
throw new ArgumentException("Count cannot be 0.", nameof(methods));
|
|
}
|
|
|
|
if (methods.Count == 1)
|
|
{
|
|
commonMethod = methods[0];
|
|
return true;
|
|
}
|
|
|
|
// We attempt to unify multiple concrete generic methods into a common base method.
|
|
|
|
var firstMethod = GetBaseMethodIfConcrete(methods[0]);
|
|
|
|
for (var i = 1; i < methods.Count; i++)
|
|
{
|
|
var method = GetBaseMethodIfConcrete(methods[i]);
|
|
if (firstMethod != method)
|
|
{
|
|
commonMethod = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
commonMethod = firstMethod;
|
|
return true;
|
|
|
|
static MethodAnalysisContext GetBaseMethodIfConcrete(MethodAnalysisContext method)
|
|
{
|
|
return method is ConcreteGenericMethodAnalysisContext genericMethod ? genericMethod.BaseMethodContext : method;
|
|
}
|
|
}
|
|
|
|
private static bool TryGetDeclaringTypeForMethod(MethodAnalysisContext annotedMethod, MethodAnalysisContext targetMethod, [NotNullWhen(true)] out TypeAnalysisContext? targetDeclaringType, [NotNullWhen(false)] out string? targetDeclaringTypeFullName)
|
|
{
|
|
var declaringType = targetMethod.DeclaringType;
|
|
if (declaringType == null)
|
|
{
|
|
targetDeclaringType = null;
|
|
targetDeclaringTypeFullName = "";
|
|
return false;
|
|
}
|
|
else if (annotedMethod.DeclaringType != null && declaringType.IsAccessibleTo(annotedMethod.DeclaringType))
|
|
{
|
|
targetDeclaringType = declaringType;
|
|
targetDeclaringTypeFullName = null;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
targetDeclaringType = null;
|
|
targetDeclaringTypeFullName = declaringType.FullName;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void Add<T>(Dictionary<T, HashSet<T>> dictionary, T key, T value) where T : notnull
|
|
{
|
|
if (!dictionary.TryGetValue(key, out var list))
|
|
{
|
|
list = [];
|
|
dictionary.Add(key, list);
|
|
}
|
|
|
|
list.Add(value);
|
|
}
|
|
}
|