Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,11 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al
// The name matching was case insensitive (no idea why this is
// different from the constructor above) and we preserve that as
// well.
BindToMethodName(null, rtTarget, method,
DelegateBindingFlags.StaticMethodOnly |
DelegateBindingFlags.OpenDelegateOnly |
DelegateBindingFlags.CaselessMatching);
if (!BindToMethodName(null, rtTarget, method,
DelegateBindingFlags.StaticMethodOnly |
DelegateBindingFlags.OpenDelegateOnly |
DelegateBindingFlags.CaselessMatching))
throw new ArgumentException(SR.Arg_DlgtTargMeth);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ private static RuntimeMethodInfo LookupMethodForCreateDelegate(RuntimeTypeInfo r
while (type != null)
{
MethodInfo? methodInfo = type.GetMethod(method, 0, bindingFlags, parameterTypes);
if (methodInfo != null && methodInfo.ReturnType.Equals(invokeMethod.ReturnType))
// A static abstract interface method has no implementation to call
if (methodInfo != null && !(methodInfo.IsStatic && methodInfo.IsAbstract) && methodInfo.ReturnType.Equals(invokeMethod.ReturnType))
return (RuntimeMethodInfo)methodInfo; // This cast is safe since we already verified that containingType is runtime implemented.

type = type.BaseType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@
<Compile Include="System\Reflection\Runtime\MethodInfos\IRuntimeMethodCommon.cs" />
<Compile Include="System\Reflection\Runtime\MethodInfos\RuntimeNamedMethodInfo.cs" />
<Compile Include="System\Reflection\Runtime\MethodInfos\RuntimeSyntheticMethodInfo.cs" />
<Compile Include="System\Reflection\Runtime\MethodInfos\StaticAbstractMethodInvoker.cs" />
<Compile Include="System\Reflection\Runtime\MethodInfos\SyntheticMethodId.cs" />
<Compile Include="System\Reflection\Runtime\Modules\RuntimeModule.cs" />
<Compile Include="System\Reflection\Runtime\Modules\NativeFormat\NativeFormatRuntimeModule.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,13 +289,34 @@ private RuntimeTypeInfo[] GenericTypeParameters

internal sealed override MethodBaseInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant)
{
// A static abstract interface method has no implementation to call. An open generic one is left to
// OpenMethodInvoker's InvalidOperationException, as on CoreCLR.
if (IsStatic && IsAbstract && !IsOpenGeneric(_common.DeclaringType, methodArguments))
return new StaticAbstractMethodInvoker(this);

MethodBaseInvoker invoker = _common.GetUncachedMethodInvoker(methodArguments, exceptionPertainant, out Exception exception);

if (invoker == null)
throw exception;

return invoker;
}

// The same open checks as ExecutionEnvironment.GetMethodInvoker, which returns OpenMethodInvoker for them
private static bool IsOpenGeneric(RuntimeTypeInfo declaringType, RuntimeTypeInfo[] methodArguments)
{
if (declaringType.ContainsGenericParameters)
return true;

for (int i = 0; i < methodArguments.Length; i++)
{
if (methodArguments[i].ContainsGenericParameters)
return true;
}

return false;
}

protected sealed override MethodBaseInvoker UncachedMethodInvoker
{
get
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Reflection;

using Internal.Reflection.Core.Execution;

namespace System.Reflection.Runtime.MethodInfos
{
// A static abstract interface method has no implementation to call: invoking it or binding a delegate to it throws
// MemberAccessException, as on CoreCLR. Taking its address throws PlatformNotSupportedException, as for other
// abstract methods.
internal sealed class StaticAbstractMethodInvoker : MethodBaseInvoker
{
private readonly MethodInfo _method;

public StaticAbstractMethodInvoker(MethodInfo method)
{
_method = method;
}

private MemberAccessException CreateException()
{
return new MemberAccessException(SR.Format(SR.Acc_CallStaticAbstract, _method.DeclaringType, _method.Name));
}

protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException)
{
throw CreateException();
}

protected sealed override object CreateInstance(object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException)
{
throw CreateException();
}

protected internal sealed override object CreateInstance(Span<object?> arguments)
{
throw CreateException();
}

protected internal sealed override object CreateInstanceWithFewArgs(Span<object?> arguments)
{
throw CreateException();
}

protected internal sealed override object? Invoke(object? thisObject, Span<object?> arguments)
{
// MethodInvoker.Invoke(object, Span<object>) leaves the argument count check to the invoker, unlike its
// fixed-arity overloads, which check before calling InvokeDirectWithFewArgs. Check the count here first, as
// DynamicInvokeInfo does for other methods, so a wrong count throws TargetParameterCountException.
if (arguments.Length != _method.GetParametersAsSpan().Length)
throw new TargetParameterCountException(SR.Arg_ParmCnt);

throw CreateException();
}

protected internal sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span<object?> arguments)
{
throw CreateException();
}

public sealed override Delegate CreateDelegate(RuntimeTypeHandle delegateType, object target, bool isStatic, bool isVirtual, bool isOpen)
{
throw CreateException();
}

public sealed override IntPtr LdFtnResult
{
get
{
throw new PlatformNotSupportedException();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public sealed override void GetInterfaceMap(Type instanceType, [DynamicallyAcces
var tMethods = new MethodInfo[ifaceMethods.Length];
for (int i = 0; i < ifaceMethods.Length; i++)
{
// A static abstract method is not implemented through the instance type's virtual slots (https://github.com/dotnet/runtime/issues/89157)
if (ifaceMethods[i].IsStatic && ifaceMethods[i].IsAbstract)
throw new NotSupportedException();

var invoker = (VirtualMethodInvoker)GetMethodInvoker(ifaceMethods[i]);

IntPtr classRtMethodHandle = invoker.ResolveTarget(instanceType.TypeHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)

MethodDesc method = mappingEntry.Entity;

// The current format requires us to have an MethodTable for the owning type. We might want to lift this.
if (!factory.MetadataManager.TypeGeneratesEEType(method.OwningType))
continue;

// We have a method body, we have a metadata token, but we can't get an invoke stub. Bail.
if (!factory.MetadataManager.IsReflectionInvokable(method))
// The runtime reads this map only for methods that have an invoke map entry
if (!factory.MetadataManager.ShouldMethodBeInInvokeMap(method))
continue;

// Only virtual methods are interesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ public bool ShouldMethodBeInInvokeMap(MethodDesc method)
if (!IsReflectionInvokable(method))
return false;

// Static abstract interface methods have no body to invoke
if (method.Signature.IsStatic && method.IsAbstract)
return false;

return true;
}

Expand Down
12 changes: 11 additions & 1 deletion src/coreclr/vm/comdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,10 @@ extern "C" BOOL QCALLTYPE Delegate_BindToMethodName(MethodTable* pDelegateMT, Me
if (pCurMethod->IsGenericMethodDefinition())
continue;

// We can't match static abstract methods (since they have no implementation to call).
if (pCurMethod->IsStaticAbstract())
continue;

// We can't match async variants (since we have only name).
if (pCurMethod->IsAsyncVariantMethod())
continue;
Expand Down Expand Up @@ -1138,6 +1142,10 @@ extern "C" BOOL QCALLTYPE Delegate_BindToMethodInfo(MethodTable* pDelegateMT, Me
if (method->IsGenericMethodDefinition())
COMPlusThrow(kArgumentException, W("Arg_DlgtTargMeth"));

// A static abstract method has no implementation to call.
if (method->IsStaticAbstract())
COMPlusThrow(kMemberAccessException, W("Acc_BindStaticAbstract"));

// get the invoke of the delegate
MethodDesc* pInvokeMeth = COMDelegate::FindDelegateInvokeMethod(pDelegateMT);
_ASSERTE(pInvokeMeth);
Expand Down Expand Up @@ -1267,7 +1275,9 @@ void COMDelegate::BindToMethod(MethodTable* pDelegateMT,
// We can get virtual delegates closed over null through this code path, so be careful to handle that case (no need to
// virtualize since we're just going to throw NullRefException at invocation time).
// </TODO>
if (pTargetMethod->IsVirtual()
// A static method's first argument is not "this", so never virtualize the call on it.
if (!pTargetMethod->IsStatic()
&& pTargetMethod->IsVirtual()
&& pTargetMT != NULL
&& pTargetMethod->GetMethodTable() != pTargetMT)
{
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/vm/method.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,16 @@ class MethodDesc
#endif // FEATURE_DEFAULT_INTERFACES
}

// Is this a static abstract method (it has no implementation to call)
inline BOOL IsStaticAbstract()
{
WRAPPER_NO_CONTRACT;

// Only abstract types (interfaces included) can declare an abstract method, and the type's flags are cached,
// unlike the method's attributes.
return IsStatic() && GetMethodTable()->IsAbstract() && IsAbstract();
}

inline BOOL HasNonVtableSlot();

void SetHasNonVtableSlot()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Acc_BindStaticAbstract" xml:space="preserve">
<value>Cannot bind a delegate to a static abstract method.</value>
</data>
<data name="Acc_CallStaticAbstract" xml:space="preserve">
<value>Cannot call static abstract method '{0}.{1}'.</value>
</data>
<data name="Acc_CreateAbst" xml:space="preserve">
<value>Cannot create an abstract class.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal InvocationFlags ComputeAndUpdateInvocationFlags()
|| IsDisallowedByRefType(returnType) // Return type is an invalid by-ref (i.e., by-ref-like or void*)
|| (CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs // Managed varargs
|| IsDisallowedAsyncHelper
|| IsStaticAbstract // Static abstract interface method has no body to call
Comment thread
Copilot marked this conversation as resolved.
)
{
invocationFlags = InvocationFlags.NoInvoke;
Expand Down Expand Up @@ -79,10 +80,9 @@ internal void ThrowNoInvokeException()
{
throw new InvalidOperationException(SR.Arg_UnboundGenParam);
}
// method is abstract class
else if (IsAbstract)
else if (IsStaticAbstract)
{
throw new MemberAccessException();
throw new MemberAccessException(SR.Format(SR.Acc_CallStaticAbstract, declaringType, Name));
}
else if (ReturnType.IsByRef)
{
Expand All @@ -100,6 +100,9 @@ internal void ThrowNoInvokeException()
throw new TargetException();
}

private bool IsStaticAbstract =>
(Attributes & (MethodAttributes.Static | MethodAttributes.Abstract)) == (MethodAttributes.Static | MethodAttributes.Abstract);

[DebuggerStepThrough]
[DebuggerHidden]
public override object? Invoke(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,42 @@ private class TestClassThatThrows
public static void Throw() => throw new Exception("Here");
}

[Theory]
[InlineData(typeof(IStaticAbstractMethods), nameof(IStaticAbstractMethods.NoArgs), null)]
[InlineData(typeof(IStaticAbstractMethods<int>), nameof(IStaticAbstractMethods<int>.OneArg), new object[] { 42 })]
[InlineData(typeof(IStaticAbstractMethods<string>), nameof(IStaticAbstractMethods<string>.OneArg), new object[] { "abc" })]
[InlineData(typeof(IStaticAbstractMethods), nameof(IStaticAbstractMethods.SpanReturn), null)]
[InlineData(typeof(IStaticAbstractMethods), nameof(IStaticAbstractMethods.RefSpanReturn), null)]
public void Invoke_StaticAbstract_ThrowsMemberAccessException(Type type, string methodName, object?[]? parameters)
{
MethodInfo method = type.GetMethod(methodName)!;
MemberAccessException ex = Assert.Throws<MemberAccessException>(() => Invoke(method, null, parameters));
Assert.Contains($"{type}.{methodName}", ex.Message);
}

[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(string))]
public void Invoke_StaticAbstractGenericMethod_ThrowsMemberAccessException(Type typeArgument)
{
MethodInfo method = typeof(IStaticAbstractMethods).GetMethod(nameof(IStaticAbstractMethods.GenericNoArgs))!.MakeGenericMethod(typeArgument);
MemberAccessException ex = Assert.Throws<MemberAccessException>(() => Invoke(method, null, null));
Assert.Contains($"{typeof(IStaticAbstractMethods)}.{nameof(IStaticAbstractMethods.GenericNoArgs)}", ex.Message);
}

protected interface IStaticAbstractMethods
{
static abstract void NoArgs();
static abstract void GenericNoArgs<T>();
static abstract Span<int> SpanReturn();
static abstract ref Span<int> RefSpanReturn();
}

protected interface IStaticAbstractMethods<T>
{
static abstract T OneArg(T value);
}

public static IEnumerable<object[]> PropertyTestData()
{
yield return new object[] { "Bool", TestClass.BoolValue };
Expand Down
Loading
Loading