-
-
Notifications
You must be signed in to change notification settings - Fork 318
Ensure the base type and interfaces of generic instance types are instantiated #549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
|
|
||
| namespace Cpp2IL.Core.Model.Contexts; | ||
|
|
||
| internal readonly record struct GenericArgumentList(List<TypeAnalysisContext> Arguments) | ||
| { | ||
| public static implicit operator GenericArgumentList(List<TypeAnalysisContext> arguments) => new(arguments); | ||
| public static implicit operator List<TypeAnalysisContext>(GenericArgumentList list) => list.Arguments; | ||
|
|
||
| public bool Equals(GenericArgumentList other) => Arguments.SequenceEqual(other.Arguments); | ||
| public override int GetHashCode() | ||
| { | ||
| HashCode hash = new(); | ||
| foreach (var arg in Arguments) | ||
| hash.Add(arg); | ||
| return hash.ToHashCode(); | ||
| } | ||
| public override string? ToString() => Arguments?.ToString(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,7 @@ public class GenericInstanceTypeAnalysisContext : ReferencedTypeAnalysisContext | |
| { | ||
| public TypeAnalysisContext GenericType { get; } | ||
|
|
||
| public List<TypeAnalysisContext> GenericArguments { get; } = []; | ||
| public List<TypeAnalysisContext> GenericArguments { get; } | ||
|
|
||
| public sealed override TypeAttributes DefaultAttributes => GenericType.DefaultAttributes; | ||
|
|
||
|
|
@@ -34,7 +34,27 @@ public sealed override string? OverrideNamespace | |
| set => GenericType.OverrideNamespace = value; | ||
| } | ||
|
|
||
| public sealed override TypeAnalysisContext? DefaultBaseType { get; } | ||
| public sealed override TypeAnalysisContext? DefaultBaseType | ||
| { | ||
| get | ||
| { | ||
| field ??= GenericType.DefaultBaseType is null ? null : GenericInstantiation.Instantiate(GenericType.DefaultBaseType, GenericArguments, []); | ||
| return field; | ||
| } | ||
| } | ||
|
|
||
| public sealed override TypeAnalysisContext? OverrideBaseType | ||
| { | ||
| get | ||
| { | ||
| if (base.OverrideBaseType is null && GenericType.OverrideBaseType is not null) | ||
| { | ||
| base.OverrideBaseType = GenericInstantiation.Instantiate(GenericType.OverrideBaseType, GenericArguments, []); | ||
| } | ||
| return base.OverrideBaseType; | ||
| } | ||
| set => throw new NotSupportedException(); | ||
| } | ||
|
|
||
| public sealed override Il2CppTypeEnum Type => Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST; | ||
|
|
||
|
|
@@ -43,35 +63,53 @@ public sealed override string? OverrideNamespace | |
| 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. | ||
|
|
||
| // instances being constructed on the current thread, keyed by their cache identity (see GetOrCreate for why) | ||
| [ThreadStatic] private static Dictionary<(ApplicationAnalysisContext, Il2CppType), GenericInstanceTypeAnalysisContext>? _underConstruction; | ||
| [ThreadStatic] private static Dictionary<(ApplicationAnalysisContext, Il2CppType), GenericInstanceTypeAnalysisContext>? _underConstruction1; | ||
| [ThreadStatic] private static Dictionary<(ApplicationAnalysisContext, TypeAnalysisContext, GenericArgumentList), GenericInstanceTypeAnalysisContext>? _underConstruction2; | ||
|
|
||
| private GenericInstanceTypeAnalysisContext(Il2CppType rawType, ApplicationAnalysisContext context) : base(context.ResolveContextForAssembly(rawType.GetGenericClass().TypeDefinition.DeclaringAssembly!)) | ||
| { | ||
| var underConstruction = _underConstruction ??= new(); | ||
| underConstruction[(context, rawType)] = this; | ||
| var underConstruction1 = _underConstruction1 ??= new(); | ||
| underConstruction1[(context, rawType)] = this; | ||
| try | ||
| { | ||
| //Generic type has to be a type definition | ||
| var gClass = rawType.GetGenericClass(); | ||
| GenericType = context.ResolveContextForType(gClass.TypeDefinition) ?? throw new($"Could not resolve type {gClass.TypeDefinition.FullName} for generic instance base type"); | ||
|
|
||
| GenericArguments.AddRange(gClass.Context.ClassInst!.Types.Select(context.ResolveIl2CppType)!); | ||
|
|
||
| SetDeclaringType(); | ||
| GenericType = AppContext.ResolveContextForType(gClass.TypeDefinition) ?? throw new($"Could not resolve type {gClass.TypeDefinition.FullName} for generic instance base type"); | ||
|
|
||
| GenericArguments = [.. gClass.Context.ClassInst!.Types.Select(t => context.ResolveIl2CppType(t))]; | ||
|
|
||
| var underConstruction2 = _underConstruction2 ??= new(); | ||
| underConstruction2.Add((context, GenericType, GenericArguments), this); | ||
| try | ||
| { | ||
| SetDeclaringType(); | ||
| } | ||
| finally | ||
| { | ||
| underConstruction2.Remove((context, GenericType, GenericArguments)); | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| underConstruction.Remove((context, rawType)); | ||
| underConstruction1.Remove((context, rawType)); | ||
| } | ||
| } | ||
|
|
||
| public GenericInstanceTypeAnalysisContext(TypeAnalysisContext genericType, IEnumerable<TypeAnalysisContext> genericArguments) : base(genericType.CustomAttributeAssembly) | ||
| private GenericInstanceTypeAnalysisContext(TypeAnalysisContext genericType, List<TypeAnalysisContext> genericArguments) : base(genericType.DeclaringAssembly) | ||
| { | ||
| GenericType = genericType; | ||
| GenericArguments.AddRange(genericArguments); | ||
| DefaultBaseType = genericType.BaseType; | ||
| GenericArguments = genericArguments; | ||
|
|
||
| SetDeclaringType(); | ||
| var underConstruction = _underConstruction2 ??= new(); | ||
| underConstruction.Add((genericType.AppContext, genericType, genericArguments), this); | ||
| try | ||
| { | ||
| SetDeclaringType(); | ||
| } | ||
| finally | ||
| { | ||
| underConstruction.Remove((genericType.AppContext, genericType, genericArguments)); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -87,14 +125,28 @@ public static GenericInstanceTypeAnalysisContext GetOrCreate(Il2CppType rawType, | |
|
|
||
| // A self-referencing generic re-enters here while we're still building it (#469). In this case, the constructing thread gets | ||
| // its own in-progress instance from a thread-local map instead (otherwise it recurses into Lazy.Value, which throws). | ||
| if (_underConstruction != null && _underConstruction.TryGetValue((referencedFrom, rawType), out var partial)) | ||
| if (_underConstruction1 != null && _underConstruction1.TryGetValue((referencedFrom, rawType), out var partial)) | ||
| return partial; | ||
|
|
||
| return referencedFrom.GenericInstanceTypesByIl2CppType | ||
| .GetOrAdd(rawType, key => new Lazy<GenericInstanceTypeAnalysisContext>(() => new GenericInstanceTypeAnalysisContext(key, referencedFrom))) | ||
| .Value; | ||
| } | ||
|
|
||
| public static GenericInstanceTypeAnalysisContext GetOrCreate(TypeAnalysisContext genericType, IEnumerable<TypeAnalysisContext> genericArguments) | ||
| { | ||
| var genericArgumentsList = genericArguments.ToList(); | ||
|
|
||
| // A self-referencing generic re-enters here while we're still building it (#469). In this case, the constructing thread gets | ||
| // its own in-progress instance from a thread-local map instead (otherwise it recurses into Lazy.Value, which throws). | ||
| if (_underConstruction2 != null && _underConstruction2.TryGetValue((genericType.AppContext, genericType, genericArgumentsList), out var partial)) | ||
| return partial; | ||
|
|
||
| return genericType.AppContext.GenericInstanceTypesByConstruction | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This means we have 2 separate caches now, |
||
| .GetOrAdd((genericType, genericArgumentsList), key => new Lazy<GenericInstanceTypeAnalysisContext>(() => new GenericInstanceTypeAnalysisContext(key.Item1, key.Item2))) | ||
| .Value; | ||
| } | ||
|
|
||
| public override string GetCSharpSourceString() | ||
| { | ||
| var sb = new StringBuilder(); | ||
|
|
@@ -117,6 +169,11 @@ public override string GetCSharpSourceString() | |
| return sb.ToString(); | ||
| } | ||
|
|
||
| protected override List<TypeAnalysisContext> GetInterfaceContexts() | ||
| { | ||
| return GenericType.InterfaceContexts.Select(i => GenericInstantiation.Instantiate(i, GenericArguments, [])).ToList(); | ||
| } | ||
|
|
||
| private void SetDeclaringType() | ||
| { | ||
| var declaringType = GenericType.DeclaringType; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This list is a) exposed publicly, b) used for hash code calculation, and c) the same reference is exposed as GenericArguments on the GIT. GenericArgumentList is used as part of the key in the GenericInstanceTypesByConstruction dict. Creates kind of a footgun because someone could change a GIT's arguments, which would change the key of an item in the dictionary which can break shit. E.g. if you have a
Dictionary<string, string>, change it toDictionary<string, object>, then call GetOrCreate to try to get aDictionary<string, object>, the key won't be found in the dict because its hashcode has changed, and you'll get a second (or potentially third, when combined with the other issue) instance for the same GIT.