Skip to content

Commit f513ff9

Browse files
committed
Implement more proper workaround for circular referencing nested generics.
closes #49
1 parent a141927 commit f513ff9

1 file changed

Lines changed: 54 additions & 12 deletions

File tree

Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public CppDeclarationGenerator(AppModel appModel) {
4242
// Configure inheritance style based on binary type; this can be overridden by setting InheritanceStyle in the object initializer
4343
InheritanceStyle = CppCompiler.GuessFromImage(model.Package.BinaryImage);
4444
}
45-
45+
4646
// C type declaration used to name variables of the given C# type
4747
private static readonly Dictionary<string, string> primitiveTypeMap = new()
4848
{
@@ -89,6 +89,7 @@ public void Reset() {
8989
_visitedTypes.Clear();
9090
_todoFieldStructs.Clear();
9191
_todoTypeStructs.Clear();
92+
_todoTypesExceedingNestingLimit.Clear();
9293
}
9394

9495
#region Field Struct Generation
@@ -313,12 +314,34 @@ private MethodBase[] GetFilledVTable(TypeInfo ti) {
313314
private readonly HashSet<TypeInfo> _visitedTypes = [];
314315
private readonly List<TypeInfo> _todoTypeStructs = [];
315316

317+
private const int MaxGenericNestingDepth = 7;
318+
private readonly HashSet<TypeInfo> _todoTypesExceedingNestingLimit = [];
319+
320+
private static int CalculateTypeDepth(TypeInfo type, int startingDepth = 0)
321+
{
322+
while (type.HasElementType)
323+
{
324+
startingDepth++;
325+
type = type.ElementType;
326+
}
327+
328+
var depth = startingDepth;
329+
330+
var args = type.GenericTypeArguments;
331+
for (int i = 0; i < args.Length; i++)
332+
{
333+
depth = Math.Max(depth, CalculateTypeDepth(args[i], startingDepth + 1));
334+
}
335+
336+
return depth;
337+
}
338+
316339
/// <summary>
317340
/// Include the given type into this generator. This will add the given type and all types it depends on.
318341
/// Call GenerateRemainingTypeDeclarations to produce the actual type declarations afterwards.
319342
/// </summary>
320343
/// <param name="ti"></param>
321-
public void IncludeType(TypeInfo ti)
344+
public void IncludeType(TypeInfo ti)
322345
{
323346
if (_visitedTypes.Contains(ti))
324347
return;
@@ -328,6 +351,23 @@ public void IncludeType(TypeInfo ti)
328351

329352
_visitedTypes.Add(ti);
330353

354+
// This is my attempt at a solution for a big problem in some games:
355+
// circular generic argument references that cause loops when inflating the types.
356+
// ex. Class<T> -> member Class<T[]> -> instantiates Class<T[]> -> member Class<T[][]> -> instantiates Class<T[][]> -> ...
357+
// or Class<T> -> member Class<SomeType<T>> -> instantiates Class<SomeType<T>> -> member Class<SomeType<SomeType<T>>> -> ...
358+
359+
// The only game (issue #49) that currently encounters this uses the MetaXR Unity SDK,
360+
// which defines OVRTask<T> which has a method that returns OVRTask<T[]>.
361+
// This can also be filtered out by filtering out methods without an address, as they get folded through generic sharing,
362+
// but this is a (hopefully) better solution to the problem.
363+
364+
var typeDepth = CalculateTypeDepth(ti);
365+
if (typeDepth > MaxGenericNestingDepth)
366+
{
367+
_todoTypesExceedingNestingLimit.Add(ti);
368+
return;
369+
}
370+
331371
if (ti.IsArray || ti.HasElementType)
332372
{
333373
IncludeType(ti.ElementType);
@@ -351,17 +391,8 @@ public void IncludeType(TypeInfo ti)
351391

352392
foreach (var mi in GetFilledVTable(ti))
353393
{
354-
// The VirtualAddress: not null constraint is a workaround for a much bigger issue:
355-
// circular generic argument references that cause loops when inflating the types.
356-
// ex. Class<T> -> member Class<T[]> -> instantiates Class<T[]> -> member Class<T[][]> -> instantiates Class<T[][]> -> ...
357-
// or Class<T> -> member Class<SomeType<T>> -> instantiates Class<SomeType<T>> -> member Class<SomeType<SomeType<T>>> -> ...
358394

359-
// The only game (issue #49) that currently encounters this uses the MetaXR Unity SDK,
360-
// which defines OVRTask<T> which has a method that returns OVRTask<T[]>.
361-
// This filters out methods like that, as they get folded through generic sharing
362-
// in the actual runtime.
363-
364-
if (mi is { ContainsGenericParameters: false, VirtualAddress: not null })
395+
if (mi is { ContainsGenericParameters: false })
365396
IncludeMethod(mi);
366397
}
367398

@@ -456,9 +487,20 @@ should display the correct method name (with a computed
456487
var (cls, statics, vtable) = GenerateTypeStruct(ti);
457488
decl.Add((ti, null, cls, null, vtable, statics));
458489
}
490+
491+
/*
492+
NOTE: This is currently not required, but if we do need custom types for deeply nested structs
493+
this can be used
494+
foreach (var ti in _todoTypesExceedingNestingLimit)
495+
{
496+
var dummyType = GenerateNestingLimitExceededStruct(ti);
497+
decl.Add((ti, null, dummyType, null, null, null));
498+
}
499+
*/
459500

460501
_todoTypeStructs.Clear();
461502
_todoFieldStructs.Clear();
503+
_todoTypesExceedingNestingLimit.Clear();
462504

463505
return decl;
464506
}

0 commit comments

Comments
 (0)