Skip to content

Commit 52fbf3e

Browse files
Allow MakeGenericType/MakeArrayType of arbitrary types (#112986)
When we worked on .NET Native, we didn't have static analysis but at least wanted to have some predictability in how dynamic code fails with AOT. We decided we want `MakeGenericType` & co. to be the thing that fails instead of subsequent operations that try to obtain a type handle (one needs a type handle to do "interesting" reflection operations). As we refined the model and introduced static analysis, we decided it's fine if trim/AOT unsafe code can get at things that are in "undefined" state [^1] (named types that might not have type handles, MethodInfos that don't have parameter names, etc.). The static analysis warning is always at the problematic operation and what happens next can be anything. But `MakeGenericType` failing early sometimes got in the way. For example xUnit uses `MakeGenericType` to create a new interface at runtime and check `IsAssignableFrom` with it. We don't actually need a usable type handle for this. We had an opt-out switch that we only enabled for testing to get xUnit working. But that also means we were not testing the shipping configuration whenever xUnit was involved. This PR makes it so `MakeGeneric`/`MakeArray` can actually succeed even if we don't have the code/data structures for the type. Trying to do "interesting" reflection with it is going to throw the good old MissingMetadata-like exception - the difference is just in the timing. I'm also changing things to allow obtaining `TypeHandle` on generic types even if they're in the "necessary" form only. This will only bring us to parity with what already happens for non-generic types. We already have blocks in places to make sure such `TypeHandle` cannot be used for much more than casting (e.g. `GetUninitializedObject` has a preexisting block for this). Cc @dotnet/ilc-contrib [^1]: We say we're fine with undefined behavior for trim-unsafe code, but that does not include GC holes.
1 parent 40ef15c commit 52fbf3e

File tree

14 files changed

+116
-107
lines changed

14 files changed

+116
-107
lines changed

eng/testing/tests.singlefile.targets

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@
5050

5151
<ItemGroup Condition="'$(TestNativeAot)' == 'true'">
5252
<RdXmlFile Include="$(MSBuildThisFileDirectory)default.rd.xml" />
53-
54-
<!-- xunit calls MakeGenericType to check if something is IEquatable -->
55-
<IlcArg Include="--feature:System.Reflection.IsTypeConstructionEagerlyValidated=false" />
56-
5753
<TrimmerRootAssembly Include="TestUtilities" />
5854
</ItemGroup>
5955

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public abstract class ExecutionEnvironment
5353
//==============================================================================================
5454
// Invoke and field access support.
5555
//==============================================================================================
56-
public abstract MethodBaseInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles);
56+
public abstract void ValidateGenericMethodConstraints(MethodInfo method);
57+
public abstract MethodBaseInvoker TryGetMethodInvokerNoConstraintCheck(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles);
5758
public abstract FieldAccessor TryGetFieldAccessor(MetadataReader reader, RuntimeTypeHandle declaringTypeHandle, RuntimeTypeHandle fieldTypeHandle, FieldHandle fieldHandle);
5859

5960
//==============================================================================================
@@ -108,7 +109,7 @@ internal MethodBaseInvoker GetMethodInvoker(RuntimeTypeInfo declaringType, QMeth
108109
{
109110
genericMethodTypeArgumentHandles[i] = genericMethodTypeArguments[i].TypeHandle;
110111
}
111-
MethodBaseInvoker methodInvoker = TryGetMethodInvoker(typeDefinitionHandle, methodHandle, genericMethodTypeArgumentHandles);
112+
MethodBaseInvoker methodInvoker = TryGetMethodInvokerNoConstraintCheck(typeDefinitionHandle, methodHandle, genericMethodTypeArgumentHandles);
112113
if (methodInvoker == null)
113114
exception = ReflectionCoreExecution.ExecutionEnvironment.CreateNonInvokabilityException(exceptionPertainant);
114115
return methodInvoker;

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using System.Runtime.InteropServices;
2727
using System.Threading;
2828

29+
using Internal.Reflection.Core.Execution;
2930
using Internal.Runtime.CompilerHelpers;
3031
using Internal.Runtime.CompilerServices;
3132

@@ -70,14 +71,33 @@ public static object RawNewObject(RuntimeTypeHandle typeHandle)
7071
return RuntimeImports.RhNewObject(typeHandle.ToMethodTable());
7172
}
7273

74+
internal static void EnsureMethodTableSafeToAllocate(MethodTable* mt)
75+
{
76+
// We might be dealing with a "necessary" MethodTable (in the ILCompiler terms).
77+
// This MethodTable is okay for casting, but must not be allocated on the GC heap.
78+
Debug.Assert(MethodTable.Of<object>()->NumVtableSlots > 0);
79+
if (mt->NumVtableSlots == 0)
80+
{
81+
// This is a type without a vtable or GCDesc. We must not allow creating an instance of it
82+
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(Type.GetTypeFromMethodTable(mt));
83+
}
84+
// Paranoid check: not-meant-for-GC-heap types should be reliably identifiable by empty vtable.
85+
Debug.Assert(!mt->ContainsGCPointers || RuntimeImports.RhGetGCDescSize(mt) != 0);
86+
}
87+
7388
//
7489
// Perform the equivalent of a "newarr" The resulting array is zero-initialized.
7590
//
7691
public static Array NewArray(RuntimeTypeHandle typeHandleForArrayType, int count)
7792
{
7893
// Don't make the easy mistake of passing in the element MethodTable rather than the "array of element" MethodTable.
7994
Debug.Assert(typeHandleForArrayType.ToMethodTable()->IsSzArray);
80-
return RuntimeImports.RhNewArray(typeHandleForArrayType.ToMethodTable(), count);
95+
96+
MethodTable* mt = typeHandleForArrayType.ToMethodTable();
97+
98+
EnsureMethodTableSafeToAllocate(mt);
99+
100+
return RuntimeImports.RhNewArray(mt, count);
81101
}
82102

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

115135
// Create a local copy of the lengths that cannot be modified by the caller

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ private static unsafe Array InternalCreate(RuntimeType elementType, int rank, in
7878

7979
if (rank == 1)
8080
{
81-
return RuntimeImports.RhNewArray(elementType.MakeArrayType().TypeHandle.ToMethodTable(), pLengths[0]);
81+
return RuntimeAugments.NewArray(elementType.MakeArrayType().TypeHandle, pLengths[0]);
8282
}
8383
else
8484
{
@@ -112,15 +112,15 @@ private static unsafe Array InternalCreateFromArrayType(RuntimeType arrayType, i
112112
}
113113
}
114114

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

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

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

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

667669
ref int bounds = ref ret.GetRawMultiDimArrayBounds();

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -37,42 +37,16 @@ namespace System.Reflection.Runtime.General
3737
{
3838
internal static partial class TypeUnifier
3939
{
40-
[FeatureSwitchDefinition("System.Reflection.IsTypeConstructionEagerlyValidated")]
41-
// This can be replaced at native compile time using a feature switch.
42-
internal static bool IsTypeConstructionEagerlyValidated => true;
43-
4440
public static RuntimeTypeInfo GetArrayType(this RuntimeTypeInfo elementType)
4541
{
4642
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: false, rank: 1);
4743
}
4844

49-
public static RuntimeTypeInfo GetArrayTypeWithTypeHandle(this RuntimeTypeInfo elementType)
50-
{
51-
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: false, rank: 1).WithVerifiedTypeHandle(elementType);
52-
}
53-
5445
public static RuntimeTypeInfo GetMultiDimArrayType(this RuntimeTypeInfo elementType, int rank)
5546
{
5647
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: true, rank: rank);
5748
}
5849

59-
public static RuntimeTypeInfo GetMultiDimArrayTypeWithTypeHandle(this RuntimeTypeInfo elementType, int rank)
60-
{
61-
return RuntimeArrayTypeInfo.GetArrayTypeInfo(elementType, multiDim: true, rank: rank).WithVerifiedTypeHandle(elementType);
62-
}
63-
64-
private static RuntimeArrayTypeInfo WithVerifiedTypeHandle(this RuntimeArrayTypeInfo arrayType, RuntimeTypeInfo elementType)
65-
{
66-
// We only permit creating parameterized types if the pay-for-play policy specifically allows them *or* if the result
67-
// type would be an open type.
68-
RuntimeTypeHandle typeHandle = arrayType.InternalTypeHandleIfAvailable;
69-
if (IsTypeConstructionEagerlyValidated
70-
&& typeHandle.IsNull() && !elementType.ContainsGenericParameters)
71-
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(arrayType.ToType());
72-
73-
return arrayType;
74-
}
75-
7650
public static RuntimeTypeInfo GetByRefType(this RuntimeTypeInfo targetType)
7751
{
7852
return RuntimeByRefTypeInfo.GetByRefTypeInfo(targetType);
@@ -88,29 +62,9 @@ public static RuntimeTypeInfo GetConstructedGenericTypeNoConstraintCheck(this Ru
8862
return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfoNoConstraintCheck(genericTypeDefinition, genericTypeArguments);
8963
}
9064

91-
public static RuntimeTypeInfo GetConstructedGenericTypeWithTypeHandle(this RuntimeTypeInfo genericTypeDefinition, RuntimeTypeInfo[] genericTypeArguments)
65+
public static RuntimeTypeInfo GetConstructedGenericType(this RuntimeTypeInfo genericTypeDefinition, RuntimeTypeInfo[] genericTypeArguments)
9266
{
93-
return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfo(genericTypeDefinition, genericTypeArguments).WithVerifiedTypeHandle(genericTypeArguments);
94-
}
95-
96-
private static RuntimeConstructedGenericTypeInfo WithVerifiedTypeHandle(this RuntimeConstructedGenericTypeInfo genericType, RuntimeTypeInfo[] genericTypeArguments)
97-
{
98-
// We only permit creating parameterized types if the pay-for-play policy specifically allows them *or* if the result
99-
// type would be an open type.
100-
RuntimeTypeHandle typeHandle = genericType.InternalTypeHandleIfAvailable;
101-
if (IsTypeConstructionEagerlyValidated && typeHandle.IsNull())
102-
{
103-
bool atLeastOneOpenType = false;
104-
foreach (RuntimeTypeInfo genericTypeArgument in genericTypeArguments)
105-
{
106-
if (genericTypeArgument.ContainsGenericParameters)
107-
atLeastOneOpenType = true;
108-
}
109-
if (!atLeastOneOpenType)
110-
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(genericType.ToType());
111-
}
112-
113-
return genericType;
67+
return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfo(genericTypeDefinition, genericTypeArguments);
11468
}
11569

11670
public static RuntimeTypeInfo GetRuntimeTypeInfoForRuntimeTypeHandle(this RuntimeTypeHandle typeHandle)

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ public sealed override MethodInfo MakeGenericMethod(params Type[] typeArguments)
144144
if (typeArguments.Length != GenericTypeParameters.Length)
145145
throw new ArgumentException(SR.Format(SR.Argument_NotEnoughGenArguments, typeArguments.Length, GenericTypeParameters.Length));
146146
RuntimeMethodInfo methodInfo = (RuntimeMethodInfo)RuntimeConstructedGenericMethodInfo.GetRuntimeConstructedGenericMethodInfo(this, genericTypeArguments);
147-
MethodBaseInvoker _ = methodInfo.MethodInvoker; // For compatibility with other Make* apis, trigger any missing metadata exceptions now rather than later.
147+
148+
ReflectionCoreExecution.ExecutionEnvironment.ValidateGenericMethodConstraints(methodInfo);
149+
148150
return methodInfo;
149151
}
150152

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,14 @@ public Type MakeArrayType()
404404
// Do not implement this as a call to MakeArrayType(1) - they are not interchangeable. MakeArrayType() returns a
405405
// vector type ("SZArray") while MakeArrayType(1) returns a multidim array of rank 1. These are distinct types
406406
// in the ECMA model and in CLR Reflection.
407-
return this.GetArrayTypeWithTypeHandle().ToType();
407+
return this.GetArrayType().ToType();
408408
}
409409

