Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,8 @@ private static Exception CreateChangeTypeException(MethodTable* srcEEType, Metho
}

internal static ArgumentException CreateChangeTypeArgumentException(MethodTable* srcEEType, MethodTable* dstEEType, bool destinationIsByRef = false)
=> CreateChangeTypeArgumentException(srcEEType, Type.GetTypeFromHandle(new RuntimeTypeHandle(dstEEType)), destinationIsByRef);

internal static ArgumentException CreateChangeTypeArgumentException(MethodTable* srcEEType, Type dstType, bool destinationIsByRef = false)
{
object? destinationTypeName = dstType;
object? destinationTypeName = Type.GetTypeFromHandle(new RuntimeTypeHandle(dstEEType));
if (destinationIsByRef)
destinationTypeName += "&";
return new ArgumentException(SR.Format(SR.Arg_ObjObjEx, Type.GetTypeFromHandle(new RuntimeTypeHandle(srcEEType)), destinationTypeName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,37 +74,36 @@ public unsafe DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk)
{
Transform transform = default;

var argumentType = (RuntimeType)parameters[i].ParameterType;
Type argumentType = parameters[i].ParameterType;
if (argumentType.IsByRef)
{
_needsCopyBack = true;
transform |= Transform.ByRef;
argumentType = (RuntimeType)argumentType.GetElementType()!;
argumentType = argumentType.GetElementType()!;
}
Debug.Assert(!argumentType.IsByRef);

// This can return a null MethodTable for reference types.
// The compiler makes sure it returns a non-null MT for everything else.
MethodTable* eeArgumentType = argumentType.ToMethodTableMayBeNull();
if (argumentType.IsValueType)
MethodTable* eeArgumentType = argumentType.TypeHandle.ToMethodTable();

if (eeArgumentType->IsValueType)
{
Debug.Assert(eeArgumentType->IsValueType);
Debug.Assert(argumentType.IsValueType);

if (eeArgumentType->IsByRefLike)
_argumentCount = ArgumentCount_NotSupported_ByRefLike;

if (eeArgumentType->IsNullable)
transform |= Transform.Nullable;
}
else if (argumentType.IsPointer)
else if (eeArgumentType->IsPointer)
{
Debug.Assert(eeArgumentType->IsPointer);
Debug.Assert(argumentType.IsPointer);

transform |= Transform.Pointer;
}
else if (argumentType.IsFunctionPointer)
else if (eeArgumentType->IsFunctionPointer)
{
Debug.Assert(eeArgumentType->IsFunctionPointer);
Debug.Assert(argumentType.IsFunctionPointer);

transform |= Transform.FunctionPointer;
}
Expand All @@ -122,18 +121,19 @@ public unsafe DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk)
{
Transform transform = default;

var returnType = (RuntimeType)methodInfo.ReturnType;
Type returnType = methodInfo.ReturnType;
if (returnType.IsByRef)
{
transform |= Transform.ByRef;
returnType = (RuntimeType)returnType.GetElementType()!;
returnType = returnType.GetElementType()!;
}
Debug.Assert(!returnType.IsByRef);

MethodTable* eeReturnType = returnType.ToMethodTableMayBeNull();
if (returnType.IsValueType)
MethodTable* eeReturnType = returnType.TypeHandle.ToMethodTable();

if (eeReturnType->IsValueType)
{
Debug.Assert(eeReturnType->IsValueType);
Debug.Assert(returnType.IsValueType);

if (returnType != typeof(void))
{
Expand All @@ -152,17 +152,17 @@ public unsafe DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk)
_argumentCount = ArgumentCount_NotSupported; // ByRef to void return
}
}
else if (returnType.IsPointer)
else if (eeReturnType->IsPointer)
{
Debug.Assert(eeReturnType->IsPointer);
Debug.Assert(returnType.IsPointer);

transform |= Transform.Pointer;
if ((transform & Transform.ByRef) == 0)
transform |= Transform.AllocateReturnBox;
}
else if (returnType.IsFunctionPointer)
else if (eeReturnType->IsFunctionPointer)
{
Debug.Assert(eeReturnType->IsFunctionPointer);
Debug.Assert(returnType.IsFunctionPointer);

transform |= Transform.FunctionPointer;
if ((transform & Transform.ByRef) == 0)
Expand Down Expand Up @@ -585,12 +585,6 @@ private unsafe ref byte InvokeDirectWithFewArguments(
return defaultValue;
}

private void ThrowForNeverValidNonNullArgument(MethodTable* srcEEType, int index)
{
Debug.Assert(index != 0 || _isStatic);
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, Method.GetParametersAsSpan()[index - (_isStatic ? 0 : 1)].ParameterType, destinationIsByRef: false);
}

private unsafe void CheckArguments(
Span<object?> copyOfParameters,
void* byrefParameters,
Expand Down Expand Up @@ -630,25 +624,16 @@ private unsafe void CheckArguments(
MethodTable* srcEEType = arg.GetMethodTable();
MethodTable* dstEEType = argumentInfo.Type;

if (srcEEType != dstEEType)
{
// Destination type can be null if we don't have a MethodTable for this type. This means one cannot
// possibly pass a valid non-null object instance here.
if (dstEEType == null)
{
ThrowForNeverValidNonNullArgument(srcEEType, i);
}

if (!(RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType->IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
if (!(srcEEType == dstEEType ||
RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType->IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
&& castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))))
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);

arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle);
}
arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle);
}

