Skip to content

Allow MakeGenericType/MakeArrayType of arbitrary types #112986

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

Merged
merged 3 commits into from
Mar 4, 2025
Merged
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
4 changes: 0 additions & 4 deletions eng/testing/tests.singlefile.targets
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@

<ItemGroup Condition="'$(TestNativeAot)' == 'true'">
<RdXmlFile Include="$(MSBuildThisFileDirectory)default.rd.xml" />

<!-- xunit calls MakeGenericType to check if something is IEquatable -->
<IlcArg Include="--feature:System.Reflection.IsTypeConstructionEagerlyValidated=false" />

<TrimmerRootAssembly Include="TestUtilities" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public abstract class ExecutionEnvironment
//==============================================================================================
// Invoke and field access support.
//==============================================================================================
public abstract MethodBaseInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles);
public abstract void ValidateGenericMethodConstraints(MethodInfo method);
public abstract MethodBaseInvoker TryGetMethodInvokerNoConstraintCheck(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles);
public abstract FieldAccessor TryGetFieldAccessor(MetadataReader reader, RuntimeTypeHandle declaringTypeHandle, RuntimeTypeHandle fieldTypeHandle, FieldHandle fieldHandle);

//==============================================================================================
Expand Down Expand Up @@ -108,7 +109,7 @@ internal MethodBaseInvoker GetMethodInvoker(RuntimeTypeInfo declaringType, QMeth
{
genericMethodTypeArgumentHandles[i] = genericMethodTypeArguments[i].TypeHandle;
}
MethodBaseInvoker methodInvoker = TryGetMethodInvoker(typeDefinitionHandle, methodHandle, genericMethodTypeArgumentHandles);
MethodBaseInvoker methodInvoker = TryGetMethodInvokerNoConstraintCheck(typeDefinitionHandle, methodHandle, genericMethodTypeArgumentHandles);
if (methodInvoker == null)
exception = ReflectionCoreExecution.ExecutionEnvironment.CreateNonInvokabilityException(exceptionPertainant);
return methodInvoker;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using System.Runtime.InteropServices;
using System.Threading;

using Internal.Reflection.Core.Execution;
using Internal.Runtime.CompilerHelpers;
using Internal.Runtime.CompilerServices;

Expand Down Expand Up @@ -70,14 +71,33 @@ public static object RawNewObject(RuntimeTypeHandle typeHandle)
return RuntimeImports.RhNewObject(typeHandle.ToMethodTable());
}

internal static void EnsureMethodTableSafeToAllocate(MethodTable* mt)
{
// We might be dealing with a "necessary" MethodTable (in the ILCompiler terms).
// This MethodTable is okay for casting, but must not be allocated on the GC heap.
Debug.Assert(MethodTable.Of<object>()->NumVtableSlots > 0);
if (mt->NumVtableSlots == 0)
{
// This is a type without a vtable or GCDesc. We must not allow creating an instance of it
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(Type.GetTypeFromMethodTable(mt));
}
// Paranoid check: not-meant-for-GC-heap types should be reliably identifiable by empty vtable.
Debug.Assert(!mt->ContainsGCPointers || RuntimeImports.RhGetGCDescSize(mt) != 0);
}

//
// Perform the equivalent of a "newarr" The resulting array is zero-initialized.
//
public static Array NewArray(RuntimeTypeHandle typeHandleForArrayType, int count)
{
// Don't make the easy mistake of passing in the element MethodTable rather than the "array of element" MethodTable.
Debug.Assert(typeHandleForArrayType.ToMethodTable()->IsSzArray);
return RuntimeImports.RhNewArray(typeHandleForArrayType.ToMethodTable(), count);

MethodTable* mt = typeHandleForArrayType.ToMethodTable();

EnsureMethodTableSafeToAllocate(mt);

return RuntimeImports.RhNewArray(mt, count);
}

//
Expand Down Expand Up @@ -109,7 +129,7 @@ public static unsafe Array NewMultiDimArray(RuntimeTypeHandle typeHandleForArray
// We just checked above that all lower bounds are zero. In that case, we should actually allocate
// a new SzArray instead.
Type elementType = Type.GetTypeFromHandle(new RuntimeTypeHandle(typeHandleForArrayType.ToMethodTable()->RelatedParameterType))!;
return RuntimeImports.RhNewArray(elementType.MakeArrayType().TypeHandle.ToMethodTable(), lengths[0]);
return NewArray(elementType.MakeArrayType().TypeHandle, lengths[0]);
}

