diff --git a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs index 33d4894a..aa4fe3d5 100644 --- a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -65,6 +66,11 @@ public virtual Version Version private readonly Dictionary TypesByDefinition = new(); + /// + /// Cache for + /// + internal readonly ConcurrentDictionary GenericInstanceTypesByIl2CppType = new(); + public override string DefaultName => Definition?.AssemblyName.Name ?? throw new($"Injected assemblies should override {nameof(DefaultName)}"); protected override bool IsInjected => Definition is null; diff --git a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs index 951781fe..1cf646a0 100644 --- a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -29,8 +31,12 @@ public class GenericInstanceTypeAnalysisContext : ReferencedTypeAnalysisContext public sealed override bool IsValueType => GenericType.IsValueType; //We don't set a definition so the default implementation cannot determine if we're a value type or not. - public GenericInstanceTypeAnalysisContext(Il2CppType rawType, AssemblyAnalysisContext referencedFrom) : base(referencedFrom) + private GenericInstanceTypeAnalysisContext(Il2CppType rawType, AssemblyAnalysisContext referencedFrom) : base(referencedFrom) { + // Cache this instance before resolving anything else, which might contain a reference to this instance. + // https://github.com/SamboyCoding/Cpp2IL/issues/469 + referencedFrom.GenericInstanceTypesByIl2CppType.TryAdd(rawType, this); + //Generic type has to be a type definition var gClass = rawType.GetGenericClass(); GenericType = AppContext.ResolveContextForType(gClass.TypeDefinition) ?? throw new($"Could not resolve type {gClass.TypeDefinition.FullName} for generic instance base type"); @@ -49,6 +55,26 @@ public GenericInstanceTypeAnalysisContext(TypeAnalysisContext genericType, IEnum SetDeclaringType(); } + /// + /// Get or create a from an . + /// + /// The underlying . + /// The assembly that is referencing this generic instance. + /// The context for the . + public static GenericInstanceTypeAnalysisContext GetOrCreate(Il2CppType rawType, AssemblyAnalysisContext referencedFrom) + { + if (rawType.Type != Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) + throw new ArgumentException($"Cannot create {nameof(GenericInstanceTypeAnalysisContext)} from type {rawType.Type}. Expected {Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST}."); + + if (!referencedFrom.GenericInstanceTypesByIl2CppType.TryGetValue(rawType, out var result)) + { + result = new GenericInstanceTypeAnalysisContext(rawType, referencedFrom); + Debug.Assert(referencedFrom.GenericInstanceTypesByIl2CppType.ContainsKey(rawType), $"The {nameof(GenericInstanceTypeAnalysisContext)} constructor should add itself to the dictionary."); + } + + return result; + } + public override string GetCSharpSourceString() { var sb = new StringBuilder(); diff --git a/Cpp2IL.Core/Utils/Il2CppTypeToContext.cs b/Cpp2IL.Core/Utils/Il2CppTypeToContext.cs index 2c390ff2..4d51aa09 100644 --- a/Cpp2IL.Core/Utils/Il2CppTypeToContext.cs +++ b/Cpp2IL.Core/Utils/Il2CppTypeToContext.cs @@ -20,7 +20,7 @@ public static class Il2CppTypeToContext else if (type.Type is Il2CppTypeEnum.IL2CPP_TYPE_CLASS or Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE) ret = context.AppContext.ResolveContextForType(type.AsClass()) ?? throw new($"Could not resolve type context for type {type.AsClass().FullName}"); else if (type.Type is Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) - ret = new GenericInstanceTypeAnalysisContext(type, context); + ret = GenericInstanceTypeAnalysisContext.GetOrCreate(type, context); else if (type.Type is Il2CppTypeEnum.IL2CPP_TYPE_BYREF or Il2CppTypeEnum.IL2CPP_TYPE_PTR or Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY or Il2CppTypeEnum.IL2CPP_TYPE_ARRAY) ret = WrappedTypeAnalysisContext.Create(type, context); else