if ((argumentInfo.Transform & Transform.Reference) == 0)
Expand Down Expand Up @@ -707,25 +692,16 @@ private unsafe void CheckArguments(
MethodTable* srcEEType = arg.GetMethodTable();
MethodTable* dstEEType = argumentInfo.Type;

if (srcEEType != dstEEType)
{
// Destination type can be null if we don't have a MethodTable for this type. This means one cannot
// possibly pass a valid non-null object instance here.
if (dstEEType == null)
{
ThrowForNeverValidNonNullArgument(srcEEType, i);
}

if (!(RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType->IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
if (!(srcEEType == dstEEType ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering: Since this conditional clause is identical to the one in the parallel method, would extracting a helper method to ensure they never diverge? Seems like this whole block doesn't reference the parameters (except by the indexed-to arg) so it should be possible to extract 682-705.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would need to validate it doesn't deoptimize codegen, this code is quite performance sensitive.

RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType->IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
&& castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))))
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);

arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle: null);
}
arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle: null);
}

if ((argumentInfo.Transform & Transform.Reference) == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
}

TypeDesc fieldType = _field.FieldType.NormalizeInstantiation();
ReflectionInvokeMapNode.AddSignatureDependency(ref dependencies, factory, fieldType, "Type of the field");
ReflectionInvokeMapNode.AddSignatureDependency(ref dependencies, factory, fieldType, "Type of the field", isOut: true);

return dependencies;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public static void AddDependenciesDueToReflectability(ref DependencyList depende
dependencies.Add(factory.MethodEntrypoint(invokeStub), "Reflection invoke");

var signature = method.Signature;
AddSignatureDependency(ref dependencies, factory, signature.ReturnType, "Reflection invoke");
AddSignatureDependency(ref dependencies, factory, signature.ReturnType, "Reflection invoke", isOut: true);
foreach (var parameterType in signature)
AddSignatureDependency(ref dependencies, factory, parameterType, "Reflection invoke");
AddSignatureDependency(ref dependencies, factory, parameterType, "Reflection invoke", isOut: false);
}

if (method.OwningType.IsValueType && !method.Signature.IsStatic)
Expand Down Expand Up @@ -96,10 +96,13 @@ public static void AddDependenciesDueToReflectability(ref DependencyList depende
ReflectionVirtualInvokeMapNode.GetVirtualInvokeMapDependencies(ref dependencies, factory, method);
}