410410
public Type MakeArrayType(int rank)
411411
{
412412
if (rank <= 0)
413413
throw new IndexOutOfRangeException();
414-
return this.GetMultiDimArrayTypeWithTypeHandle(rank).ToType();
414+
return this.GetMultiDimArrayType(rank).ToType();
415415
}
416416

417417
public Type MakePointerType()
@@ -475,7 +475,7 @@ public Type MakeGenericType(Type[] typeArguments)
475475
throw new TypeLoadException(SR.CannotUseByRefLikeTypeInInstantiation);
476476
}
477477

478-
return this.GetConstructedGenericTypeWithTypeHandle(runtimeTypeArguments!).ToType();
478+
return this.GetConstructedGenericType(runtimeTypeArguments!).ToType();
479479
}
480480

481481
public Type DeclaringType

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,7 @@ public static unsafe object GetUninitializedObject(
322322
throw new NotSupportedException(SR.NotSupported_ByRefLike);
323323
}
324324

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

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

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

375362
if (!mt->IsValueType)
376363
{

src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,14 @@ public sealed override unsafe bool TryGetConstructedGenericTypeForComponentsNoCo
218218
return TypeLoaderEnvironment.Instance.TryGetConstructedGenericTypeForComponents(genericTypeDefinitionHandle, genericTypeArgumentHandles, out runtimeTypeHandle);
219219
}
220220

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

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

230230
MethodSignatureComparer methodSignatureComparer = new MethodSignatureComparer(methodHandle);
231231

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ArrayMapNode.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,14 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
4949
Section hashTableSection = writer.NewSection();
5050
hashTableSection.Place(typeMapHashTable);
5151

52-
foreach (var type in factory.MetadataManager.GetTypesWithConstructedEETypes())
52+
foreach (var type in factory.MetadataManager.GetTypesWithEETypes())
5353
{
5454
if (!type.IsArray)
5555
continue;
5656

5757
var arrayType = (ArrayType)type;
5858

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

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

0 commit comments

Comments
 (0)