From 1970879e523a2edf16c7cbb8e2e203344ab969d0 Mon Sep 17 00:00:00 2001 From: Robert Xiao Date: Sat, 11 Apr 2020 22:20:21 -0700 Subject: [PATCH] Implement proper generic parameter substitution With this patch, generic parameters in BaseType and method param/return types are substituted correctly and deeply. Next up will be to apply the same substitution rules to fields, properties, events, ... --- .../Reflection/ParameterInfo.cs | 11 ++---- Il2CppInspector.Common/Reflection/TypeInfo.cs | 38 +++++++++++++++++-- Il2CppTests/TestGenerics.cs | 6 +++ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/Il2CppInspector.Common/Reflection/ParameterInfo.cs b/Il2CppInspector.Common/Reflection/ParameterInfo.cs index 2b339cd..c4099ed 100644 --- a/Il2CppInspector.Common/Reflection/ParameterInfo.cs +++ b/Il2CppInspector.Common/Reflection/ParameterInfo.cs @@ -110,14 +110,11 @@ namespace Il2CppInspector.Reflection DefaultValueMetadataAddress = generic.DefaultValueMetadataAddress; } - public ParameterInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments) { - /* TODO: Deep substitution */ - if (ParameterType.IsGenericTypeParameter) - return new ParameterInfo(this, typeArguments[ParameterType.GenericParameterPosition]); - else if (ParameterType.IsGenericMethodParameter) - return new ParameterInfo(this, methodArguments[ParameterType.GenericParameterPosition]); - else + public ParameterInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) { + TypeInfo t = ParameterType.SubstituteGenericArguments(typeArguments, methodArguments); + if (t == ParameterType) return this; + return new ParameterInfo(this, t); } // ref will be handled as part of the type name diff --git a/Il2CppInspector.Common/Reflection/TypeInfo.cs b/Il2CppInspector.Common/Reflection/TypeInfo.cs index 303779b..175dbff 100644 --- a/Il2CppInspector.Common/Reflection/TypeInfo.cs +++ b/Il2CppInspector.Common/Reflection/TypeInfo.cs @@ -66,8 +66,7 @@ namespace Il2CppInspector.Reflection { return Assembly.Model.TypesByReferenceIndex[Definition.parentIndex]; } if (genericTypeDefinition != null) { - /* TODO substitute generic arguments */ - return genericTypeDefinition.BaseType; + return genericTypeDefinition.BaseType.SubstituteGenericArguments(genericArguments); } if (Namespace != "System" || BaseName != "Object") return Assembly.Model.TypesByFullName["System.Object"]; @@ -90,8 +89,7 @@ namespace Il2CppInspector.Reflection { return type; } if (genericTypeDefinition != null) { - /* Generic type instance */ - /* TODO substitute generic arguments */ + // Generic parameters are *not* substituted in the DeclaringType return genericTypeDefinition.DeclaringType; } return base.DeclaringType; @@ -264,6 +262,9 @@ namespace Il2CppInspector.Reflection { n += "*"; return n; } else { + /* XXX This is not exactly accurate to C# Type.Name: + * Type.Name should be the bare name (with & * [] suffixes) + * but without nested types or generic arguments */ var n = base.Name; if (DeclaringType != null) n = DeclaringType.Name + "+" + n; @@ -832,6 +833,10 @@ namespace Il2CppInspector.Reflection { // and returns a TypeInfo object representing the resulting constructed type. // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype?view=netframework-4.8 public TypeInfo MakeGenericType(params TypeInfo[] typeArguments) { + if(typeArguments.Length != genericArguments.Length) { + throw new ArgumentException("The number of generic arguments provided does not match the generic type definition."); + } + TypeInfo result; if (genericTypeInstances.TryGetValue(typeArguments, out result)) return result; @@ -840,6 +845,31 @@ namespace Il2CppInspector.Reflection { return result; } + public TypeInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) { + if (!ContainsGenericParameters) + return this; + + if (IsGenericTypeParameter) + return typeArguments[GenericParameterPosition]; + else if (IsGenericMethodParameter) + return methodArguments[GenericParameterPosition]; + else if(IsGenericTypeDefinition) + return MakeGenericType(typeArguments); + else if(HasElementType) { + var elementType = ElementType.SubstituteGenericArguments(typeArguments, methodArguments); + if (IsArray) + return elementType.MakeArrayType(GetArrayRank()); + else if (IsByRef) + return elementType.MakeByRefType(); + else if (IsPointer) + return elementType.MakePointerType(); + throw new InvalidOperationException("TypeInfo element type state is invalid!"); + } else { + var newGenericArguments = genericArguments.Select(x => x.SubstituteGenericArguments(typeArguments, methodArguments)); + return genericTypeDefinition.MakeGenericType(newGenericArguments.ToArray()); + } + } + // Initialize a type that is a generic parameter of a generic type // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8 public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) { diff --git a/Il2CppTests/TestGenerics.cs b/Il2CppTests/TestGenerics.cs index 59fe56c..408bd33 100644 --- a/Il2CppTests/TestGenerics.cs +++ b/Il2CppTests/TestGenerics.cs @@ -32,8 +32,11 @@ namespace Il2CppInspector // Act TypeInfo tBase = asm.GetType("Il2CppTests.TestSources.Base`2"); TypeInfo tDerived = asm.GetType("Il2CppTests.TestSources.Derived`1"); + TypeInfo tDerived_closed = model.GetType("Il2CppTests.TestSources.Derived`1[System.Int32]"); TypeInfo tDerivedBase = tDerived.BaseType; + TypeInfo tDerivedBase_closed = tDerived_closed.BaseType; TypeInfo tDerivedArray = model.GetType("Il2CppTests.TestSources.Derived`1[System.Int32][]"); + Assert.That(tDerivedArray, Is.EqualTo(tDerived_closed.MakeArrayType())); TypeInfo tT = tBase.GenericTypeParameters[0]; TypeInfo tU = tBase.GenericTypeParameters[1]; @@ -74,6 +77,7 @@ namespace Il2CppInspector (tBase, "Base`2[T,U]", true, true, true, false, -1), (tDerived, "Derived`1[V]", true, true, true, false, -1), (tDerivedBase, "Base`2[System.String,V]", true, false, true, false, -1), + (tDerivedBase_closed, "Base`2[System.String,System.Int32]", true, false, false, false, -1), (tDerivedArray, "Derived`1[System.Int32][]", false, false, false, false, -1), (tT, "T", false, false, true, true, 0), (tU, "U", false, false, true, true, 1), @@ -103,6 +107,8 @@ namespace Il2CppInspector Assert.That(t.IsGenericTypeDefinition, Is.EqualTo(check.Item4)); Assert.That(t.ContainsGenericParameters, Is.EqualTo(check.Item5)); Assert.That(t.IsGenericParameter, Is.EqualTo(check.Item6)); + if (t.IsGenericParameter) + Assert.That(t.GenericParameterPosition, Is.EqualTo(check.Item7)); } foreach (var check in methodChecks) {