internal static void AddSignatureDependency(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, string reason)
internal static void AddSignatureDependency(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, string reason, bool isOut)
{
if (type.IsByRef)
{
type = ((ParameterizedType)type).ParameterType;
isOut = true;
}

// Pointer runtime type handles can be created at runtime if necessary
while (type.IsPointer)
Expand All @@ -109,16 +112,15 @@ internal static void AddSignatureDependency(ref DependencyList dependencies, Nod
if (type.IsPrimitive || type.IsVoid)
return;

// Reflection doesn't need the ability to generate MethodTables out of thin air for reference types.
// Skip generating the dependencies.
if (type.IsGCPointer)
return;

TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
if (canonType.IsCanonicalSubtype(CanonicalFormKind.Any))
GenericTypesTemplateMap.GetTemplateTypeDependencies(ref dependencies, factory, canonType);
// Reflection might need to create boxed instances of valuetypes as part of reflection invocation.
// Non-valuetypes are only needed for the purposes of casting/type checks.
// If this is a non-exact type, we need the type loader template to get the type handle.
if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
GenericTypesTemplateMap.GetTemplateTypeDependencies(ref dependencies, factory, type.NormalizeInstantiation());
else if (isOut && !type.IsGCPointer)
dependencies.Add(factory.MaximallyConstructableType(type.NormalizeInstantiation()), reason);
else
dependencies.Add(factory.MaximallyConstructableType(type), reason);
dependencies.Add(factory.NecessaryTypeSymbol(type.NormalizeInstantiation()), reason);
}

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,13 @@ public static void Run()

{
MethodInfo mi = typeof(TestReflectionInvokeSignatures).GetMethod(nameof(Invoke2));
mi.Invoke(null, new object[1]);
var args = new object[1];
mi.Invoke(null, args);
ThrowIfNotPresent(typeof(TestReflectionInvokeSignatures), nameof(Allocated1));
if (args[0].GetType().Name != nameof(Allocated1))
throw new Exception();
if (!args[0].ToString().Contains(nameof(Allocated1)))
throw new Exception();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ interface IDog
string Name { get; set; }
}

[Kept (By = Tool.Trimmer)]
[Kept]
interface IFoo<T>
{

Expand All @@ -62,6 +62,7 @@ interface IFoo3<T, K, J>
int Bar3 { get; set; }
}

[Kept (By = Tool.NativeAot)]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is not actually testing anything relevant, so I'm not worried about "regression" here.

  • In ILLinker, looks like preserve=fields is going to skip over compiler generated backing fields. Not sure where that logic is but we clearly don't do that in native AOT and we do preserve them. Won't lose my sleep over that.
  • The test infrastructure doesn't check field preservation on native AOT side since we don't really "trim" fields (we vacate space for them no matter what, there's leeway whether reflection metadata is generated).

So the only observable effect in this test is whether the type of the field survived and now it by design survives if the field was a target of reflection.

class Cat
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,8 +1230,8 @@ class DerivedFromMethodsBase : MethodAnnotatedBase
void PrivateMethod () { }
}

[Kept(By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)]
[KeptBaseType (typeof (MethodAnnotatedBase), By = Tool.Trimmer)]
[Kept]
[KeptBaseType (typeof (MethodAnnotatedBase))]
class AnotherMethodsDerived : MethodAnnotatedBase
{
[Kept(By = Tool.Trimmer)]
Expand Down Expand Up @@ -1343,9 +1343,9 @@ interface INestedInterface
void InterfaceMethod ();
}

[Kept (By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)]
[Kept]
[KeptMember (".ctor()", By = Tool.Trimmer)]
[KeptBaseType (typeof (AnnotatedBase), By = Tool.Trimmer)]
[KeptBaseType (typeof (AnnotatedBase))]
class AnotherAnnotatedType : AnnotatedBase
{
[Kept (By = Tool.Trimmer)]
Expand Down
Loading