// Create a local copy of the lengths that cannot be modified by the caller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private static unsafe Array InternalCreate(RuntimeType elementType, int rank, in

if (rank == 1)
{
return RuntimeImports.RhNewArray(elementType.MakeArrayType().TypeHandle.ToMethodTable(), pLengths[0]);
return RuntimeAugments.NewArray(elementType.MakeArrayType().TypeHandle, pLengths[0]);
}
else
{
Expand Down Expand Up @@ -112,15 +112,15 @@ private static unsafe Array InternalCreateFromArrayType(RuntimeType arrayType, i
}
}

MethodTable* eeType = arrayType.TypeHandle.ToMethodTable();
if (rank == 1)
{
// Multidimensional array of rank 1 with 0 lower bounds gets actually allocated
// as an SzArray. SzArray is castable to MdArray rank 1.
if (!eeType->IsSzArray)
eeType = arrayType.GetElementType().MakeArrayType().TypeHandle.ToMethodTable();
RuntimeTypeHandle arrayTypeHandle = arrayType.IsSZArray
? arrayType.TypeHandle
: arrayType.GetElementType().MakeArrayType().TypeHandle;

return RuntimeImports.RhNewArray(eeType, pLengths[0]);
return RuntimeAugments.NewArray(arrayTypeHandle, pLengths[0]);
}
else
{
Expand All @@ -129,6 +129,7 @@ private static unsafe Array InternalCreateFromArrayType(RuntimeType arrayType, i
for (int i = 0; i < rank; i++)
pImmutableLengths[i] = pLengths[i];

MethodTable* eeType = arrayType.TypeHandle.ToMethodTable();
return NewMultiDimArray(eeType, pImmutableLengths, rank);
}
}
Expand Down Expand Up @@ -662,6 +663,7 @@ internal static unsafe Array NewMultiDimArray(MethodTable* eeType, int* pLengths
if (maxArrayDimensionLengthOverflow)
throw new OutOfMemoryException(); // "Array dimensions exceeded supported range."

Debug.Assert(eeType->NumVtableSlots != 0, "Compiler enforces we never have unconstructed MTs for multi-dim arrays since those can be template-constructed anytime");
Array ret = RuntimeImports.RhNewArray(eeType, (int)totalLength);

ref int bounds = ref ret.GetRawMultiDimArrayBounds();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,42 +37,16 @@ namespace System.Reflection.Runtime.General
{
internal static partial class TypeUnifier
{
[FeatureSwitchDefinition("System.Reflection.IsTypeConstructionEagerlyValidated")]
// This can be replaced at native compile time using a feature switch.
internal static bool IsTypeConstructionEagerlyValidated => true;

public static RuntimeTypeInfo GetArrayType(this RuntimeTypeInfo elementType)
{
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: false, rank: 1);
}

public static RuntimeTypeInfo GetArrayTypeWithTypeHandle(this RuntimeTypeInfo elementType)
{
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: false, rank: 1).WithVerifiedTypeHandle(elementType);
}

public static RuntimeTypeInfo GetMultiDimArrayType(this RuntimeTypeInfo elementType, int rank)
{
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: true, rank: rank);
}

public static RuntimeTypeInfo GetMultiDimArrayTypeWithTypeHandle(this RuntimeTypeInfo elementType, int rank)
{
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: true, rank: rank).WithVerifiedTypeHandle(elementType);
}

private static RuntimeArrayTypeInfo WithVerifiedTypeHandle(this RuntimeArrayTypeInfo arrayType, RuntimeTypeInfo elementType)
{
// We only permit creating parameterized types if the pay-for-play policy specifically allows them *or* if the result
// type would be an open type.
RuntimeTypeHandle typeHandle = arrayType.InternalTypeHandleIfAvailable;
if (IsTypeConstructionEagerlyValidated
&& typeHandle.IsNull() && !elementType.ContainsGenericParameters)
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(arrayType.ToType());

return arrayType;
}

