Skip to content

Handle self-referencing generic classes #472

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

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -65,6 +66,11 @@ public virtual Version Version

private readonly Dictionary<Il2CppTypeDefinition, TypeAnalysisContext> TypesByDefinition = new();

/// <summary>
/// Cache for <see cref="GenericInstanceTypeAnalysisContext.GetOrCreate(Il2CppType, AssemblyAnalysisContext)"/>
/// </summary>
internal readonly ConcurrentDictionary<Il2CppType, GenericInstanceTypeAnalysisContext> GenericInstanceTypesByIl2CppType = new();

public override string DefaultName => Definition?.AssemblyName.Name ?? throw new($"Injected assemblies should override {nameof(DefaultName)}");

protected override bool IsInjected => Definition is null;
Expand Down
28 changes: 27 additions & 1 deletion Cpp2IL.Core/Model/Contexts/GenericInstanceTypeAnalysisContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
Expand Down Expand Up @@ -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");
Expand All @@ -49,6 +55,26 @@ public GenericInstanceTypeAnalysisContext(TypeAnalysisContext genericType, IEnum
SetDeclaringType();
}

/// <summary>
/// Get or create a <see cref="GenericInstanceTypeAnalysisContext"/> from an <see cref="Il2CppType"/>.
/// </summary>
/// <param name="rawType">The underlying <see cref="Il2CppType"/>.</param>
/// <param name="referencedFrom">The assembly that is referencing this generic instance.</param>
/// <returns>The context for the <paramref name="rawType"/>.</returns>
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();
Expand Down
2 changes: 1 addition & 1 deletion Cpp2IL.Core/Utils/Il2CppTypeToContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading