From d258793295dfde9a9b15c67a0d4c309b9652627f Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:05:41 -0700 Subject: [PATCH 1/4] Handle self-referencing generic classes --- .../Model/Contexts/AssemblyAnalysisContext.cs | 2 ++ .../GenericInstanceTypeAnalysisContext.cs | 18 +++++++++++++++++- Cpp2IL.Core/Utils/Il2CppTypeToContext.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs index 33d4894a..086c1f53 100644 --- a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs @@ -65,6 +65,8 @@ public virtual Version Version private readonly Dictionary TypesByDefinition = new(); + internal readonly Dictionary 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..9e4516b1 100644 --- a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -29,8 +30,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.Add(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 +54,17 @@ public GenericInstanceTypeAnalysisContext(TypeAnalysisContext genericType, IEnum SetDeclaringType(); } + public static GenericInstanceTypeAnalysisContext GetOrCreate(Il2CppType rawType, AssemblyAnalysisContext referencedFrom) + { + 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 From a04e5f9ecef6eb10035f1e818ba0bfc9e1df286e Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:14:03 -0700 Subject: [PATCH 2/4] Add validation --- .../Model/Contexts/GenericInstanceTypeAnalysisContext.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs index 9e4516b1..0f5d553a 100644 --- a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -56,6 +57,9 @@ public GenericInstanceTypeAnalysisContext(TypeAnalysisContext genericType, IEnum 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); From 0b951862c2a9e638b97dea9f238ffcc296a257da Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:17:41 -0700 Subject: [PATCH 3/4] Add more documentation --- Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs | 3 +++ .../Model/Contexts/GenericInstanceTypeAnalysisContext.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs index 086c1f53..e554199d 100644 --- a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs @@ -65,6 +65,9 @@ public virtual Version Version private readonly Dictionary TypesByDefinition = new(); + /// + /// Cache for + /// internal readonly Dictionary GenericInstanceTypesByIl2CppType = new(); public override string DefaultName => Definition?.AssemblyName.Name ?? throw new($"Injected assemblies should override {nameof(DefaultName)}"); diff --git a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs index 0f5d553a..886dabea 100644 --- a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs @@ -55,6 +55,12 @@ 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) From 87268a79927a01ec031c7741978e96b139824821 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:40:30 -0700 Subject: [PATCH 4/4] Concurrent dictionary --- Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs | 3 ++- .../Model/Contexts/GenericInstanceTypeAnalysisContext.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs index e554199d..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; @@ -68,7 +69,7 @@ public virtual Version Version /// /// Cache for /// - internal readonly Dictionary GenericInstanceTypesByIl2CppType = new(); + internal readonly ConcurrentDictionary GenericInstanceTypesByIl2CppType = new(); public override string DefaultName => Definition?.AssemblyName.Name ?? throw new($"Injected assemblies should override {nameof(DefaultName)}"); diff --git a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs index 886dabea..1cf646a0 100644 --- a/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs @@ -35,7 +35,7 @@ private GenericInstanceTypeAnalysisContext(Il2CppType rawType, AssemblyAnalysisC { // Cache this instance before resolving anything else, which might contain a reference to this instance. // https://github.com/SamboyCoding/Cpp2IL/issues/469 - referencedFrom.GenericInstanceTypesByIl2CppType.Add(rawType, this); + referencedFrom.GenericInstanceTypesByIl2CppType.TryAdd(rawType, this); //Generic type has to be a type definition var gClass = rawType.GetGenericClass();