public static RuntimeTypeInfo GetByRefType(this RuntimeTypeInfo targetType)
{
return RuntimeByRefTypeInfo.GetByRefTypeInfo(targetType);
Expand All @@ -88,29 +62,9 @@ public static RuntimeTypeInfo GetConstructedGenericTypeNoConstraintCheck(this Ru
return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfoNoConstraintCheck(genericTypeDefinition, genericTypeArguments);
}

public static RuntimeTypeInfo GetConstructedGenericTypeWithTypeHandle(this RuntimeTypeInfo genericTypeDefinition, RuntimeTypeInfo[] genericTypeArguments)
public static RuntimeTypeInfo GetConstructedGenericType(this RuntimeTypeInfo genericTypeDefinition, RuntimeTypeInfo[] genericTypeArguments)
{
return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfo(genericTypeDefinition, genericTypeArguments).WithVerifiedTypeHandle(genericTypeArguments);
}

private static RuntimeConstructedGenericTypeInfo WithVerifiedTypeHandle(this RuntimeConstructedGenericTypeInfo genericType, RuntimeTypeInfo[] genericTypeArguments)
{
// We only permit creating parameterized types if the pay-for-play policy specifically allows them *or* if the result
// type would be an open type.
RuntimeTypeHandle typeHandle = genericType.InternalTypeHandleIfAvailable;
if (IsTypeConstructionEagerlyValidated && typeHandle.IsNull())
{
bool atLeastOneOpenType = false;
foreach (RuntimeTypeInfo genericTypeArgument in genericTypeArguments)
{
if (genericTypeArgument.ContainsGenericParameters)
atLeastOneOpenType = true;
}
if (!atLeastOneOpenType)
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(genericType.ToType());
}

return genericType;
return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfo(genericTypeDefinition, genericTypeArguments);
}

public static RuntimeTypeInfo GetRuntimeTypeInfoForRuntimeTypeHandle(this RuntimeTypeHandle typeHandle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ public sealed override MethodInfo MakeGenericMethod(params Type[] typeArguments)
if (typeArguments.Length != GenericTypeParameters.Length)
throw new ArgumentException(SR.Format(SR.Argument_NotEnoughGenArguments, typeArguments.Length, GenericTypeParameters.Length));
RuntimeMethodInfo methodInfo = (RuntimeMethodInfo)RuntimeConstructedGenericMethodInfo.GetRuntimeConstructedGenericMethodInfo(this, genericTypeArguments);
MethodBaseInvoker _ = methodInfo.MethodInvoker; // For compatibility with other Make* apis, trigger any missing metadata exceptions now rather than later.

ReflectionCoreExecution.ExecutionEnvironment.ValidateGenericMethodConstraints(methodInfo);

return methodInfo;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,14 @@ public Type MakeArrayType()
// Do not implement this as a call to MakeArrayType(1) - they are not interchangeable. MakeArrayType() returns a
// vector type ("SZArray") while MakeArrayType(1) returns a multidim array of rank 1. These are distinct types
// in the ECMA model and in CLR Reflection.
return this.GetArrayTypeWithTypeHandle().ToType();
return this.GetArrayType().ToType();
}

public Type MakeArrayType(int rank)
{
if (rank <= 0)
throw new IndexOutOfRangeException();
return this.GetMultiDimArrayTypeWithTypeHandle(rank).ToType();
return this.GetMultiDimArrayType(rank).ToType();
}

public Type MakePointerType()
Expand Down Expand Up @@ -475,7 +475,7 @@ public Type MakeGenericType(Type[] typeArguments)
throw new TypeLoadException(SR.CannotUseByRefLikeTypeInInstantiation);
}

return this.GetConstructedGenericTypeWithTypeHandle(runtimeTypeArguments!).ToType();
return this.GetConstructedGenericType(runtimeTypeArguments!).ToType();
}

public Type DeclaringType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,14 +322,7 @@ public static unsafe object GetUninitializedObject(
throw new NotSupportedException(SR.NotSupported_ByRefLike);
}

Debug.Assert(MethodTable.Of<object>()->NumVtableSlots > 0);
if (mt->NumVtableSlots == 0)
{
// This is a type without a vtable or GCDesc. We must not allow creating an instance of it
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(type);
}
// Paranoid check: not-meant-for-GC-heap types should be reliably identifiable by empty vtable.
Debug.Assert(!mt->ContainsGCPointers || RuntimeImports.RhGetGCDescSize(mt) != 0);
RuntimeAugments.EnsureMethodTableSafeToAllocate(mt);

