From 4dca4c8577941af19c75476639b6d7ef6190b97d Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 6 Jan 2026 13:01:15 -0800 Subject: [PATCH] [clr-interp] Fix EmptyThisCall test - Add detection for attempting to call function with this call calling convention without actually passing a this argument. --- src/coreclr/interpreter/compiler.cpp | 25 +++++++++++++++++++++++++ src/coreclr/interpreter/compiler.h | 1 + 2 files changed, 26 insertions(+) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 74f6ddbae385c0..ce9beb419e0a40 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -4248,6 +4248,24 @@ class InterpAsyncCallPeeps } } AsyncCallPeeps; +void InterpCompiler::CheckForPInvokeThisCallWithNoArgs(CORINFO_SIG_INFO* sigInfo, CORINFO_METHOD_HANDLE methodHnd) +{ + if (sigInfo->numArgs == 0) + { + CorInfoCallConv callConv = (CorInfoCallConv)(sigInfo->callConv & IMAGE_CEE_CS_CALLCONV_MASK); + bool isPInvoke = methodHnd != NULL || (callConv != CORINFO_CALLCONV_DEFAULT && callConv != CORINFO_CALLCONV_VARARG); + if (isPInvoke) + { + bool suppressGCTransition = false; + CorInfoCallConvExtension unmanagedCallConv = m_compHnd->getUnmanagedCallConv(methodHnd, sigInfo, &suppressGCTransition); + if (callConvIsInstanceMethodCallConv(unmanagedCallConv)) + { + BADCODE("thiscall with 0 arguments"); + } + } + } +} + void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool readonly, bool tailcall, bool newObj, bool isCalli) { uint32_t token = getU4LittleEndian(m_ip + 1); @@ -4379,6 +4397,8 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re BADCODE("Vararg methods are not supported in interpreted code"); } + CheckForPInvokeThisCallWithNoArgs(&callInfo.sig, NULL); + callIFunctionPointerVar = m_pStackPointer[-1].var; m_pStackPointer--; calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig); @@ -4534,6 +4554,11 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re } } + if (isPInvoke && !isMarshaledPInvoke) + { + CheckForPInvokeThisCallWithNoArgs(&callInfo.sig, callInfo.hMethod); + } + // Process sVars int numArgsFromStack = callInfo.sig.numArgs + (newObj ? 0 : callInfo.sig.hasImplicitThis()); int newObjThisArgLocation = newObj && !doCallInsteadOfNew ? 0 : INT_MAX; diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index fce9781d3846d8..481755b1d1c3d8 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -947,6 +947,7 @@ class InterpCompiler void EmitPushSyncObject(); void EmitCallsiteCallout(CorInfoIsAccessAllowedResult accessAllowed, CORINFO_HELPER_DESC* calloutDesc); void EmitCanAccessCallout(CORINFO_RESOLVED_TOKEN *pResolvedToken); + void CheckForPInvokeThisCallWithNoArgs(CORINFO_SIG_INFO* sigInfo, CORINFO_METHOD_HANDLE methodHnd); // Var Offset allocator TArray *m_pActiveCalls;