if (mt->IsNullable)
{
Expand Down Expand Up @@ -364,13 +357,7 @@ public static unsafe object GetUninitializedObject(
if (mt->ElementType == EETypeElementType.Void || mt->IsGenericTypeDefinition || mt->IsByRef || mt->IsPointer || mt->IsFunctionPointer)
throw new ArgumentException(SR.Arg_TypeNotSupported);

if (mt->NumVtableSlots == 0)
{
// This is a type without a vtable or GCDesc. We must not allow creating an instance of it
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(Type.GetTypeFromHandle(type));
}
// Paranoid check: not-meant-for-GC-heap types should be reliably identifiable by empty vtable.
Debug.Assert(!mt->ContainsGCPointers || RuntimeImports.RhGetGCDescSize(mt) != 0);
RuntimeAugments.EnsureMethodTableSafeToAllocate(mt);

if (!mt->IsValueType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,14 @@ public sealed override unsafe bool TryGetConstructedGenericTypeForComponentsNoCo
return TypeLoaderEnvironment.Instance.TryGetConstructedGenericTypeForComponents(genericTypeDefinitionHandle, genericTypeArgumentHandles, out runtimeTypeHandle);
}

public sealed override MethodBaseInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles)
public sealed override void ValidateGenericMethodConstraints(MethodInfo method)
{
MethodBase methodInfo = ExecutionDomain.GetMethod(declaringTypeHandle, methodHandle, genericMethodTypeArgumentHandles);
ConstraintValidator.EnsureSatisfiesClassConstraints(method);
}

// Validate constraints first. This is potentially useless work if the method already exists, but it prevents bad
// inputs to reach the type loader (we don't have support to e.g. represent pointer types within the type loader)
if (genericMethodTypeArgumentHandles != null && genericMethodTypeArgumentHandles.Length > 0)
ConstraintValidator.EnsureSatisfiesClassConstraints((MethodInfo)methodInfo);
public sealed override MethodBaseInvoker TryGetMethodInvokerNoConstraintCheck(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles)
{
MethodBase methodInfo = ExecutionDomain.GetMethod(declaringTypeHandle, methodHandle, genericMethodTypeArgumentHandles);

MethodSignatureComparer methodSignatureComparer = new MethodSignatureComparer(methodHandle);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
Section hashTableSection = writer.NewSection();
hashTableSection.Place(typeMapHashTable);

foreach (var type in factory.MetadataManager.GetTypesWithConstructedEETypes())
foreach (var type in factory.MetadataManager.GetTypesWithEETypes())
{
if (!type.IsArray)
continue;

var arrayType = (ArrayType)type;

// Look at the constructed type symbol. If a constructed type wasn't emitted, then the array map entry isn't valid for use
IEETypeNode arrayTypeSymbol = factory.ConstructedTypeSymbol(arrayType);
IEETypeNode arrayTypeSymbol = factory.NecessaryTypeSymbol(arrayType);

Vertex vertex = writer.GetUnsignedConstant(_externalReferences.GetIndex(arrayTypeSymbol));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
Section nativeSection = nativeWriter.NewSection();
nativeSection.Place(hashtable);

// We go over constructed EETypes only. The places that need to consult this hashtable at runtime
// all need constructed EETypes. Placing unconstructed EETypes into this hashtable could make us
// accidentally satisfy e.g. MakeGenericType for something that was only used in a cast. Those
// should throw MissingRuntimeArtifact instead.
//
// We already make sure "necessary" EETypes that could potentially be loaded at runtime through
// the dynamic type loader get upgraded to constructed EETypes at AOT compile time.
foreach (var type in factory.MetadataManager.GetTypesWithConstructedEETypes())
foreach (var type in factory.MetadataManager.GetTypesWithEETypes())
{
// If this is an instantiated non-canonical generic type, add it to the generic instantiations hashtable
if (!type.HasInstantiation || type.IsGenericDefinition || type.IsCanonicalSubtype(CanonicalFormKind.Any))
Expand Down
3 changes: 0 additions & 3 deletions src/tests/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -565,9 +565,6 @@
<CustomLinkerArg Condition="'$(CrossBuild)' == 'true' and '$(_hostArchitecture)' == '$(_targetArchitecture)' and '$(_hostOS)' != 'windows'" Include="--gcc-toolchain=$(ROOTFS_DIR)/usr" />
<IlcReference Include="$(TargetingPackPath)/*.dll" />

<!-- xunit calls MakeGenericType to check if something is IEquatable -->
<IlcArg Condition="'$(EagerlyValidateTypeConstruction)' != 'false'" Include="--feature:System.Reflection.IsTypeConstructionEagerlyValidated=false" />

<!-- Bump the generic cycle tolerance. There's at least one test with a cycle that is reachable at runtime to depth 6 -->
<IlcArg Include="--maxgenericcycle:7" />

Expand Down
Loading
Loading