From 0e1836895fca432f4b7bff9eab2496e5e9fca37b Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 26 May 2023 03:55:40 -0700 Subject: [PATCH 01/54] General cleanup --- src/coreclr/vm/amd64/asmconstants.h | 7 - src/coreclr/vm/dynamicmethod.h | 6 +- src/coreclr/vm/frames.cpp | 2 +- src/coreclr/vm/frames.h | 2 +- src/coreclr/vm/gcenv.ee.common.cpp | 2 +- src/coreclr/vm/ilstubresolver.cpp | 85 +++++------- src/coreclr/vm/jitinterface.cpp | 191 +++++++++++++------------- src/coreclr/vm/jitinterface.h | 64 +-------- src/coreclr/vm/methodtablebuilder.h | 4 +- src/coreclr/vm/methodtablebuilder.inl | 4 +- src/coreclr/vm/siginfo.hpp | 6 +- src/coreclr/vm/stubgen.cpp | 8 +- src/coreclr/vm/stubgen.h | 2 +- 13 files changed, 150 insertions(+), 233 deletions(-) diff --git a/src/coreclr/vm/amd64/asmconstants.h b/src/coreclr/vm/amd64/asmconstants.h index 22082547234275..ebe9cb81042e5f 100644 --- a/src/coreclr/vm/amd64/asmconstants.h +++ b/src/coreclr/vm/amd64/asmconstants.h @@ -461,13 +461,6 @@ ASMCONSTANTS_C_ASSERT(OFFSETOF__PtrArray__m_NumComponents ASMCONSTANTS_C_ASSERT(OFFSETOF__PtrArray__m_Array == offsetof(PtrArray, m_Array)); - -#define MethodDescClassification__mdcClassification 0x7 -ASMCONSTANTS_C_ASSERT(MethodDescClassification__mdcClassification == mdcClassification); - -#define MethodDescClassification__mcInstantiated 0x5 -ASMCONSTANTS_C_ASSERT(MethodDescClassification__mcInstantiated == mcInstantiated); - #ifndef TARGET_UNIX #define OFFSET__TEB__ThreadLocalStoragePointer 0x58 ASMCONSTANTS_C_ASSERT(OFFSET__TEB__ThreadLocalStoragePointer == offsetof(TEB, ThreadLocalStoragePointer)); diff --git a/src/coreclr/vm/dynamicmethod.h b/src/coreclr/vm/dynamicmethod.h index a3917320bd31cc..78b9a133fe7474 100644 --- a/src/coreclr/vm/dynamicmethod.h +++ b/src/coreclr/vm/dynamicmethod.h @@ -331,14 +331,14 @@ inline MethodDesc* GetMethod(CORINFO_METHOD_HANDLE methodHandle) #ifndef DACCESS_COMPILE -#define CORINFO_MODULE_HANDLE_TYPE_MASK 1 - enum CORINFO_MODULE_HANDLE_TYPES { CORINFO_NORMAL_MODULE = 0, - CORINFO_DYNAMIC_MODULE, + CORINFO_DYNAMIC_MODULE = 1, }; +#define CORINFO_MODULE_HANDLE_TYPE_MASK (CORINFO_NORMAL_MODULE | CORINFO_DYNAMIC_MODULE) + inline bool IsDynamicScope(CORINFO_MODULE_HANDLE module) { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index 0ff440c227f632..fbdafacc16d2e5 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -2200,7 +2200,7 @@ void ComputeCallRefMap(MethodDesc* pMD, // an instantiation argument. But if we're in a situation where we haven't resolved the method yet // we need to pretent that unresolved default interface methods are like any other interface // methods and don't have an instantiation argument. - // See code:CEEInfo::getMethodSigInternal + // See code:getMethodSigInternal // assert(!isDispatchCell || !pMD->RequiresInstArg() || pMD->GetMethodTable()->IsInterface()); if (pMD->RequiresInstArg() && !isDispatchCell) diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 83c218b149bad8..a00f9159c3f2ad 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2233,7 +2233,7 @@ class StubDispatchFrame : public FramedMethodFrame // So we need to pretent that unresolved default interface methods are like any other interface // methods and don't have an instantiation argument. // - // See code:CEEInfo::getMethodSigInternal + // See code:getMethodSigInternal // assert(GetFunction()->GetMethodTable()->IsInterface()); return TRUE; diff --git a/src/coreclr/vm/gcenv.ee.common.cpp b/src/coreclr/vm/gcenv.ee.common.cpp index 3137f6921642ca..79921a1b69b66b 100644 --- a/src/coreclr/vm/gcenv.ee.common.cpp +++ b/src/coreclr/vm/gcenv.ee.common.cpp @@ -97,7 +97,7 @@ unsigned FindFirstInterruptiblePoint(CrawlFrame* pCF, unsigned offs, unsigned en // such methods require a generic context, but since we didn't resolve the // method to an implementation yet, we don't have the right context (in fact, // there's no context provided by the caller). -// See code:CEEInfo::getMethodSigInternal +// See code:getMethodSigInternal // inline bool SafeToReportGenericParamContext(CrawlFrame* pCF) { diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 1ae8614e342df1..a9e5b3690a0a69 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -293,59 +293,44 @@ ILStubResolver::AllocGeneratedIL( #if !defined(DACCESS_COMPILE) _ASSERTE(0 != cbCode); - if (!UseLoaderHeap()) - { - NewArrayHolder pNewILCodeBuffer = new BYTE[cbCode]; - NewHolder pNewCompileTimeState = new CompileTimeState{}; - NewArrayHolder pNewLocalSig = NULL; - - if (0 != cbLocalSig) - { - pNewLocalSig = new BYTE[cbLocalSig]; - } + // Perform a single allocation for all needed memory + AllocMemHolder allocMemory; + NewArrayHolder newMemory; + BYTE* memory; - COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader; + S_SIZE_T toAlloc = (S_SIZE_T(sizeof(CompileTimeState)) + S_SIZE_T(cbCode) + S_SIZE_T(cbLocalSig)); + _ASSERTE(!toAlloc.IsOverflow()); - CreateILHeader(pILHeader, cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig); - -#ifdef _DEBUG - LPVOID pPrevCompileTimeState = -#endif // _DEBUG - InterlockedExchangeT(&m_pCompileTimeState, pNewCompileTimeState.GetValue()); - CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState); - - pNewLocalSig.SuppressRelease(); - pNewILCodeBuffer.SuppressRelease(); - pNewCompileTimeState.SuppressRelease(); - return pILHeader; + if (UseLoaderHeap()) + { + allocMemory = m_loaderHeap->AllocMem(toAlloc); + memory = allocMemory; } else { - AllocMemHolder pNewILCodeBuffer(m_loaderHeap->AllocMem(S_SIZE_T(cbCode))); - AllocMemHolder pNewCompileTimeState(m_loaderHeap->AllocMem(S_SIZE_T(sizeof(CompileTimeState)))); - memset(pNewCompileTimeState, 0, sizeof(CompileTimeState)); - AllocMemHolder pNewLocalSig; + newMemory = new BYTE[toAlloc.Value()]; + memory = newMemory; + } - if (0 != cbLocalSig) - { - pNewLocalSig = m_loaderHeap->AllocMem(S_SIZE_T(cbLocalSig)); - } + CompileTimeState* pNewCompileTimeState = (CompileTimeState*)memory; + memset(pNewCompileTimeState, 0, sizeof(*pNewCompileTimeState)); - COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader; + BYTE* pNewILCodeBuffer = ((BYTE*)pNewCompileTimeState) + sizeof(*pNewCompileTimeState); + BYTE* pNewLocalSig = (0 == cbLocalSig) + ? NULL + : (pNewILCodeBuffer + cbCode); - CreateILHeader(pILHeader, cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig); + COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader; -#ifdef _DEBUG - LPVOID pPrevCompileTimeState = -#endif // _DEBUG - InterlockedExchangeT(&m_pCompileTimeState, (CompileTimeState*)pNewCompileTimeState); - CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState); + CreateILHeader(pILHeader, cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig); - pNewLocalSig.SuppressRelease(); - pNewILCodeBuffer.SuppressRelease(); - pNewCompileTimeState.SuppressRelease(); - return pILHeader; - } + LPVOID pPrevCompileTimeState = InterlockedExchangeT(&m_pCompileTimeState, pNewCompileTimeState); + CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState); + (void*)pPrevCompileTimeState; + + allocMemory.SuppressRelease(); + newMemory.SuppressRelease(); + return pILHeader; #else // DACCESS_COMPILE DacNotImpl(); @@ -436,16 +421,6 @@ ILStubResolver::ClearCompileTimeState(CompileTimeStatePtrSpecialValues newState) // See allocations in AllocGeneratedIL and SetStubTargetMethodSig // - COR_ILMETHOD_DECODER * pILHeader = &m_pCompileTimeState->m_ILHeader; - - CONSISTENCY_CHECK(NULL != pILHeader->Code); - delete[] pILHeader->Code; - - if (NULL != pILHeader->LocalVarSig) - { - delete[] pILHeader->LocalVarSig; - } - if (!m_pCompileTimeState->m_StubTargetMethodSig.IsNull()) { delete[] m_pCompileTimeState->m_StubTargetMethodSig.GetPtr(); @@ -456,7 +431,9 @@ ILStubResolver::ClearCompileTimeState(CompileTimeStatePtrSpecialValues newState) delete[] m_pCompileTimeState->m_pEHSect; } - delete m_pCompileTimeState; + // The allocation being deleted here is a bulk allocation + // that is typed as a BYTE[]. + delete[] m_pCompileTimeState; InterlockedExchangeT(&m_pCompileTimeState, dac_cast((TADDR)newState)); } // ILStubResolver::ClearCompileTimeState diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 15365a90216a84..5c3018212a085a 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -382,6 +382,12 @@ inline static CorInfoType toJitType(TypeHandle typeHnd, CORINFO_CLASS_HANDLE *cl return CEEInfo::asCorInfoType(typeHnd.GetInternalCorElementType(), typeHnd, clsRet); } +enum ConvToJitSigFlags : int +{ + CONV_TO_JITSIG_FLAGS_NONE = 0x0, + CONV_TO_JITSIG_FLAGS_LOCALSIG = 0x1, +}; + //--------------------------------------------------------------------------------------- // //@GENERICS: @@ -396,9 +402,7 @@ inline static CorInfoType toJitType(TypeHandle typeHnd, CORINFO_CLASS_HANDLE *cl // localSig - Is it a local variables declaration, or a method signature (with return type, etc). // contextType - The type with any instantiaton information // -//static -void -CEEInfo::ConvToJitSig( +static void ConvToJitSig( PCCOR_SIGNATURE pSig, DWORD cbSig, CORINFO_MODULE_HANDLE scopeHnd, @@ -520,7 +524,7 @@ CEEInfo::ConvToJitSig( sigRet->flags = sigRetFlags; _ASSERTE(SigInfoFlagsAreValid(sigRet)); -} // CEEInfo::ConvToJitSig +} // ConvToJitSig //--------------------------------------------------------------------------------------- // @@ -854,17 +858,6 @@ size_t CEEInfo::findNameOfToken (Module* module, return strlen (szFQName); } -#ifdef HOST_WINDOWS -/* static */ -uint32_t CEEInfo::ThreadLocalOffset(void* p) -{ - PTEB Teb = NtCurrentTeb(); - uint8_t** pTls = (uint8_t**)Teb->ThreadLocalStoragePointer; - uint8_t* pOurTls = pTls[_tls_index]; - return (uint32_t)((uint8_t*)p - pOurTls); -} -#endif // HOST_WINDOWS - CorInfoHelpFunc CEEInfo::getLazyStringLiteralHelper(CORINFO_MODULE_HANDLE handle) { CONTRACTL { @@ -1806,6 +1799,14 @@ uint32_t CEEInfo::getThreadLocalFieldInfo (CORINFO_FIELD_HANDLE field, bool isG } /*********************************************************************/ +static uint32_t ThreadLocalOffset(void* p) +{ + PTEB Teb = NtCurrentTeb(); + uint8_t** pTls = (uint8_t**)Teb->ThreadLocalStoragePointer; + uint8_t* pOurTls = pTls[_tls_index]; + return (uint32_t)((uint8_t*)p - pOurTls); +} + void CEEInfo::getThreadLocalStaticBlocksInfo (CORINFO_THREAD_STATIC_BLOCKS_INFO* pInfo, bool isGCType) { CONTRACTL { @@ -1822,13 +1823,13 @@ void CEEInfo::getThreadLocalStaticBlocksInfo (CORINFO_THREAD_STATIC_BLOCKS_INFO* pInfo->offsetOfThreadLocalStoragePointer = offsetof(_TEB, ThreadLocalStoragePointer); if (isGCType) { - pInfo->offsetOfThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_GCThreadStaticBlocks); - pInfo->offsetOfMaxThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_GCMaxThreadStaticBlocks); + pInfo->offsetOfThreadStaticBlocks = ThreadLocalOffset(&t_GCThreadStaticBlocks); + pInfo->offsetOfMaxThreadStaticBlocks = ThreadLocalOffset(&t_GCMaxThreadStaticBlocks); } else { - pInfo->offsetOfThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_NonGCThreadStaticBlocks); - pInfo->offsetOfMaxThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_NonGCMaxThreadStaticBlocks); + pInfo->offsetOfThreadStaticBlocks = ThreadLocalOffset(&t_NonGCThreadStaticBlocks); + pInfo->offsetOfMaxThreadStaticBlocks = ThreadLocalOffset(&t_NonGCMaxThreadStaticBlocks); } pInfo->offsetOfGCDataPointer = static_cast(PtrArray::GetDataOffset()); @@ -1989,7 +1990,7 @@ CEEInfo::findCallSiteSig( SigTypeContext typeContext; GetTypeContext(context, &typeContext); - CEEInfo::ConvToJitSig( + ConvToJitSig( pSig, cbSig, scopeHnd, @@ -2040,7 +2041,7 @@ CEEInfo::findSig( SigTypeContext typeContext; GetTypeContext(context, &typeContext); - CEEInfo::ConvToJitSig( + ConvToJitSig( pSig, cbSig, scopeHnd, @@ -4901,6 +4902,61 @@ CorInfoIsAccessAllowedResult CEEInfo::canAccessClass( return isAccessAllowed; } +//--------------------------------------------------------------------------------------- +// Given a method descriptor ftnHnd, extract signature information into sigInfo +// Obtain (representative) instantiation information from ftnHnd's owner class +//@GENERICSVER: added explicit owner parameter +// Internal version without JIT-EE transition +static void getMethodSigInternal( + CORINFO_METHOD_HANDLE ftnHnd, + CORINFO_SIG_INFO * sigRet, + CORINFO_CLASS_HANDLE owner, + SignatureKind signatureKind) +{ + STANDARD_VM_CONTRACT; + + MethodDesc * ftn = GetMethod(ftnHnd); + + PCCOR_SIGNATURE pSig = NULL; + DWORD cbSig = 0; + ftn->GetSig(&pSig, &cbSig); + + SigTypeContext context(ftn, (TypeHandle)owner); + + // Type parameters in the signature are instantiated + // according to the class/method/array instantiation of ftnHnd and owner + ConvToJitSig( + pSig, + cbSig, + GetScopeHandle(ftn), + mdTokenNil, + &context, + CONV_TO_JITSIG_FLAGS_NONE, + sigRet); + + //@GENERICS: + // Shared generic methods and shared methods on generic structs take an extra argument representing their instantiation + if (ftn->RequiresInstArg()) + { + // + // If we are making a virtual call to an instance method on an interface, we need to lie to the JIT. + // The reason being that we already made sure target is always directly callable (through instantiation stubs), + // JIT should not generate shared generics aware call code and insert the secret argument again at the callsite. + // Otherwise we would end up with two secret generic dictionary arguments (since the stub also provides one). + // + BOOL isCallSiteThatGoesThroughInstantiatingStub = + (signatureKind == SK_VIRTUAL_CALLSITE && + !ftn->IsStatic() && + ftn->GetMethodTable()->IsInterface()) || + signatureKind == SK_STATIC_VIRTUAL_CODEPOINTER_CALLSITE; + if (!isCallSiteThatGoesThroughInstantiatingStub) + sigRet->callConv = (CorInfoCallConv) (sigRet->callConv | CORINFO_CALLCONV_PARAMTYPE); + } + + // We want the calling convention bit to be consistant with the method attribute bit + _ASSERTE( (IsMdStatic(ftn->GetAttrs()) == 0) == ((sigRet->callConv & CORINFO_CALLCONV_HASTHIS) != 0) ); +} + /***********************************************************************/ // return the address of a pointer to a callable stub that will do the // virtual or interface call @@ -6703,6 +6759,15 @@ mdToken FindGenericMethodArgTypeSpec(IMDInternalImport* pInternalImport) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } +static void setILIntrinsicMethodInfo(CORINFO_METHOD_INFO* methInfo,uint8_t* ilcode, int ilsize, int maxstack) +{ + methInfo->ILCode = ilcode; + methInfo->ILCodeSize = ilsize; + methInfo->maxStack = maxstack; + methInfo->EHcount = 0; + methInfo->options = (CorInfoOptions)0; +} + /********************************************************************* IL is the most efficient and portable way to implement certain low level methods @@ -7686,13 +7751,13 @@ getMethodInfoHelper( /* Fetch the method signature */ // Type parameters in the signature should be instantiated according to the // class/method/array instantiation of ftnHnd - CEEInfo::ConvToJitSig( + ConvToJitSig( pSig, cbSig, GetScopeHandle(ftn), mdTokenNil, &context, - CEEInfo::CONV_TO_JITSIG_FLAGS_NONE, + CONV_TO_JITSIG_FLAGS_NONE, &methInfo->args); // Shared generic or static per-inst methods and shared methods on generic structs @@ -7705,13 +7770,13 @@ getMethodInfoHelper( /* And its local variables */ // Type parameters in the signature should be instantiated according to the // class/method/array instantiation of ftnHnd - CEEInfo::ConvToJitSig( + ConvToJitSig( pLocalSig, cbLocalSig, GetScopeHandle(ftn), mdTokenNil, &context, - CEEInfo::CONV_TO_JITSIG_FLAGS_LOCALSIG, + CONV_TO_JITSIG_FLAGS_LOCALSIG, &methInfo->locals); } // getMethodInfoHelper @@ -8369,7 +8434,7 @@ void CEEInfo::reportTailCallDecision (CORINFO_METHOD_HANDLE callerHnd, EE_TO_JIT_TRANSITION(); } -void CEEInfo::getEHinfoHelper( +static void getEHinfoHelper( CORINFO_METHOD_HANDLE ftnHnd, unsigned EHnumber, CORINFO_EH_CLAUSE* clause, @@ -8441,64 +8506,11 @@ CEEInfo::getMethodSig( JIT_TO_EE_TRANSITION(); - getMethodSigInternal(ftnHnd, sigRet, owner); + getMethodSigInternal(ftnHnd, sigRet, owner, SK_NOT_CALLSITE); EE_TO_JIT_TRANSITION(); } -//--------------------------------------------------------------------------------------- -// -void -CEEInfo::getMethodSigInternal( - CORINFO_METHOD_HANDLE ftnHnd, - CORINFO_SIG_INFO * sigRet, - CORINFO_CLASS_HANDLE owner, - SignatureKind signatureKind) -{ - STANDARD_VM_CONTRACT; - - MethodDesc * ftn = GetMethod(ftnHnd); - - PCCOR_SIGNATURE pSig = NULL; - DWORD cbSig = 0; - ftn->GetSig(&pSig, &cbSig); - - SigTypeContext context(ftn, (TypeHandle)owner); - - // Type parameters in the signature are instantiated - // according to the class/method/array instantiation of ftnHnd and owner - CEEInfo::ConvToJitSig( - pSig, - cbSig, - GetScopeHandle(ftn), - mdTokenNil, - &context, - CONV_TO_JITSIG_FLAGS_NONE, - sigRet); - - //@GENERICS: - // Shared generic methods and shared methods on generic structs take an extra argument representing their instantiation - if (ftn->RequiresInstArg()) - { - // - // If we are making a virtual call to an instance method on an interface, we need to lie to the JIT. - // The reason being that we already made sure target is always directly callable (through instantiation stubs), - // JIT should not generate shared generics aware call code and insert the secret argument again at the callsite. - // Otherwise we would end up with two secret generic dictionary arguments (since the stub also provides one). - // - BOOL isCallSiteThatGoesThroughInstantiatingStub = - (signatureKind == SK_VIRTUAL_CALLSITE && - !ftn->IsStatic() && - ftn->GetMethodTable()->IsInterface()) || - signatureKind == SK_STATIC_VIRTUAL_CODEPOINTER_CALLSITE; - if (!isCallSiteThatGoesThroughInstantiatingStub) - sigRet->callConv = (CorInfoCallConv) (sigRet->callConv | CORINFO_CALLCONV_PARAMTYPE); - } - - // We want the calling convention bit to be consistant with the method attribute bit - _ASSERTE( (IsMdStatic(ftn->GetAttrs()) == 0) == ((sigRet->callConv & CORINFO_CALLCONV_HASTHIS) != 0) ); -} - //--------------------------------------------------------------------------------------- // //@GENERICSVER: for a method desc in a typical instantiation of a generic class, @@ -12700,7 +12712,7 @@ CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags) return flags; } -CORJIT_FLAGS GetCompileFlags(MethodDesc * ftn, CORJIT_FLAGS flags, CORINFO_METHOD_INFO * methodInfo) +static CORJIT_FLAGS GetCompileFlags(MethodDesc * ftn, CORJIT_FLAGS flags, CORINFO_METHOD_INFO * methodInfo) { STANDARD_VM_CONTRACT; @@ -12907,17 +12919,6 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, LOG((LF_JIT, LL_INFO10000, "{ Jitting method (%p) %s %s\n", ftn, methodString.GetUTF8(), ftn->m_pszDebugMethodSignature)); } - -#if 0 - if (!SString::_stricmp(cls,"ENC") && - (!SString::_stricmp(name,"G"))) - { - static count = 0; - count++; - if (count > 0) - DebugBreak(); - } -#endif // 0 #endif // _DEBUG CORINFO_METHOD_HANDLE ftnHnd = (CORINFO_METHOD_HANDLE)ftn; @@ -12954,8 +12955,8 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, { CEEJitInfo jitInfo(ftn, ILHeader, jitMgr, !flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_NO_INLINING)); -#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_AMD64) if (fForceJumpStubOverflow) jitInfo.SetJumpStubOverflow(fAllowRel32); jitInfo.SetAllowRel32(fAllowRel32); @@ -12964,7 +12965,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, jitInfo.SetJumpStubOverflow(fForceJumpStubOverflow); #endif jitInfo.SetReserveForJumpStubs(reserveForJumpStubs); -#endif +#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64) #ifdef FEATURE_ON_STACK_REPLACEMENT // If this is an OSR jit request, grab the OSR info so we can pass it to the jit diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 73778a1ca6baba..0d3d2973e53231 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -71,17 +71,6 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, CORJIT_FLAGS flags, ULONG* sizeOfCode = NULL); -void setILIntrinsicMethodInfo(CORINFO_METHOD_INFO* methInfo, - uint8_t* ilcode, - int ilsize, - int maxstack); - - -void getMethodInfoHelper(MethodDesc * ftn, - CORINFO_METHOD_HANDLE ftnHnd, - COR_ILMETHOD_DECODER * header, - CORINFO_METHOD_INFO * methInfo); - void getMethodInfoILMethodHeaderHelper( COR_ILMETHOD_DECODER* header, CORINFO_METHOD_INFO* methInfo @@ -439,23 +428,8 @@ class CEEInfo : public ICorJitInfo static size_t findNameOfToken (Module* module, mdToken metaTOK, _Out_writes_ (FQNameCapacity) char * szFQName, size_t FQNameCapacity); -#ifdef HOST_WINDOWS - static uint32_t ThreadLocalOffset(void* p); -#endif // HOST_WINDOWS - DWORD getMethodAttribsInternal (CORINFO_METHOD_HANDLE ftnHnd); - // Given a method descriptor ftnHnd, extract signature information into sigInfo - // Obtain (representative) instantiation information from ftnHnd's owner class - //@GENERICSVER: added explicit owner parameter - // Internal version without JIT-EE transition - void getMethodSigInternal ( - CORINFO_METHOD_HANDLE ftnHnd, - CORINFO_SIG_INFO* sigInfo, - CORINFO_CLASS_HANDLE owner = NULL, - SignatureKind signatureKind = SK_NOT_CALLSITE - ); - bool resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info); CORINFO_CLASS_HANDLE getDefaultComparerClassHelper( @@ -469,13 +443,6 @@ class CEEInfo : public ICorJitInfo CorInfoType getFieldTypeInternal (CORINFO_FIELD_HANDLE field, CORINFO_CLASS_HANDLE* structType = NULL,CORINFO_CLASS_HANDLE owner = NULL); protected: - - static void getEHinfoHelper( - CORINFO_METHOD_HANDLE ftnHnd, - unsigned EHnumber, - CORINFO_EH_CLAUSE* clause, - COR_ILMETHOD_DECODER* pILHeader); - void freeArrayInternal(void* array); public: @@ -550,27 +517,6 @@ class CEEInfo : public ICorJitInfo #endif public: - - enum ConvToJitSigFlags : int - { - CONV_TO_JITSIG_FLAGS_NONE = 0x0, - CONV_TO_JITSIG_FLAGS_LOCALSIG = 0x1, - }; - - //@GENERICS: - // The method handle is used to instantiate method and class type parameters - // It's also used to determine whether an extra dictionary parameter is required - static - void - ConvToJitSig( - PCCOR_SIGNATURE pSig, - DWORD cbSig, - CORINFO_MODULE_HANDLE scopeHnd, - mdToken token, - SigTypeContext* context, - ConvToJitSigFlags flags, - CORINFO_SIG_INFO * sigRet); - MethodDesc * GetMethodForSecurity(CORINFO_METHOD_HANDLE callerHandle); // Prepare the information about how to do a runtime lookup of the handle with shared @@ -697,9 +643,7 @@ class CEEJitInfo : public CEEInfo } CONTRACTL_END; if (m_CodeHeaderRW != m_CodeHeader) - { - delete [] (BYTE*)m_CodeHeaderRW; - } + freeArrayInternal(m_CodeHeaderRW); m_CodeHeader = NULL; m_CodeHeaderRW = NULL; @@ -798,7 +742,7 @@ class CEEJitInfo : public CEEInfo LIMITED_METHOD_CONTRACT; return 0; } -#endif +#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64) #ifdef FEATURE_ON_STACK_REPLACEMENT // Called by the runtime to supply patchpoint information to the jit. @@ -873,9 +817,7 @@ class CEEJitInfo : public CEEInfo } CONTRACTL_END; if (m_CodeHeaderRW != m_CodeHeader) - { - delete [] (BYTE*)m_CodeHeaderRW; - } + freeArrayInternal(m_CodeHeaderRW); if (m_pOffsetMapping != NULL) freeArrayInternal(m_pOffsetMapping); diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 8e91efe45ad472..6c82a78855f07d 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -126,10 +126,12 @@ class MethodTableBuilder METHOD_TYPE_FCALL = 1, METHOD_TYPE_NDIRECT = 2, METHOD_TYPE_EEIMPL = 3, + METHOD_TYPE_ARRAY = 4, METHOD_TYPE_INSTANTIATED = 5, #ifdef FEATURE_COMINTEROP - METHOD_TYPE_COMINTEROP = 6, + METHOD_TYPE_COMINTEROP = 6, #endif + METHOD_TYPE_DYNAMIC = 7 }; private: diff --git a/src/coreclr/vm/methodtablebuilder.inl b/src/coreclr/vm/methodtablebuilder.inl index 78424be5d14f6f..5a6cc75d1e12d9 100644 --- a/src/coreclr/vm/methodtablebuilder.inl +++ b/src/coreclr/vm/methodtablebuilder.inl @@ -15,7 +15,7 @@ //*************************************************************************************** inline MethodTableBuilder::DeclaredMethodIterator::DeclaredMethodIterator( - MethodTableBuilder &mtb) : + MethodTableBuilder &mtb) : m_numDeclaredMethods((int)mtb.NumDeclaredMethods()), m_declaredMethods(mtb.bmtMethod->m_rgDeclaredMethods), m_idx(-1) @@ -513,10 +513,12 @@ MethodTableBuilder::GetMethodClassification(MethodTableBuilder::METHOD_TYPE type C_ASSERT((DWORD)METHOD_TYPE_FCALL == (DWORD)mcFCall); C_ASSERT((DWORD)METHOD_TYPE_NDIRECT == (DWORD)mcNDirect); C_ASSERT((DWORD)METHOD_TYPE_EEIMPL == (DWORD)mcEEImpl); + C_ASSERT((DWORD)METHOD_TYPE_ARRAY == (DWORD)mcArray); C_ASSERT((DWORD)METHOD_TYPE_INSTANTIATED == (DWORD)mcInstantiated); #ifdef FEATURE_COMINTEROP C_ASSERT((DWORD)METHOD_TYPE_COMINTEROP == (DWORD)mcComInterop); #endif + C_ASSERT((DWORD)METHOD_TYPE_DYNAMIC == (DWORD)mcDynamic); return (DWORD)type; } diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 13f3fb4de9a729..0e19b6b14505da 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -602,7 +602,7 @@ class MetaSig //------------------------------------------------------------------------ // Returns # of arguments. Does not count the return value. - // Does not count the "this" argument (which is not reflected om the + // Does not count the "this" argument (which is not reflected on the // sig.) 64-bit arguments are counted as one argument. //------------------------------------------------------------------------ UINT NumFixedArgs() @@ -759,8 +759,8 @@ class MetaSig } //------------------------------------------------------------------ - // Like NextArg, but return only normalized type (enums flattned to - // underlying type ... + // Like NextArg, but return only normalized type (enums flattened to + // the underlying type ... //------------------------------------------------------------------ CorElementType NextArgNormalized(TypeHandle * pthValueType = NULL) diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 3528b0656964f4..d96b3779f4f912 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -2587,7 +2587,7 @@ void ILStubLinker::GetStubReturnType(LocalDesc* pLoc, Module* pModule) IfFailThrow(ptr.GetData(&nArgs)); - GetManagedTypeHelper(pLoc, pModule, ptr.GetPtr(), m_pTypeContext, m_pMD); + GetManagedTypeHelper(pLoc, pModule, ptr.GetPtr(), m_pTypeContext); } CorCallingConvention ILStubLinker::GetStubTargetCallingConv() @@ -2897,7 +2897,7 @@ static size_t GetManagedTypeForMDArray(LocalDesc* pLoc, Module* pModule, PCCOR_S // static -void ILStubLinker::GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE psigManagedArg, SigTypeContext *pTypeContext, MethodDesc *pMD) +void ILStubLinker::GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE psigManagedArg, SigTypeContext *pTypeContext) { CONTRACTL { @@ -3046,7 +3046,7 @@ void ILStubLinker::GetStubTargetReturnType(LocalDesc* pLoc, Module* pModule) } CONTRACTL_END; - GetManagedTypeHelper(pLoc, pModule, m_nativeFnSigBuilder.GetReturnSig(), m_pTypeContext, NULL); + GetManagedTypeHelper(pLoc, pModule, m_nativeFnSigBuilder.GetReturnSig(), m_pTypeContext); } void ILStubLinker::GetStubArgType(LocalDesc* pLoc) @@ -3073,7 +3073,7 @@ void ILStubLinker::GetStubArgType(LocalDesc* pLoc, Module* pModule) } CONTRACTL_END; - GetManagedTypeHelper(pLoc, pModule, m_managedSigPtr.GetPtr(), m_pTypeContext, m_pMD); + GetManagedTypeHelper(pLoc, pModule, m_managedSigPtr.GetPtr(), m_pTypeContext); } //--------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index 257bd245ec798c..5c83f23a94aed6 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -524,7 +524,7 @@ class ILStubLinker CorElementType GetStubTargetReturnElementType() { WRAPPER_NO_CONTRACT; return m_nativeFnSigBuilder.GetReturnElementType(); } - static void GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE pSig, SigTypeContext *pTypeContext, MethodDesc *pMD); + static void GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE pSig, SigTypeContext *pTypeContext); BOOL StubHasVoidReturnType(); From 292da738df74b5153fc197745b67ca3af59e02bd Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 26 May 2023 03:56:27 -0700 Subject: [PATCH 02/54] Prototype for UnsafeAccessorAttribute - Hardcoded to default ctor --- src/coreclr/inc/sarray.h | 10 +- src/coreclr/vm/jitinterface.cpp | 259 +++++++++++++---- src/coreclr/vm/jitinterface.h | 35 ++- src/coreclr/vm/method.hpp | 27 +- src/coreclr/vm/methodtablebuilder.cpp | 17 -- src/coreclr/vm/prestub.cpp | 385 +++++++++++++++++++++----- src/coreclr/vm/wellknownattributes.h | 3 + 7 files changed, 571 insertions(+), 165 deletions(-) diff --git a/src/coreclr/inc/sarray.h b/src/coreclr/inc/sarray.h index 29d02e68cbceca..4987af7d660e0c 100644 --- a/src/coreclr/inc/sarray.h +++ b/src/coreclr/inc/sarray.h @@ -70,15 +70,7 @@ class SArray void Append(ELEMENT elem) { WRAPPER_NO_CONTRACT; - *Append() = elem; - } - - ELEMENT AppendEx(ELEMENT elem) - { - WRAPPER_NO_CONTRACT; - - *Append() = elem; - return elem; + *Append() = std::move(elem); } void Insert(const Iterator &i); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 5c3018212a085a..b841c6a9eeb107 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -201,6 +201,49 @@ BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver, return doAccessCheck; } +TransientMethodDetails::TransientMethodDetails(MethodDesc* pMD, _In_opt_ COR_ILMETHOD_DECODER* header, CORINFO_MODULE_HANDLE scope) + : Method{ pMD } + , Header{ header } + , Scope{ scope } +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(Method != NULL); + _ASSERTE(Scope == NULL || IsDynamicScope(Scope)); +} + +TransientMethodDetails::TransientMethodDetails(TransientMethodDetails&& other) +{ + LIMITED_METHOD_CONTRACT; + *this = std::move(other); +} + +TransientMethodDetails::~TransientMethodDetails() +{ + STANDARD_VM_CONTRACT; + // If the supplied scope is dynamic, release resources. + if (IsDynamicScope(Scope)) + { + DynamicResolver* resolver = GetDynamicResolver(Scope); + resolver->FreeCompileTimeState(); + delete resolver; + } +} + +TransientMethodDetails& TransientMethodDetails::operator=(TransientMethodDetails&& other) +{ + LIMITED_METHOD_CONTRACT; + if (this != &other) + { + Method = other.Method; + Header = other.Header; + Scope = other.Scope; + other.Method = NULL; + other.Header = NULL; + other.Scope = NULL; + } + return *this; +} + /*****************************************************************************/ // Initialize from data we passed across to the JIT @@ -3372,6 +3415,38 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr } } +void CEEInfo::AddTransientMethodDetails(TransientMethodDetails details) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(details.Method != NULL); + + if (m_transientDetails == NULL) + m_transientDetails = new SArray(); + m_transientDetails->Append(std::move(details)); +} + +bool CEEInfo::FindTransientMethodDetails(MethodDesc* pMD, TransientMethodDetails** details) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(pMD != NULL); + _ASSERTE(details != NULL); + + if (m_transientDetails != NULL) + { + TransientMethodDetails* curr = m_transientDetails->GetElements(); + TransientMethodDetails* end = curr + m_transientDetails->GetCount(); + for (;curr != end; ++curr) + { + if (curr->Method == pMD) + { + *details = curr; + return true; + } + } + } + return false; +} + /*********************************************************************/ size_t CEEInfo::printClassName(CORINFO_CLASS_HANDLE cls, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize) { @@ -5397,8 +5472,10 @@ void CEEInfo::getCallInfo( pResult->hMethod = CORINFO_METHOD_HANDLE(pTargetMD); pResult->accessAllowed = CORINFO_ACCESS_ALLOWED; - if ((flags & CORINFO_CALLINFO_SECURITYCHECKS) && - !((MethodDesc *)callerHandle)->IsILStub()) // IL stubs can access everything, don't bother doing access checks + MethodDesc* callerMethod = (MethodDesc*)callerHandle; + if ((flags & CORINFO_CALLINFO_SECURITYCHECKS) + && !(callerMethod->IsILStub()) // IL stubs can access everything, don't bother doing access checks + && !(callerMethod->IsUnsafeAccessor())) // UnsafeAccessor method, by definition, can access everything { //Our type system doesn't always represent the target exactly with the MethodDesc. In all cases, //carry around the parent MethodTable for both Caller and Callee. @@ -7619,44 +7696,78 @@ bool getILIntrinsicImplementationForActivator(MethodDesc* ftn, return true; } -void setILIntrinsicMethodInfo(CORINFO_METHOD_INFO* methInfo,uint8_t* ilcode, int ilsize, int maxstack) -{ - methInfo->ILCode = ilcode; - methInfo->ILCodeSize = ilsize; - methInfo->maxStack = maxstack; - methInfo->EHcount = 0; - methInfo->options = (CorInfoOptions)0; -} - //--------------------------------------------------------------------------------------- // -//static -void -getMethodInfoHelper( - MethodDesc * ftn, - CORINFO_METHOD_HANDLE ftnHnd, - COR_ILMETHOD_DECODER * header, - CORINFO_METHOD_INFO * methInfo) + +class MethodInfoHelperContext final +{ +public: + MethodDesc* Method; + COR_ILMETHOD_DECODER* Header; + DynamicResolver* TransientResolver; + + MethodInfoHelperContext(MethodDesc* pMD, _In_opt_ COR_ILMETHOD_DECODER* header = NULL) + : Method{ pMD } + , Header{ header } + , TransientResolver{} + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(pMD != NULL); + } + + MethodInfoHelperContext(const TransientMethodDetails* details) + : Method{ details->Method } + , Header{ details->Header } + , TransientResolver{ NULL } + { + LIMITED_METHOD_CONTRACT; + if (IsDynamicScope(details->Scope)) + TransientResolver = GetDynamicResolver(details->Scope); + } + + MethodInfoHelperContext(const MethodInfoHelperContext&) = delete; + MethodInfoHelperContext(MethodInfoHelperContext&& other) = delete; + + ~MethodInfoHelperContext() = default; + + MethodInfoHelperContext& operator=(const MethodInfoHelperContext&) = delete; + MethodInfoHelperContext& operator=(MethodInfoHelperContext&& other) = delete; + + bool HasTransientMethodDetails() const + { + return TransientResolver != NULL; + } + + TransientMethodDetails CreateTransientMethodDetails() const + { + _ASSERTE(HasTransientMethodDetails()); + return TransientMethodDetails{Method, Header, MakeDynamicScope(TransientResolver)}; + } +}; + +static bool getMethodInfoHelper( + MethodInfoHelperContext& cxt, + CORINFO_METHOD_INFO* methInfo) { STANDARD_VM_CONTRACT; + _ASSERTE(methInfo != NULL); - _ASSERTE(ftn == GetMethod(ftnHnd)); + MethodDesc* ftn = cxt.Method; + methInfo->ftn = (CORINFO_METHOD_HANDLE)ftn; + methInfo->regionKind = CORINFO_REGION_JIT; - methInfo->ftn = ftnHnd; - methInfo->scope = GetScopeHandle(ftn); - methInfo->regionKind = CORINFO_REGION_JIT; - // - // For Jitted code the regionKind is JIT; - // For Ngen-ed code the zapper will set this to HOT or COLD, if we - // are using IBC data to partition methods into Hot/Cold regions + CORINFO_MODULE_HANDLE scopeHnd = NULL; /* Grab information from the IL header */ + PCCOR_SIGNATURE pLocalSig = NULL; + uint32_t cbLocalSig = 0; - PCCOR_SIGNATURE pLocalSig = NULL; - uint32_t cbLocalSig = 0; - - if (NULL != header) + // Having a header means there is backing IL for the method. + if (NULL != cxt.Header) { + scopeHnd = cxt.HasTransientMethodDetails() + ? MakeDynamicScope(cxt.TransientResolver) + : GetScopeHandle(ftn->GetModule()); bool fILIntrinsic = false; MethodTable * pMT = ftn->GetMethodTable(); @@ -7692,16 +7803,16 @@ getMethodInfoHelper( if (!fILIntrinsic) { - getMethodInfoILMethodHeaderHelper(header, methInfo); - pLocalSig = header->LocalVarSig; - cbLocalSig = header->cbLocalVarSig; + getMethodInfoILMethodHeaderHelper(cxt.Header, methInfo); + pLocalSig = cxt.Header->LocalVarSig; + cbLocalSig = cxt.Header->cbLocalVarSig; } } - else + else if (ftn->IsDynamicMethod()) { - _ASSERTE(ftn->IsDynamicMethod()); + DynamicResolver* pResolver = ftn->AsDynamicMethodDesc()->GetResolver(); + scopeHnd = MakeDynamicScope(pResolver); - DynamicResolver * pResolver = ftn->AsDynamicMethodDesc()->GetResolver(); unsigned int EHCount; methInfo->ILCode = pResolver->GetCodeInfo(&methInfo->ILCodeSize, &methInfo->maxStack, @@ -7711,7 +7822,22 @@ getMethodInfoHelper( SigPointer localSig = pResolver->GetLocalSig(); localSig.GetSignature(&pLocalSig, &cbLocalSig); } + else if (ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header)) + { + scopeHnd = MakeDynamicScope(cxt.TransientResolver); + _ASSERTE(cxt.Header != NULL); + getMethodInfoILMethodHeaderHelper(cxt.Header, methInfo); + pLocalSig = cxt.Header->LocalVarSig; + cbLocalSig = cxt.Header->cbLocalVarSig; + } + else + { + return false; + } + + _ASSERTE(scopeHnd != NULL); + methInfo->scope = scopeHnd; methInfo->options = (CorInfoOptions)(((UINT32)methInfo->options) | ((ftn->AcquiresInstMethodTableFromThis() ? CORINFO_GENERICS_CTXT_FROM_THIS : 0) | (ftn->RequiresInstMethodTableArg() ? CORINFO_GENERICS_CTXT_FROM_METHODTABLE : 0) | @@ -7754,7 +7880,7 @@ getMethodInfoHelper( ConvToJitSig( pSig, cbSig, - GetScopeHandle(ftn), + methInfo->scope, mdTokenNil, &context, CONV_TO_JITSIG_FLAGS_NONE, @@ -7773,12 +7899,13 @@ getMethodInfoHelper( ConvToJitSig( pLocalSig, cbLocalSig, - GetScopeHandle(ftn), + methInfo->scope, mdTokenNil, &context, CONV_TO_JITSIG_FLAGS_LOCALSIG, &methInfo->locals); + return true; } // getMethodInfoHelper //--------------------------------------------------------------------------------------- @@ -7800,30 +7927,36 @@ CEEInfo::getMethodInfo( MethodDesc * ftn = GetMethod(ftnHnd); - if (!ftn->IsDynamicMethod() && (!ftn->IsIL() || !ftn->GetRVA() || ftn->IsWrapperStub())) + // Get the IL header + MethodInfoHelperContext cxt{ ftn }; + if (ftn->IsDynamicMethod()) { - /* Return false if not IL or has no code */ - result = false; + result = getMethodInfoHelper(cxt, methInfo); } - else + else if (!ftn->IsWrapperStub() && ftn->HasILHeader()) { - /* Get the IL header */ - - if (ftn->IsDynamicMethod()) + COR_ILMETHOD_DECODER header(ftn->GetILHeader(TRUE), ftn->GetMDImport(), NULL); + cxt.Header = &header; + result = getMethodInfoHelper(cxt, methInfo); + } + else if (ftn->IsIL() && ftn->GetRVA() == 0) + { + // IL methods with no RVA indicate there is no implementation defined in metadata. + // Check if we previously generated details/implementation for this method. + TransientMethodDetails* detailsMaybe = NULL; + if (FindTransientMethodDetails(ftn, &detailsMaybe)) { - getMethodInfoHelper(ftn, ftnHnd, NULL, methInfo); + cxt.Header = detailsMaybe->Header; + cxt.TransientResolver = GetDynamicResolver(detailsMaybe->Scope); } - else - { - COR_ILMETHOD_DECODER header(ftn->GetILHeader(TRUE), ftn->GetMDImport(), NULL); - getMethodInfoHelper(ftn, ftnHnd, &header, methInfo); - } + result = getMethodInfoHelper(cxt, methInfo); + } + if (result) + { LOG((LF_JIT, LL_INFO100000, "Getting method info (possible inline) %s::%s%s\n", ftn->m_pszDebugClassName, ftn->m_pszDebugMethodName, ftn->m_pszDebugMethodSignature)); - - result = true; } EE_TO_JIT_TRANSITION(); @@ -12857,11 +12990,12 @@ BOOL g_fAllowRel32 = TRUE; // Calls to this method that occur to check if inlining can occur on x86, // are OK since they discard the return value of this method. PCODE UnsafeJitFunction(PrepareCodeConfig* config, - COR_ILMETHOD_DECODER* ILHeader, + _In_opt_ COR_ILMETHOD_DECODER* ILHeader, CORJIT_FLAGS flags, ULONG * pSizeOfCode) { STANDARD_VM_CONTRACT; + _ASSERTE(config != NULL); NativeCodeVersion nativeCodeVersion = config->GetCodeVersion(); MethodDesc* ftn = nativeCodeVersion.GetMethodDesc(); @@ -12921,12 +13055,14 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, } #endif // _DEBUG - CORINFO_METHOD_HANDLE ftnHnd = (CORINFO_METHOD_HANDLE)ftn; + MethodInfoHelperContext cxt{ ftn, ILHeader }; CORINFO_METHOD_INFO methodInfo; + if (!getMethodInfoHelper(cxt, &methodInfo)) + { + _ASSERTE(!"[TODO] What exception to throw?"); + } - getMethodInfoHelper(ftn, ftnHnd, ILHeader, &methodInfo); - - // If it's generic then we can only enter through an instantiated md + // If it's generic then we can only enter through an instantiated MethodDesc _ASSERTE(!ftn->IsGenericMethodDefinition()); // method attributes and signature are consistant @@ -12975,9 +13111,12 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, PatchpointInfo* patchpointInfo = nativeCodeVersion.GetOSRInfo(&ilOffset); jitInfo.SetOSRInfo(patchpointInfo, ilOffset); } -#endif +#endif // FEATURE_ON_STACK_REPLACEMENT + + if (cxt.HasTransientMethodDetails()) + jitInfo.AddTransientMethodDetails(std::move(cxt.CreateTransientMethodDetails())); - MethodDesc * pMethodForSecurity = jitInfo.GetMethodForSecurity(ftnHnd); + MethodDesc * pMethodForSecurity = jitInfo.GetMethodForSecurity(methodInfo.ftn); //Since the check could trigger a demand, we have to do this every time. //This is actually an overly complicated way to make sure that a method can access all its arguments diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 0d3d2973e53231..0e241c66efe00c 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -395,9 +395,27 @@ extern "C" #endif // TARGET_ARM64 }; - /*********************************************************************/ /*********************************************************************/ + +// Transient data for a MethodDesc involved +// in the current JIT compilation. +struct TransientMethodDetails final +{ + MethodDesc* Method; + COR_ILMETHOD_DECODER* Header; + CORINFO_MODULE_HANDLE Scope; + + TransientMethodDetails() = default; + TransientMethodDetails(MethodDesc* pMD, _In_opt_ COR_ILMETHOD_DECODER* header, CORINFO_MODULE_HANDLE scope); + TransientMethodDetails(const TransientMethodDetails&) = delete; + TransientMethodDetails(TransientMethodDetails&&); + ~TransientMethodDetails(); + + TransientMethodDetails& operator=(const TransientMethodDetails&) = delete; + TransientMethodDetails& operator=(TransientMethodDetails&&); +}; + class CEEInfo : public ICorJitInfo { friend class CEEDynamicCodeInfo; @@ -464,6 +482,7 @@ class CEEInfo : public ICorJitInfo CEEInfo(MethodDesc * fd = NULL, bool fAllowInlining = true) : m_pJitHandles(nullptr), m_pMethodBeingCompiled(fd), + m_transientDetails(NULL), m_pThread(GetThreadNULLOk()), m_hMethodForSecurity_Key(NULL), m_pMethodForSecurity_Value(NULL), @@ -490,8 +509,9 @@ class CEEInfo : public ICorJitInfo DestroyHandle(elements[i]); } delete m_pJitHandles; - m_pJitHandles = nullptr; } + + delete m_transientDetails; #endif } @@ -531,10 +551,15 @@ class CEEInfo : public ICorJitInfo CalledMethod * GetCalledMethods() { return m_pCalledMethods; } #endif + // Add/Find transient method details. + void AddTransientMethodDetails(TransientMethodDetails details); + bool FindTransientMethodDetails(MethodDesc* pMD, TransientMethodDetails** details); + protected: - SArray* m_pJitHandles; // GC handles used by JIT - MethodDesc* m_pMethodBeingCompiled; // Top-level method being compiled - Thread * m_pThread; // Cached current thread for faster JIT-EE transitions + SArray* m_pJitHandles; // GC handles used by JIT + MethodDesc* m_pMethodBeingCompiled; // Top-level method being compiled + SArray* m_transientDetails; // Transient details for dynamic codegen scenarios. + Thread * m_pThread; // Cached current thread for faster JIT-EE transitions CORJIT_FLAGS m_jitFlags; CORINFO_METHOD_HANDLE getMethodBeingCompiled() diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index e59c2dd71dc357..6af5e58782f637 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1633,17 +1633,12 @@ class MethodDesc // enum_flag2_HasPrecode implies that enum_flag2_HasStableEntryPoint is set. enum_flag2_HasStableEntryPoint = 0x01, // The method entrypoint is stable (either precode or actual code) enum_flag2_HasPrecode = 0x02, // Precode has been allocated for this method - enum_flag2_IsUnboxingStub = 0x04, - // unused = 0x08, - + enum_flag2_IsUnsafeAccessor = 0x08, // This is discovered and marked late in the MethodDescs lifetime enum_flag2_IsIntrinsic = 0x10, // Jit may expand method as an intrinsic - enum_flag2_IsEligibleForTieredCompilation = 0x20, - enum_flag2_RequiresCovariantReturnTypeChecking = 0x40 - - // unused = 0x80, + // unused = 0x80, }; BYTE m_bFlags2; @@ -1715,6 +1710,18 @@ class MethodDesc m_bFlags2 |= enum_flag2_IsIntrinsic; } + inline BOOL IsUnsafeAccessor() + { + LIMITED_METHOD_DAC_CONTRACT; + return (m_bFlags2 & enum_flag2_IsUnsafeAccessor) != 0; + } + + inline void SetIsUnsafeAccessor() + { + LIMITED_METHOD_CONTRACT; + InterlockedUpdateFlags2(enum_flag2_IsUnsafeAccessor, TRUE); + } + BOOL RequiresCovariantReturnTypeChecking() { LIMITED_METHOD_DAC_CONTRACT; @@ -1818,12 +1825,12 @@ class MethodDesc PCODE GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldTier); PCODE GetPrecompiledR2RCode(PrepareCodeConfig* pConfig); PCODE GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier0); - COR_ILMETHOD_DECODER* GetAndVerifyILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory); - COR_ILMETHOD_DECODER* GetAndVerifyMetadataILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory); - COR_ILMETHOD_DECODER* GetAndVerifyNoMetadataILHeader(); PCODE JitCompileCode(PrepareCodeConfig* pConfig); PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry); PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); + +public: + bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); #endif // DACCESS_COMPILE #ifdef HAVE_GCCOVER diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 0349ca2684e990..3f33c667982a59 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -4981,23 +4981,6 @@ MethodTableBuilder::ValidateMethods() DeclaredMethodIterator it(*this); while (it.Next()) { - // The RVA is only valid/testable if it has not been overwritten - // for something like edit-and-continue - // Complete validation of non-zero RVAs is done later inside MethodDesc::GetILHeader. - if ((it.RVA() == 0) && (pModule->GetDynamicIL(it.Token(), FALSE) == NULL)) - { - // for IL code that is implemented here must have a valid code RVA - // this came up due to a linker bug where the ImplFlags/DescrOffset were - // being set to null and we weren't coping with it - if((IsMiIL(it.ImplFlags()) || IsMiOPTIL(it.ImplFlags())) && - !IsMdAbstract(it.Attrs()) && - !IsReallyMdPinvokeImpl(it.Attrs()) && - !IsMiInternalCall(it.ImplFlags())) - { - BuildMethodTableThrowException(IDS_CLASSLOAD_MISSINGMETHODRVA, it.Token()); - } - } - if (IsMdRTSpecialName(it.Attrs())) { if (IsMdVirtual(it.Attrs())) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index e555da0436607b..3f5a43b88b29ed 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -42,6 +42,8 @@ #ifndef DACCESS_COMPILE +#include "customattribute.h" + #if defined(FEATURE_JIT_PITCHING) EXTERN_C void CheckStacksAndPitch(); EXTERN_C void SavePitchingCandidate(MethodDesc* pMD, ULONG sizeOfCode); @@ -567,69 +569,6 @@ PCODE MethodDesc::GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier return codeInfo.GetEntryPoint(); } -COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyMetadataILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pDecoderMemory) -{ - STANDARD_VM_CONTRACT; - - _ASSERTE(!IsNoMetadata()); - - COR_ILMETHOD_DECODER* pHeader = NULL; - COR_ILMETHOD* ilHeader = pConfig->GetILHeader(); - if (ilHeader == NULL) - { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL); - } - - COR_ILMETHOD_DECODER::DecoderStatus status = COR_ILMETHOD_DECODER::FORMAT_ERROR; - { - // Decoder ctor can AV on a malformed method header - AVInRuntimeImplOkayHolder AVOkay; - pHeader = new (pDecoderMemory) COR_ILMETHOD_DECODER(ilHeader, GetMDImport(), &status); - } - - if (status == COR_ILMETHOD_DECODER::FORMAT_ERROR) - { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL); - } - - return pHeader; -} - -COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyNoMetadataILHeader() -{ - STANDARD_VM_CONTRACT; - - if (IsILStub()) - { - ILStubResolver* pResolver = AsDynamicMethodDesc()->GetILStubResolver(); - return pResolver->GetILHeader(); - } - else - { - return NULL; - } - - // NoMetadata currently doesn't verify the IL. I'm not sure if that was - // a deliberate decision in the past or not, but I've left the behavior - // as-is during refactoring. -} - -COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory) -{ - STANDARD_VM_CONTRACT; - _ASSERTE(IsIL() || IsNoMetadata()); - - if (IsNoMetadata()) - { - // The NoMetadata version already has a decoder to use, it doesn't need the stack allocated one - return GetAndVerifyNoMetadataILHeader(); - } - else - { - return GetAndVerifyMetadataILHeader(pConfig, pIlDecoderMemory); - } -} - // ******************************************************************** // README!! // ******************************************************************** @@ -922,6 +861,53 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J return pCode; } +namespace +{ + COR_ILMETHOD_DECODER* GetAndVerifyMetadataILHeader(MethodDesc* pMD, PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pDecoderMemory) + { + STANDARD_VM_CONTRACT; + _ASSERTE(pMD != NULL); + _ASSERTE(!pMD->IsNoMetadata()); + _ASSERTE(pConfig != NULL); + _ASSERTE(pDecoderMemory != NULL); + + COR_ILMETHOD_DECODER* pHeader = NULL; + COR_ILMETHOD* ilHeader = pConfig->GetILHeader(); + if (ilHeader == NULL) + return NULL; + + COR_ILMETHOD_DECODER::DecoderStatus status = COR_ILMETHOD_DECODER::FORMAT_ERROR; + { + // Decoder ctor can AV on a malformed method header + AVInRuntimeImplOkayHolder AVOkay; + pHeader = new (pDecoderMemory) COR_ILMETHOD_DECODER(ilHeader, pMD->GetMDImport(), &status); + } + + if (status == COR_ILMETHOD_DECODER::FORMAT_ERROR) + COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL); + + return pHeader; + } + + COR_ILMETHOD_DECODER* GetAndVerifyILHeader(MethodDesc* pMD, PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory) + { + STANDARD_VM_CONTRACT; + _ASSERTE(pMD != NULL); + if (pMD->IsIL()) + { + return GetAndVerifyMetadataILHeader(pMD, pConfig, pIlDecoderMemory); + } + else if (pMD->IsILStub()) + { + ILStubResolver* pResolver = pMD->AsDynamicMethodDesc()->GetILStubResolver(); + return pResolver->GetILHeader(); + } + + _ASSERTE(pMD->IsNoMetadata()); + return NULL; + } +} + PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags) { STANDARD_VM_CONTRACT; @@ -933,7 +919,8 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn // // (don't want this for OSR, need to see how it works) COR_ILMETHOD_DECODER ilDecoderTemp; - COR_ILMETHOD_DECODER *pilHeader = GetAndVerifyILHeader(pConfig, &ilDecoderTemp); + COR_ILMETHOD_DECODER* pilHeader = GetAndVerifyILHeader(this, pConfig, &ilDecoderTemp); + *pFlags = pConfig->GetJitCompilationFlags(); PCODE pOtherCode = NULL; @@ -1059,7 +1046,277 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn return pCode; } +namespace +{ + enum class UnsafeAccessorKind + { + Constructor, // call instance constructor (`newobj` in IL) + Method, // call instance method (`callvirt` in IL) + StaticMethod, // call static method (`call` in IL) + Field, // address of instance field (`ldflda` in IL) + StaticField // address of static field (`ldsflda` in IL) + }; + + bool TryParseUnsafeAccessorAttribute( + MethodDesc* pMD, + CustomAttributeParser& ca, + UnsafeAccessorKind& kind, + SString& name) + { + STANDARD_VM_CONTRACT; + _ASSERTE(pMD != NULL); + + // Get the kind of accessor + CaArg args[1]; + args[0].InitEnum(SERIALIZATION_TYPE_I4, 0); + if (FAILED(::ParseKnownCaArgs(ca, args, ARRAY_SIZE(args)))) + return false; + + kind = (UnsafeAccessorKind)args[0].val.i4; + + // Check the name of the target to access. This is the name we + // use to look up the intended token in metadata. + CaNamedArg namedArgs[1]; + CaType namedArgTypes[1]; + namedArgTypes[0].Init(SERIALIZATION_TYPE_STRING); + namedArgs[0].Init("Name", SERIALIZATION_TYPE_PROPERTY, namedArgTypes[0], NULL); + if (FAILED(::ParseKnownCaNamedArgs(ca, namedArgs, ARRAY_SIZE(namedArgs)))) + return false; + + // If the Name isn't defined, then use the name of the method. + if (namedArgs[0].val.type.tag == SERIALIZATION_TYPE_UNDEFINED) + { + // The Constructor case has an implied value provided by + // the runtime. We are going to enforce this during consumption + // so we avoid the setting of the value. We validate the name + // as empty at the use site. + if (kind != UnsafeAccessorKind::Constructor) + name.SetUTF8(pMD->GetName()); + } + else + { + const CaValue& val = namedArgs[0].val; + name.SetUTF8(val.str.pStr, val.str.cbStr); + } + + return true; + } + + struct GenerationContext final + { + GenerationContext(MethodDesc* pMD) + : TargetDeclaration{ pMD } + , SigReader{ pMD } + , TargetType{} + , IsTargetStatic{} + , TargetMethod{} + , TargetField{} + { } + MethodDesc* TargetDeclaration; + MetaSig SigReader; + TypeHandle TargetType; + bool IsTargetStatic; + MethodDesc* TargetMethod; + FieldDesc* TargetField; + }; + + bool TrySetTargetMethod( + GenerationContext& cxt, + LPCUTF8 name) + { + STANDARD_VM_CONTRACT; + _ASSERTE(name != NULL); + + return false; + } + + bool TrySetTargetMethodCtor(GenerationContext& cxt) + { + STANDARD_VM_CONTRACT; + _ASSERTE(!cxt.TargetType.IsNull()); + + PTR_MethodTable pMT = cxt.TargetType.AsMethodTable(); + + // Special case the default constructor case. + if (cxt.SigReader.NumFixedArgs() == 0 + && pMT->HasDefaultConstructor()) + { + cxt.TargetMethod = pMT->GetDefaultConstructor(); + return true; + } + + // Defer to the normal method look up for + // cases beyond the default constructor. + return TrySetTargetMethod(cxt, ".ctor"); + } + + bool TrySetTargetField( + GenerationContext& cxt, + LPCUTF8 name) + { + STANDARD_VM_CONTRACT; + _ASSERTE(!cxt.TargetType.IsNull()); + _ASSERTE(name != NULL); + + return false; + } + + bool TryGeneratedMethodAccessor( + GenerationContext& cxt, + DynamicResolver** resolver, + COR_ILMETHOD_DECODER** methodILDecoder) + { + STANDARD_VM_CONTRACT; + + NewHolder ilResolver = new ILStubResolver(); + + // Initialize the resolver target details. + ilResolver->SetStubMethodDesc(cxt.TargetDeclaration); + ilResolver->SetStubTargetMethodDesc(cxt.TargetMethod); + + // [TODO] Handle generics + SigTypeContext emptyContext; + ILStubLinker sl( + cxt.TargetDeclaration->GetModule(), + cxt.TargetDeclaration->GetSignature(), + &emptyContext, + cxt.TargetMethod, + (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); + + ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + pCode->EmitNEWOBJ(pCode->GetToken(cxt.TargetMethod), 0); + pCode->EmitRET(); + + { + UINT maxStack; + size_t cbCode = sl.Link(&maxStack); + DWORD cbSig = sl.GetLocalSigSize(); + + COR_ILMETHOD_DECODER* pILHeader = ilResolver->AllocGeneratedIL(cbCode, cbSig, maxStack); + BYTE* pbBuffer = (BYTE*)pILHeader->Code; + BYTE* pbLocalSig = (BYTE*)pILHeader->LocalVarSig; + _ASSERTE(cbSig == pILHeader->cbLocalVarSig); + sl.GenerateCode(pbBuffer, cbCode); + sl.GetLocalSig(pbLocalSig, cbSig); + + // Store the token lookup map + ilResolver->SetTokenLookupMap(sl.GetTokenLookupMap()); + + *resolver = (DynamicResolver*)ilResolver; + *methodILDecoder = pILHeader; + } + + ilResolver.SuppressRelease(); + return true; + } + + bool TryGeneratedFieldAccessor( + GenerationContext& cxt, + DynamicResolver** resolver, + COR_ILMETHOD_DECODER** methodILDecoder) + { + STANDARD_VM_CONTRACT; + return false; + } +} + +bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(IsIL()); + _ASSERTE(GetRVA() == 0); + _ASSERTE(resolver != NULL); + _ASSERTE(methodILDecoder != NULL); + + // + // [TODO] Check if supplied resolver/methodILDecoder are non-null + // + + // The UnsafeAccessorAttribute is applied to methods with an + // RVA of 0 (for example, C#'s extern keyword). + const void* data; + ULONG dataLen; + HRESULT hr = GetCustomAttribute(WellKnownAttribute::UnsafeAccessorAttribute, &data, &dataLen); + if (hr != S_OK) + return false; + + // This flag is always set here and is never used to indicate skipping any work. + // We don't change our behavior based on this flag because we always need to + // generate the IL for this API. + SetIsUnsafeAccessor(); + + UnsafeAccessorKind kind; + SString name; + + CustomAttributeParser ca(data, dataLen); + if (!TryParseUnsafeAccessorAttribute(this, ca, kind, name)) + return false; + + GenerationContext context{ this }; + + // Parse the signature to determine the type to use: + // * Constructor access - examine the return type + // * Instance member access - examine type of first parameter + // * Static member access - examine type of first parameter + TypeHandle retType; + TypeHandle firstArgType; + retType = context.SigReader.GetRetTypeHandleNT(); + UINT argCount = context.SigReader.NumFixedArgs(); + if (argCount > 0) + { + context.SigReader.NextArg(); + firstArgType = context.SigReader.GetLastTypeHandleNT(); + } + + // Using the kind type, perform the following: + // 1) Resolve the name to the appropriate token type + // 2) Generate the IL for the accessor + switch (kind) + { + case UnsafeAccessorKind::Constructor: + // A return type is required for a constructor, otherwise + // we don't know the type to construct. + // The name is defined by the runtime and should be empty. + if (retType.IsNull() || !name.IsEmpty()) + { + return false; + } + + context.TargetType = retType; + context.IsTargetStatic = false; + return TrySetTargetMethodCtor(context) + && TryGeneratedMethodAccessor(context, resolver, methodILDecoder); + + case UnsafeAccessorKind::Method: + case UnsafeAccessorKind::StaticMethod: + // Method access requires a target type. + if (firstArgType.IsNull()) + return false; + + context.TargetType = firstArgType; + context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; + return TrySetTargetMethod(context, name.GetUTF8()) + && TryGeneratedMethodAccessor(context, resolver, methodILDecoder); + + case UnsafeAccessorKind::Field: + case UnsafeAccessorKind::StaticField: + // Field access requires a target type and return type. + if (firstArgType.IsNull() || retType.IsNull()) + { + return false; + } + + context.TargetType = firstArgType; + context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField; + return TrySetTargetField(context, name.GetUTF8()) + && TryGeneratedFieldAccessor(context, resolver, methodILDecoder); + + default: + _ASSERTE(!"Unknown UnsafeAccessorKind"); + return false; + } +} PrepareCodeConfig::PrepareCodeConfig() {} diff --git a/src/coreclr/vm/wellknownattributes.h b/src/coreclr/vm/wellknownattributes.h index 837f75c2ac083b..85a747b2c9a948 100644 --- a/src/coreclr/vm/wellknownattributes.h +++ b/src/coreclr/vm/wellknownattributes.h @@ -37,6 +37,7 @@ enum class WellKnownAttribute : DWORD PreserveBaseOverridesAttribute, ObjectiveCTrackedTypeAttribute, InlineArrayAttribute, + UnsafeAccessorAttribute, CountOfWellKnownAttributes }; @@ -107,6 +108,8 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute) return "System.Runtime.InteropServices.ObjectiveC.ObjectiveCTrackedTypeAttribute"; case WellKnownAttribute::InlineArrayAttribute: return "System.Runtime.CompilerServices.InlineArrayAttribute"; + case WellKnownAttribute::UnsafeAccessorAttribute: + return "System.Runtime.CompilerServices.UnsafeAccessorAttribute"; case WellKnownAttribute::CountOfWellKnownAttributes: default: break; // Silence compiler warnings From 40111fa0d5757413368e31191f9b6b7c898f2ac4 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 27 May 2023 11:11:29 -0700 Subject: [PATCH 03/54] Remove IsUnsafeAccessor flag from MethodDesc and instead encode details on the scopeHandle. --- src/coreclr/vm/dynamicmethod.h | 27 +++++++++++++++++++++------ src/coreclr/vm/jitinterface.cpp | 19 ++++++++++++------- src/coreclr/vm/method.hpp | 14 +------------- src/coreclr/vm/prestub.cpp | 5 ----- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/coreclr/vm/dynamicmethod.h b/src/coreclr/vm/dynamicmethod.h index 78b9a133fe7474..746f5330ca0293 100644 --- a/src/coreclr/vm/dynamicmethod.h +++ b/src/coreclr/vm/dynamicmethod.h @@ -333,23 +333,38 @@ inline MethodDesc* GetMethod(CORINFO_METHOD_HANDLE methodHandle) enum CORINFO_MODULE_HANDLE_TYPES { - CORINFO_NORMAL_MODULE = 0, - CORINFO_DYNAMIC_MODULE = 1, + // The module handle is a Module + CORINFO_NORMAL_MODULE = 0, + + // The module handle is a DynamicResolver + CORINFO_DYNAMIC_MODULE = 1, + + // The module handle permits unrestricted access + CORINFO_MODULE_ALLACCESS = 2, }; -#define CORINFO_MODULE_HANDLE_TYPE_MASK (CORINFO_NORMAL_MODULE | CORINFO_DYNAMIC_MODULE) +#define CORINFO_MODULE_HANDLE_TYPE_MASK (CORINFO_NORMAL_MODULE | CORINFO_DYNAMIC_MODULE | CORINFO_MODULE_ALLACCESS) inline bool IsDynamicScope(CORINFO_MODULE_HANDLE module) { LIMITED_METHOD_CONTRACT; - return (CORINFO_DYNAMIC_MODULE == (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK)); + return !!(CORINFO_DYNAMIC_MODULE & (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK)); +} + +inline bool IsAllAccessScope(CORINFO_MODULE_HANDLE module) +{ + LIMITED_METHOD_CONTRACT; + return !!(CORINFO_MODULE_ALLACCESS & (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK)); } -inline CORINFO_MODULE_HANDLE MakeDynamicScope(DynamicResolver* pResolver) +inline CORINFO_MODULE_HANDLE MakeDynamicScope(DynamicResolver* pResolver, bool permitAllAccess) { LIMITED_METHOD_CONTRACT; CONSISTENCY_CHECK(0 == (((size_t)pResolver) & CORINFO_MODULE_HANDLE_TYPE_MASK)); - return (CORINFO_MODULE_HANDLE)(((size_t)pResolver) | CORINFO_DYNAMIC_MODULE); + uint32_t type = CORINFO_DYNAMIC_MODULE; + if (permitAllAccess) + type |= CORINFO_MODULE_ALLACCESS; + return (CORINFO_MODULE_HANDLE)(((size_t)pResolver) | type); } inline DynamicResolver* GetDynamicResolver(CORINFO_MODULE_HANDLE module) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index b841c6a9eeb107..598a38e2a7eff3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -152,7 +152,7 @@ inline CORINFO_MODULE_HANDLE GetScopeHandle(MethodDesc* method) LIMITED_METHOD_CONTRACT; if (method->IsDynamicMethod()) { - return MakeDynamicScope(method->AsDynamicMethodDesc()->GetResolver()); + return MakeDynamicScope(method->AsDynamicMethodDesc()->GetResolver(), method->IsILStub()); } else { @@ -5474,8 +5474,7 @@ void CEEInfo::getCallInfo( pResult->accessAllowed = CORINFO_ACCESS_ALLOWED; MethodDesc* callerMethod = (MethodDesc*)callerHandle; if ((flags & CORINFO_CALLINFO_SECURITYCHECKS) - && !(callerMethod->IsILStub()) // IL stubs can access everything, don't bother doing access checks - && !(callerMethod->IsUnsafeAccessor())) // UnsafeAccessor method, by definition, can access everything + && !(IsAllAccessScope(pResolvedToken->tokenScope))) { //Our type system doesn't always represent the target exactly with the MethodDesc. In all cases, //carry around the parent MethodTable for both Caller and Callee. @@ -7738,10 +7737,16 @@ class MethodInfoHelperContext final return TransientResolver != NULL; } + CORINFO_MODULE_HANDLE CreateScopeHandle() const + { + _ASSERTE(HasTransientMethodDetails()); + return MakeDynamicScope(TransientResolver, true /* permitAllAccess */); + } + TransientMethodDetails CreateTransientMethodDetails() const { _ASSERTE(HasTransientMethodDetails()); - return TransientMethodDetails{Method, Header, MakeDynamicScope(TransientResolver)}; + return TransientMethodDetails{Method, Header, CreateScopeHandle() }; } }; @@ -7766,7 +7771,7 @@ static bool getMethodInfoHelper( if (NULL != cxt.Header) { scopeHnd = cxt.HasTransientMethodDetails() - ? MakeDynamicScope(cxt.TransientResolver) + ? cxt.CreateScopeHandle() : GetScopeHandle(ftn->GetModule()); bool fILIntrinsic = false; @@ -7811,7 +7816,7 @@ static bool getMethodInfoHelper( else if (ftn->IsDynamicMethod()) { DynamicResolver* pResolver = ftn->AsDynamicMethodDesc()->GetResolver(); - scopeHnd = MakeDynamicScope(pResolver); + scopeHnd = MakeDynamicScope(pResolver, ftn->IsILStub()); unsigned int EHCount; methInfo->ILCode = pResolver->GetCodeInfo(&methInfo->ILCodeSize, @@ -7824,7 +7829,7 @@ static bool getMethodInfoHelper( } else if (ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header)) { - scopeHnd = MakeDynamicScope(cxt.TransientResolver); + scopeHnd = cxt.CreateScopeHandle(); _ASSERTE(cxt.Header != NULL); getMethodInfoILMethodHeaderHelper(cxt.Header, methInfo); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 6af5e58782f637..a2cfec402a4ceb 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1634,7 +1634,7 @@ class MethodDesc enum_flag2_HasStableEntryPoint = 0x01, // The method entrypoint is stable (either precode or actual code) enum_flag2_HasPrecode = 0x02, // Precode has been allocated for this method enum_flag2_IsUnboxingStub = 0x04, - enum_flag2_IsUnsafeAccessor = 0x08, // This is discovered and marked late in the MethodDescs lifetime + // unused = 0x08, enum_flag2_IsIntrinsic = 0x10, // Jit may expand method as an intrinsic enum_flag2_IsEligibleForTieredCompilation = 0x20, enum_flag2_RequiresCovariantReturnTypeChecking = 0x40 @@ -1710,18 +1710,6 @@ class MethodDesc m_bFlags2 |= enum_flag2_IsIntrinsic; } - inline BOOL IsUnsafeAccessor() - { - LIMITED_METHOD_DAC_CONTRACT; - return (m_bFlags2 & enum_flag2_IsUnsafeAccessor) != 0; - } - - inline void SetIsUnsafeAccessor() - { - LIMITED_METHOD_CONTRACT; - InterlockedUpdateFlags2(enum_flag2_IsUnsafeAccessor, TRUE); - } - BOOL RequiresCovariantReturnTypeChecking() { LIMITED_METHOD_DAC_CONTRACT; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 3f5a43b88b29ed..47f877097d5403 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1241,11 +1241,6 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET if (hr != S_OK) return false; - // This flag is always set here and is never used to indicate skipping any work. - // We don't change our behavior based on this flag because we always need to - // generate the IL for this API. - SetIsUnsafeAccessor(); - UnsafeAccessorKind kind; SString name; From 0191e0d450549ee834b2c67d4bace0a07c28fc45 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 27 May 2023 11:12:30 -0700 Subject: [PATCH 04/54] Cleanup memory managment confusion with ILStubResolver. --- src/coreclr/vm/ilstubresolver.cpp | 24 +++++++++--------------- src/coreclr/vm/ilstubresolver.h | 3 +++ src/coreclr/vm/jitinterface.cpp | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index a9e5b3690a0a69..58ac90394e7507 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -312,8 +312,8 @@ ILStubResolver::AllocGeneratedIL( memory = newMemory; } - CompileTimeState* pNewCompileTimeState = (CompileTimeState*)memory; - memset(pNewCompileTimeState, 0, sizeof(*pNewCompileTimeState)); + // Using placement new + CompileTimeState* pNewCompileTimeState = new (memory) CompileTimeState{}; BYTE* pNewILCodeBuffer = ((BYTE*)pNewCompileTimeState) + sizeof(*pNewCompileTimeState); BYTE* pNewLocalSig = (0 == cbLocalSig) @@ -418,22 +418,16 @@ ILStubResolver::ClearCompileTimeState(CompileTimeStatePtrSpecialValues newState) CONTRACTL_END; // - // See allocations in AllocGeneratedIL and SetStubTargetMethodSig + // See allocations in AllocGeneratedIL, SetStubTargetMethodSig and AllocEHSect // - if (!m_pCompileTimeState->m_StubTargetMethodSig.IsNull()) - { - delete[] m_pCompileTimeState->m_StubTargetMethodSig.GetPtr(); - } - - if (NULL != m_pCompileTimeState->m_pEHSect) - { - delete[] m_pCompileTimeState->m_pEHSect; - } + delete[](void*)m_pCompileTimeState->m_StubTargetMethodSig.GetPtr(); + delete[](void*)m_pCompileTimeState->m_pEHSect; - // The allocation being deleted here is a bulk allocation - // that is typed as a BYTE[]. - delete[] m_pCompileTimeState; + // The allocation being deleted here was allocated using placement new + // from a bulk allocation so manually call the destructor. + m_pCompileTimeState->~CompileTimeState(); + delete[](void*)m_pCompileTimeState; InterlockedExchangeT(&m_pCompileTimeState, dac_cast((TADDR)newState)); } // ILStubResolver::ClearCompileTimeState diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index 99f6f406363fa5..7dba08f018c0e4 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -89,6 +89,9 @@ class ILStubResolver : DynamicResolver // struct CompileTimeState { + CompileTimeState() = default; + ~CompileTimeState() = default; + COR_ILMETHOD_DECODER m_ILHeader; COR_ILMETHOD_SECT_EH * m_pEHSect; SigPointer m_StubTargetMethodSig; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 598a38e2a7eff3..97ae912af873e2 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7772,7 +7772,7 @@ static bool getMethodInfoHelper( { scopeHnd = cxt.HasTransientMethodDetails() ? cxt.CreateScopeHandle() - : GetScopeHandle(ftn->GetModule()); + : GetScopeHandle(ftn); bool fILIntrinsic = false; MethodTable * pMT = ftn->GetMethodTable(); From ef239fb90b5623bdbd00397d1446d7704b7ab494 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 29 May 2023 09:44:57 +0200 Subject: [PATCH 05/54] Fix non-standard C++ --- src/coreclr/vm/ilstubresolver.cpp | 6 +++--- src/coreclr/vm/jitinterface.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 58ac90394e7507..72ac0f723722e0 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -421,13 +421,13 @@ ILStubResolver::ClearCompileTimeState(CompileTimeStatePtrSpecialValues newState) // See allocations in AllocGeneratedIL, SetStubTargetMethodSig and AllocEHSect // - delete[](void*)m_pCompileTimeState->m_StubTargetMethodSig.GetPtr(); - delete[](void*)m_pCompileTimeState->m_pEHSect; + delete[](BYTE*)m_pCompileTimeState->m_StubTargetMethodSig.GetPtr(); + delete[](BYTE*)m_pCompileTimeState->m_pEHSect; // The allocation being deleted here was allocated using placement new // from a bulk allocation so manually call the destructor. m_pCompileTimeState->~CompileTimeState(); - delete[](void*)m_pCompileTimeState; + delete[](BYTE*)(void*)m_pCompileTimeState; InterlockedExchangeT(&m_pCompileTimeState, dac_cast((TADDR)newState)); } // ILStubResolver::ClearCompileTimeState diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 97ae912af873e2..a550f8e62bf24e 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -13119,7 +13119,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, #endif // FEATURE_ON_STACK_REPLACEMENT if (cxt.HasTransientMethodDetails()) - jitInfo.AddTransientMethodDetails(std::move(cxt.CreateTransientMethodDetails())); + jitInfo.AddTransientMethodDetails(cxt.CreateTransientMethodDetails()); MethodDesc * pMethodForSecurity = jitInfo.GetMethodForSecurity(methodInfo.ftn); From a396e04e22320714d4eef2e93f550c1e71c86173 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 29 May 2023 09:45:15 +0200 Subject: [PATCH 06/54] Add UnsafeAccessorAttribute API --- .../System.Private.CoreLib.Shared.projitems | 1 + .../UnsafeAccessorAttribute.cs | 89 +++++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 15 ++++ 3 files changed, 105 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d80ad76e071a21..decebdb1c2adef 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -843,6 +843,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs new file mode 100644 index 00000000000000..964900dc87e5e6 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + /// + /// The kind of target an is + /// providing access. + /// + public enum UnsafeAccessorKind + { + /// + /// Provide access to a Constructor. + /// + Constructor, + + /// + /// Provide access to a Method. + /// + Method, + + /// + /// Provide access to a static Method. + /// + StaticMethod, + + /// + /// Provide access to a Field. + /// + Field, + + /// + /// Provide access to a static Field. + /// + StaticField + }; + + /// + /// Provide access to an unaccessible API on a specific type. + /// + /// + /// This attribute should be applied on a extern static method. + /// The implementation of the extern static method annotated with + /// this attribute will be provided by the runtime based on the information in + /// the attribute and the signature of the method that the attribute is applied to. + /// The runtime will try to find the matching method or field and forward the call + /// to it. If the matching method or field is not found, the body of the extern + /// method will throw or . + /// + /// For / and + /// /, the type of + /// the first argument of the annotated extern method identifies the owning type. + /// The value of the first argument is treated as this pointer for instance fields and methods. + /// The first argument must be passed as ref for instance fields and methods on structs. + /// The value of the first argument is not used by the implementation for static fields and methods. + /// + /// The generic parameters of the extern static method are concatenation of the type and + /// method generic arguments of the target method. For example, + /// extern static void Method1<T1, T2>(Class1<T1> @this) + /// can be used to call Class1<T1>.Method1<T2>(). The generic constraints of the + /// extern static method must match generic constraints of the target type, field or method. + /// + /// Return type is considered for the signature match. modreqs and modopts are not considered for the signature match. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class UnsafeAccessorAttribute : Attribute + { + /// + /// Instantiates an providing access to an API of kind . + /// + /// The kind of the target to provide access. + public UnsafeAccessorAttribute(UnsafeAccessorKind kind) + => Kind = kind; + + /// + /// Kind of API to provide access. + /// + public UnsafeAccessorKind Kind { get; } + + /// + /// Name of the API to provide access. + /// + /// + /// The name defaults to the annotated method name if not specified. + /// The name must be unset/null for . + /// + public string? Name { get; set; } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f961d94d5e1454..19192d3dee4901 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13092,6 +13092,21 @@ public unsafe static void WriteUnaligned(void* destination, T value) { } [System.CLSCompliantAttribute(false)] public unsafe static void Write(void* destination, T value) { } } + [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class UnsafeAccessorAttribute : Attribute + { + public UnsafeAccessorAttribute(System.Runtime.CompilerServices.UnsafeAccessorKind kind) { } + public System.Runtime.CompilerServices.UnsafeAccessorKind Kind { get; } + public string? Name { get; set; } + } + public enum UnsafeAccessorKind + { + Constructor, + Method, + StaticMethod, + Field, + StaticField + }; [System.AttributeUsageAttribute(System.AttributeTargets.Struct)] public sealed partial class UnsafeValueTypeAttribute : System.Attribute { From 64374200e45fcdaec799479d878f04887f78a12b Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 29 May 2023 10:49:39 +0200 Subject: [PATCH 07/54] C++ clean-up --- src/coreclr/inc/corhlpr.h | 4 +-- src/coreclr/vm/ilstubresolver.cpp | 44 +++++++++++++------------------ src/coreclr/vm/ilstubresolver.h | 2 -- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/coreclr/inc/corhlpr.h b/src/coreclr/inc/corhlpr.h index a26676eda15831..3cc0a36701e7e7 100644 --- a/src/coreclr/inc/corhlpr.h +++ b/src/coreclr/inc/corhlpr.h @@ -626,9 +626,7 @@ extern "C" { class COR_ILMETHOD_DECODER : public COR_ILMETHOD_FAT { public: - // This returns an uninitialized decoder, suitable for placement new but nothing - // else. Use with caution. - COR_ILMETHOD_DECODER() {} + COR_ILMETHOD_DECODER() = default; // Typically the ONLY way you should access COR_ILMETHOD is through // this constructor so format changes are easier. diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 72ac0f723722e0..c6ecbaa914dc3b 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -268,16 +268,15 @@ void ILStubResolver::SetLoaderHeap(PTR_LoaderHeap pLoaderHeap) m_loaderHeap = pLoaderHeap; } -void ILStubResolver::CreateILHeader(COR_ILMETHOD_DECODER* pILHeader, size_t cbCode, UINT maxStack, BYTE* pNewILCodeBuffer, BYTE* pNewLocalSig, DWORD cbLocalSig) -{ - pILHeader->Flags = 0; - pILHeader->CodeSize = (DWORD)cbCode; - pILHeader->MaxStack = maxStack; - pILHeader->EH = 0; - pILHeader->Sect = 0; - pILHeader->Code = pNewILCodeBuffer; - pILHeader->LocalVarSig = pNewLocalSig; - pILHeader->cbLocalVarSig = cbLocalSig; +static COR_ILMETHOD_DECODER CreateILHeader(size_t cbCode, UINT maxStack, BYTE* pNewILCodeBuffer, BYTE* pNewLocalSig, DWORD cbLocalSig) +{ + COR_ILMETHOD_DECODER ilHeader{}; + ilHeader.CodeSize = (DWORD)cbCode; + ilHeader.MaxStack = maxStack; + ilHeader.Code = pNewILCodeBuffer; + ilHeader.LocalVarSig = pNewLocalSig; + ilHeader.cbLocalVarSig = cbLocalSig; + return ilHeader; } //--------------------------------------------------------------------------------------- @@ -321,8 +320,7 @@ ILStubResolver::AllocGeneratedIL( : (pNewILCodeBuffer + cbCode); COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader; - - CreateILHeader(pILHeader, cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig); + *pILHeader = CreateILHeader(cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig); LPVOID pPrevCompileTimeState = InterlockedExchangeT(&m_pCompileTimeState, pNewCompileTimeState); CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState); @@ -359,20 +357,16 @@ COR_ILMETHOD_SECT_EH* ILStubResolver::AllocEHSect(size_t nClauses) { STANDARD_VM_CONTRACT; - if (nClauses >= 1) - { - size_t cbSize = sizeof(COR_ILMETHOD_SECT_EH) - - sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT) - + (nClauses * sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT)); - m_pCompileTimeState->m_pEHSect = (COR_ILMETHOD_SECT_EH*) new BYTE[cbSize]; - CONSISTENCY_CHECK(NULL == m_pCompileTimeState->m_ILHeader.EH); - m_pCompileTimeState->m_ILHeader.EH = m_pCompileTimeState->m_pEHSect; - return m_pCompileTimeState->m_pEHSect; - } - else - { + if (nClauses == 0) return NULL; - } + + size_t cbSize = sizeof(COR_ILMETHOD_SECT_EH) + - sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT) + + (nClauses * sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT)); + m_pCompileTimeState->m_pEHSect = (COR_ILMETHOD_SECT_EH*) new BYTE[cbSize]; + CONSISTENCY_CHECK(NULL == m_pCompileTimeState->m_ILHeader.EH); + m_pCompileTimeState->m_ILHeader.EH = m_pCompileTimeState->m_pEHSect; + return m_pCompileTimeState->m_pEHSect; } bool ILStubResolver::UseLoaderHeap() diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index 7dba08f018c0e4..fc0d7f7f2773ea 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -51,8 +51,6 @@ class ILStubResolver : DynamicResolver void SetStubTargetMethodSig(PCCOR_SIGNATURE pStubTargetMethodSig, DWORD cbStubTargetSigLength); void SetStubMethodDesc(MethodDesc* pStubMD); - void CreateILHeader(COR_ILMETHOD_DECODER* pILHeader, size_t cbCode, UINT maxStack, BYTE* pNewILCodeBuffer, BYTE* pNewLocalSig, DWORD cbLocalSig); - COR_ILMETHOD_DECODER * AllocGeneratedIL(size_t cbCode, DWORD cbLocalSig, UINT maxStack); COR_ILMETHOD_DECODER * GetILHeader(); COR_ILMETHOD_SECT_EH* AllocEHSect(size_t nClauses); From b24717abaec255bdead2d270fb349853556e317e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 29 May 2023 11:23:35 +0200 Subject: [PATCH 08/54] Return missing(field/method) HRESULT when appropriate --- src/coreclr/vm/jitinterface.cpp | 31 +++++++++++---------- src/coreclr/vm/method.hpp | 2 +- src/coreclr/vm/prestub.cpp | 49 ++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index a550f8e62bf24e..7ccc3c92827958 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7750,7 +7750,7 @@ class MethodInfoHelperContext final } }; -static bool getMethodInfoHelper( +static HRESULT getMethodInfoHelper( MethodInfoHelperContext& cxt, CORINFO_METHOD_INFO* methInfo) { @@ -7827,8 +7827,16 @@ static bool getMethodInfoHelper( SigPointer localSig = pResolver->GetLocalSig(); localSig.GetSignature(&pLocalSig, &cbLocalSig); } - else if (ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header)) + else { + HRESULT hr = ftn->GenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header); + if (FAILED(hr)) + { + if (hr == E_FAIL) + hr = COR_E_BADIMAGEFORMAT; + return hr; + } + scopeHnd = cxt.CreateScopeHandle(); _ASSERTE(cxt.Header != NULL); @@ -7836,10 +7844,6 @@ static bool getMethodInfoHelper( pLocalSig = cxt.Header->LocalVarSig; cbLocalSig = cxt.Header->cbLocalVarSig; } - else - { - return false; - } _ASSERTE(scopeHnd != NULL); methInfo->scope = scopeHnd; @@ -7910,7 +7914,7 @@ static bool getMethodInfoHelper( CONV_TO_JITSIG_FLAGS_LOCALSIG, &methInfo->locals); - return true; + return S_OK; } // getMethodInfoHelper //--------------------------------------------------------------------------------------- @@ -7936,13 +7940,13 @@ CEEInfo::getMethodInfo( MethodInfoHelperContext cxt{ ftn }; if (ftn->IsDynamicMethod()) { - result = getMethodInfoHelper(cxt, methInfo); + result = SUCCEEDED(getMethodInfoHelper(cxt, methInfo)); } else if (!ftn->IsWrapperStub() && ftn->HasILHeader()) { COR_ILMETHOD_DECODER header(ftn->GetILHeader(TRUE), ftn->GetMDImport(), NULL); cxt.Header = &header; - result = getMethodInfoHelper(cxt, methInfo); + result = SUCCEEDED(getMethodInfoHelper(cxt, methInfo)); } else if (ftn->IsIL() && ftn->GetRVA() == 0) { @@ -7955,7 +7959,7 @@ CEEInfo::getMethodInfo( cxt.TransientResolver = GetDynamicResolver(detailsMaybe->Scope); } - result = getMethodInfoHelper(cxt, methInfo); + result = SUCCEEDED(getMethodInfoHelper(cxt, methInfo)); } if (result) @@ -13060,12 +13064,11 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, } #endif // _DEBUG + HRESULT hr; MethodInfoHelperContext cxt{ ftn, ILHeader }; CORINFO_METHOD_INFO methodInfo; - if (!getMethodInfoHelper(cxt, &methodInfo)) - { - _ASSERTE(!"[TODO] What exception to throw?"); - } + if (FAILED(hr = getMethodInfoHelper(cxt, &methodInfo))) + ThrowHR(hr); // If it's generic then we can only enter through an instantiated MethodDesc _ASSERTE(!ftn->IsGenericMethodDefinition()); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index a2cfec402a4ceb..32710a520a69f8 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1818,7 +1818,7 @@ class MethodDesc PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); public: - bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); + HRESULT GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); #endif // DACCESS_COMPILE #ifdef HAVE_GCCOVER diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 47f877097d5403..982ffadac48a64 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1221,17 +1221,14 @@ namespace } } -bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) { STANDARD_VM_CONTRACT; - _ASSERTE(IsIL()); - _ASSERTE(GetRVA() == 0); _ASSERTE(resolver != NULL); _ASSERTE(methodILDecoder != NULL); - - // - // [TODO] Check if supplied resolver/methodILDecoder are non-null - // + _ASSERTE(*resolver == NULL && *methodILDecoder == NULL); + _ASSERTE(IsIL()); + _ASSERTE(GetRVA() == 0); // The UnsafeAccessorAttribute is applied to methods with an // RVA of 0 (for example, C#'s extern keyword). @@ -1239,14 +1236,14 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET ULONG dataLen; HRESULT hr = GetCustomAttribute(WellKnownAttribute::UnsafeAccessorAttribute, &data, &dataLen); if (hr != S_OK) - return false; + return E_FAIL; UnsafeAccessorKind kind; SString name; CustomAttributeParser ca(data, dataLen); if (!TryParseUnsafeAccessorAttribute(this, ca, kind, name)) - return false; + return COR_E_BADIMAGEFORMAT; GenerationContext context{ this }; @@ -1275,42 +1272,56 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // The name is defined by the runtime and should be empty. if (retType.IsNull() || !name.IsEmpty()) { - return false; + return COR_E_BADIMAGEFORMAT; } context.TargetType = retType; context.IsTargetStatic = false; - return TrySetTargetMethodCtor(context) - && TryGeneratedMethodAccessor(context, resolver, methodILDecoder); + if (!TrySetTargetMethodCtor(context) + || !TryGeneratedMethodAccessor(context, resolver, methodILDecoder)) + { + return COR_E_MISSINGMETHOD; + } + break; case UnsafeAccessorKind::Method: case UnsafeAccessorKind::StaticMethod: // Method access requires a target type. if (firstArgType.IsNull()) - return false; + return COR_E_BADIMAGEFORMAT; context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; - return TrySetTargetMethod(context, name.GetUTF8()) - && TryGeneratedMethodAccessor(context, resolver, methodILDecoder); + if (!TrySetTargetMethod(context, name.GetUTF8()) + || !TryGeneratedMethodAccessor(context, resolver, methodILDecoder)) + { + return COR_E_MISSINGMETHOD; + } + break; case UnsafeAccessorKind::Field: case UnsafeAccessorKind::StaticField: // Field access requires a target type and return type. if (firstArgType.IsNull() || retType.IsNull()) { - return false; + return COR_E_BADIMAGEFORMAT; } context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField; - return TrySetTargetField(context, name.GetUTF8()) - && TryGeneratedFieldAccessor(context, resolver, methodILDecoder); + if (!TrySetTargetField(context, name.GetUTF8()) + || !TryGeneratedFieldAccessor(context, resolver, methodILDecoder)) + { + return COR_E_MISSINGFIELD; + } + break; default: _ASSERTE(!"Unknown UnsafeAccessorKind"); - return false; + return COR_E_BADIMAGEFORMAT; } + + return S_OK; } PrepareCodeConfig::PrepareCodeConfig() {} From edd7fb7e8c2d77244e53877ee4aaac99b0fdd4aa Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 30 May 2023 12:14:41 +0200 Subject: [PATCH 09/54] Implement IL generation for all accessor paths --- src/coreclr/vm/prestub.cpp | 114 ++++++++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 32 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 982ffadac48a64..f6d78e05cfe9dc 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1104,15 +1104,17 @@ namespace struct GenerationContext final { - GenerationContext(MethodDesc* pMD) - : TargetDeclaration{ pMD } + GenerationContext(UnsafeAccessorKind kind, MethodDesc* pMD) + : Kind{ kind } + , TargetDeclaration{ pMD } , SigReader{ pMD } , TargetType{} - , IsTargetStatic{} + , IsTargetStatic{ false } , TargetMethod{} , TargetField{} { } + UnsafeAccessorKind Kind; MethodDesc* TargetDeclaration; MetaSig SigReader; TypeHandle TargetType; @@ -1123,10 +1125,13 @@ namespace bool TrySetTargetMethod( GenerationContext& cxt, - LPCUTF8 name) + LPCUTF8 methodName) { STANDARD_VM_CONTRACT; - _ASSERTE(name != NULL); + _ASSERTE(methodName != NULL); + _ASSERTE(cxt.Kind == UnsafeAccessorKind::Constructor + || cxt.Kind == UnsafeAccessorKind::Method + || cxt.Kind == UnsafeAccessorKind::StaticMethod); return false; } @@ -1135,6 +1140,7 @@ namespace { STANDARD_VM_CONTRACT; _ASSERTE(!cxt.TargetType.IsNull()); + _ASSERTE(cxt.Kind == UnsafeAccessorKind::Constructor); PTR_MethodTable pMT = cxt.TargetType.AsMethodTable(); @@ -1153,16 +1159,18 @@ namespace bool TrySetTargetField( GenerationContext& cxt, - LPCUTF8 name) + LPCUTF8 fieldName, + TypeHandle fieldType) { STANDARD_VM_CONTRACT; - _ASSERTE(!cxt.TargetType.IsNull()); - _ASSERTE(name != NULL); + _ASSERTE(fieldName != NULL); + _ASSERTE(cxt.Kind == UnsafeAccessorKind::Field + || cxt.Kind == UnsafeAccessorKind::StaticField); return false; } - bool TryGeneratedMethodAccessor( + bool TryGenerateAccessor( GenerationContext& cxt, DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) @@ -1185,7 +1193,47 @@ namespace (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); - pCode->EmitNEWOBJ(pCode->GetToken(cxt.TargetMethod), 0); + + // Load stub arguments. + // When the target is static, the first argument is only + // used to look up the target member to access and ignored + // during dispatch. + UINT beginIndex = cxt.IsTargetStatic ? 1 : 0; + UINT stubArgCount = cxt.SigReader.NumFixedArgs(); + for (UINT i = beginIndex; i < stubArgCount; ++i) + pCode->EmitLDARG(i); + + // Provide access to the target member + UINT targetArgCount = stubArgCount - beginIndex; + UINT targetRetCount = cxt.SigReader.IsReturnTypeVoid() ? 0 : 1; + switch (cxt.Kind) + { + case UnsafeAccessorKind::Constructor: + _ASSERTE(cxt.TargetMethod != NULL); + pCode->EmitNEWOBJ(pCode->GetToken(cxt.TargetMethod), targetArgCount); + break; + case UnsafeAccessorKind::Method: + _ASSERTE(cxt.TargetMethod != NULL); + pCode->EmitCALLVIRT(pCode->GetToken(cxt.TargetMethod), targetArgCount, targetRetCount); + break; + case UnsafeAccessorKind::StaticMethod: + _ASSERTE(cxt.TargetMethod != NULL); + pCode->EmitCALL(pCode->GetToken(cxt.TargetMethod), targetArgCount, targetRetCount); + break; + case UnsafeAccessorKind::Field: + _ASSERTE(cxt.TargetField != NULL); + pCode->EmitLDFLDA(pCode->GetToken(cxt.TargetField)); + break; + case UnsafeAccessorKind::StaticField: + _ASSERTE(cxt.TargetField != NULL); + pCode->EmitLDSFLDA(pCode->GetToken(cxt.TargetField)); + break; + default: + _ASSERTE(!"Unknown UnsafeAccessorKind"); + return false; + } + + // Return from the generated stub pCode->EmitRET(); { @@ -1210,15 +1258,6 @@ namespace ilResolver.SuppressRelease(); return true; } - - bool TryGeneratedFieldAccessor( - GenerationContext& cxt, - DynamicResolver** resolver, - COR_ILMETHOD_DECODER** methodILDecoder) - { - STANDARD_VM_CONTRACT; - return false; - } } HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) @@ -1245,7 +1284,7 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET if (!TryParseUnsafeAccessorAttribute(this, ca, kind, name)) return COR_E_BADIMAGEFORMAT; - GenerationContext context{ this }; + GenerationContext context{ kind, this }; // Parse the signature to determine the type to use: // * Constructor access - examine the return type @@ -1253,18 +1292,19 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // * Static member access - examine type of first parameter TypeHandle retType; TypeHandle firstArgType; - retType = context.SigReader.GetRetTypeHandleNT(); + retType = context.SigReader.GetRetTypeHandleThrowing(); UINT argCount = context.SigReader.NumFixedArgs(); if (argCount > 0) { context.SigReader.NextArg(); - firstArgType = context.SigReader.GetLastTypeHandleNT(); + firstArgType = context.SigReader.GetLastTypeHandleThrowing(); } // Using the kind type, perform the following: - // 1) Resolve the name to the appropriate token type - // 2) Generate the IL for the accessor - switch (kind) + // 1) Validate the basic type information from the signature + // 2) Resolve the name to the appropriate member + // 3) Generate the IL for the accessor + switch (context.Kind) { case UnsafeAccessorKind::Constructor: // A return type is required for a constructor, otherwise @@ -1276,9 +1316,8 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET } context.TargetType = retType; - context.IsTargetStatic = false; if (!TrySetTargetMethodCtor(context) - || !TryGeneratedMethodAccessor(context, resolver, methodILDecoder)) + || !TryGenerateAccessor(context, resolver, methodILDecoder)) { return COR_E_MISSINGMETHOD; } @@ -1293,7 +1332,7 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; if (!TrySetTargetMethod(context, name.GetUTF8()) - || !TryGeneratedMethodAccessor(context, resolver, methodILDecoder)) + || !TryGenerateAccessor(context, resolver, methodILDecoder)) { return COR_E_MISSINGMETHOD; } @@ -1301,16 +1340,27 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET case UnsafeAccessorKind::Field: case UnsafeAccessorKind::StaticField: - // Field access requires a target type and return type. - if (firstArgType.IsNull() || retType.IsNull()) + // Field access requires a single argument for target type and a return type. + if (argCount != 1 || firstArgType.IsNull() || retType.IsNull()) + { + return COR_E_BADIMAGEFORMAT; + } + + // The return type must be byref. + // If the non-static field access is for a + // value type, the instance must be byref. + if (!retType.IsByRef() + || (kind == UnsafeAccessorKind::Field + && firstArgType.IsValueType() + && !firstArgType.IsByRef())) { return COR_E_BADIMAGEFORMAT; } context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField; - if (!TrySetTargetField(context, name.GetUTF8()) - || !TryGeneratedFieldAccessor(context, resolver, methodILDecoder)) + if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam()) + || !TryGenerateAccessor(context, resolver, methodILDecoder)) { return COR_E_MISSINGFIELD; } From 03a9ef306c2912c6dce2ed20ae260ebd06b54198 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 30 May 2023 12:15:28 +0200 Subject: [PATCH 10/54] Implement static/instance field lookup --- src/coreclr/vm/prestub.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index f6d78e05cfe9dc..6f4d9269eb7b53 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1167,6 +1167,26 @@ namespace _ASSERTE(cxt.Kind == UnsafeAccessorKind::Field || cxt.Kind == UnsafeAccessorKind::StaticField); + TypeHandle targetType = cxt.TargetType.IsByRef() + ? cxt.TargetType.GetTypeParam() + : cxt.TargetType; + _ASSERTE(!fieldType.IsByRef()); + + // [TODO] Do we care about EnC scenarios? + ApproxFieldDescIterator fdIterator( + targetType.GetMethodTable(), + (cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS)); + PTR_FieldDesc pField; + while ((pField = fdIterator.Next()) != NULL) + { + // Validate the name and target type match. + if (strcmp(fieldName, pField->GetName()) == 0 + && fieldType == pField->LookupFieldTypeHandle()) + { + cxt.TargetField = pField; + return true; + } + } return false; } From d771f040d4800286a32052fef25955c9a6f163ba Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 30 May 2023 23:50:57 +0200 Subject: [PATCH 11/54] Implement static/instance method lookup --- src/coreclr/inc/formattype.cpp | 4 +- src/coreclr/vm/prestub.cpp | 87 +++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/coreclr/inc/formattype.cpp b/src/coreclr/inc/formattype.cpp index 6a79d4b6e5f354..66b12184edba4d 100644 --- a/src/coreclr/inc/formattype.cpp +++ b/src/coreclr/inc/formattype.cpp @@ -688,8 +688,8 @@ PCCOR_SIGNATURE PrettyPrintType( appendStr(out, "(null)"); } - char sz[32]; - sprintf_s(sz, ARRAY_SIZE(sz), " /* MT: 0x%p */", pMT); + char sz[Max64BitHexString]; + sprintf_s(sz, ARRAY_SIZE(sz), " /* MT: %p */", pMT); appendStr(out, sz); break; } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 6f4d9269eb7b53..4088adf040493f 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1133,6 +1133,89 @@ namespace || cxt.Kind == UnsafeAccessorKind::Method || cxt.Kind == UnsafeAccessorKind::StaticMethod); + TypeHandle targetType = cxt.TargetType.IsByRef() + ? cxt.TargetType.GetTypeParam() + : cxt.TargetType; + + // Following the iteration pattern found in MemberLoader::FindMethod(). + // Reverse order is recommended - see comments in MemberLoader::FindMethod(). + CorElementType declCorType; + CorElementType maybeCorType; + TypeHandle declType; + TypeHandle maybeType; + + // [TODO] Do we care about EnC scenarios? + MethodTable::MethodIterator iter(targetType.GetMethodTable()); + iter.MoveToEnd(); + for (; iter.IsValid(); iter.Prev()) + { + MethodDesc* curr = iter.GetDeclMethodDesc(); + + // Check the target and current method match static/instance state. + if (cxt.IsTargetStatic != (!!curr->IsStatic())) + continue; + + // Check for matching name + if (strcmp(methodName, curr->GetNameThrowing()) != 0) + continue; + + // Reset reading the declaration signature + cxt.SigReader.Reset(); + + MetaSig currSig(curr); + + // Validate calling convention. + if (cxt.SigReader.GetCallingConvention() != currSig.GetCallingConvention()) + continue; + + // Validate the return type and prepare for validating the + // new signature's argument list. + UINT argCount = cxt.SigReader.NumFixedArgs(); + if (cxt.Kind == UnsafeAccessorKind::Constructor) + { + // Constructors must return void. + if (currSig.GetReturnType() != ELEMENT_TYPE_VOID) + continue; + } + else + { + declCorType = cxt.SigReader.GetReturnTypeNormalized(&declType); + maybeCorType = currSig.GetReturnTypeNormalized(&maybeType); + if (declCorType != maybeCorType) + continue; + if (declType != maybeType) + continue; + + // Non-constructor accessors skip the first argument + // when validating the target argument list. + cxt.SigReader.SkipArg(); + argCount--; + } + + // Validate argument count matches. + if (argCount != currSig.NumFixedArgs()) + continue; + + // Validate arguments match. + for (; argCount > 0; --argCount) + { + declCorType = cxt.SigReader.PeekArgNormalized(&declType); + maybeCorType = currSig.PeekArgNormalized(&maybeType); + if (declCorType != maybeCorType) + break; + if (declType != maybeType) + break; + cxt.SigReader.NextArg(); + currSig.NextArg(); + } + + // If we validated all arguments, we have a match. + if (argCount != 0) + continue; + + cxt.TargetMethod = curr; + return true; + } return false; } @@ -1142,7 +1225,7 @@ namespace _ASSERTE(!cxt.TargetType.IsNull()); _ASSERTE(cxt.Kind == UnsafeAccessorKind::Constructor); - PTR_MethodTable pMT = cxt.TargetType.AsMethodTable(); + PTR_MethodTable pMT = cxt.TargetType.GetMethodTable(); // Special case the default constructor case. if (cxt.SigReader.NumFixedArgs() == 0 @@ -1170,7 +1253,7 @@ namespace TypeHandle targetType = cxt.TargetType.IsByRef() ? cxt.TargetType.GetTypeParam() : cxt.TargetType; - _ASSERTE(!fieldType.IsByRef()); + _ASSERTE(!targetType.IsByRef()); // [TODO] Do we care about EnC scenarios? ApproxFieldDescIterator fdIterator( From 02267ff3667721e7fbbab8df5ac361d72182517f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 00:22:12 +0200 Subject: [PATCH 12/54] Alter member names for clarity. --- src/coreclr/vm/prestub.cpp | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 4088adf040493f..1e3ac398f27341 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1106,8 +1106,8 @@ namespace { GenerationContext(UnsafeAccessorKind kind, MethodDesc* pMD) : Kind{ kind } - , TargetDeclaration{ pMD } - , SigReader{ pMD } + , Declaration{ pMD } + , DeclarationSig{ pMD } , TargetType{} , IsTargetStatic{ false } , TargetMethod{} @@ -1115,8 +1115,8 @@ namespace { } UnsafeAccessorKind Kind; - MethodDesc* TargetDeclaration; - MetaSig SigReader; + MethodDesc* Declaration; + MetaSig DeclarationSig; TypeHandle TargetType; bool IsTargetStatic; MethodDesc* TargetMethod; @@ -1137,14 +1137,14 @@ namespace ? cxt.TargetType.GetTypeParam() : cxt.TargetType; - // Following the iteration pattern found in MemberLoader::FindMethod(). - // Reverse order is recommended - see comments in MemberLoader::FindMethod(). CorElementType declCorType; CorElementType maybeCorType; TypeHandle declType; TypeHandle maybeType; // [TODO] Do we care about EnC scenarios? + // Following the iteration pattern found in MemberLoader::FindMethod(). + // Reverse order is recommended - see comments in MemberLoader::FindMethod(). MethodTable::MethodIterator iter(targetType.GetMethodTable()); iter.MoveToEnd(); for (; iter.IsValid(); iter.Prev()) @@ -1160,17 +1160,17 @@ namespace continue; // Reset reading the declaration signature - cxt.SigReader.Reset(); + cxt.DeclarationSig.Reset(); MetaSig currSig(curr); // Validate calling convention. - if (cxt.SigReader.GetCallingConvention() != currSig.GetCallingConvention()) + if (cxt.DeclarationSig.GetCallingConvention() != currSig.GetCallingConvention()) continue; - // Validate the return type and prepare for validating the - // new signature's argument list. - UINT argCount = cxt.SigReader.NumFixedArgs(); + // Validate the return type and prepare for validating + // the current signature's argument list. + UINT argCount = cxt.DeclarationSig.NumFixedArgs(); if (cxt.Kind == UnsafeAccessorKind::Constructor) { // Constructors must return void. @@ -1179,7 +1179,7 @@ namespace } else { - declCorType = cxt.SigReader.GetReturnTypeNormalized(&declType); + declCorType = cxt.DeclarationSig.GetReturnTypeNormalized(&declType); maybeCorType = currSig.GetReturnTypeNormalized(&maybeType); if (declCorType != maybeCorType) continue; @@ -1188,7 +1188,7 @@ namespace // Non-constructor accessors skip the first argument // when validating the target argument list. - cxt.SigReader.SkipArg(); + cxt.DeclarationSig.SkipArg(); argCount--; } @@ -1199,13 +1199,13 @@ namespace // Validate arguments match. for (; argCount > 0; --argCount) { - declCorType = cxt.SigReader.PeekArgNormalized(&declType); + declCorType = cxt.DeclarationSig.PeekArgNormalized(&declType); maybeCorType = currSig.PeekArgNormalized(&maybeType); if (declCorType != maybeCorType) break; if (declType != maybeType) break; - cxt.SigReader.NextArg(); + cxt.DeclarationSig.NextArg(); currSig.NextArg(); } @@ -1228,7 +1228,7 @@ namespace PTR_MethodTable pMT = cxt.TargetType.GetMethodTable(); // Special case the default constructor case. - if (cxt.SigReader.NumFixedArgs() == 0 + if (cxt.DeclarationSig.NumFixedArgs() == 0 && pMT->HasDefaultConstructor()) { cxt.TargetMethod = pMT->GetDefaultConstructor(); @@ -1283,14 +1283,14 @@ namespace NewHolder ilResolver = new ILStubResolver(); // Initialize the resolver target details. - ilResolver->SetStubMethodDesc(cxt.TargetDeclaration); + ilResolver->SetStubMethodDesc(cxt.Declaration); ilResolver->SetStubTargetMethodDesc(cxt.TargetMethod); // [TODO] Handle generics SigTypeContext emptyContext; ILStubLinker sl( - cxt.TargetDeclaration->GetModule(), - cxt.TargetDeclaration->GetSignature(), + cxt.Declaration->GetModule(), + cxt.Declaration->GetSignature(), &emptyContext, cxt.TargetMethod, (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); @@ -1302,13 +1302,13 @@ namespace // used to look up the target member to access and ignored // during dispatch. UINT beginIndex = cxt.IsTargetStatic ? 1 : 0; - UINT stubArgCount = cxt.SigReader.NumFixedArgs(); + UINT stubArgCount = cxt.DeclarationSig.NumFixedArgs(); for (UINT i = beginIndex; i < stubArgCount; ++i) pCode->EmitLDARG(i); // Provide access to the target member UINT targetArgCount = stubArgCount - beginIndex; - UINT targetRetCount = cxt.SigReader.IsReturnTypeVoid() ? 0 : 1; + UINT targetRetCount = cxt.DeclarationSig.IsReturnTypeVoid() ? 0 : 1; switch (cxt.Kind) { case UnsafeAccessorKind::Constructor: @@ -1395,18 +1395,18 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // * Static member access - examine type of first parameter TypeHandle retType; TypeHandle firstArgType; - retType = context.SigReader.GetRetTypeHandleThrowing(); - UINT argCount = context.SigReader.NumFixedArgs(); + retType = context.DeclarationSig.GetRetTypeHandleThrowing(); + UINT argCount = context.DeclarationSig.NumFixedArgs(); if (argCount > 0) { - context.SigReader.NextArg(); - firstArgType = context.SigReader.GetLastTypeHandleThrowing(); + context.DeclarationSig.NextArg(); + firstArgType = context.DeclarationSig.GetLastTypeHandleThrowing(); } // Using the kind type, perform the following: - // 1) Validate the basic type information from the signature - // 2) Resolve the name to the appropriate member - // 3) Generate the IL for the accessor + // 1) Validate the basic type information from the signature. + // 2) Resolve the name to the appropriate member. + // 3) Generate the IL for the accessor. switch (context.Kind) { case UnsafeAccessorKind::Constructor: @@ -1471,7 +1471,7 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET default: _ASSERTE(!"Unknown UnsafeAccessorKind"); - return COR_E_BADIMAGEFORMAT; + return E_UNEXPECTED; } return S_OK; From e911d3a1270d1a462ebb50679ef090ee83a2fed0 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 30 May 2023 21:02:44 -0700 Subject: [PATCH 13/54] Add libraries tests --- .../tests/System.Runtime.Tests.csproj | 1 + .../UnsafeAccessorAttributeTests.cs | 247 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index b4e389485006a6..17cabefaaa32e0 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -259,6 +259,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs new file mode 100644 index 00000000000000..fd88fcb405b188 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs @@ -0,0 +1,247 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +using Xunit; + +// CS0414: The field '' is assigned but its value is never used +#pragma warning disable 0414 + +namespace System.Runtime.CompilerServices.Tests; + +public static class UnsafeAccessorAttributeTests +{ + const string PrivateStatic = nameof(PrivateStatic); + const string Private = nameof(Private); + const string PrivateArg = nameof(PrivateArg); + + class UserDataClass + { + public const string StaticFieldName = nameof(_F); + public const string FieldName = nameof(_f); + public const string StaticMethodName = nameof(_M); + public const string MethodName = nameof(_m); + public const string StaticMethodVoidName = nameof(_MVV); + public const string MethodVoidName = nameof(_mvv); + + private static string _F = PrivateStatic; + private string _f; + + public string Value => _f; + + private UserDataClass(string a) { _f = a; } + private UserDataClass() { _f = Private; } + + private static string _M(string s, ref string sr, in string si) => s; + private string _m(string s, ref string sr, in string si) => s; + + private static void _MVV() {} + private void _mvv() {} + } + + [StructLayout(LayoutKind.Sequential)] + struct UserDataValue + { + public const string StaticFieldName = nameof(_F); + public const string FieldName = nameof(_f); + public const string StaticMethodName = nameof(_M); + public const string MethodName = nameof(_m); + + private static string _F = PrivateStatic; + private string _f; + + public string Value => _f; + + private UserDataValue(string a) { _f = a; } + + // ValueClass are not permitted to define a private default constructor. + public UserDataValue() { _f = Private; } + + private static string _M(string s, ref string sr, in string si) => s; + private string _m(string s, ref string sr, in string si) => s; + } + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static UserDataClass CallPrivateConstructorClass(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static UserDataClass CallPrivateConstructorClass(string a); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static UserDataValue CallPrivateConstructorValue(string a); + + [Fact] + public static void VerifyCallDefaultCtorClass() + { + var local = CallPrivateConstructorClass(); + Assert.Equal(nameof(UserDataClass), local.GetType().Name); + } + + [Fact] + public static void VerifyCallCtorClass() + { + var local = CallPrivateConstructorClass(PrivateArg); + Assert.Equal(nameof(UserDataClass), local.GetType().Name); + Assert.Equal(PrivateArg, local.Value); + } + + [Fact] + public static void VerifyCallCtorValue() + { + var local = CallPrivateConstructorValue(PrivateArg); + Assert.Equal(nameof(UserDataValue), local.GetType().Name); + Assert.Equal(PrivateArg, local.Value); + } + + [Fact] + public static void VerifyAccessStaticFieldClass() + { + Assert.Equal(PrivateStatic, GetPrivateStaticField((UserDataClass)null)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataClass.StaticFieldName)] + extern static ref string GetPrivateStaticField(UserDataClass d); + } + + [Fact] + public static void VerifyAccessFieldClass() + { + var local = CallPrivateConstructorClass(); + Assert.Equal(Private, GetPrivateField(local)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataClass.FieldName)] + extern static ref string GetPrivateField(UserDataClass d); + } + + [Fact] + public static void VerifyAccessStaticFieldValue() + { + Assert.Equal(PrivateStatic, GetPrivateStaticField(new UserDataValue())); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] + extern static ref string GetPrivateStaticField(UserDataValue d); + } + + [Fact] + public static void VerifyAccessFieldValue() + { + UserDataValue local = new(); + Assert.Equal(Private, GetPrivateField(ref local)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static ref string GetPrivateField(ref UserDataValue d); + } + + [Fact] + public static void VerifyAccessStaticMethodClass() + { + var sr = string.Empty; + var si = string.Empty; + Assert.Equal(PrivateStatic, GetPrivateStaticMethod((UserDataClass)null, PrivateStatic, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodName)] + extern static string GetPrivateStaticMethod(UserDataClass d, string s, ref string sr, in string si); + } + + [Fact] + public static void VerifyAccessMethodClass() + { + var sr = string.Empty; + var si = string.Empty; + var local = CallPrivateConstructorClass(); + Assert.Equal(Private, GetPrivateMethod(local, Private, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodName)] + extern static string GetPrivateMethod(UserDataClass d, string s, ref string sr, in string si); + } + + [Fact] + public static void VerifyAccessStaticMethodVoidClass() + { + GetPrivateStaticMethod((UserDataClass)null); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodVoidName)] + extern static void GetPrivateStaticMethod(UserDataClass d); + } + + [Fact] + public static void VerifyAccessMethodVoidClass() + { + var local = CallPrivateConstructorClass(); + GetPrivateMethod(local); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodVoidName)] + extern static void GetPrivateMethod(UserDataClass d); + } + + [Fact] + public static void VerifyAccessStaticMethodValue() + { + var sr = string.Empty; + var si = string.Empty; + Assert.Equal(PrivateStatic, GetPrivateStaticMethod(new UserDataValue(), PrivateStatic, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataValue.StaticMethodName)] + extern static string GetPrivateStaticMethod(UserDataValue d, string s, ref string sr, in string si); + } + + [Fact] + public static void VerifyAccessMethodValue() + { + var sr = string.Empty; + var si = string.Empty; + UserDataValue local = new(); + Assert.Equal(Private, GetPrivateMethod(ref local, Private, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataValue.MethodName)] + extern static string GetPrivateMethod(ref UserDataValue d, string s, ref string sr, in string si); + } + + [Fact] + public static void VerifyInvalidUnsafeAccessor() + { + Assert.Throws(() => MethodNotFound(null)); + Assert.Throws(() => StaticMethodNotFound(null)); + + Assert.Throws(() => FieldNotFound(null)); + Assert.Throws(() => StaticFieldNotFound(null)); + + Assert.Throws(() => FieldReturnMustBeByRefClass((UserDataClass)null)); + Assert.Throws(() => + { + UserDataValue local = new(); + FieldReturnMustBeByRefValue(ref local); + }); + Assert.Throws(() => FieldArgumentMustBeByRef(new UserDataValue())); + + Assert.Throws(() => FieldMustHaveSingleArgument((UserDataClass)null, 0)); + Assert.Throws(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name="_DoesNotExist_")] + extern static void MethodNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="_DoesNotExist_")] + extern static void StaticMethodNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name="_DoesNotExist_")] + extern static ref string FieldNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name="_DoesNotExist_")] + extern static ref string StaticFieldNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static string FieldReturnMustBeByRefClass(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static string FieldReturnMustBeByRefValue(ref UserDataValue d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static ref string FieldArgumentMustBeByRef(UserDataValue d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static ref string FieldMustHaveSingleArgument(UserDataClass d, int a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] + extern static ref string StaticFieldMustHaveSingleArgument(UserDataClass d, int a); + } +} \ No newline at end of file From c6dbb50f023e03bda5482a3b64fca3bc4e713bf5 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 08:14:22 -0700 Subject: [PATCH 14/54] Remove CORINFO_MODULE_ALLACCESS scope --- src/coreclr/vm/dynamicmethod.cpp | 10 ++++++++++ src/coreclr/vm/dynamicmethod.h | 32 +++++++++++++++---------------- src/coreclr/vm/ilstubresolver.cpp | 14 ++++++++++++++ src/coreclr/vm/ilstubresolver.h | 1 + src/coreclr/vm/jitinterface.cpp | 6 +++--- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index 37a3add79e3716..9dc91add571689 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -1104,6 +1104,16 @@ ChunkAllocator* LCGMethodResolver::GetJitMetaHeap() return &m_jitMetaHeap; } +bool LCGMethodResolver::SuppressVisibilityChecks() +{ + LIMITED_METHOD_CONTRACT; + + SecurityControlFlags securityControlFlags; + TypeHandle typeOwner; + GetJitContext(&securityControlFlags, &typeOwner); + return (securityControlFlags & DynamicResolver::SkipVisibilityChecks) == DynamicResolver::SkipVisibilityChecks; +} + BYTE* LCGMethodResolver::GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned *pEHSize) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/dynamicmethod.h b/src/coreclr/vm/dynamicmethod.h index 746f5330ca0293..d56d814e535299 100644 --- a/src/coreclr/vm/dynamicmethod.h +++ b/src/coreclr/vm/dynamicmethod.h @@ -59,6 +59,9 @@ class DynamicResolver TypeHandle *typeOwner) = 0; virtual ChunkAllocator* GetJitMetaHeap() = 0; + // Quick check for SecurityControlFlags::SkipVisibilityChecks. + virtual bool SuppressVisibilityChecks() = 0; + // // code info data virtual BYTE * GetCodeInfo( @@ -121,6 +124,7 @@ class LCGMethodResolver : public DynamicResolver void GetJitContext(SecurityControlFlags * securityControlFlags, TypeHandle * typeOwner); ChunkAllocator* GetJitMetaHeap(); + bool SuppressVisibilityChecks(); BYTE* GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned* pEHSize); SigPointer GetLocalSig(); @@ -334,16 +338,13 @@ inline MethodDesc* GetMethod(CORINFO_METHOD_HANDLE methodHandle) enum CORINFO_MODULE_HANDLE_TYPES { // The module handle is a Module - CORINFO_NORMAL_MODULE = 0, + CORINFO_NORMAL_MODULE = 0, // The module handle is a DynamicResolver - CORINFO_DYNAMIC_MODULE = 1, - - // The module handle permits unrestricted access - CORINFO_MODULE_ALLACCESS = 2, + CORINFO_DYNAMIC_MODULE = 1, }; -#define CORINFO_MODULE_HANDLE_TYPE_MASK (CORINFO_NORMAL_MODULE | CORINFO_DYNAMIC_MODULE | CORINFO_MODULE_ALLACCESS) +#define CORINFO_MODULE_HANDLE_TYPE_MASK (CORINFO_NORMAL_MODULE | CORINFO_DYNAMIC_MODULE) inline bool IsDynamicScope(CORINFO_MODULE_HANDLE module) { @@ -351,20 +352,11 @@ inline bool IsDynamicScope(CORINFO_MODULE_HANDLE module) return !!(CORINFO_DYNAMIC_MODULE & (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK)); } -inline bool IsAllAccessScope(CORINFO_MODULE_HANDLE module) -{ - LIMITED_METHOD_CONTRACT; - return !!(CORINFO_MODULE_ALLACCESS & (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK)); -} - -inline CORINFO_MODULE_HANDLE MakeDynamicScope(DynamicResolver* pResolver, bool permitAllAccess) +inline CORINFO_MODULE_HANDLE MakeDynamicScope(DynamicResolver* pResolver) { LIMITED_METHOD_CONTRACT; CONSISTENCY_CHECK(0 == (((size_t)pResolver) & CORINFO_MODULE_HANDLE_TYPE_MASK)); - uint32_t type = CORINFO_DYNAMIC_MODULE; - if (permitAllAccess) - type |= CORINFO_MODULE_ALLACCESS; - return (CORINFO_MODULE_HANDLE)(((size_t)pResolver) | type); + return (CORINFO_MODULE_HANDLE)(((size_t)pResolver) | CORINFO_DYNAMIC_MODULE); } inline DynamicResolver* GetDynamicResolver(CORINFO_MODULE_HANDLE module) @@ -374,6 +366,12 @@ inline DynamicResolver* GetDynamicResolver(CORINFO_MODULE_HANDLE module) return (DynamicResolver*)(((size_t)module) & ~((size_t)CORINFO_MODULE_HANDLE_TYPE_MASK)); } +inline bool IsAllAccessScope(CORINFO_MODULE_HANDLE module) +{ + LIMITED_METHOD_CONTRACT; + return IsDynamicScope(module) && GetDynamicResolver(module)->SuppressVisibilityChecks(); +} + inline Module* GetModule(CORINFO_MODULE_HANDLE scope) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index c6ecbaa914dc3b..29b5e6641aafcf 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -80,6 +80,20 @@ ChunkAllocator* ILStubResolver::GetJitMetaHeap() return NULL; } +bool ILStubResolver::SuppressVisibilityChecks() +{ + LIMITED_METHOD_CONTRACT; + +#ifdef _DEBUG + SecurityControlFlags securityControlFlags; + TypeHandle typeOwner; + GetJitContext(&securityControlFlags, &typeOwner); + _ASSERTE((securityControlFlags & DynamicResolver::SkipVisibilityChecks) == DynamicResolver::SkipVisibilityChecks); +#endif // _DEBUG + + return true; +} + SigPointer ILStubResolver::GetLocalSig() { diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index fc0d7f7f2773ea..0479dcced233f3 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -26,6 +26,7 @@ class ILStubResolver : DynamicResolver void GetJitContext(SecurityControlFlags* pSecurityControlFlags, TypeHandle* pTypeOwner); ChunkAllocator* GetJitMetaHeap(); + bool SuppressVisibilityChecks(); BYTE* GetCodeInfo(unsigned* pCodeSize, unsigned* pStackSize, CorInfoOptions* pOptions, unsigned* pEHSize); SigPointer GetLocalSig(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 7ccc3c92827958..c74f9bab13d829 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -152,7 +152,7 @@ inline CORINFO_MODULE_HANDLE GetScopeHandle(MethodDesc* method) LIMITED_METHOD_CONTRACT; if (method->IsDynamicMethod()) { - return MakeDynamicScope(method->AsDynamicMethodDesc()->GetResolver(), method->IsILStub()); + return MakeDynamicScope(method->AsDynamicMethodDesc()->GetResolver()); } else { @@ -7740,7 +7740,7 @@ class MethodInfoHelperContext final CORINFO_MODULE_HANDLE CreateScopeHandle() const { _ASSERTE(HasTransientMethodDetails()); - return MakeDynamicScope(TransientResolver, true /* permitAllAccess */); + return MakeDynamicScope(TransientResolver); } TransientMethodDetails CreateTransientMethodDetails() const @@ -7816,7 +7816,7 @@ static HRESULT getMethodInfoHelper( else if (ftn->IsDynamicMethod()) { DynamicResolver* pResolver = ftn->AsDynamicMethodDesc()->GetResolver(); - scopeHnd = MakeDynamicScope(pResolver, ftn->IsILStub()); + scopeHnd = MakeDynamicScope(pResolver); unsigned int EHCount; methInfo->ILCode = pResolver->GetCodeInfo(&methInfo->ILCodeSize, From 9864b4548972668567f5507415e04f13095d8ac2 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 08:36:18 -0700 Subject: [PATCH 15/54] Fix message buffer --- src/coreclr/inc/formattype.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/inc/formattype.cpp b/src/coreclr/inc/formattype.cpp index 66b12184edba4d..64d401ffb4d618 100644 --- a/src/coreclr/inc/formattype.cpp +++ b/src/coreclr/inc/formattype.cpp @@ -688,8 +688,9 @@ PCCOR_SIGNATURE PrettyPrintType( appendStr(out, "(null)"); } - char sz[Max64BitHexString]; - sprintf_s(sz, ARRAY_SIZE(sz), " /* MT: %p */", pMT); + const char fmt[] = " /* MT: %p */"; + char sz[Max64BitHexString + ARRAY_SIZE(fmt)]; + sprintf_s(sz, ARRAY_SIZE(sz), fmt, pMT); appendStr(out, sz); break; } From 60c38dd073b6c88c35d52c0d0988af26a18559f5 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 11:57:50 -0700 Subject: [PATCH 16/54] Throw exceptions instead of returning HRESULT. --- src/coreclr/vm/jitinterface.cpp | 26 ++++++++++---------------- src/coreclr/vm/method.hpp | 2 +- src/coreclr/vm/prestub.cpp | 24 ++++++++++++------------ 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index c74f9bab13d829..0c78c3625ef7b9 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7750,7 +7750,7 @@ class MethodInfoHelperContext final } }; -static HRESULT getMethodInfoHelper( +static void getMethodInfoHelper( MethodInfoHelperContext& cxt, CORINFO_METHOD_INFO* methInfo) { @@ -7829,13 +7829,8 @@ static HRESULT getMethodInfoHelper( } else { - HRESULT hr = ftn->GenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header); - if (FAILED(hr)) - { - if (hr == E_FAIL) - hr = COR_E_BADIMAGEFORMAT; - return hr; - } + if (!ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header)) + ThrowHR(COR_E_BADIMAGEFORMAT); scopeHnd = cxt.CreateScopeHandle(); @@ -7913,8 +7908,6 @@ static HRESULT getMethodInfoHelper( &context, CONV_TO_JITSIG_FLAGS_LOCALSIG, &methInfo->locals); - - return S_OK; } // getMethodInfoHelper //--------------------------------------------------------------------------------------- @@ -7940,13 +7933,15 @@ CEEInfo::getMethodInfo( MethodInfoHelperContext cxt{ ftn }; if (ftn->IsDynamicMethod()) { - result = SUCCEEDED(getMethodInfoHelper(cxt, methInfo)); + getMethodInfoHelper(cxt, methInfo); + result = true; } else if (!ftn->IsWrapperStub() && ftn->HasILHeader()) { COR_ILMETHOD_DECODER header(ftn->GetILHeader(TRUE), ftn->GetMDImport(), NULL); cxt.Header = &header; - result = SUCCEEDED(getMethodInfoHelper(cxt, methInfo)); + getMethodInfoHelper(cxt, methInfo); + result = true; } else if (ftn->IsIL() && ftn->GetRVA() == 0) { @@ -7959,7 +7954,8 @@ CEEInfo::getMethodInfo( cxt.TransientResolver = GetDynamicResolver(detailsMaybe->Scope); } - result = SUCCEEDED(getMethodInfoHelper(cxt, methInfo)); + getMethodInfoHelper(cxt, methInfo); + result = true; } if (result) @@ -13064,11 +13060,9 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, } #endif // _DEBUG - HRESULT hr; MethodInfoHelperContext cxt{ ftn, ILHeader }; CORINFO_METHOD_INFO methodInfo; - if (FAILED(hr = getMethodInfoHelper(cxt, &methodInfo))) - ThrowHR(hr); + getMethodInfoHelper(cxt, &methodInfo); // If it's generic then we can only enter through an instantiated MethodDesc _ASSERTE(!ftn->IsGenericMethodDefinition()); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 32710a520a69f8..a2cfec402a4ceb 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1818,7 +1818,7 @@ class MethodDesc PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); public: - HRESULT GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); + bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); #endif // DACCESS_COMPILE #ifdef HAVE_GCCOVER diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 1e3ac398f27341..3be07d85de7ce5 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1363,7 +1363,7 @@ namespace } } -HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) { STANDARD_VM_CONTRACT; _ASSERTE(resolver != NULL); @@ -1378,14 +1378,14 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET ULONG dataLen; HRESULT hr = GetCustomAttribute(WellKnownAttribute::UnsafeAccessorAttribute, &data, &dataLen); if (hr != S_OK) - return E_FAIL; + return false; UnsafeAccessorKind kind; SString name; CustomAttributeParser ca(data, dataLen); if (!TryParseUnsafeAccessorAttribute(this, ca, kind, name)) - return COR_E_BADIMAGEFORMAT; + ThrowHR(COR_E_BADIMAGEFORMAT); GenerationContext context{ kind, this }; @@ -1415,14 +1415,14 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // The name is defined by the runtime and should be empty. if (retType.IsNull() || !name.IsEmpty()) { - return COR_E_BADIMAGEFORMAT; + ThrowHR(COR_E_BADIMAGEFORMAT); } context.TargetType = retType; if (!TrySetTargetMethodCtor(context) || !TryGenerateAccessor(context, resolver, methodILDecoder)) { - return COR_E_MISSINGMETHOD; + ThrowHR(COR_E_MISSINGMETHOD); } break; @@ -1430,14 +1430,14 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET case UnsafeAccessorKind::StaticMethod: // Method access requires a target type. if (firstArgType.IsNull()) - return COR_E_BADIMAGEFORMAT; + ThrowHR(COR_E_BADIMAGEFORMAT); context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; if (!TrySetTargetMethod(context, name.GetUTF8()) || !TryGenerateAccessor(context, resolver, methodILDecoder)) { - return COR_E_MISSINGMETHOD; + ThrowHR(COR_E_MISSINGMETHOD); } break; @@ -1446,7 +1446,7 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // Field access requires a single argument for target type and a return type. if (argCount != 1 || firstArgType.IsNull() || retType.IsNull()) { - return COR_E_BADIMAGEFORMAT; + ThrowHR(COR_E_BADIMAGEFORMAT); } // The return type must be byref. @@ -1457,7 +1457,7 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET && firstArgType.IsValueType() && !firstArgType.IsByRef())) { - return COR_E_BADIMAGEFORMAT; + ThrowHR(COR_E_BADIMAGEFORMAT); } context.TargetType = firstArgType; @@ -1465,16 +1465,16 @@ HRESULT MethodDesc::GenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam()) || !TryGenerateAccessor(context, resolver, methodILDecoder)) { - return COR_E_MISSINGFIELD; + ThrowHR(COR_E_MISSINGFIELD); } break; default: _ASSERTE(!"Unknown UnsafeAccessorKind"); - return E_UNEXPECTED; + ThrowHR(E_UNEXPECTED); } - return S_OK; + return true; } PrepareCodeConfig::PrepareCodeConfig() {} From 10b9c75f359fc6b22be6ef366533beb09a7a573d Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 13:29:43 -0700 Subject: [PATCH 17/54] Update visibility check API --- src/coreclr/vm/dynamicmethod.cpp | 8 ++------ src/coreclr/vm/dynamicmethod.h | 19 ++++++++++++++----- src/coreclr/vm/ilstubresolver.cpp | 6 ++++-- src/coreclr/vm/ilstubresolver.h | 2 +- src/coreclr/vm/jitinterface.cpp | 10 ++++------ 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index 9dc91add571689..cfba23e332e99f 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -1104,14 +1104,10 @@ ChunkAllocator* LCGMethodResolver::GetJitMetaHeap() return &m_jitMetaHeap; } -bool LCGMethodResolver::SuppressVisibilityChecks() +bool LCGMethodResolver::RequiresSuppressVisibilityChecks() { LIMITED_METHOD_CONTRACT; - - SecurityControlFlags securityControlFlags; - TypeHandle typeOwner; - GetJitContext(&securityControlFlags, &typeOwner); - return (securityControlFlags & DynamicResolver::SkipVisibilityChecks) == DynamicResolver::SkipVisibilityChecks; + return true; } BYTE* LCGMethodResolver::GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned *pEHSize) diff --git a/src/coreclr/vm/dynamicmethod.h b/src/coreclr/vm/dynamicmethod.h index d56d814e535299..881d74ec113e1e 100644 --- a/src/coreclr/vm/dynamicmethod.h +++ b/src/coreclr/vm/dynamicmethod.h @@ -59,8 +59,12 @@ class DynamicResolver TypeHandle *typeOwner) = 0; virtual ChunkAllocator* GetJitMetaHeap() = 0; - // Quick check for SecurityControlFlags::SkipVisibilityChecks. - virtual bool SuppressVisibilityChecks() = 0; + // API to check if visibility checks are needed. + // If the API requires checks, the callers should use + // the potentially expensive GetJitContext() API. + // If it returns "false", there is no need for any + // further visibility checks. + virtual bool RequiresSuppressVisibilityChecks() = 0; // // code info data @@ -124,7 +128,7 @@ class LCGMethodResolver : public DynamicResolver void GetJitContext(SecurityControlFlags * securityControlFlags, TypeHandle * typeOwner); ChunkAllocator* GetJitMetaHeap(); - bool SuppressVisibilityChecks(); + bool RequiresSuppressVisibilityChecks(); BYTE* GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned* pEHSize); SigPointer GetLocalSig(); @@ -366,10 +370,15 @@ inline DynamicResolver* GetDynamicResolver(CORINFO_MODULE_HANDLE module) return (DynamicResolver*)(((size_t)module) & ~((size_t)CORINFO_MODULE_HANDLE_TYPE_MASK)); } -inline bool IsAllAccessScope(CORINFO_MODULE_HANDLE module) +inline bool RequiresAccessCheck(CORINFO_MODULE_HANDLE module) { LIMITED_METHOD_CONTRACT; - return IsDynamicScope(module) && GetDynamicResolver(module)->SuppressVisibilityChecks(); + + // Non-dynamic scopes always require access checks. + if (!IsDynamicScope(module)) + return true; + + return GetDynamicResolver(module)->RequiresSuppressVisibilityChecks(); } inline Module* GetModule(CORINFO_MODULE_HANDLE scope) diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 29b5e6641aafcf..d2594228646097 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -80,7 +80,7 @@ ChunkAllocator* ILStubResolver::GetJitMetaHeap() return NULL; } -bool ILStubResolver::SuppressVisibilityChecks() +bool ILStubResolver::RequiresSuppressVisibilityChecks() { LIMITED_METHOD_CONTRACT; @@ -88,10 +88,12 @@ bool ILStubResolver::SuppressVisibilityChecks() SecurityControlFlags securityControlFlags; TypeHandle typeOwner; GetJitContext(&securityControlFlags, &typeOwner); + + // Verify we can return false below because we skip visibility checks _ASSERTE((securityControlFlags & DynamicResolver::SkipVisibilityChecks) == DynamicResolver::SkipVisibilityChecks); #endif // _DEBUG - return true; + return false; } SigPointer diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index 0479dcced233f3..5491e41d6fe644 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -26,7 +26,7 @@ class ILStubResolver : DynamicResolver void GetJitContext(SecurityControlFlags* pSecurityControlFlags, TypeHandle* pTypeOwner); ChunkAllocator* GetJitMetaHeap(); - bool SuppressVisibilityChecks(); + bool RequiresSuppressVisibilityChecks(); BYTE* GetCodeInfo(unsigned* pCodeSize, unsigned* pStackSize, CorInfoOptions* pOptions, unsigned* pEHSize); SigPointer GetLocalSig(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 0c78c3625ef7b9..4f3f7f9be1ba25 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -161,7 +161,7 @@ inline CORINFO_MODULE_HANDLE GetScopeHandle(MethodDesc* method) } //This is common refactored code from within several of the access check functions. -BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver, +static BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver, TypeHandle *pOwnerTypeForSecurity, AccessCheckOptions::AccessCheckType *pAccessCheckType, DynamicResolver** ppAccessContext) @@ -5474,7 +5474,7 @@ void CEEInfo::getCallInfo( pResult->accessAllowed = CORINFO_ACCESS_ALLOWED; MethodDesc* callerMethod = (MethodDesc*)callerHandle; if ((flags & CORINFO_CALLINFO_SECURITYCHECKS) - && !(IsAllAccessScope(pResolvedToken->tokenScope))) + && RequiresAccessCheck(pResolvedToken->tokenScope)) { //Our type system doesn't always represent the target exactly with the MethodDesc. In all cases, //carry around the parent MethodTable for both Caller and Callee. @@ -5516,8 +5516,7 @@ void CEEInfo::getCallInfo( // is not part of instantiation. We have to special case it. pCalleeForSecurity = calleeTypeForSecurity.GetMethodTable()->GetParallelMethodDesc(pCalleeForSecurity); } - else - if (pResolvedToken->pMethodSpec != NULL) + else if (pResolvedToken->pMethodSpec != NULL) { uint32_t nGenericMethodArgs = 0; CQuickBytes qbGenericMethodArgs; @@ -5550,8 +5549,7 @@ void CEEInfo::getCallInfo( pCalleeForSecurity = MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, calleeTypeForSecurity.GetMethodTable(), FALSE, Instantiation(genericMethodArgs, nGenericMethodArgs), FALSE); } - else - if (pResolvedToken->pTypeSpec != NULL) + else if (pResolvedToken->pTypeSpec != NULL) { pCalleeForSecurity = MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, calleeTypeForSecurity.GetMethodTable(), FALSE, Instantiation(), TRUE); } From 3e9ee12a7ae95feb271117d75dd91271176dfab7 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 13:44:35 -0700 Subject: [PATCH 18/54] Remove enum METHOD_TYPE. --- src/coreclr/vm/methodtablebuilder.cpp | 56 +++++++++++++-------------- src/coreclr/vm/methodtablebuilder.h | 31 +++------------ src/coreclr/vm/methodtablebuilder.inl | 23 +---------- 3 files changed, 34 insertions(+), 76 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 3f33c667982a59..0f69147a431598 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1007,7 +1007,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( DWORD dwDeclAttrs, DWORD dwImplAttrs, DWORD dwRVA, - METHOD_TYPE type, + MethodClassification type, METHOD_IMPL_TYPE implType) : m_pOwningType(pOwningType), m_dwDeclAttrs(dwDeclAttrs), @@ -2698,7 +2698,7 @@ MethodTableBuilder::EnumerateClassMethods() { ULONG dwMethodRVA; DWORD dwImplFlags; - METHOD_TYPE type; + MethodClassification type; METHOD_IMPL_TYPE implType; LPSTR strMethodName; @@ -3126,31 +3126,31 @@ MethodTableBuilder::EnumerateClassMethods() { // ComImport classes have methods which are just used // for implementing all interfaces the class supports - type = METHOD_TYPE_COMINTEROP; + type = mcComInterop; // constructor is special if (IsMdRTSpecialName(dwMemberAttrs)) { // Note: Method name (.ctor) will be checked in code:ValidateMethods - type = METHOD_TYPE_FCALL; + type = mcFCall; } } else #endif //FEATURE_COMINTEROP if (dwMethodRVA == 0) { - type = METHOD_TYPE_FCALL; + type = mcFCall; } else { - type = METHOD_TYPE_NDIRECT; + type = mcNDirect; } } // The NAT_L attribute is present, marking this method as NDirect else { CONSISTENCY_CHECK(hr == S_OK); - type = METHOD_TYPE_NDIRECT; + type = mcNDirect; } } else if (IsMiRuntime(dwImplFlags)) @@ -3170,7 +3170,7 @@ MethodTableBuilder::EnumerateClassMethods() BuildMethodTableThrowException(BFA_BAD_FLAGS_ON_DELEGATE); } newDelegateMethodSeen = SeenCtor; - type = METHOD_TYPE_FCALL; + type = mcFCall; } else { @@ -3184,7 +3184,7 @@ MethodTableBuilder::EnumerateClassMethods() { BuildMethodTableThrowException(BFA_UNKNOWN_DELEGATE_METHOD); } - type = METHOD_TYPE_EEIMPL; + type = mcEEImpl; } // If we get here we have either set newDelegateMethodSeen or we have thrown a BMT exception @@ -3200,7 +3200,7 @@ MethodTableBuilder::EnumerateClassMethods() else if (numGenericMethodArgs != 0) { //We use an instantiated method desc to represent a generic method - type = METHOD_TYPE_INSTANTIATED; + type = mcInstantiated; } else if (fIsClassInterface) { @@ -3208,18 +3208,18 @@ MethodTableBuilder::EnumerateClassMethods() if (IsMdStatic(dwMemberAttrs)) { // Static methods in interfaces need nothing special. - type = METHOD_TYPE_NORMAL; + type = mcIL; } else if (bmtGenerics->GetNumGenericArgs() != 0 && (bmtGenerics->fSharedByGenericInstantiations)) { // Methods in instantiated interfaces need nothing special - they are not visible from COM etc. - type = METHOD_TYPE_NORMAL; + type = mcIL; } else if (bmtProp->fIsMngStandardItf) { // If the interface is a standard managed interface then allocate space for an FCall method desc. - type = METHOD_TYPE_FCALL; + type = mcFCall; } else if (IsMdAbstract(dwMemberAttrs)) { @@ -3227,21 +3227,21 @@ MethodTableBuilder::EnumerateClassMethods() // accessed via COM interop. mcComInterop MDs have an additional // pointer-sized field pointing to COM interop data which are // allocated lazily when/if the MD actually gets used for interop. - type = METHOD_TYPE_COMINTEROP; + type = mcComInterop; } else #endif // !FEATURE_COMINTEROP { - type = METHOD_TYPE_NORMAL; + type = mcIL; } } else { - type = METHOD_TYPE_NORMAL; + type = mcIL; } - // Generic methods should always be METHOD_TYPE_INSTANTIATED - if ((numGenericMethodArgs != 0) && (type != METHOD_TYPE_INSTANTIATED)) + // Generic methods should always be mcInstantiated + if ((numGenericMethodArgs != 0) && (type != mcInstantiated)) { BuildMethodTableThrowException(BFA_GENERIC_METHODS_INST); } @@ -5028,7 +5028,7 @@ MethodTableBuilder::ValidateMethods() } // Make sure that fcalls have a 0 rva. This is assumed by the prejit fixup logic - if (it.MethodType() == METHOD_TYPE_FCALL && it.RVA() != 0) + if (it.MethodType() == mcFCall && it.RVA() != 0) { BuildMethodTableThrowException(BFA_ECALLS_MUST_HAVE_ZERO_RVA, it.Token()); } @@ -5065,7 +5065,7 @@ MethodTableBuilder::ValidateMethods() { BuildMethodTableThrowException(IDS_CLASSLOAD_BAD_UNMANAGED_RVA, it.Token()); } - if (it.MethodType() != METHOD_TYPE_NDIRECT) + if (it.MethodType() != mcNDirect) { BuildMethodTableThrowException(BFA_BAD_UNMANAGED_ENTRY_POINT); } @@ -5073,7 +5073,7 @@ MethodTableBuilder::ValidateMethods() // Vararg methods are not allowed inside generic classes // and nor can they be generic methods. - if (bmtGenerics->GetNumGenericArgs() > 0 || (it.MethodType() == METHOD_TYPE_INSTANTIATED) ) + if (bmtGenerics->GetNumGenericArgs() > 0 || (it.MethodType() == mcInstantiated) ) { DWORD cMemberSignature; PCCOR_SIGNATURE pMemberSignature = it.GetSig(&cMemberSignature); @@ -5129,7 +5129,7 @@ MethodTableBuilder::InitNewMethodDesc( // // First, set all flags that control layout of optional slots // - pNewMD->SetClassification(GetMethodClassification(pMethod->GetMethodType())); + pNewMD->SetClassification(pMethod->GetMethodType()); if (pMethod->GetMethodImplType() == METHOD_IMPL) pNewMD->SetHasMethodImplSlot(); @@ -5169,7 +5169,7 @@ MethodTableBuilder::InitNewMethodDesc( // Do the init specific to each classification of MethodDesc & assign some common fields InitMethodDesc(pNewMD, - GetMethodClassification(pMethod->GetMethodType()), + pMethod->GetMethodType(), pMethod->GetMethodSignature().GetToken(), pMethod->GetImplAttrs(), pMethod->GetDeclAttrs(), @@ -5290,7 +5290,7 @@ MethodTableBuilder::PlaceNonVirtualMethods() #endif // _DEBUG if (!fCanHaveNonVtableSlots || - it->GetMethodType() == METHOD_TYPE_INSTANTIATED) + it->GetMethodType() == mcInstantiated) { // We use slot during remoting and to map methods between generic instantiations // (see MethodTable::GetParallelMethodDesc). The current implementation @@ -6936,7 +6936,7 @@ VOID MethodTableBuilder::AllocAndInitMethodDescs() // the code will still work with small performance penalty (method desc chunk layout will be less efficient). _ASSERTE(tokenRange >= currentTokenRange); - SIZE_T size = MethodDesc::GetBaseSize(GetMethodClassification(it->GetMethodType())); + SIZE_T size = MethodDesc::GetBaseSize(it->GetMethodType()); // Add size of optional slots @@ -7112,7 +7112,7 @@ MethodTableBuilder::NeedsTightlyBoundUnboxingStub(bmtMDMethod * pMDMethod) return IsValueClass() && !IsMdStatic(pMDMethod->GetDeclAttrs()) && IsMdVirtual(pMDMethod->GetDeclAttrs()) && - (pMDMethod->GetMethodType() != METHOD_TYPE_INSTANTIATED) && + (pMDMethod->GetMethodType() != mcInstantiated) && !IsMdRTSpecialName(pMDMethod->GetDeclAttrs()); } @@ -7131,7 +7131,7 @@ MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod) // for tiering currently to avoid some unnecessary overhead (g_pConfig->TieredCompilation_QuickJit() || GetModule()->IsReadyToRun()) && - (pMDMethod->GetMethodType() == METHOD_TYPE_NORMAL || pMDMethod->GetMethodType() == METHOD_TYPE_INSTANTIATED)) + (pMDMethod->GetMethodType() == mcIL || pMDMethod->GetMethodType() == mcInstantiated)) #ifdef FEATURE_REJIT || @@ -7139,7 +7139,7 @@ MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod) // Methods that are R2R need precode if ReJIT is enabled. Keep this in sync with MethodDesc::IsEligibleForReJIT() (ReJitManager::IsReJITEnabled() && - GetMethodClassification(pMDMethod->GetMethodType()) == mcIL && + pMDMethod->GetMethodType() == mcIL && !GetModule()->IsCollectible() && diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 6c82a78855f07d..68beb4483dae31 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -118,22 +118,6 @@ class MethodTableBuilder METHOD_IMPL }; - enum METHOD_TYPE - { - // The values of the enum are in sync with MethodClassification. - // GetMethodClassification depends on this - METHOD_TYPE_NORMAL = 0, - METHOD_TYPE_FCALL = 1, - METHOD_TYPE_NDIRECT = 2, - METHOD_TYPE_EEIMPL = 3, - METHOD_TYPE_ARRAY = 4, - METHOD_TYPE_INSTANTIATED = 5, -#ifdef FEATURE_COMINTEROP - METHOD_TYPE_COMINTEROP = 6, -#endif - METHOD_TYPE_DYNAMIC = 7 - }; - private: // Determine if this is the special SIMD type System.Numerics.Vector, and set its size. BOOL CheckIfSIMDAndUpdateSize(); @@ -922,14 +906,14 @@ class MethodTableBuilder // Constructor. This takes all the information already extracted from metadata interface // because the place that creates these types already has this data. Alternatively, // a constructor could be written to take a token and metadata scope instead. Also, - // it might be interesting to move METHOD_TYPE and METHOD_IMPL_TYPE to setter functions. + // it might be interesting to move MethodClassification and METHOD_IMPL_TYPE to setter functions. bmtMDMethod( bmtMDType * pOwningType, mdMethodDef tok, DWORD dwDeclAttrs, DWORD dwImplAttrs, DWORD dwRVA, - METHOD_TYPE type, + MethodClassification type, METHOD_IMPL_TYPE implType); //----------------------------------------------------------------------------------------- @@ -958,7 +942,7 @@ class MethodTableBuilder //----------------------------------------------------------------------------------------- // Returns the method type (normal, fcall, etc.) that this type was constructed with. - METHOD_TYPE + MethodClassification GetMethodType() const { LIMITED_METHOD_CONTRACT; return m_type; } @@ -1039,7 +1023,7 @@ class MethodTableBuilder DWORD m_dwDeclAttrs; DWORD m_dwImplAttrs; DWORD m_dwRVA; - METHOD_TYPE m_type; // Specific MethodDesc flavour + MethodClassification m_type; // Specific MethodDesc flavour METHOD_IMPL_TYPE m_implType; // Whether or not the method is a methodImpl body MethodSignature m_methodSig; @@ -2281,7 +2265,7 @@ class MethodTableBuilder inline PCCOR_SIGNATURE GetSig(DWORD *pcbSig); inline METHOD_IMPL_TYPE MethodImpl(); inline BOOL IsMethodImpl(); - inline METHOD_TYPE MethodType(); + inline MethodClassification MethodType(); inline bmtMDMethod *GetMDMethod() const; inline MethodDesc *GetIntroducingMethodDesc(); inline bmtMDMethod * operator->(); @@ -2635,11 +2619,6 @@ class MethodTableBuilder COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature)); - // -------------------------------------------------------------------------------------------- - // Convert code:MethodTableBuilder::METHOD_TYPE to code:MethodClassification - static DWORD - GetMethodClassification(METHOD_TYPE type); - // -------------------------------------------------------------------------------------------- // Essentially, this is a helper method that combines calls to InitMethodDesc and // SetSecurityFlagsOnMethod. It then assigns the newly initialized MethodDesc to diff --git a/src/coreclr/vm/methodtablebuilder.inl b/src/coreclr/vm/methodtablebuilder.inl index 5a6cc75d1e12d9..6e55c063db53fa 100644 --- a/src/coreclr/vm/methodtablebuilder.inl +++ b/src/coreclr/vm/methodtablebuilder.inl @@ -121,7 +121,7 @@ inline BOOL MethodTableBuilder::DeclaredMethodIterator::IsMethodImpl() } //*************************************************************************************** -inline MethodTableBuilder::METHOD_TYPE MethodTableBuilder::DeclaredMethodIterator::MethodType() +inline MethodClassification MethodTableBuilder::DeclaredMethodIterator::MethodType() { LIMITED_METHOD_CONTRACT; return GetMDMethod()->GetMethodType(); @@ -502,26 +502,5 @@ MethodTableBuilder::bmtMDMethod::SetUnboxedSlotIndex(SLOT_INDEX idx) CONSISTENCY_CHECK(m_pUnboxedMD == NULL); m_unboxedSlotIndex = idx; } - -//*************************************************************************************** -inline DWORD -MethodTableBuilder::GetMethodClassification(MethodTableBuilder::METHOD_TYPE type) -{ - LIMITED_METHOD_CONTRACT; - // Verify that the enums are in sync, so we can do the conversion by simple cast. - C_ASSERT((DWORD)METHOD_TYPE_NORMAL == (DWORD)mcIL); - C_ASSERT((DWORD)METHOD_TYPE_FCALL == (DWORD)mcFCall); - C_ASSERT((DWORD)METHOD_TYPE_NDIRECT == (DWORD)mcNDirect); - C_ASSERT((DWORD)METHOD_TYPE_EEIMPL == (DWORD)mcEEImpl); - C_ASSERT((DWORD)METHOD_TYPE_ARRAY == (DWORD)mcArray); - C_ASSERT((DWORD)METHOD_TYPE_INSTANTIATED == (DWORD)mcInstantiated); -#ifdef FEATURE_COMINTEROP - C_ASSERT((DWORD)METHOD_TYPE_COMINTEROP == (DWORD)mcComInterop); -#endif - C_ASSERT((DWORD)METHOD_TYPE_DYNAMIC == (DWORD)mcDynamic); - - return (DWORD)type; -} - #endif // _METHODTABLEBUILDER_INL_ From 62f9e8120dd0b3a674c15cbc45d42fcd5ccc30d1 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 13:48:04 -0700 Subject: [PATCH 19/54] Disable tests on Mono --- .../UnsafeAccessorAttributeTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs index fd88fcb405b188..df289b5e3baa1e 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs @@ -72,6 +72,7 @@ struct UserDataValue extern static UserDataValue CallPrivateConstructorValue(string a); [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyCallDefaultCtorClass() { var local = CallPrivateConstructorClass(); @@ -79,6 +80,7 @@ public static void VerifyCallDefaultCtorClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyCallCtorClass() { var local = CallPrivateConstructorClass(PrivateArg); @@ -87,6 +89,7 @@ public static void VerifyCallCtorClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyCallCtorValue() { var local = CallPrivateConstructorValue(PrivateArg); @@ -95,6 +98,7 @@ public static void VerifyCallCtorValue() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessStaticFieldClass() { Assert.Equal(PrivateStatic, GetPrivateStaticField((UserDataClass)null)); @@ -104,6 +108,7 @@ public static void VerifyAccessStaticFieldClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessFieldClass() { var local = CallPrivateConstructorClass(); @@ -114,6 +119,7 @@ public static void VerifyAccessFieldClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessStaticFieldValue() { Assert.Equal(PrivateStatic, GetPrivateStaticField(new UserDataValue())); @@ -123,6 +129,7 @@ public static void VerifyAccessStaticFieldValue() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessFieldValue() { UserDataValue local = new(); @@ -133,6 +140,7 @@ public static void VerifyAccessFieldValue() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessStaticMethodClass() { var sr = string.Empty; @@ -144,6 +152,7 @@ public static void VerifyAccessStaticMethodClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessMethodClass() { var sr = string.Empty; @@ -156,6 +165,7 @@ public static void VerifyAccessMethodClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessStaticMethodVoidClass() { GetPrivateStaticMethod((UserDataClass)null); @@ -165,6 +175,7 @@ public static void VerifyAccessStaticMethodVoidClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessMethodVoidClass() { var local = CallPrivateConstructorClass(); @@ -175,6 +186,7 @@ public static void VerifyAccessMethodVoidClass() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessStaticMethodValue() { var sr = string.Empty; @@ -186,6 +198,7 @@ public static void VerifyAccessStaticMethodValue() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessMethodValue() { var sr = string.Empty; @@ -198,6 +211,7 @@ public static void VerifyAccessMethodValue() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyInvalidUnsafeAccessor() { Assert.Throws(() => MethodNotFound(null)); From a5066015758bcbae488f6cc32467abcba2015e8a Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 31 May 2023 14:55:09 -0700 Subject: [PATCH 20/54] Missing virtual destructor --- src/coreclr/vm/dynamicmethod.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/vm/dynamicmethod.h b/src/coreclr/vm/dynamicmethod.h index 881d74ec113e1e..dca409b7212db6 100644 --- a/src/coreclr/vm/dynamicmethod.h +++ b/src/coreclr/vm/dynamicmethod.h @@ -52,6 +52,7 @@ class DynamicResolver CanSkipCSEvaluation = 0x8, }; + virtual ~DynamicResolver() { } // set up and clean up for jitting virtual void FreeCompileTimeState() = 0; From b8c94bfbd8382e63ff083b97c21ff51ba6a27e40 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 1 Jun 2023 11:18:13 -0700 Subject: [PATCH 21/54] Apply suggestions from code review Co-authored-by: Stephen Toub --- .../UnsafeAccessorAttribute.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs index 964900dc87e5e6..36162f6200da8c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -4,42 +4,41 @@ namespace System.Runtime.CompilerServices { /// - /// The kind of target an is - /// providing access. + /// Specifies the kind of target to which an is providing access. /// public enum UnsafeAccessorKind { /// - /// Provide access to a Constructor. + /// Provide access to a constructor. /// Constructor, /// - /// Provide access to a Method. + /// Provide access to a method. /// Method, /// - /// Provide access to a static Method. + /// Provide access to a static method. /// StaticMethod, /// - /// Provide access to a Field. + /// Provide access to a field. /// Field, /// - /// Provide access to a static Field. + /// Provide access to a static field. /// StaticField }; /// - /// Provide access to an unaccessible API on a specific type. + /// Provides access to an inaccessible member of a specific type. /// /// - /// This attribute should be applied on a extern static method. + /// This attribute may be applied to an extern static method. /// The implementation of the extern static method annotated with /// this attribute will be provided by the runtime based on the information in /// the attribute and the signature of the method that the attribute is applied to. @@ -47,14 +46,14 @@ public enum UnsafeAccessorKind /// to it. If the matching method or field is not found, the body of the extern /// method will throw or . /// - /// For / and - /// /, the type of + /// For , , + /// , and , the type of /// the first argument of the annotated extern method identifies the owning type. /// The value of the first argument is treated as this pointer for instance fields and methods. /// The first argument must be passed as ref for instance fields and methods on structs. /// The value of the first argument is not used by the implementation for static fields and methods. /// - /// The generic parameters of the extern static method are concatenation of the type and + /// The generic parameters of the extern static method are a concatenation of the type and /// method generic arguments of the target method. For example, /// extern static void Method1<T1, T2>(Class1<T1> @this) /// can be used to call Class1<T1>.Method1<T2>(). The generic constraints of the From 636e616abd415c5c8ee2d2ba5dceb986412875ac Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 1 Jun 2023 11:19:17 -0700 Subject: [PATCH 22/54] Apply suggestions from code review Co-authored-by: Stephen Toub --- .../Runtime/CompilerServices/UnsafeAccessorAttribute.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs index 36162f6200da8c..7b12f54ca0296f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -65,19 +65,19 @@ public enum UnsafeAccessorKind public sealed class UnsafeAccessorAttribute : Attribute { /// - /// Instantiates an providing access to an API of kind . + /// Instantiates an providing access to a member of kind . /// - /// The kind of the target to provide access. + /// The kind of the target to which access is provided. public UnsafeAccessorAttribute(UnsafeAccessorKind kind) => Kind = kind; /// - /// Kind of API to provide access. + /// Gets the kind of member to which access is provided. /// public UnsafeAccessorKind Kind { get; } /// - /// Name of the API to provide access. + /// Gets or sets the name of the member to which access is provided. /// /// /// The name defaults to the annotated method name if not specified. From 72bc7f582c14f6de6d40334e46a9ed4969144432 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 09:03:36 -0700 Subject: [PATCH 23/54] Add error message for invalid UnsafeAccessor use Additional negative tests Specifically block array and pointer types Consolidate JIT flag lookup --- src/coreclr/dlls/mscorrc/mscorrc.rc | 1 + src/coreclr/dlls/mscorrc/resource.h | 1 + src/coreclr/vm/dynamicmethod.cpp | 7 +- src/coreclr/vm/dynamicmethod.h | 11 ++- src/coreclr/vm/ilstubresolver.cpp | 14 ++-- src/coreclr/vm/ilstubresolver.h | 4 +- src/coreclr/vm/jitinterface.cpp | 41 ++++++---- src/coreclr/vm/jitinterface.h | 6 +- src/coreclr/vm/memberload.h | 8 +- src/coreclr/vm/method.hpp | 2 +- src/coreclr/vm/prestub.cpp | 75 +++++++++---------- src/coreclr/vm/typehandle.h | 2 - .../UnsafeAccessorAttribute.cs | 2 +- .../UnsafeAccessorAttributeTests.cs | 48 +++++++++--- 14 files changed, 133 insertions(+), 89 deletions(-) diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index 4516daeda00656..c8c21bfd74790b 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -607,6 +607,7 @@ BEGIN BFA_GENERIC_METHODS_INST "Generic methods should always be mcInstantiated." BFA_BAD_FIELD_TOKEN "Field token out of range." BFA_INVALID_FIELD_ACC_FLAGS "Invalid Field Access Flags." + BFA_INVALID_UNSAFEACCESSOR "Invalid usage of UnsafeAccessorAttribute." BFA_FIELD_LITERAL_AND_INIT "Field is Literal and InitOnly." BFA_NONSTATIC_GLOBAL_FIELD "Non-Static Global Field." BFA_INSTANCE_FIELD_IN_INT "Instance Field in an Interface." diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index 9942b1ae4429ee..878fd3c509da64 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -427,6 +427,7 @@ #define BFA_BAD_COMPLUS_SIG 0x2049 #define BFA_BAD_ELEM_IN_SIZEOF 0x204b #define BFA_IJW_IN_COLLECTIBLE_ALC 0x204c +#define BFA_INVALID_UNSAFEACCESSOR 0x204d #define IDS_CLASSLOAD_INTERFACE_NO_ACCESS 0x204f diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index cfba23e332e99f..6bb94a6144c769 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -1104,12 +1104,17 @@ ChunkAllocator* LCGMethodResolver::GetJitMetaHeap() return &m_jitMetaHeap; } -bool LCGMethodResolver::RequiresSuppressVisibilityChecks() +bool LCGMethodResolver::RequiresAccessCheck() { LIMITED_METHOD_CONTRACT; return true; } +CORJIT_FLAGS LCGMethodResolver::GetJitFlags() +{ + return{}; +} + BYTE* LCGMethodResolver::GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned *pEHSize) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/dynamicmethod.h b/src/coreclr/vm/dynamicmethod.h index dca409b7212db6..ddbe3c795cfe38 100644 --- a/src/coreclr/vm/dynamicmethod.h +++ b/src/coreclr/vm/dynamicmethod.h @@ -65,7 +65,11 @@ class DynamicResolver // the potentially expensive GetJitContext() API. // If it returns "false", there is no need for any // further visibility checks. - virtual bool RequiresSuppressVisibilityChecks() = 0; + virtual bool RequiresAccessCheck() = 0; + + // Return the flags that should be used in JIT compilation + // of the associated method. + virtual CORJIT_FLAGS GetJitFlags() = 0; // // code info data @@ -129,7 +133,8 @@ class LCGMethodResolver : public DynamicResolver void GetJitContext(SecurityControlFlags * securityControlFlags, TypeHandle * typeOwner); ChunkAllocator* GetJitMetaHeap(); - bool RequiresSuppressVisibilityChecks(); + bool RequiresAccessCheck(); + CORJIT_FLAGS GetJitFlags(); BYTE* GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned* pEHSize); SigPointer GetLocalSig(); @@ -379,7 +384,7 @@ inline bool RequiresAccessCheck(CORINFO_MODULE_HANDLE module) if (!IsDynamicScope(module)) return true; - return GetDynamicResolver(module)->RequiresSuppressVisibilityChecks(); + return GetDynamicResolver(module)->RequiresAccessCheck(); } inline Module* GetModule(CORINFO_MODULE_HANDLE scope) diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index d2594228646097..c24be260c692e3 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -80,7 +80,7 @@ ChunkAllocator* ILStubResolver::GetJitMetaHeap() return NULL; } -bool ILStubResolver::RequiresSuppressVisibilityChecks() +bool ILStubResolver::RequiresAccessCheck() { LIMITED_METHOD_CONTRACT; @@ -96,6 +96,12 @@ bool ILStubResolver::RequiresSuppressVisibilityChecks() return false; } +CORJIT_FLAGS ILStubResolver::GetJitFlags() +{ + LIMITED_METHOD_CONTRACT; + return m_jitFlags; +} + SigPointer ILStubResolver::GetLocalSig() { @@ -476,12 +482,6 @@ void ILStubResolver::SetJitFlags(CORJIT_FLAGS jitFlags) m_jitFlags = jitFlags; } -CORJIT_FLAGS ILStubResolver::GetJitFlags() -{ - LIMITED_METHOD_CONTRACT; - return m_jitFlags; -} - // static void ILStubResolver::StubGenFailed(ILStubResolver* pResolver) { diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index 5491e41d6fe644..82a1217d79c7e3 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -26,7 +26,8 @@ class ILStubResolver : DynamicResolver void GetJitContext(SecurityControlFlags* pSecurityControlFlags, TypeHandle* pTypeOwner); ChunkAllocator* GetJitMetaHeap(); - bool RequiresSuppressVisibilityChecks(); + bool RequiresAccessCheck(); + CORJIT_FLAGS GetJitFlags(); BYTE* GetCodeInfo(unsigned* pCodeSize, unsigned* pStackSize, CorInfoOptions* pOptions, unsigned* pEHSize); SigPointer GetLocalSig(); @@ -64,7 +65,6 @@ class ILStubResolver : DynamicResolver void SetTokenLookupMap(TokenLookupMap* pMap); void SetJitFlags(CORJIT_FLAGS jitFlags); - CORJIT_FLAGS GetJitFlags(); // This is only set for StructMarshal interop stubs. // See callsites for more details. diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4f3f7f9be1ba25..6727949fea6d59 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -219,7 +219,14 @@ TransientMethodDetails::TransientMethodDetails(TransientMethodDetails&& other) TransientMethodDetails::~TransientMethodDetails() { - STANDARD_VM_CONTRACT; + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + // If the supplied scope is dynamic, release resources. if (IsDynamicScope(Scope)) { @@ -3421,7 +3428,7 @@ void CEEInfo::AddTransientMethodDetails(TransientMethodDetails details) _ASSERTE(details.Method != NULL); if (m_transientDetails == NULL) - m_transientDetails = new SArray(); + m_transientDetails = new SArray(); m_transientDetails->Append(std::move(details)); } @@ -12848,12 +12855,15 @@ CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags) return flags; } -static CORJIT_FLAGS GetCompileFlags(MethodDesc * ftn, CORJIT_FLAGS flags, CORINFO_METHOD_INFO * methodInfo) +static CORJIT_FLAGS GetCompileFlags(PrepareCodeConfig* prepareConfig, MethodDesc* ftn, CORINFO_METHOD_INFO* methodInfo) { STANDARD_VM_CONTRACT; - + _ASSERTE(prepareConfig != NULL); + _ASSERTE(ftn != NULL); _ASSERTE(methodInfo->regionKind == CORINFO_REGION_JIT); + CORJIT_FLAGS flags = prepareConfig->GetJitCompilationFlags(); + // // Get the compile flags that are shared between JIT and NGen // @@ -12915,10 +12925,14 @@ static CORJIT_FLAGS GetCompileFlags(MethodDesc * ftn, CORJIT_FLAGS flags, CORINF } } - if (ftn->IsILStub() && !g_pConfig->GetTrackDynamicMethodDebugInfo()) + if (IsDynamicScope(methodInfo->scope)) { // no debug info available for IL stubs - flags.Clear(CORJIT_FLAGS::CORJIT_FLAG_DEBUG_INFO); + if (!g_pConfig->GetTrackDynamicMethodDebugInfo()) + flags.Clear(CORJIT_FLAGS::CORJIT_FLAG_DEBUG_INFO); + + DynamicResolver* pResolver = GetDynamicResolver(methodInfo->scope); + flags.Add(pResolver->GetJitFlags()); } #ifdef ARM_SOFTFP @@ -12994,11 +13008,12 @@ BOOL g_fAllowRel32 = TRUE; // are OK since they discard the return value of this method. PCODE UnsafeJitFunction(PrepareCodeConfig* config, _In_opt_ COR_ILMETHOD_DECODER* ILHeader, - CORJIT_FLAGS flags, - ULONG * pSizeOfCode) + _In_ CORJIT_FLAGS* pJitFlags, + _In_opt_ ULONG* pSizeOfCode) { STANDARD_VM_CONTRACT; _ASSERTE(config != NULL); + _ASSERTE(pJitFlags != NULL); NativeCodeVersion nativeCodeVersion = config->GetCodeVersion(); MethodDesc* ftn = nativeCodeVersion.GetMethodDesc(); @@ -13068,7 +13083,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, // method attributes and signature are consistant _ASSERTE(!!ftn->IsStatic() == ((methodInfo.args.callConv & CORINFO_CALLCONV_HASTHIS) == 0)); - flags = GetCompileFlags(ftn, flags, &methodInfo); + *pJitFlags = GetCompileFlags(config, ftn, &methodInfo); #if defined(TARGET_AMD64) || defined(TARGET_ARM64) BOOL fForceJumpStubOverflow = FALSE; @@ -13089,7 +13104,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, while (true) { - CEEJitInfo jitInfo(ftn, ILHeader, jitMgr, !flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_NO_INLINING)); + CEEJitInfo jitInfo(ftn, ILHeader, jitMgr, !pJitFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_NO_INLINING)); #if defined(TARGET_AMD64) || defined(TARGET_ARM64) #if defined(TARGET_AMD64) @@ -13105,7 +13120,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, #ifdef FEATURE_ON_STACK_REPLACEMENT // If this is an OSR jit request, grab the OSR info so we can pass it to the jit - if (flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_OSR)) + if (pJitFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_OSR)) { unsigned ilOffset = 0; PatchpointInfo* patchpointInfo = nativeCodeVersion.GetOSRInfo(&ilOffset); @@ -13179,7 +13194,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, res = invokeCompileMethod(jitMgr, &jitInfo, &methodInfo, - flags, + *pJitFlags, &nativeEntry, &sizeOfCode); @@ -13222,7 +13237,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, { jitInfo.WriteCode(jitMgr); #if defined(DEBUGGING_SUPPORTED) - if (!flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_MCJIT_BACKGROUND)) + if (!pJitFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_MCJIT_BACKGROUND)) { // // Notify the debugger that we have successfully jitted the function diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 0e241c66efe00c..862b363dbe772b 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -68,8 +68,8 @@ void InitJITHelpers2(); PCODE UnsafeJitFunction(PrepareCodeConfig* config, COR_ILMETHOD_DECODER* header, - CORJIT_FLAGS flags, - ULONG* sizeOfCode = NULL); + CORJIT_FLAGS* pJitFlags, + ULONG* pSizeOfCode); void getMethodInfoILMethodHeaderHelper( COR_ILMETHOD_DECODER* header, @@ -558,7 +558,7 @@ class CEEInfo : public ICorJitInfo protected: SArray* m_pJitHandles; // GC handles used by JIT MethodDesc* m_pMethodBeingCompiled; // Top-level method being compiled - SArray* m_transientDetails; // Transient details for dynamic codegen scenarios. + SArray* m_transientDetails; // Transient details for dynamic codegen scenarios. Thread * m_pThread; // Cached current thread for faster JIT-EE transitions CORJIT_FLAGS m_jitFlags; diff --git a/src/coreclr/vm/memberload.h b/src/coreclr/vm/memberload.h index 4ad52f8feb2a3e..f705b1be0d7caf 100644 --- a/src/coreclr/vm/memberload.h +++ b/src/coreclr/vm/memberload.h @@ -62,10 +62,10 @@ class MemberLoader public: static void DECLSPEC_NORETURN ThrowMissingMethodException(MethodTable* pMT, LPCSTR szMember, - ModuleBase *pModule, - PCCOR_SIGNATURE pSig, - DWORD cSig, - const SigTypeContext *pTypeContext); + ModuleBase *pModule = NULL, + PCCOR_SIGNATURE pSig = NULL, + DWORD cSig = 0, + const SigTypeContext *pTypeContext = NULL); static void DECLSPEC_NORETURN ThrowMissingFieldException( MethodTable *pMT, LPCSTR szMember); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index a2cfec402a4ceb..3efd4669ef3d37 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1815,7 +1815,7 @@ class MethodDesc PCODE GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier0); PCODE JitCompileCode(PrepareCodeConfig* pConfig); PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry); - PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); + PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode); public: bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 3be07d85de7ce5..b6ddfd5683e8d7 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -706,7 +706,6 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J PCODE pCode = NULL; ULONG sizeOfCode = 0; - CORJIT_FLAGS flags; #ifdef PROFILING_SUPPORTED { @@ -755,7 +754,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J TRACE_LEVEL_VERBOSE, CLR_JIT_KEYWORD)) { - pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode, &flags); + pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode); } else { @@ -775,7 +774,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J &methodSignature); #endif - pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode, &flags); + pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode); // Interpretted methods skip this notification #ifdef FEATURE_INTERPRETER @@ -908,7 +907,7 @@ namespace } } -PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags) +PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, ULONG* pSizeOfCode) { STANDARD_VM_CONTRACT; @@ -921,14 +920,14 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn COR_ILMETHOD_DECODER ilDecoderTemp; COR_ILMETHOD_DECODER* pilHeader = GetAndVerifyILHeader(this, pConfig, &ilDecoderTemp); - *pFlags = pConfig->GetJitCompilationFlags(); + CORJIT_FLAGS jitFlags; PCODE pOtherCode = NULL; EX_TRY { Thread::CurrentPrepareCodeConfigHolder threadPrepareCodeConfigHolder(GetThread(), pConfig); - pCode = UnsafeJitFunction(pConfig, pilHeader, *pFlags, pSizeOfCode); + pCode = UnsafeJitFunction(pConfig, pilHeader, &jitFlags, pSizeOfCode); } EX_CATCH { @@ -987,7 +986,7 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn #ifdef FEATURE_TIERED_COMPILATION // Finalize the optimization tier before SetNativeCode() is called - bool shouldCountCalls = pFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_TIER0) && pConfig->FinalizeOptimizationTierForTier0LoadOrJit(); + bool shouldCountCalls = jitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_TIER0) && pConfig->FinalizeOptimizationTierForTier0LoadOrJit(); #endif // Aside from rejit, performing a SetNativeCodeInterlocked at this point @@ -1273,7 +1272,7 @@ namespace return false; } - bool TryGenerateAccessor( + void GenerateAccessor( GenerationContext& cxt, DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) @@ -1333,7 +1332,6 @@ namespace break; default: _ASSERTE(!"Unknown UnsafeAccessorKind"); - return false; } // Return from the generated stub @@ -1353,13 +1351,13 @@ namespace // Store the token lookup map ilResolver->SetTokenLookupMap(sl.GetTokenLookupMap()); + ilResolver->SetJitFlags(CORJIT_FLAGS(CORJIT_FLAGS::CORJIT_FLAG_IL_STUB)); *resolver = (DynamicResolver*)ilResolver; *methodILDecoder = pILHeader; } ilResolver.SuppressRelease(); - return true; } } @@ -1385,7 +1383,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET CustomAttributeParser ca(data, dataLen); if (!TryParseUnsafeAccessorAttribute(this, ca, kind, name)) - ThrowHR(COR_E_BADIMAGEFORMAT); + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); GenerationContext context{ kind, this }; @@ -1406,47 +1404,40 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // Using the kind type, perform the following: // 1) Validate the basic type information from the signature. // 2) Resolve the name to the appropriate member. - // 3) Generate the IL for the accessor. switch (context.Kind) { case UnsafeAccessorKind::Constructor: // A return type is required for a constructor, otherwise // we don't know the type to construct. // The name is defined by the runtime and should be empty. - if (retType.IsNull() || !name.IsEmpty()) + if (context.DeclarationSig.IsReturnTypeVoid() || retType.IsNull() || !name.IsEmpty()) { - ThrowHR(COR_E_BADIMAGEFORMAT); + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } context.TargetType = retType; - if (!TrySetTargetMethodCtor(context) - || !TryGenerateAccessor(context, resolver, methodILDecoder)) - { - ThrowHR(COR_E_MISSINGMETHOD); - } + if (!TrySetTargetMethodCtor(context)) + MemberLoader::ThrowMissingMethodException(firstArgType.GetMethodTable(), ".ctor"); break; case UnsafeAccessorKind::Method: case UnsafeAccessorKind::StaticMethod: // Method access requires a target type. if (firstArgType.IsNull()) - ThrowHR(COR_E_BADIMAGEFORMAT); + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; - if (!TrySetTargetMethod(context, name.GetUTF8()) - || !TryGenerateAccessor(context, resolver, methodILDecoder)) - { - ThrowHR(COR_E_MISSINGMETHOD); - } + if (!TrySetTargetMethod(context, name.GetUTF8())) + MemberLoader::ThrowMissingMethodException(firstArgType.GetMethodTable(), name.GetUTF8()); break; case UnsafeAccessorKind::Field: case UnsafeAccessorKind::StaticField: // Field access requires a single argument for target type and a return type. - if (argCount != 1 || firstArgType.IsNull() || retType.IsNull()) + if (argCount != 1 || firstArgType.IsNull() || context.DeclarationSig.IsReturnTypeVoid() || retType.IsNull()) { - ThrowHR(COR_E_BADIMAGEFORMAT); + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } // The return type must be byref. @@ -1457,23 +1448,32 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET && firstArgType.IsValueType() && !firstArgType.IsByRef())) { - ThrowHR(COR_E_BADIMAGEFORMAT); + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } context.TargetType = firstArgType; context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField; - if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam()) - || !TryGenerateAccessor(context, resolver, methodILDecoder)) - { - ThrowHR(COR_E_MISSINGFIELD); - } + if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam())) + MemberLoader::ThrowMissingFieldException(firstArgType.GetMethodTable(), name.GetUTF8()); break; default: - _ASSERTE(!"Unknown UnsafeAccessorKind"); - ThrowHR(E_UNEXPECTED); + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } + // Due to how some types degrade, unsafe access on these + // target types is blocked, even if the lookup succeeds. + // For example, pointers can become lookups on typeof(uint)'s MethodTable. + // Regardless of lookup success, we are going to block + // unsafe access for some types. + if (context.TargetType.IsArray() + || context.TargetType.IsPointer()) + { + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + } + + // Generate the IL for the accessor. + GenerateAccessor(context, resolver, methodILDecoder); return true; } @@ -1577,11 +1577,6 @@ CORJIT_FLAGS PrepareCodeConfig::GetJitCompilationFlags() STANDARD_VM_CONTRACT; CORJIT_FLAGS flags; - if (m_pMethodDesc->IsILStub()) - { - ILStubResolver* pResolver = m_pMethodDesc->AsDynamicMethodDesc()->GetILStubResolver(); - flags = pResolver->GetJitFlags(); - } #ifdef FEATURE_TIERED_COMPILATION flags.Add(TieredCompilationManager::GetJitFlags(this)); #endif diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 38251e8d9cbe35..037bc10205e17b 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -486,8 +486,6 @@ class TypeHandle // PTR BOOL IsPointer() const; - BOOL IsUnmanagedFunctionPointer() const; - // True if this type *is* a formal generic type parameter or any component of it is a formal generic type parameter BOOL ContainsGenericVariables(BOOL methodOnly=FALSE) const; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs index 7b12f54ca0296f..a86b38e22bf48d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -46,7 +46,7 @@ public enum UnsafeAccessorKind /// to it. If the matching method or field is not found, the body of the extern /// method will throw or . /// - /// For , , + /// For , , /// , and , the type of /// the first argument of the annotated extern method identifies the owning type. /// The value of the first argument is treated as this pointer for instance fields and methods. diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs index df289b5e3baa1e..081df125997f3c 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs @@ -212,7 +212,7 @@ public static void VerifyAccessMethodValue() [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyInvalidUnsafeAccessor() + public static unsafe void VerifyInvalidTargetUnsafeAccessor() { Assert.Throws(() => MethodNotFound(null)); Assert.Throws(() => StaticMethodNotFound(null)); @@ -220,17 +220,6 @@ public static void VerifyInvalidUnsafeAccessor() Assert.Throws(() => FieldNotFound(null)); Assert.Throws(() => StaticFieldNotFound(null)); - Assert.Throws(() => FieldReturnMustBeByRefClass((UserDataClass)null)); - Assert.Throws(() => - { - UserDataValue local = new(); - FieldReturnMustBeByRefValue(ref local); - }); - Assert.Throws(() => FieldArgumentMustBeByRef(new UserDataValue())); - - Assert.Throws(() => FieldMustHaveSingleArgument((UserDataClass)null, 0)); - Assert.Throws(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0)); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name="_DoesNotExist_")] extern static void MethodNotFound(UserDataClass d); @@ -242,6 +231,26 @@ public static void VerifyInvalidUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name="_DoesNotExist_")] extern static ref string StaticFieldNotFound(UserDataClass d); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static unsafe void VerifyInvalidUseUnsafeAccessor() + { + Assert.Throws(() => FieldReturnMustBeByRefClass((UserDataClass)null)); + Assert.Throws(() => + { + UserDataValue local = new(); + FieldReturnMustBeByRefValue(ref local); + }); + Assert.Throws(() => FieldArgumentMustBeByRef(new UserDataValue())); + Assert.Throws(() => FieldMustHaveSingleArgument((UserDataClass)null, 0)); + Assert.Throws(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0)); + Assert.Throws(() => InvalidKindValue(null)); + Assert.Throws(() => InvalidCtorName()); + Assert.Throws(() => InvalidCtorType()); + Assert.Throws(() => LookUpFailsOnPointers(null)); + Assert.Throws(() => LookUpFailsOnArrays(Array.Empty())); [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] extern static string FieldReturnMustBeByRefClass(UserDataClass d); @@ -257,5 +266,20 @@ public static void VerifyInvalidUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] extern static ref string StaticFieldMustHaveSingleArgument(UserDataClass d, int a); + + [UnsafeAccessor((UnsafeAccessorKind)100, Name=UserDataClass.StaticMethodVoidName)] + extern static void InvalidKindValue(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor, Name="_ShouldBeNull_")] + extern static UserDataClass InvalidCtorName(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static void InvalidCtorType(); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] + extern static string LookUpFailsOnPointers(void* d); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] + extern static string LookUpFailsOnArrays(int[] d); } } \ No newline at end of file From 2859b6ecdde17ea56cc38b70b1bbf209ee7cf4fa Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 09:50:20 -0700 Subject: [PATCH 24/54] Debugger logging updates --- src/coreclr/debug/ee/controller.cpp | 134 ++++++++++++-------------- src/coreclr/debug/ee/debugger.cpp | 12 +-- src/coreclr/debug/ee/functioninfo.cpp | 4 +- src/coreclr/vm/prestub.cpp | 8 +- src/coreclr/vm/stubmgr.cpp | 79 +++++++-------- 5 files changed, 116 insertions(+), 121 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index b69f66b3a43291..b624162f9bc77d 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -2373,7 +2373,7 @@ bool DebuggerController::PatchTrace(TraceDestination *trace, // trace->address is actually a MethodDesc* of the method that we'll // soon JIT, so put a relative bp at offset zero in. LOG((LF_CORDB, LL_INFO10000, - "Setting unjitted method patch in MethodDesc 0x%p %s\n", trace->GetMethodDesc(), trace->GetMethodDesc() ? trace->GetMethodDesc()->m_pszDebugMethodName : "")); + "Setting unjitted method patch in MethodDesc %p %s\n", trace->GetMethodDesc(), trace->GetMethodDesc() ? trace->GetMethodDesc()->m_pszDebugMethodName : "")); // Note: we have to make sure to bind here. If this function is prejitted, this may be our only chance to get a // DebuggerJITInfo and thereby cause a JITComplete callback. @@ -2484,7 +2484,7 @@ bool DebuggerController::MatchPatch(Thread *thread, } } - LOG((LF_CORDB, LL_INFO100000, "DC::MP: Returning true")); + LOG((LF_CORDB, LL_INFO100000, "DC::MP: Returning true\n")); return true; } @@ -3317,11 +3317,11 @@ BOOL DebuggerController::DispatchExceptionHook(Thread *thread, } CONTRACTL_END; - LOG((LF_CORDB, LL_INFO1000, "DC:: DispatchExceptionHook\n")); + LOG((LF_CORDB, LL_INFO1000, "DC::DEH: DispatchExceptionHook\n")); if (!g_patchTableValid) { - LOG((LF_CORDB, LL_INFO1000, "DC::DEH returning, no patch table.\n")); + LOG((LF_CORDB, LL_INFO1000, "DC::DEH: returning, no patch table.\n")); return (TRUE); } @@ -3339,16 +3339,16 @@ BOOL DebuggerController::DispatchExceptionHook(Thread *thread, DebuggerController *pNext = p->m_next; if (p->m_exceptionHook - && (p->m_thread == NULL || p->m_thread == thread) && - tpr != TPR_IGNORE_AND_STOP) + && (p->m_thread == NULL || p->m_thread == thread) + && tpr != TPR_IGNORE_AND_STOP) { - LOG((LF_CORDB, LL_INFO1000, "DC::DEH calling TEH...\n")); + LOG((LF_CORDB, LL_INFO1000, "DC::DEH: calling TEH...\n")); tpr = p->TriggerExceptionHook(thread, context , pException); - LOG((LF_CORDB, LL_INFO1000, "DC::DEH ... returned.\n")); + LOG((LF_CORDB, LL_INFO1000, "DC::DEH: ... returned.\n")); if (tpr == TPR_IGNORE_AND_STOP) { - LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: leaving early!\n")); + LOG((LF_CORDB, LL_INFO1000, "DC::DEH: leaving early!\n")); break; } } @@ -3356,7 +3356,7 @@ BOOL DebuggerController::DispatchExceptionHook(Thread *thread, p = pNext; } - LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: returning 0x%x!\n", tpr)); + LOG((LF_CORDB, LL_INFO1000, "DC::DEH: returning 0x%x!\n", tpr)); return (tpr != TPR_IGNORE_AND_STOP); } @@ -4725,11 +4725,11 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont SIZE_T *sp = (SIZE_T *) GetSP(context); LOG((LF_CORDB, LL_INFO10000, - "Bypass call return address redirected from 0x%p\n", *sp)); + "Bypass call return address redirected from %p\n", *sp)); *sp -= patchBypass - (BYTE*)m_address; - LOG((LF_CORDB, LL_INFO10000, "to 0x%p\n", *sp)); + LOG((LF_CORDB, LL_INFO10000, "to %p\n", *sp)); #else PORTABILITY_ASSERT("DebuggerPatchSkip::TriggerExceptionHook -- return address fixup NYI"); #endif @@ -4739,7 +4739,7 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont { // Fixup IP - LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected from 0x%p\n", GetIP(context))); + LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected from %p\n", GetIP(context))); if (IsSingleStep(exception->ExceptionCode)) { @@ -4786,9 +4786,9 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont ((size_t)GetIP(context) > (size_t)patchBypass && (size_t)GetIP(context) <= (size_t)(patchBypass + MAX_INSTRUCTION_LENGTH + 1))) { - LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because still in skip area.\n")); - LOG((LF_CORDB, LL_INFO10000, "m_fIsCall = %d, patchBypass = 0x%x, m_address = 0x%x\n", - m_instrAttrib.m_fIsCall, patchBypass, m_address)); + LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because still in skip area.\n" + "\tm_fIsCall = %s, patchBypass = %p, m_address = %p\n", + (m_instrAttrib.m_fIsCall ? "true" : "false"), patchBypass, m_address)); SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address))); } else @@ -4822,8 +4822,7 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address))); } - LOG((LF_CORDB, LL_ALWAYS, "to 0x%x\n", GetIP(context))); - + LOG((LF_CORDB, LL_ALWAYS, "DPS::TEH: IP now at %p\n", GetIP(context))); } #endif @@ -5376,39 +5375,36 @@ bool DebuggerStepper::DetectHandleInterceptors(ControllerStackInfo *info) BOOL DebuggerStepper::DetectHandleLCGMethods(const PCODE ip, MethodDesc * pMD, ControllerStackInfo * pInfo) { + // If a MethodDesc is specified, it has to match the given IP. + _ASSERTE(pMD == NULL || pMD == g_pEEInterface->GetNativeCodeMethodDesc(ip)); + // Look up the MethodDesc for the given IP. if (pMD == NULL) { - if (g_pEEInterface->IsManagedNativeCode((const BYTE *)ip)) - { - pMD = g_pEEInterface->GetNativeCodeMethodDesc(ip); - _ASSERTE(pMD != NULL); - } - } -#if defined(_DEBUG) - else - { - // If a MethodDesc is specified, it has to match the given IP. - _ASSERTE(pMD == g_pEEInterface->GetNativeCodeMethodDesc(ip)); + // If the given IP is in unmanaged code, then it isn't an LCG method + if (!g_pEEInterface->IsManagedNativeCode((const BYTE *)ip)) + return FALSE; + + pMD = g_pEEInterface->GetNativeCodeMethodDesc(ip); } -#endif // _DEBUG - // If the given IP is in unmanaged code, then we won't have a MethodDesc by this point. - if (pMD != NULL) - { - if (pMD->IsLCGMethod()) - { - // Enable all the traps to catch the thread. - EnableUnwind(m_fp); - EnableJMCBackStop(pMD); + _ASSERTE(pMD != NULL); + LOG((LF_CORDB, LL_INFO10000, "DS::DHLCGM: ip:%zx pMD:%p (%s::%s)\n", + ip, + pMD, + pMD->m_pszDebugClassName, + pMD->m_pszDebugMethodName)); - pInfo->SetReturnFrameWithActiveFrame(); - TrapStepOut(pInfo); - return TRUE; - } - } + if (!pMD->IsLCGMethod()) + return FALSE; - return FALSE; + // Enable all the traps to catch the thread. + EnableUnwind(m_fp); + EnableJMCBackStop(pMD); + + pInfo->SetReturnFrameWithActiveFrame(); + TrapStepOut(pInfo); + return TRUE; } @@ -5523,19 +5519,19 @@ bool DebuggerStepper::TrapStepInto(ControllerStackInfo *info, if (IsCloserToRoot(info->m_activeFrame.fp, m_fpStepInto)) m_fpStepInto = info->m_activeFrame.fp; - LOG((LF_CORDB, LL_INFO1000, "DS::TSI this:0x%p m_fpStepInto:0x%p\n", - this, m_fpStepInto.GetSPValue())); + LOG((LF_CORDB, LL_INFO1000, "DS::TSI this:%p m_fpStepInto:%p ip:%p\n", + this, m_fpStepInto.GetSPValue(), ip)); TraceDestination trace; // Trace through the stubs. // If we're calling from managed code, this should either succeed - // or become an ecall into mscorwks. - // @Todo - what about stubs in mscorwks. + // or become an ecall into coreclr. // @todo - if this fails, we want to provide as much info as possible. if (!g_pEEInterface->TraceStub(ip, &trace) || !g_pEEInterface->FollowTrace(&trace)) { + LOG((LF_CORDB, LL_INFO1000, "DS::TSI Failed to step into\n")); return false; } @@ -5964,8 +5960,8 @@ bool DebuggerStepper::TrapStep(ControllerStackInfo *info, bool in) case WALK_UNKNOWN: LWALK_UNKNOWN: - LOG((LF_CORDB,LL_INFO10000,"DS::TS:WALK_UNKNOWN - curIP:0x%p " - "nextIP:0x%p skipIP:0x%p 1st byte of opcode:0x%x\n", (BYTE*)GetControlPC(&(info->m_activeFrame. + LOG((LF_CORDB,LL_INFO10000,"DS::TS:WALK_UNKNOWN - curIP:%p " + "nextIP:%p skipIP:%p 1st byte of opcode:0x%x\n", (BYTE*)GetControlPC(&(info->m_activeFrame. registers)), walker.GetNextIP(),walker.GetSkipIP(), *(BYTE*)GetControlPC(&(info->m_activeFrame.registers)))); @@ -6713,7 +6709,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE { // {0...-1} means use the entire method as the range // Code dup'd from below case. - LOG((LF_CORDB, LL_INFO10000, "DS:Step: Have DJI, special (0,-1) entry\n")); + LOG((LF_CORDB, LL_INFO10000, "DS::Step: Have DJI, special (0,-1) entry\n")); rTo->startOffset = 0; rTo->endOffset = (ULONG32)g_pEEInterface->GetFunctionSize(fd); } @@ -6777,7 +6773,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE } } - LOG((LF_CORDB, LL_INFO10000, "DS:Step: nat off:0x%x to 0x%x\n", rTo->startOffset, rTo->endOffset)); + LOG((LF_CORDB, LL_INFO10000, "DS::Step: nat off:0x%x to 0x%x\n", rTo->startOffset, rTo->endOffset)); } } } @@ -6799,7 +6795,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE { if (r->startOffset == 0 && r->endOffset == (ULONG) ~0) { - LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, (0,-1) special entry\n")); + LOG((LF_CORDB, LL_INFO10000, "DS::Step:No DJI, (0,-1) special entry\n")); // Code dup'd from above case. // {0...-1} means use the entire method as the range rTo->startOffset = 0; @@ -6807,7 +6803,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE } else { - LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, regular entry\n")); + LOG((LF_CORDB, LL_INFO10000, "DS::Step:No DJI, regular entry\n")); // We can't just leave ths IL entry - we have to // get rid of it. // This will just be ignored @@ -6949,12 +6945,12 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, } m_eMode = m_stepIn ? cStepIn : cStepOver; - LOG((LF_CORDB,LL_INFO10000,"DS 0x%p Step: STEP_NORMAL\n",this)); + LOG((LF_CORDB,LL_INFO10000,"DS::Step %p STEP_NORMAL\n",this)); m_reason = STEP_NORMAL; //assume it'll be a normal step & set it to //something else if we walk over it if (fIsILStub) { - LOG((LF_CORDB, LL_INFO10000, "DS:Step: stepping in an IL stub\n")); + LOG((LF_CORDB, LL_INFO10000, "DS::Step: stepping in an IL stub\n")); // Enable the right triggers if the user wants to step in. if (in) @@ -6978,12 +6974,12 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, } else if (!TrapStep(&info, in)) { - LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS\n")); + LOG((LF_CORDB,LL_INFO10000,"DS::Step: Did TS\n")); m_stepIn = true; TrapStepNext(&info); } - LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS,TSO\n")); + LOG((LF_CORDB,LL_INFO10000,"DS::Step: Did TS,TSO\n")); EnableUnwind(m_fp); @@ -7316,13 +7312,13 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread, _ASSERTE(!IsFrozen()); MethodDesc * pDesc = dji->m_nativeCodeVersion.GetMethodDesc(); - LOG((LF_CORDB, LL_INFO10000, "DebuggerStepper::TME, desc=%p, addr=%p\n", + LOG((LF_CORDB, LL_INFO10000, "DS::TME, desc=%p, addr=%p\n", pDesc, ip)); // JMC steppers won't stop in Lightweight codegen (LCG). Just return & keep executing. if (pDesc->IsNoMetadata()) { - LOG((LF_CORDB, LL_INFO100000, "DebuggerStepper::TME, skipping b/c it's dynamic code (LCG)\n")); + LOG((LF_CORDB, LL_INFO100000, "DS::TME, skipping b/c it's dynamic code (LCG)\n")); return; } @@ -7382,9 +7378,7 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread, } #endif - - - // Place a patch to stopus. + // Place a patch to stop us. // Don't bind to a particular AppDomain so that we can do a Cross-Appdomain step. AddBindAndActivateNativeManagedPatch(pDesc, dji, @@ -7393,9 +7387,9 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread, NULL // AppDomain ); - LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, after setting patch to stop\n")); + LOG((LF_CORDB, LL_INFO10000, "DS::TME, after setting patch to stop\n")); - // Once we resume, we'll go hit that patch (duh, we patched our return address) + // Once we resume, we'll go hit that patch since we patched our return address. // Furthermore, we know the step will complete with reason = call, so set that now. m_reason = STEP_CALL; } @@ -7406,7 +7400,7 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread, // We never single-step into calls (we place a patch at the call destination). bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip) { - LOG((LF_CORDB,LL_INFO10000,"DS:TSS this:0x%p, @ ip:0x%p\n", this, ip)); + LOG((LF_CORDB,LL_INFO10000,"DS::TSS this:0x%p, @ ip:0x%p\n", this, ip)); _ASSERTE(!IsFrozen()); @@ -7507,12 +7501,12 @@ bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip) void DebuggerStepper::TriggerTraceCall(Thread *thread, const BYTE *ip) { - LOG((LF_CORDB,LL_INFO10000,"DS:TTC this:0x%x, @ ip:0x%x\n",this,ip)); + LOG((LF_CORDB,LL_INFO10000,"DS::TTC this:0x%x, @ ip:0x%x\n",this,ip)); TraceDestination trace; if (IsFrozen()) { - LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n")); + LOG((LF_CORDB,LL_INFO10000,"DS::TTC exit b/c of Frozen\n")); return; } @@ -7573,7 +7567,7 @@ void DebuggerStepper::TriggerUnwind(Thread *thread, if (IsFrozen()) { - LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n")); + LOG((LF_CORDB,LL_INFO10000,"DS::TTC exit b/c of Frozen\n")); return; } @@ -7853,7 +7847,7 @@ bool DebuggerJMCStepper::TrapStepInHelper( SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ipNext); - LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TSIH, at '%s::%s', calling=0x%p, next=0x%p, offset=%d\n", + LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TSIH, at '%s::%s', calling=%p, next=%p, offset=%d\n", pDesc->m_pszDebugClassName, pDesc->m_pszDebugMethodName, ipCallTarget, ipNext, diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index aa433342287331..b94e3258e1b231 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -2529,7 +2529,7 @@ void Debugger::JITComplete(NativeCodeVersion nativeCodeVersion, TADDR newAddress goto Exit; } - LOG((LF_CORDB, LL_INFO1000000, "D::JITComplete: md:0x%p (%s::%s), address:%p. Created dji:%p\n", + LOG((LF_CORDB, LL_INFO1000000, "D::JITComplete: md:%p (%s::%s), address:%p. Created dji:%p\n", fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName, newAddress, dji)); // Bind any IL patches to the newly jitted native code. @@ -3070,7 +3070,7 @@ CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDes if (dji && dji->m_addrOfCode) { - LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: simple case: CodeRegionInfo* 0x%p\n", &dji->m_codeRegionInfo)); + LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: simple case: CodeRegionInfo* %p\n", &dji->m_codeRegionInfo)); return dji->m_codeRegionInfo; } else @@ -3096,7 +3096,7 @@ CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDes (addr == dac_cast(g_pEEInterface->GetFunctionAddress(md)))); } - LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: Initializing CodeRegionInfo from 0x%p, md=0x%p\n", addr, md)); + LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: Initializing CodeRegionInfo from %p, md=%p\n", addr, md)); if (addr) { PCODE pCode = PINSTRToPCODE(dac_cast(addr)); @@ -3130,7 +3130,7 @@ void Debugger::getBoundariesHelper(MethodDesc * md, if (dmi != NULL) { - LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi 0x%x\n",dmi)); + LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi %p\n",dmi)); #if defined(FEATURE_ISYM_READER) // Note: we need to make sure to enable preemptive GC here just in case we block in the symbol reader. @@ -3161,7 +3161,7 @@ void Debugger::getBoundariesHelper(MethodDesc * md, LOG((LF_CORDB, LL_INFO100000, - "D::NGB: Reader seq pt count is %d\n", n)); + "D::NGB: Reader seq pt count is %u\n", n)); ULONG32 *p; @@ -13922,7 +13922,7 @@ Debugger::InsertToMethodInfoList( DebuggerMethodInfo *dmi ) } else { - LOG((LF_CORDB, LL_EVERYTHING, "AddMethodInfo being called in D:IAHOL\n")); + LOG((LF_CORDB, LL_EVERYTHING, "D:IAHOL: AddMethodInfo called\n")); hr = m_pMethodInfos->AddMethodInfo(dmi->m_module, dmi->m_token, dmi); diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 5602dc2580681e..9621b00aaa3986 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -280,7 +280,7 @@ DebuggerJitInfo::DebuggerJitInfo(DebuggerMethodInfo *minfo, NativeCodeVersion na _ASSERTE(minfo); m_encVersion = minfo->GetCurrentEnCVersion(); _ASSERTE(m_encVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION); - LOG((LF_CORDB,LL_EVERYTHING, "DJI::DJI : created at 0x%p\n", this)); + LOG((LF_CORDB,LL_EVERYTHING, "DJI::DJI: created at %p\n", this)); // Debugger doesn't track LightWeight codegen methods. // We should never even be creating a DJI for one. @@ -1423,7 +1423,7 @@ DebuggerMethodInfo::DebuggerMethodInfo(Module *module, mdMethodDef token) : } CONTRACTL_END; - LOG((LF_CORDB,LL_EVERYTHING, "DMI::DMI : created at 0x%p\n", this)); + LOG((LF_CORDB,LL_EVERYTHING, "DMI::DMI: created at %p\n", this)); _ASSERTE(g_pDebugger->HasDebuggerDataLock()); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index b6ddfd5683e8d7..a09c3cbf7fa697 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -585,9 +585,9 @@ PCODE MethodDesc::JitCompileCode(PrepareCodeConfig* pConfig) STANDARD_VM_CONTRACT; LOG((LF_JIT, LL_INFO1000000, - "JitCompileCode(" FMT_ADDR ", ILStub: %s) for %s::%s\n", - DBG_ADDR(this), - IsILStub() ? " TRUE" : "FALSE", + "JitCompileCode(%p, ILStub: %s) for %s::%s\n", + this, + IsILStub() ? "true" : "false", GetMethodTable()->GetDebugClassName(), m_pszDebugMethodName)); @@ -2477,7 +2477,7 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT, CallerGCMode callerGCMo } #endif // _DEBUG - STRESS_LOG1(LF_CLASSLOADER, LL_INFO10000, "Prestubworker: method %pM\n", this); + STRESS_LOG1(LF_CLASSLOADER, LL_INFO10000, "DoPrestub: method %p\n", this); GCStress::MaybeTrigger(); diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index 2a11b63fbfe48e..ee757227c72a19 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -37,13 +37,13 @@ void LogTraceDestination(const char * szHint, PCODE stubAddr, TraceDestination * if (pTrace->GetTraceType() == TRACE_UNJITTED_METHOD) { MethodDesc * md = pTrace->GetMethodDesc(); - LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to method 0x%p for input 0x%p.\n", + LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to method %p for input %p.\n", szHint, GetTType(pTrace->GetTraceType()), md, stubAddr)); } else { - LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to address 0x%p for input 0x%p.\n", + LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to address %p for input %p.\n", szHint, GetTType(pTrace->GetTraceType()), pTrace->GetAddress(), stubAddr)); } @@ -81,40 +81,40 @@ const CHAR * TraceDestination::DbgToString(SString & buffer) switch(this->type) { case TRACE_ENTRY_STUB: - buffer.Printf("TRACE_ENTRY_STUB(addr=0x%p)", GetAddress()); + buffer.Printf("TRACE_ENTRY_STUB(addr=%p)", GetAddress()); pValue = buffer.GetUTF8(); break; case TRACE_STUB: - buffer.Printf("TRACE_STUB(addr=0x%p)", GetAddress()); + buffer.Printf("TRACE_STUB(addr=%p)", GetAddress()); pValue = buffer.GetUTF8(); break; case TRACE_UNMANAGED: - buffer.Printf("TRACE_UNMANAGED(addr=0x%p)", GetAddress()); + buffer.Printf("TRACE_UNMANAGED(addr=%p)", GetAddress()); pValue = buffer.GetUTF8(); break; case TRACE_MANAGED: - buffer.Printf("TRACE_MANAGED(addr=0x%p)", GetAddress()); + buffer.Printf("TRACE_MANAGED(addr=%p)", GetAddress()); pValue = buffer.GetUTF8(); break; case TRACE_UNJITTED_METHOD: { MethodDesc * md = this->GetMethodDesc(); - buffer.Printf("TRACE_UNJITTED_METHOD(md=0x%p, %s::%s)", md, md->m_pszDebugClassName, md->m_pszDebugMethodName); + buffer.Printf("TRACE_UNJITTED_METHOD(md=%p, %s::%s)", md, md->m_pszDebugClassName, md->m_pszDebugMethodName); pValue = buffer.GetUTF8(); } break; case TRACE_FRAME_PUSH: - buffer.Printf("TRACE_FRAME_PUSH(addr=0x%p)", GetAddress()); + buffer.Printf("TRACE_FRAME_PUSH(addr=%p)", GetAddress()); pValue = buffer.GetUTF8(); break; case TRACE_MGR_PUSH: - buffer.Printf("TRACE_MGR_PUSH(addr=0x%p, sm=%s)", GetAddress(), this->GetStubManager()->DbgGetName()); + buffer.Printf("TRACE_MGR_PUSH(addr=%p, sm=%s)", GetAddress(), this->GetStubManager()->DbgGetName()); pValue = buffer.GetUTF8(); break; @@ -471,7 +471,7 @@ BOOL StubManager::CheckIsStub_Worker(PCODE stubStartAddress) EX_CATCH #endif { - LOG((LF_CORDB, LL_INFO10000, "D::GASTSI: exception indicated addr is bad.\n")); + LOG((LF_CORDB, LL_INFO10000, "SM::CISWorker: exception indicated addr is bad.\n")); param.fIsStub = FALSE; } @@ -505,10 +505,15 @@ PTR_StubManager StubManager::FindStubManager(PCODE stubAddress) if (it.Current()->CheckIsStub_Worker(stubAddress)) { _ASSERTE_IMPL(IsSingleOwner(stubAddress, it.Current())); + + LOG((LF_CORDB, LL_INFO10000, "SM::FSM: %p claims %p\n", + it.Current(), stubAddress)); return it.Current(); } } + LOG((LF_CORDB, LL_INFO10000, "SM::FSM: no stub manager claims %p\n", + stubAddress)); return NULL; } @@ -524,19 +529,19 @@ BOOL StubManager::TraceStub(PCODE stubStartAddress, TraceDestination *trace) while (it.Next()) { StubManager * pCurrent = it.Current(); - if (pCurrent->CheckIsStub_Worker(stubStartAddress)) - { - LOG((LF_CORDB, LL_INFO10000, - "StubManager::TraceStub: addr 0x%p claimed by mgr " - "0x%p.\n", stubStartAddress, pCurrent)); + if (!pCurrent->CheckIsStub_Worker(stubStartAddress)) + continue; + + LOG((LF_CORDB, LL_INFO10000, + "StubManager::TraceStub: '%s' (%p) claimed %p.\n", pCurrent->DbgGetName(), pCurrent, stubStartAddress)); - _ASSERTE_IMPL(IsSingleOwner(stubStartAddress, pCurrent)); + _ASSERTE_IMPL(IsSingleOwner(stubStartAddress, pCurrent)); - BOOL fValid = pCurrent->DoTraceStub(stubStartAddress, trace); + BOOL fValid = pCurrent->DoTraceStub(stubStartAddress, trace); #ifdef _DEBUG - if (IsStubLoggingEnabled()) - { - DbgWriteLog("Doing TraceStub for Address 0x%p, claimed by '%s' (0x%p)\n", stubStartAddress, pCurrent->DbgGetName(), pCurrent); + if (IsStubLoggingEnabled()) + { + DbgWriteLog("Doing TraceStub for %p, claimed by '%s' (%p)\n", stubStartAddress, pCurrent->DbgGetName(), pCurrent); if (fValid) { SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; @@ -547,35 +552,32 @@ BOOL StubManager::TraceStub(PCODE stubStartAddress, TraceDestination *trace) else { DbgWriteLog(" stubmanager returned false. Does not expect to call managed code\n"); - } - } // logging + } // logging #endif - return fValid; - } + return fValid; } if (ExecutionManager::IsManagedCode(stubStartAddress)) { + LOG((LF_CORDB, LL_INFO10000, + "StubManager::TraceStub: addr %p is managed code\n", + stubStartAddress)); + trace->InitForManaged(stubStartAddress); #ifdef _DEBUG - DbgWriteLog("Doing TraceStub for Address 0x%p is jitted code claimed by codemanager\n", stubStartAddress); + DbgWriteLog("Doing TraceStub for Address %p is jitted code claimed by codemanager\n", stubStartAddress); #endif - - LOG((LF_CORDB, LL_INFO10000, - "StubManager::TraceStub: addr 0x%p is managed code\n", - stubStartAddress)); - return TRUE; } LOG((LF_CORDB, LL_INFO10000, - "StubManager::TraceStub: addr 0x%p unknown. TRACE_OTHER...\n", + "StubManager::TraceStub: addr %p unknown. TRACE_OTHER...\n", stubStartAddress)); #ifdef _DEBUG - DbgWriteLog("Doing TraceStub for Address 0x%p is unknown!!!\n", stubStartAddress); + DbgWriteLog("Doing TraceStub for Address %p is unknown!!!\n", stubStartAddress); #endif trace->InitForOther(stubStartAddress); @@ -593,7 +595,7 @@ BOOL StubManager::FollowTrace(TraceDestination *trace) while (trace->GetTraceType() == TRACE_STUB) { LOG((LF_CORDB, LL_INFO10000, - "StubManager::FollowTrace: TRACE_STUB for 0x%p\n", + "StubManager::FollowTrace: TRACE_STUB for %p\n", trace->GetAddress())); if (!TraceStub(trace->GetAddress(), trace)) @@ -963,7 +965,6 @@ BOOL ThePreStubManager::CheckIsStub_Internal(PCODE stubStartAddress) { LIMITED_METHOD_DAC_CONTRACT; return stubStartAddress == GetPreStubEntryPoint(); - } @@ -1175,13 +1176,13 @@ BOOL StubLinkStubManager::DoTraceStub(PCODE stubStartAddress, CONTRACTL_END LOG((LF_CORDB, LL_INFO10000, - "StubLinkStubManager::DoTraceStub: stubStartAddress=0x%p\n", + "StubLinkStubManager::DoTraceStub: stubStartAddress=%p\n", stubStartAddress)); Stub *stub = Stub::RecoverStub(stubStartAddress); LOG((LF_CORDB, LL_INFO10000, - "StubLinkStubManager::DoTraceStub: stub=0x%p\n", stub)); + "StubLinkStubManager::DoTraceStub: stub=%p\n", stub)); // // If this is an intercept stub, we may be able to step @@ -1265,7 +1266,7 @@ BOOL StubLinkStubManager::TraceManager(Thread *thread, LPVOID pc = (LPVOID)GetIP(pContext); *pRetAddr = (BYTE *)StubManagerHelpers::GetReturnAddress(pContext); - LOG((LF_CORDB,LL_INFO10000, "SLSM:TM 0x%p, retAddr is 0x%p\n", pc, (*pRetAddr))); + LOG((LF_CORDB,LL_INFO10000, "SLSM:TM %p, retAddr is %p\n", pc, (*pRetAddr))); Stub *stub = Stub::RecoverStub((PCODE)pc); if (stub->IsInstantiatingStub()) @@ -1277,7 +1278,7 @@ BOOL StubLinkStubManager::TraceManager(Thread *thread, PCODE target = GetStubTarget(pMD); if (target == NULL) { - LOG((LF_CORDB,LL_INFO10000, "SLSM:TM Unable to determine stub target, fd 0x%p\n", pMD)); + LOG((LF_CORDB,LL_INFO10000, "SLSM:TM Unable to determine stub target, pMD %p\n", pMD)); trace->InitForUnjittedMethod(pMD); return TRUE; } @@ -2046,7 +2047,7 @@ BOOL DelegateInvokeStubManager::CheckIsStub_Internal(PCODE stubStartAddress) fIsStub = fIsStub || GetRangeList()->IsInRange(stubStartAddress); - return fIsStub; + return fIsStub ? TRUE : FALSE; } BOOL DelegateInvokeStubManager::DoTraceStub(PCODE stubStartAddress, TraceDestination *trace) From 331f4ccaef4544f484b408d550e2221cd6d114f6 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 10:46:47 -0700 Subject: [PATCH 25/54] Update BOTR on TypeDesc Add test for function pointers --- docs/design/coreclr/botr/type-loader.md | 6 +---- src/coreclr/vm/prestub.cpp | 24 +++++++------------ .../UnsafeAccessorAttributeTests.cs | 4 ++-- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/docs/design/coreclr/botr/type-loader.md b/docs/design/coreclr/botr/type-loader.md index e8ccf1181d60bb..630a2c3b569cbd 100644 --- a/docs/design/coreclr/botr/type-loader.md +++ b/docs/design/coreclr/botr/type-loader.md @@ -100,7 +100,7 @@ If `MyClass` fails to load, for example because it's supposed to be defined in a ## Key Data Structures -The most universal type designation in the CLR is the `TypeHandle`. It's an abstract entity which encapsulates a pointer to either a `MethodTable` (representing "ordinary" types like `System.Object` or `List`) or a `TypeDesc` (representing byrefs, pointers, function pointers, arrays, and generic variables). It constitutes the identity of a type in that two handles are equal if and only if they represent the same type. To save space, the fact that a `TypeHandle` contains a `TypeDesc` is indicated by setting the second lowest bit of the pointer to 1 (i.e. (ptr | 2)) instead of using additional flags2. `TypeDesc` is "abstract" and has the following inheritance hierarchy. +The most universal type designation in the CLR is the `TypeHandle`. It's an abstract entity which encapsulates a pointer to either a `MethodTable` (representing "ordinary" types like `System.Object` or `List`) or a `TypeDesc` (representing byrefs, pointers, function pointers and generic variables). It constitutes the identity of a type in that two handles are equal if and only if they represent the same type. To save space, the fact that a `TypeHandle` contains a `TypeDesc` is indicated by setting the second lowest bit of the pointer to 1 (i.e. (ptr | 2)) instead of using additional flags2. `TypeDesc` is "abstract" and has the following inheritance hierarchy. ![Figure 2](images/typeloader-fig2.png) @@ -122,10 +122,6 @@ Represents a function pointer, essentially a variable-length list of type handle This descriptor represents a byref and pointer types. Byrefs are the results of the `ref` and `out` C# keywords applied to method parameters3 whereas pointer types are unmanaged pointers to data used in unsafe C# and managed C++. -**`ArrayTypeDesc`** - -Represents array types. It is derived from `ParamTypeDesc` because arrays are also parameterized by a single parameter (the type of their element). This is opposed to generic instantiations whose number of parameters is variable. - **`MethodTable`** This is by far the central data structure of the runtime. It represents any type which does not fall into one of the categories above (this includes primitive types, and generic types, both "open" and "closed"). It contains everything about the type that needs to be looked up quickly, such as its parent type, implemented interfaces, and the v-table. diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index a09c3cbf7fa697..9eef59e71ddeb3 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1136,12 +1136,17 @@ namespace ? cxt.TargetType.GetTypeParam() : cxt.TargetType; + // Due to how some types degrade, unsafe access on these + // target types is blocked, even if the lookup succeeds. + // For example, pointers can become lookups on typeof(uint)'s MethodTable. + if (targetType.IsTypeDesc()) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + CorElementType declCorType; CorElementType maybeCorType; TypeHandle declType; TypeHandle maybeType; - // [TODO] Do we care about EnC scenarios? // Following the iteration pattern found in MemberLoader::FindMethod(). // Reverse order is recommended - see comments in MemberLoader::FindMethod(). MethodTable::MethodIterator iter(targetType.GetMethodTable()); @@ -1254,7 +1259,6 @@ namespace : cxt.TargetType; _ASSERTE(!targetType.IsByRef()); - // [TODO] Do we care about EnC scenarios? ApproxFieldDescIterator fdIterator( targetType.GetMethodTable(), (cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS)); @@ -1337,6 +1341,7 @@ namespace // Return from the generated stub pCode->EmitRET(); + // Generate all IL associated data for JIT { UINT maxStack; size_t cbCode = sl.Link(&maxStack); @@ -1410,7 +1415,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // A return type is required for a constructor, otherwise // we don't know the type to construct. // The name is defined by the runtime and should be empty. - if (context.DeclarationSig.IsReturnTypeVoid() || retType.IsNull() || !name.IsEmpty()) + if (context.DeclarationSig.IsReturnTypeVoid() || !name.IsEmpty()) { ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } @@ -1435,7 +1440,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET case UnsafeAccessorKind::Field: case UnsafeAccessorKind::StaticField: // Field access requires a single argument for target type and a return type. - if (argCount != 1 || firstArgType.IsNull() || context.DeclarationSig.IsReturnTypeVoid() || retType.IsNull()) + if (argCount != 1 || firstArgType.IsNull() || context.DeclarationSig.IsReturnTypeVoid()) { ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } @@ -1461,17 +1466,6 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } - // Due to how some types degrade, unsafe access on these - // target types is blocked, even if the lookup succeeds. - // For example, pointers can become lookups on typeof(uint)'s MethodTable. - // Regardless of lookup success, we are going to block - // unsafe access for some types. - if (context.TargetType.IsArray() - || context.TargetType.IsPointer()) - { - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); - } - // Generate the IL for the accessor. GenerateAccessor(context, resolver, methodILDecoder); return true; diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs index 081df125997f3c..ec9dabec95c4a9 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs @@ -250,7 +250,7 @@ public static unsafe void VerifyInvalidUseUnsafeAccessor() Assert.Throws(() => InvalidCtorName()); Assert.Throws(() => InvalidCtorType()); Assert.Throws(() => LookUpFailsOnPointers(null)); - Assert.Throws(() => LookUpFailsOnArrays(Array.Empty())); + Assert.Throws(() => LookUpFailsOnFunctionPointers(null)); [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] extern static string FieldReturnMustBeByRefClass(UserDataClass d); @@ -280,6 +280,6 @@ public static unsafe void VerifyInvalidUseUnsafeAccessor() extern static string LookUpFailsOnPointers(void* d); [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] - extern static string LookUpFailsOnArrays(int[] d); + extern static string LookUpFailsOnFunctionPointers(delegate* fptr); } } \ No newline at end of file From c01080e8fdcd35b313a45bb94f9981e7d13b1f49 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 15:55:29 -0700 Subject: [PATCH 26/54] Fix type used in message --- src/coreclr/vm/prestub.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 9eef59e71ddeb3..f157a397a236c9 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1422,7 +1422,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET context.TargetType = retType; if (!TrySetTargetMethodCtor(context)) - MemberLoader::ThrowMissingMethodException(firstArgType.GetMethodTable(), ".ctor"); + MemberLoader::ThrowMissingMethodException(retType.GetMethodTable(), ".ctor"); break; case UnsafeAccessorKind::Method: From 1d5c1ab32d46b939b2beff92fa4069c0c90b31d4 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 16:02:33 -0700 Subject: [PATCH 27/54] Support UnsafeAccessor in NativeAOT --- .../TypeSystem/IL/NativeAotILProvider.cs | 4 + .../Common/TypeSystem/IL/UnsafeAccessors.cs | 364 ++++++++++++++++++ .../ILCompiler.TypeSystem.csproj | 3 + 3 files changed, 371 insertions(+) create mode 100644 src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs index 4fec12a673e11e..1760aab1218703 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs @@ -311,6 +311,10 @@ public override MethodIL GetMethodIL(MethodDesc method) if (methodIL != null) return methodIL; + methodIL = UnsafeAccessors.TryGetIL(ecmaMethod); + if (methodIL != null) + return methodIL; + return null; } else diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs new file mode 100644 index 00000000000000..e8117148e87f96 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -0,0 +1,364 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Reflection.Metadata; +using System.Runtime.InteropServices; +using Internal.IL.Stubs; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace Internal.IL +{ + public sealed class UnsafeAccessors + { + private const string InvalidUnsafeAccessorUsage = "Invalid usage of UnsafeAccessorAttribute."; + + public static MethodIL TryGetIL(EcmaMethod method) + { + Debug.Assert(method != null); + + CustomAttributeValue? decodedAttribute = method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "UnsafeAccessorAttribute"); + if (!decodedAttribute.HasValue) + { + return null; + } + + if (!TryParseUnsafeAccessorAttribute(method, decodedAttribute.Value, out UnsafeAccessorKind kind, out string name)) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + GenerationContext context = new() + { + Kind = kind, + Declaration = method + }; + + MethodSignature sig = method.Signature; + TypeDesc retType = sig.ReturnType; + TypeDesc firstArgType = null; + if (sig.Length > 0) + { + firstArgType = sig[0]; + } + + // Using the kind type, perform the following: + // 1) Validate the basic type information from the signature. + // 2) Resolve the name to the appropriate member. + switch (kind) + { + case UnsafeAccessorKind.Constructor: + // A return type is required for a constructor, otherwise + // we don't know the type to construct. + // The name is defined by the runtime and should be empty. + if (sig.ReturnType.IsVoid || !string.IsNullOrEmpty(name)) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + context.TargetType = retType; + if (!TrySetTargetMethodCtor(ref context)) + { + ThrowHelper.ThrowMissingMethodException(retType, ".ctor", null); + } + break; + case UnsafeAccessorKind.Method: + case UnsafeAccessorKind.StaticMethod: + // Method access requires a target type. + if (firstArgType == null) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + context.TargetType = firstArgType; + context.IsTargetStatic = kind == UnsafeAccessorKind.StaticMethod; + if (!TrySetTargetMethod(ref context, name)) + { + ThrowHelper.ThrowMissingMethodException(firstArgType, name, null); + } + break; + + case UnsafeAccessorKind.Field: + case UnsafeAccessorKind.StaticField: + // Field access requires a single argument for target type and a return type. + if (sig.Length != 1 || sig.ReturnType.IsVoid) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + // The return type must be byref. + // If the non-static field access is for a + // value type, the instance must be byref. + if (!sig.ReturnType.IsByRef + || (kind == UnsafeAccessorKind.Field + && firstArgType.IsValueType + && !firstArgType.IsByRef)) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + context.TargetType = firstArgType; + context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField; + if (!TrySetTargetField(ref context, name, retType)) + { + ThrowHelper.ThrowMissingFieldException(sig.ReturnType, name); + } + break; + + default: + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + break; + } + + // Generate the IL for the accessor. + return GenerateAccessor(ref context); + } + + // This is a redeclaration of the new type in the .NET 8 TFM. + private enum UnsafeAccessorKind + { + Constructor, + Method, + StaticMethod, + Field, + StaticField + }; + + private static bool TryParseUnsafeAccessorAttribute(MethodDesc method, CustomAttributeValue decodedValue, out UnsafeAccessorKind kind, out string name) + { + kind = default; + name = default; + + var context = method.Context; + + // Get the kind of accessor + if (decodedValue.FixedArguments.Length != 1 + || decodedValue.FixedArguments[0].Type.UnderlyingType != context.GetWellKnownType(WellKnownType.Int32)) + { + return false; + } + + kind = (UnsafeAccessorKind)decodedValue.FixedArguments[0].Value!; + + // Check the name of the target to access. This is the name we + // use to look up the intended token in metadata. + string nameMaybe = null; + foreach (var argument in decodedValue.NamedArguments) + { + if (argument.Name == "Name") + { + nameMaybe = (string)argument.Value; + } + } + + // If the Name isn't defined, then use the name of the method. + if (nameMaybe != null) + { + name = nameMaybe; + } + else + { + // The Constructor case has an implied value provided by + // the runtime. We are going to enforce this during consumption + // so we avoid the setting of the value. We validate the name + // as empty at the use site. + if (kind is not UnsafeAccessorKind.Constructor) + { + name = nameMaybe; + } + } + + return true; + } + + [StructLayout(LayoutKind.Sequential)] + private struct GenerationContext + { + public UnsafeAccessorKind Kind; + public EcmaMethod Declaration; + public TypeDesc TargetType; + public bool IsTargetStatic; + public MethodDesc TargetMethod; + public FieldDesc TargetField; + } + + private static bool TrySetTargetMethod(ref GenerationContext context, string name) + { + TypeDesc targetType = context.TargetType.IsByRef + ? ((ParameterizedType)context.TargetType).ParameterType + : context.TargetType; + + // Due to how some types degrade, unsafe access on these + // target types is blocked, even if the lookup succeeds. + // For example, pointers can become lookups on typeof(uint)'s MethodTable. + if (targetType.IsParameterizedType) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + TypeDesc declTypeDesc; + TypeDesc maybeTypeDesc; + foreach (MethodDesc md in targetType.GetMethods()) + { + // Check the target and current method match static/instance state. + if (context.IsTargetStatic != md.Signature.IsStatic) + { + continue; + } + + // Check for matching name + if (!md.Name.Equals(name)) + { + continue; + } + + // Validate calling convention. + if ((MethodSignatureFlags.UnmanagedCallingConventionMask & md.Signature.Flags) + != (MethodSignatureFlags.UnmanagedCallingConventionMask & context.Declaration.Signature.Flags)) + { + continue; + } + + // Validate the return type and prepare for validating + // the current signature's argument list. + int sigCount = context.Declaration.Signature.Length; + if (context.Kind == UnsafeAccessorKind.Constructor) + { + if (!md.Signature.ReturnType.IsVoid) + { + continue; + } + } + else + { + declTypeDesc = context.Declaration.Signature.ReturnType; + maybeTypeDesc = md.Signature.ReturnType; + if (declTypeDesc != maybeTypeDesc) + { + continue; + } + + // Non-constructor accessors skip the first argument + // when validating the target argument list. + sigCount--; + } + + // Validate argument count matches. + if (sigCount != md.Signature.Length) + { + continue; + } + + // Validate arguments match - reverse order + for (; sigCount > 0; --sigCount) + { + declTypeDesc = context.Declaration.Signature[sigCount]; + maybeTypeDesc = md.Signature[sigCount]; + if (declTypeDesc != maybeTypeDesc) + { + break; + } + } + + // If we validated all arguments, we have a match. + if (sigCount != 0) + { + continue; + } + + context.TargetMethod = md; + return true; + } + return false; + } + + private static bool TrySetTargetMethodCtor(ref GenerationContext context) + { + // Special case the default constructor case. + if (context.Declaration.Signature.Length == 0 + && context.TargetType.HasExplicitOrImplicitDefaultConstructor()) + { + context.TargetMethod = context.TargetType.GetDefaultConstructor(); + return true; + } + + // Defer to the normal method look up for + // cases beyond the default constructor. + return TrySetTargetMethod(ref context, ".ctor"); + } + + private static bool TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType) + { + TypeDesc targetType = context.TargetType.IsByRef + ? ((ParameterizedType)context.TargetType).ParameterType + : context.TargetType; + + foreach (FieldDesc fd in targetType.GetFields()) + { + if (context.IsTargetStatic != fd.IsStatic) + { + continue; + } + + // Validate the name and target type match. + if (fd.Name.Equals(name) + && fieldType == fd.FieldType) + { + context.TargetField = fd; + return true; + } + } + return false; + } + + private static MethodIL GenerateAccessor(ref GenerationContext context) + { + ILEmitter emit = new ILEmitter(); + ILCodeStream codeStream = emit.NewCodeStream(); + + // Load stub arguments. + // When the target is static, the first argument is only + // used to look up the target member to access and ignored + // during dispatch. + int beginIndex = context.IsTargetStatic ? 1 : 0; + int stubArgCount = context.Declaration.Signature.Length; + for (int i = beginIndex; i < stubArgCount; ++i) + { + codeStream.EmitLdArg(i); + } + + // Provide access to the target member + switch (context.Kind) + { + case UnsafeAccessorKind.Constructor: + Debug.Assert(context.TargetMethod != null); + codeStream.Emit(ILOpcode.newobj, emit.NewToken(context.TargetMethod)); + break; + case UnsafeAccessorKind.Method: + Debug.Assert(context.TargetMethod != null); + codeStream.Emit(ILOpcode.callvirt, emit.NewToken(context.TargetMethod)); + break; + case UnsafeAccessorKind.StaticMethod: + Debug.Assert(context.TargetMethod != null); + codeStream.Emit(ILOpcode.call, emit.NewToken(context.TargetMethod)); + break; + case UnsafeAccessorKind.Field: + Debug.Assert(context.TargetField != null); + codeStream.Emit(ILOpcode.ldflda, emit.NewToken(context.TargetField)); + break; + case UnsafeAccessorKind.StaticField: + Debug.Assert(context.TargetField != null); + codeStream.Emit(ILOpcode.ldsflda, emit.NewToken(context.TargetField)); + break; + default: + Debug.Fail("Unknown UnsafeAccessorKind"); + break; + } + + // Return from the generated stub + codeStream.Emit(ILOpcode.ret); + return emit.Link(context.TargetMethod); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj index 23f3ac4005a57c..7f0248c02e6d40 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -507,6 +507,9 @@ IL\ILReader.cs + + IL\UnsafeAccessors.cs + IL\Stubs\ILEmitter.cs From 34ada73da697e00bed9e51569ebf3a4dbfe60771 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 17:19:46 -0700 Subject: [PATCH 28/54] Review feedback --- .../tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 8 ++++---- src/coreclr/vm/prestub.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index e8117148e87f96..c91421cac1721a 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -189,10 +189,10 @@ private static bool TrySetTargetMethod(ref GenerationContext context, string nam ? ((ParameterizedType)context.TargetType).ParameterType : context.TargetType; - // Due to how some types degrade, unsafe access on these - // target types is blocked, even if the lookup succeeds. - // For example, pointers can become lookups on typeof(uint)'s MethodTable. - if (targetType.IsParameterizedType) + // Due to how some types degrade, we block on parameterized + // types that are represented as TypeDesc. For example ref or pointer. + if ((targetType.IsParameterizedType && !targetType.IsArray) + || targetType.IsFunctionPointerType) { ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index f157a397a236c9..41336b502a6e23 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1136,9 +1136,8 @@ namespace ? cxt.TargetType.GetTypeParam() : cxt.TargetType; - // Due to how some types degrade, unsafe access on these - // target types is blocked, even if the lookup succeeds. - // For example, pointers can become lookups on typeof(uint)'s MethodTable. + // Due to how some types degrade, we block on parameterized + // types that are represented as TypeDesc. For example ref or pointer. if (targetType.IsTypeDesc()) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); @@ -1149,7 +1148,7 @@ namespace // Following the iteration pattern found in MemberLoader::FindMethod(). // Reverse order is recommended - see comments in MemberLoader::FindMethod(). - MethodTable::MethodIterator iter(targetType.GetMethodTable()); + MethodTable::MethodIterator iter(targetType.AsMethodTable()); iter.MoveToEnd(); for (; iter.IsValid(); iter.Prev()) { @@ -1259,6 +1258,11 @@ namespace : cxt.TargetType; _ASSERTE(!targetType.IsByRef()); + // Due to how some types degrade, we block on parameterized + // types that are represented as TypeDesc. For example ref or pointer. + if (targetType.IsTypeDesc()) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + ApproxFieldDescIterator fdIterator( targetType.GetMethodTable(), (cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS)); From a56bc405f7469eeaa2969d5376f58aaa0aa8306f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 Jun 2023 18:55:03 -0700 Subject: [PATCH 29/54] Additional test for ctor --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 49 ++++++++++------ src/coreclr/vm/prestub.cpp | 57 +++++++++++-------- .../UnsafeAccessorAttributeTests.cs | 4 ++ 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index c91421cac1721a..c8bba7c1734aab 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -52,15 +52,23 @@ public static MethodIL TryGetIL(EcmaMethod method) // A return type is required for a constructor, otherwise // we don't know the type to construct. // The name is defined by the runtime and should be empty. - if (sig.ReturnType.IsVoid || !string.IsNullOrEmpty(name)) + if (retType.IsVoid || !string.IsNullOrEmpty(name)) { ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } - context.TargetType = retType; + // Due to how some types degrade, we block on parameterized + // types. For example ref or pointer. + if ((retType.IsParameterizedType && !retType.IsArray) + || retType.IsFunctionPointer) + { + ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + } + + context.TargetType = ValidateTargetType(retType); if (!TrySetTargetMethodCtor(ref context)) { - ThrowHelper.ThrowMissingMethodException(retType, ".ctor", null); + ThrowHelper.ThrowMissingMethodException(context.TargetType, ".ctor", null); } break; case UnsafeAccessorKind.Method: @@ -71,18 +79,18 @@ public static MethodIL TryGetIL(EcmaMethod method) ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } - context.TargetType = firstArgType; + context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind.StaticMethod; if (!TrySetTargetMethod(ref context, name)) { - ThrowHelper.ThrowMissingMethodException(firstArgType, name, null); + ThrowHelper.ThrowMissingMethodException(context.TargetType, name, null); } break; case UnsafeAccessorKind.Field: case UnsafeAccessorKind.StaticField: // Field access requires a single argument for target type and a return type. - if (sig.Length != 1 || sig.ReturnType.IsVoid) + if (sig.Length != 1 || retType.IsVoid) { ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } @@ -90,7 +98,7 @@ public static MethodIL TryGetIL(EcmaMethod method) // The return type must be byref. // If the non-static field access is for a // value type, the instance must be byref. - if (!sig.ReturnType.IsByRef + if (!retType.IsByRef || (kind == UnsafeAccessorKind.Field && firstArgType.IsValueType && !firstArgType.IsByRef)) @@ -98,11 +106,11 @@ public static MethodIL TryGetIL(EcmaMethod method) ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } - context.TargetType = firstArgType; + context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField; if (!TrySetTargetField(ref context, name, retType)) { - ThrowHelper.ThrowMissingFieldException(sig.ReturnType, name); + ThrowHelper.ThrowMissingFieldException(context.TargetType, name); } break; @@ -183,20 +191,27 @@ private struct GenerationContext public FieldDesc TargetField; } - private static bool TrySetTargetMethod(ref GenerationContext context, string name) + private static TypeDesc ValidateTargetType(TypeDesc targetTypeMaybe) { - TypeDesc targetType = context.TargetType.IsByRef - ? ((ParameterizedType)context.TargetType).ParameterType - : context.TargetType; + TypeDesc targetType = targetTypeMaybe.IsByRef + ? ((ParameterizedType)targetTypeMaybe).ParameterType + : targetTypeMaybe; // Due to how some types degrade, we block on parameterized - // types that are represented as TypeDesc. For example ref or pointer. + // types. For example ref or pointer. if ((targetType.IsParameterizedType && !targetType.IsArray) - || targetType.IsFunctionPointerType) + || targetType.IsFunctionPointer) { ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } + return targetType; + } + + private static bool TrySetTargetMethod(ref GenerationContext context, string name) + { + TypeDesc targetType = context.TargetType; + TypeDesc declTypeDesc; TypeDesc maybeTypeDesc; foreach (MethodDesc md in targetType.GetMethods()) @@ -290,9 +305,7 @@ private static bool TrySetTargetMethodCtor(ref GenerationContext context) private static bool TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType) { - TypeDesc targetType = context.TargetType.IsByRef - ? ((ParameterizedType)context.TargetType).ParameterType - : context.TargetType; + TypeDesc targetType = context.TargetType; foreach (FieldDesc fd in targetType.GetFields()) { diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 41336b502a6e23..3b6d77e2283831 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1122,6 +1122,20 @@ namespace FieldDesc* TargetField; }; + TypeHandle ValidateTargetType(TypeHandle targetTypeMaybe) + { + TypeHandle targetType = targetTypeMaybe.IsByRef() + ? targetTypeMaybe.GetTypeParam() + : targetTypeMaybe; + + // Due to how some types degrade, we block on parameterized + // types that are represented as TypeDesc. For example ref or pointer. + if (targetType.IsTypeDesc()) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + + return targetType; + } + bool TrySetTargetMethod( GenerationContext& cxt, LPCUTF8 methodName) @@ -1132,14 +1146,8 @@ namespace || cxt.Kind == UnsafeAccessorKind::Method || cxt.Kind == UnsafeAccessorKind::StaticMethod); - TypeHandle targetType = cxt.TargetType.IsByRef() - ? cxt.TargetType.GetTypeParam() - : cxt.TargetType; - - // Due to how some types degrade, we block on parameterized - // types that are represented as TypeDesc. For example ref or pointer. - if (targetType.IsTypeDesc()) - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + TypeHandle targetType = cxt.TargetType; + _ASSERTE(!targetType.IsTypeDesc()); CorElementType declCorType; CorElementType maybeCorType; @@ -1228,7 +1236,10 @@ namespace _ASSERTE(!cxt.TargetType.IsNull()); _ASSERTE(cxt.Kind == UnsafeAccessorKind::Constructor); - PTR_MethodTable pMT = cxt.TargetType.GetMethodTable(); + TypeHandle targetType = cxt.TargetType; + _ASSERTE(!targetType.IsTypeDesc()); + + PTR_MethodTable pMT = targetType.GetMethodTable(); // Special case the default constructor case. if (cxt.DeclarationSig.NumFixedArgs() == 0 @@ -1253,15 +1264,8 @@ namespace _ASSERTE(cxt.Kind == UnsafeAccessorKind::Field || cxt.Kind == UnsafeAccessorKind::StaticField); - TypeHandle targetType = cxt.TargetType.IsByRef() - ? cxt.TargetType.GetTypeParam() - : cxt.TargetType; - _ASSERTE(!targetType.IsByRef()); - - // Due to how some types degrade, we block on parameterized - // types that are represented as TypeDesc. For example ref or pointer. - if (targetType.IsTypeDesc()) - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + TypeHandle targetType = cxt.TargetType; + _ASSERTE(!targetType.IsTypeDesc()); ApproxFieldDescIterator fdIterator( targetType.GetMethodTable(), @@ -1424,9 +1428,14 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } - context.TargetType = retType; + // Due to how some types degrade, we block on parameterized + // types that are represented as TypeDesc. For example ref or pointer. + if (retType.IsTypeDesc()) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + + context.TargetType = ValidateTargetType(retType); if (!TrySetTargetMethodCtor(context)) - MemberLoader::ThrowMissingMethodException(retType.GetMethodTable(), ".ctor"); + MemberLoader::ThrowMissingMethodException(context.TargetType.GetMethodTable(), ".ctor"); break; case UnsafeAccessorKind::Method: @@ -1435,10 +1444,10 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET if (firstArgType.IsNull()) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); - context.TargetType = firstArgType; + context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; if (!TrySetTargetMethod(context, name.GetUTF8())) - MemberLoader::ThrowMissingMethodException(firstArgType.GetMethodTable(), name.GetUTF8()); + MemberLoader::ThrowMissingMethodException(context.TargetType.GetMethodTable(), name.GetUTF8()); break; case UnsafeAccessorKind::Field: @@ -1460,10 +1469,10 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } - context.TargetType = firstArgType; + context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField; if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam())) - MemberLoader::ThrowMissingFieldException(firstArgType.GetMethodTable(), name.GetUTF8()); + MemberLoader::ThrowMissingFieldException(context.TargetType.GetMethodTable(), name.GetUTF8()); break; default: diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs index ec9dabec95c4a9..70d7dd14ced218 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs @@ -247,6 +247,7 @@ public static unsafe void VerifyInvalidUseUnsafeAccessor() Assert.Throws(() => FieldMustHaveSingleArgument((UserDataClass)null, 0)); Assert.Throws(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0)); Assert.Throws(() => InvalidKindValue(null)); + Assert.Throws(() => InvalidCtorSignature()); Assert.Throws(() => InvalidCtorName()); Assert.Throws(() => InvalidCtorType()); Assert.Throws(() => LookUpFailsOnPointers(null)); @@ -270,6 +271,9 @@ public static unsafe void VerifyInvalidUseUnsafeAccessor() [UnsafeAccessor((UnsafeAccessorKind)100, Name=UserDataClass.StaticMethodVoidName)] extern static void InvalidKindValue(UserDataClass d); + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static ref UserDataClass InvalidCtorSignature(); + [UnsafeAccessor(UnsafeAccessorKind.Constructor, Name="_ShouldBeNull_")] extern static UserDataClass InvalidCtorName(); From a43b3cca93e693b3995664d4416d4d984948b167 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 6 Jun 2023 09:07:03 -0700 Subject: [PATCH 30/54] Remove ctor special case lookup Add runtime sniff test for UnsafeAccessor scenarios Add call ctor as method test --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 31 ++------- src/coreclr/vm/prestub.cpp | 42 ++---------- .../UnsafeAccessorAttributeTests.cs | 14 ++++ .../UnsafeAccessors/UnsafeAccessorsTests.cs | 64 +++++++++++++++++++ .../UnsafeAccessorsTests.csproj | 9 +++ 5 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs create mode 100644 src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index c8bba7c1734aab..d7ea8852347f58 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -51,24 +51,18 @@ public static MethodIL TryGetIL(EcmaMethod method) case UnsafeAccessorKind.Constructor: // A return type is required for a constructor, otherwise // we don't know the type to construct. + // Types should not be parameterized (that is, byref). // The name is defined by the runtime and should be empty. - if (retType.IsVoid || !string.IsNullOrEmpty(name)) - { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); - } - - // Due to how some types degrade, we block on parameterized - // types. For example ref or pointer. - if ((retType.IsParameterizedType && !retType.IsArray) - || retType.IsFunctionPointer) + if (retType.IsVoid || retType.IsByRef || !string.IsNullOrEmpty(name)) { ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); } + const string ctorName = ".ctor"; context.TargetType = ValidateTargetType(retType); - if (!TrySetTargetMethodCtor(ref context)) + if (!TrySetTargetMethod(ref context, ctorName)) { - ThrowHelper.ThrowMissingMethodException(context.TargetType, ".ctor", null); + ThrowHelper.ThrowMissingMethodException(context.TargetType, ctorName, null); } break; case UnsafeAccessorKind.Method: @@ -288,21 +282,6 @@ private static bool TrySetTargetMethod(ref GenerationContext context, string nam return false; } - private static bool TrySetTargetMethodCtor(ref GenerationContext context) - { - // Special case the default constructor case. - if (context.Declaration.Signature.Length == 0 - && context.TargetType.HasExplicitOrImplicitDefaultConstructor()) - { - context.TargetMethod = context.TargetType.GetDefaultConstructor(); - return true; - } - - // Defer to the normal method look up for - // cases beyond the default constructor. - return TrySetTargetMethod(ref context, ".ctor"); - } - private static bool TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType) { TypeDesc targetType = context.TargetType; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 3b6d77e2283831..e395c2affe7b36 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1230,30 +1230,6 @@ namespace return false; } - bool TrySetTargetMethodCtor(GenerationContext& cxt) - { - STANDARD_VM_CONTRACT; - _ASSERTE(!cxt.TargetType.IsNull()); - _ASSERTE(cxt.Kind == UnsafeAccessorKind::Constructor); - - TypeHandle targetType = cxt.TargetType; - _ASSERTE(!targetType.IsTypeDesc()); - - PTR_MethodTable pMT = targetType.GetMethodTable(); - - // Special case the default constructor case. - if (cxt.DeclarationSig.NumFixedArgs() == 0 - && pMT->HasDefaultConstructor()) - { - cxt.TargetMethod = pMT->GetDefaultConstructor(); - return true; - } - - // Defer to the normal method look up for - // cases beyond the default constructor. - return TrySetTargetMethod(cxt, ".ctor"); - } - bool TrySetTargetField( GenerationContext& cxt, LPCUTF8 fieldName, @@ -1268,7 +1244,7 @@ namespace _ASSERTE(!targetType.IsTypeDesc()); ApproxFieldDescIterator fdIterator( - targetType.GetMethodTable(), + targetType.AsMethodTable(), (cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS)); PTR_FieldDesc pField; while ((pField = fdIterator.Next()) != NULL) @@ -1422,20 +1398,16 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET case UnsafeAccessorKind::Constructor: // A return type is required for a constructor, otherwise // we don't know the type to construct. + // Types should not be parameterized (that is, byref). // The name is defined by the runtime and should be empty. - if (context.DeclarationSig.IsReturnTypeVoid() || !name.IsEmpty()) + if (context.DeclarationSig.IsReturnTypeVoid() || retType.IsByRef() || !name.IsEmpty()) { ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } - // Due to how some types degrade, we block on parameterized - // types that are represented as TypeDesc. For example ref or pointer. - if (retType.IsTypeDesc()) - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); - context.TargetType = ValidateTargetType(retType); - if (!TrySetTargetMethodCtor(context)) - MemberLoader::ThrowMissingMethodException(context.TargetType.GetMethodTable(), ".ctor"); + if (!TrySetTargetMethod(context, ".ctor")) + MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), ".ctor"); break; case UnsafeAccessorKind::Method: @@ -1447,7 +1419,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; if (!TrySetTargetMethod(context, name.GetUTF8())) - MemberLoader::ThrowMissingMethodException(context.TargetType.GetMethodTable(), name.GetUTF8()); + MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), name.GetUTF8()); break; case UnsafeAccessorKind::Field: @@ -1472,7 +1444,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField; if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam())) - MemberLoader::ThrowMissingFieldException(context.TargetType.GetMethodTable(), name.GetUTF8()); + MemberLoader::ThrowMissingFieldException(context.TargetType.AsMethodTable(), name.GetUTF8()); break; default: diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs index 70d7dd14ced218..ebe1e8de4b7750 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs @@ -97,6 +97,20 @@ public static void VerifyCallCtorValue() Assert.Equal(PrivateArg, local.Value); } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void VerifyCallCtorAsMethod() + { + UserDataClass ud = (UserDataClass)RuntimeHelpers.GetUninitializedObject(typeof(UserDataClass)); + Assert.Null(ud.Value); + + CallPrivateConstructor(ud, PrivateArg); + Assert.Equal(PrivateArg, ud.Value); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=".ctor")] + extern static void CallPrivateConstructor(UserDataClass _this, string a); + } + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void VerifyAccessStaticFieldClass() diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs new file mode 100644 index 00000000000000..61027aa91bc9ed --- /dev/null +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -0,0 +1,64 @@ +// 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.Runtime.CompilerServices; + +using Xunit; + +// These runtime tests are a minimum set to help validate +// runtime related test passes (e.g., CrossGen, IL round trip, etc). +// A complete set of testing can be found in the +// libraries System.Runtime.CompilerServices test suite. +static class UnsafeAccessorsTests +{ + class UserData + { + public const string FieldName = nameof(_f); + public const string MethodName = nameof(GetF); + public const string FieldValue = "Field"; + + private string _f; + private string GetF() => _f; + private UserData() + { + _f = FieldValue; + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static UserData CallPrivateConstructor(); + + [Fact] + public static void ValidateUnsafeAccess_Constructor() + { + Console.WriteLine($"Running {nameof(ValidateUnsafeAccess_Constructor)}"); + + var ud = CallPrivateConstructor(); + Assert.Equal(typeof(UserData), ud.GetType()); + } + + [Fact] + public static void ValidateUnsafeAccess_Field() + { + Console.WriteLine($"Running {nameof(ValidateUnsafeAccess_Field)}"); + + var ud = CallPrivateConstructor(); + Assert.Equal(UserData.FieldValue, AccessPrivateField(ud)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserData.FieldName)] + extern static ref string AccessPrivateField(UserData d); + } + + [Fact] + public static void ValidateUnsafeAccess_Method() + { + Console.WriteLine($"Running {nameof(ValidateUnsafeAccess_Method)}"); + + var ud = CallPrivateConstructor(); + Assert.Equal(UserData.FieldValue, CallPrivateMethod(ud)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserData.MethodName)] + extern static string CallPrivateMethod(UserData d); + } +} \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj new file mode 100644 index 00000000000000..804eae426209da --- /dev/null +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj @@ -0,0 +1,9 @@ + + + Exe + 1 + + + + + From 3da55de72e4a128c8880500ee5c17825c0206e2f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 6 Jun 2023 16:12:30 -0700 Subject: [PATCH 31/54] Remove usage of MetaSig --- src/coreclr/vm/prestub.cpp | 145 ++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 57 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index e395c2affe7b36..6208fe28e51bb7 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1136,6 +1136,92 @@ namespace return targetType; } + bool DoesMethodMatchUnsafeAccessorDeclaration( + GenerationContext& cxt, + MethodDesc* method) + { + STANDARD_VM_CONTRACT; + _ASSERTE(method != NULL); + + PCCOR_SIGNATURE pSig1; + DWORD cSig1; + cxt.Declaration->GetSig(&pSig1, &cSig1); + PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1; + ModuleBase* pModule1 = cxt.Declaration->GetModule(); + const Substitution* pSubst1 = NULL; + + PCCOR_SIGNATURE pSig2; + DWORD cSig2; + method->GetSig(&pSig2, &cSig2); + PCCOR_SIGNATURE pEndSig2 = pSig2 + cSig2; + ModuleBase* pModule2 = method->GetModule(); + const Substitution* pSubst2 = NULL; + + // Validate calling convention + if ((*pSig1 & IMAGE_CEE_CS_CALLCONV_MASK) != (*pSig2 & IMAGE_CEE_CS_CALLCONV_MASK)) + { + return false; + } + + BYTE callConv = *pSig1; + pSig1++; + pSig2++; + + // Generics are not supported + _ASSERTE((callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) == 0); + + DWORD declArgCount; + DWORD methodArgCount; + IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &declArgCount)); + IfFailThrow(CorSigUncompressData_EndPtr(pSig2, pEndSig2, &methodArgCount)); + + DWORD i = 0; + DWORD argCount = min(declArgCount, methodArgCount); + for (; i <= argCount; ++i) + { + if (i == 0 && cxt.Kind == UnsafeAccessorKind::Constructor) + { + // Skip return value (index 0) validation on constructor accessors + SigPointer ptr1(pSig1, (DWORD)(pEndSig1 - pSig1)); + IfFailThrow(ptr1.SkipExactlyOne()); + pSig1 = ptr1.GetPtr(); + + SigPointer ptr2(pSig2, (DWORD)(pEndSig2 - pSig2)); + IfFailThrow(ptr2.SkipExactlyOne()); + pSig2 = ptr2.GetPtr(); + continue; + } + else if (i == 1 && cxt.Kind != UnsafeAccessorKind::Constructor) + { + // Skip over first argument (index 1) on non-constructor accessors + // This is skipped since the first argument is used only for target + // lookup and passing an instance. + SigPointer ptr1(pSig1, (DWORD)(pEndSig1 - pSig1)); + IfFailThrow(ptr1.SkipExactlyOne()); + pSig1 = ptr1.GetPtr(); + continue; + } + + // Compare the actual element + if (FALSE == MetaSig::CompareElementType( + pSig1, + pSig2, + pEndSig1, + pEndSig2, + pModule1, + pModule2, + pSubst1, + pSubst2, + NULL)) + { + return false; + } + } + + // If we validated all arguments, then it is a match. + return (i - 1) == methodArgCount; + } + bool TrySetTargetMethod( GenerationContext& cxt, LPCUTF8 methodName) @@ -1149,11 +1235,6 @@ namespace TypeHandle targetType = cxt.TargetType; _ASSERTE(!targetType.IsTypeDesc()); - CorElementType declCorType; - CorElementType maybeCorType; - TypeHandle declType; - TypeHandle maybeType; - // Following the iteration pattern found in MemberLoader::FindMethod(). // Reverse order is recommended - see comments in MemberLoader::FindMethod(). MethodTable::MethodIterator iter(targetType.AsMethodTable()); @@ -1170,58 +1251,8 @@ namespace if (strcmp(methodName, curr->GetNameThrowing()) != 0) continue; - // Reset reading the declaration signature - cxt.DeclarationSig.Reset(); - - MetaSig currSig(curr); - - // Validate calling convention. - if (cxt.DeclarationSig.GetCallingConvention() != currSig.GetCallingConvention()) - continue; - - // Validate the return type and prepare for validating - // the current signature's argument list. - UINT argCount = cxt.DeclarationSig.NumFixedArgs(); - if (cxt.Kind == UnsafeAccessorKind::Constructor) - { - // Constructors must return void. - if (currSig.GetReturnType() != ELEMENT_TYPE_VOID) - continue; - } - else - { - declCorType = cxt.DeclarationSig.GetReturnTypeNormalized(&declType); - maybeCorType = currSig.GetReturnTypeNormalized(&maybeType); - if (declCorType != maybeCorType) - continue; - if (declType != maybeType) - continue; - - // Non-constructor accessors skip the first argument - // when validating the target argument list. - cxt.DeclarationSig.SkipArg(); - argCount--; - } - - // Validate argument count matches. - if (argCount != currSig.NumFixedArgs()) - continue; - - // Validate arguments match. - for (; argCount > 0; --argCount) - { - declCorType = cxt.DeclarationSig.PeekArgNormalized(&declType); - maybeCorType = currSig.PeekArgNormalized(&maybeType); - if (declCorType != maybeCorType) - break; - if (declType != maybeType) - break; - cxt.DeclarationSig.NextArg(); - currSig.NextArg(); - } - - // If we validated all arguments, we have a match. - if (argCount != 0) + // Check signature + if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr)) continue; cxt.TargetMethod = curr; From bbe820c372b7438af08be5ed025970ca386e9b73 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 7 Jun 2023 08:24:48 -0700 Subject: [PATCH 32/54] Collect custom modifiers counts Permit ignoring custom modifiers during comparison --- src/coreclr/vm/methodtablebuilder.cpp | 2 +- src/coreclr/vm/prestub.cpp | 13 +++- src/coreclr/vm/siginfo.cpp | 98 ++++++++++++++++++++------- src/coreclr/vm/siginfo.hpp | 28 +++++++- 4 files changed, 110 insertions(+), 31 deletions(-) diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 0f69147a431598..9841f7ae7fcd5d 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -3718,7 +3718,7 @@ BOOL MethodTableBuilder::IsSelfReferencingStaticValueTypeField(mdToken dwByV return MetaSig::CompareElementType(pFakeSig, pFieldSig, pFakeSig + cFakeSig, pMemberSignature + cMemberSignature, GetModule(), GetModule(), - NULL, NULL, FALSE); + NULL, NULL); } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 6208fe28e51bb7..e883aeebfd37a0 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1138,7 +1138,8 @@ namespace bool DoesMethodMatchUnsafeAccessorDeclaration( GenerationContext& cxt, - MethodDesc* method) + MethodDesc* method, + MetaSig::CompareState& state) { STANDARD_VM_CONTRACT; _ASSERTE(method != NULL); @@ -1212,7 +1213,7 @@ namespace pModule2, pSubst1, pSubst2, - NULL)) + &state)) { return false; } @@ -1252,9 +1253,15 @@ namespace continue; // Check signature - if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr)) + MetaSig::CompareState state{}; + state.IgnoreCustomModifiers = false; + if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr, state)) continue; + // + // [TODO] Collect all matching methods and compare custom modifiers count. + // + cxt.TargetMethod = curr; return true; } diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 23185646df61a4..3a0580fcb3b63e 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -1016,7 +1016,6 @@ TypeHandle SigPointer::GetTypeHandleThrowing( // This may throw an exception using the FullModule _ASSERTE(pModule->IsFullModule()); } - // We have an invariant that before we call a method, we must have loaded all of the valuetype parameters of that // method visible from the signature of the method. Normally we do this via type loading before the method is called @@ -1467,7 +1466,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( argLevel, argDrop, pSubst, - pZapSigContext, + pZapSigContext, NULL, pRecursiveFieldGenericHandling); if (typeHnd.IsNull()) @@ -1534,7 +1533,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( else { // At this point thFound is the instantiation over Byte and thRet is set to the instantiation over __Canon. - // If the two have the same GC layout, then the field layout is not affected by the type parameters, and the type load can continue + // If the two have the same GC layout, then the field layout is not affected by the type parameters, and the type load can continue // with just using the __Canon variant. // To simplify the calculation, all we really need to compute is the number of GC pointers in the representation and the Base size. // For if the type parameter is used in field layout, there will be at least 1 more pointer in the __Canon instantiation as compared to the Byte instantiation. @@ -1548,7 +1547,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( #ifndef DACCESS_COMPILE failedLayoutCompare = CGCDesc::GetNumPointers(thRet.AsMethodTable(), objectSizeCanonInstantiation, 0) != CGCDesc::GetNumPointers(thFound.AsMethodTable(), objectSizeCanonInstantiation, 0); -#else +#else DacNotImpl(); #endif } @@ -1557,7 +1556,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( { #ifndef DACCESS_COMPILE static_cast(pOrigModule)->ThrowTypeLoadException(pOrigModule->GetMDImport(), pRecursiveFieldGenericHandling->tkTypeDefToAvoidIfPossible, IDS_INVALID_RECURSIVE_GENERIC_FIELD_LOAD); -#else +#else DacNotImpl(); #endif } @@ -1780,7 +1779,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( } // Find an existing function pointer or make a new one - thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, fLoadTypes, level); + thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, fLoadTypes, level); #else // Function pointers are interpreted as IntPtr to the debugger. thRet = TypeHandle(CoreLibBinder::GetElementType(ELEMENT_TYPE_I)); @@ -3615,7 +3614,7 @@ MetaSig::CompareElementType( ModuleBase * pModule2, const Substitution * pSubst1, const Substitution * pSubst2, - TokenPairList * pVisited) // = NULL + CompareState * state) // = NULL { CONTRACTL { @@ -3626,6 +3625,10 @@ MetaSig::CompareElementType( } CONTRACTL_END + CompareState temp{}; + if (state == NULL) + state = &temp; + redo: // We jump here if the Type was a ET_CMOD prefix. // The caller expects us to handle CMOD's but not present them as types on their own. @@ -3659,7 +3662,7 @@ MetaSig::CompareElementType( pSubst2->GetModule(), pSubst1, pSubst2->GetNext(), - pVisited); + state); } if ((*pSig1 == ELEMENT_TYPE_VAR) && (pSubst1 != NULL) && !pSubst1->GetInst().IsNull()) @@ -3686,7 +3689,7 @@ MetaSig::CompareElementType( pModule2, pSubst1->GetNext(), pSubst2, - pVisited); + state); } CorElementType Type1 = ELEMENT_TYPE_MAX; // initialize to illegal @@ -3782,10 +3785,40 @@ MetaSig::CompareElementType( } } } - else + else if (state->IgnoreCustomModifiers) { - return FALSE; // types must be the same + mdToken tk; + bool consumedCustomModifier = false; + switch (Type1) + { + case ELEMENT_TYPE_CMOD_REQD: + case ELEMENT_TYPE_CMOD_OPT: + IfFailThrow(CorSigUncompressToken_EndPtr(pSig1, pEndSig1, &tk)); + state->CustomModifierCount1++; + consumedCustomModifier = true; + break; + default: + break; + } + + switch (Type2) + { + case ELEMENT_TYPE_CMOD_REQD: + case ELEMENT_TYPE_CMOD_OPT: + IfFailThrow(CorSigUncompressToken_EndPtr(pSig2, pEndSig2, &tk)); + state->CustomModifierCount2++; + consumedCustomModifier = true; + break; + default: + break; + } + + // Custom modifiers were consumed, try again. + if (consumedCustomModifier) + goto redo; } + + return FALSE; // types must be the same } switch (Type1) @@ -3837,6 +3870,9 @@ MetaSig::CompareElementType( IfFailThrow(CorSigUncompressToken_EndPtr(pSig1, pEndSig1, &tk1)); IfFailThrow(CorSigUncompressToken_EndPtr(pSig2, pEndSig2, &tk2)); + state->CustomModifierCount1++; + state->CustomModifierCount2++; + #ifndef DACCESS_COMPILE if (!CompareTypeDefOrRefOrSpec( pModule1, @@ -3845,7 +3881,7 @@ MetaSig::CompareElementType( pModule2, tk2, pSubst2, - pVisited)) + state->Visited)) { return FALSE; } @@ -3868,7 +3904,7 @@ MetaSig::CompareElementType( pModule2, pSubst1, pSubst2, - pVisited)) + state)) { return FALSE; } @@ -3883,7 +3919,7 @@ MetaSig::CompareElementType( IfFailThrow(CorSigUncompressToken_EndPtr(pSig1, pEndSig1, &tk1)); IfFailThrow(CorSigUncompressToken_EndPtr(pSig2, pEndSig2, &tk2)); - return CompareTypeTokens(tk1, tk2, pModule1, pModule2, pVisited); + return CompareTypeTokens(tk1, tk2, pModule1, pModule2, state->Visited); } case ELEMENT_TYPE_FNPTR: @@ -3914,7 +3950,9 @@ MetaSig::CompareElementType( // Add return parameter into the parameter count (it cannot overflow) argCnt1++; - TokenPairList newVisited = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(pVisited); + TokenPairList newVisited = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(state->Visited); + state->Visited = &newVisited; + // Compare all parameters, incl. return parameter while (argCnt1 > 0) { @@ -3927,7 +3965,7 @@ MetaSig::CompareElementType( pModule2, pSubst1, pSubst2, - &newVisited)) + state)) { return FALSE; } @@ -3939,11 +3977,12 @@ MetaSig::CompareElementType( case ELEMENT_TYPE_GENERICINST: { TokenPairList newVisited = TokenPairList::AdjustForTypeSpec( - pVisited, + state->Visited, pModule1, pSig1 - 1, (DWORD)(pEndSig1 - pSig1) + 1); - TokenPairList newVisitedAlwaysForbidden = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(pVisited); + TokenPairList newVisitedAlwaysForbidden = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(state->Visited); + state->Visited = &newVisitedAlwaysForbidden; // Type constructors - The actual type is never permitted to participate in type equivalence. if (!CompareElementType( @@ -3955,7 +3994,7 @@ MetaSig::CompareElementType( pModule2, pSubst1, pSubst2, - &newVisitedAlwaysForbidden)) + state)) { return FALSE; } @@ -3969,6 +4008,7 @@ MetaSig::CompareElementType( return FALSE; } + state->Visited = &newVisited; while (argCnt1 > 0) { if (!CompareElementType( @@ -3980,7 +4020,7 @@ MetaSig::CompareElementType( pModule2, pSubst1, pSubst2, - &newVisited)) + state)) { return FALSE; } @@ -4008,7 +4048,7 @@ MetaSig::CompareElementType( pModule2, pSubst1, pSubst2, - pVisited)) + state)) { return FALSE; } @@ -4146,6 +4186,8 @@ MetaSig::CompareTypeDefsUnderSubstitutions( SigPointer inst1 = pSubst1->GetInst(); SigPointer inst2 = pSubst2->GetInst(); + + CompareState state{ pVisited }; for (DWORD i = 0; i < pTypeDef1->GetNumGenericArgs(); i++) { PCCOR_SIGNATURE startInst1 = inst1.GetPtr(); @@ -4163,7 +4205,7 @@ MetaSig::CompareTypeDefsUnderSubstitutions( pSubst2->GetModule(), pSubst1->GetNext(), pSubst2->GetNext(), - pVisited)) + &state)) { return FALSE; } @@ -4372,6 +4414,7 @@ MetaSig::CompareMethodSigs( // to correctly handle overloads, where there are a number of varargs methods // to pick from, like m1(int,...) and m2(int,int,...), etc. + CompareState state{ pVisited }; // <= because we want to include a check of the return value! for (i = 0; i <= ArgCount1; i++) { @@ -4412,7 +4455,7 @@ MetaSig::CompareMethodSigs( pModule2, pSubst1, pSubst2, - pVisited)) + &state)) { return FALSE; } @@ -4427,6 +4470,7 @@ MetaSig::CompareMethodSigs( } // do return type as well + CompareState state{ pVisited }; for (i = 0; i <= ArgCount1; i++) { if (i == 0 && skipReturnTypeSig) @@ -4450,7 +4494,7 @@ MetaSig::CompareMethodSigs( pModule2, pSubst1, pSubst2, - pVisited)) + &state)) { return FALSE; } @@ -4491,7 +4535,8 @@ BOOL MetaSig::CompareFieldSigs( pEndSig1 = pSig1 + cSig1; pEndSig2 = pSig2 + cSig2; - return(CompareElementType(++pSig1, ++pSig2, pEndSig1, pEndSig2, pModule1, pModule2, NULL, NULL, pVisited)); + CompareState state{ pVisited }; + return(CompareElementType(++pSig1, ++pSig2, pEndSig1, pEndSig2, pModule1, pModule2, NULL, NULL, &state)); } #ifndef DACCESS_COMPILE @@ -4733,7 +4778,8 @@ BOOL MetaSig::CompareTypeDefOrRefOrSpec(ModuleBase *pModule1, mdToken tok1, ULONG cSig1,cSig2; IfFailThrow(pInternalImport1->GetTypeSpecFromToken(tok1, &pSig1, &cSig1)); IfFailThrow(pInternalImport2->GetTypeSpecFromToken(tok2, &pSig2, &cSig2)); - return MetaSig::CompareElementType(pSig1, pSig2, pSig1 + cSig1, pSig2 + cSig2, pModule1, pModule2, pSubst1, pSubst2, pVisited); + CompareState state{ pVisited }; + return MetaSig::CompareElementType(pSig1, pSig2, pSig1 + cSig1, pSig2 + cSig2, pModule1, pModule2, pSubst1, pSubst2, &state); } // MetaSig::CompareTypeDefOrRefOrSpec /* static */ diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 0e19b6b14505da..54e2619524ddf2 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -944,6 +944,32 @@ class MetaSig //------------------------------------------------------------------ CorElementType GetByRefType(TypeHandle* pTy) const; + // Struct used to capture in/out state during the comparison + // of element types. + struct CompareState + { + // List of tokens that are currently being compared. + // See TokenPairList for more use details. + TokenPairList* Visited; + + // Custom modifiers found during the comparison. + DWORD CustomModifierCount1; + DWORD CustomModifierCount2; + + // Boolean indicating if custom modifiers should + // be compared. + bool IgnoreCustomModifiers; + + CompareState() = default; + + CompareState(TokenPairList* list) + : Visited{ list } + , CustomModifierCount1{} + , CustomModifierCount2{} + , IgnoreCustomModifiers{ false } + { } + }; + //------------------------------------------------------------------ // Compare types in two signatures, first applying // - optional substitutions pSubst1 and pSubst2 @@ -958,7 +984,7 @@ class MetaSig ModuleBase * pModule2, const Substitution * pSubst1, const Substitution * pSubst2, - TokenPairList * pVisited = NULL); + CompareState * state = NULL); From 18a2749e5f7b0bda7ead5df1c93130e805b36d14 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 7 Jun 2023 18:30:06 -0700 Subject: [PATCH 33/54] Convert all tests to be runtime tests. --- .../tests/System.Runtime.Tests.csproj | 1 - .../UnsafeAccessorAttributeTests.cs | 303 ------------------ 2 files changed, 304 deletions(-) delete mode 100644 src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 17cabefaaa32e0..b4e389485006a6 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -259,7 +259,6 @@ - diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs deleted file mode 100644 index ebe1e8de4b7750..00000000000000 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/UnsafeAccessorAttributeTests.cs +++ /dev/null @@ -1,303 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; - -using Xunit; - -// CS0414: The field '' is assigned but its value is never used -#pragma warning disable 0414 - -namespace System.Runtime.CompilerServices.Tests; - -public static class UnsafeAccessorAttributeTests -{ - const string PrivateStatic = nameof(PrivateStatic); - const string Private = nameof(Private); - const string PrivateArg = nameof(PrivateArg); - - class UserDataClass - { - public const string StaticFieldName = nameof(_F); - public const string FieldName = nameof(_f); - public const string StaticMethodName = nameof(_M); - public const string MethodName = nameof(_m); - public const string StaticMethodVoidName = nameof(_MVV); - public const string MethodVoidName = nameof(_mvv); - - private static string _F = PrivateStatic; - private string _f; - - public string Value => _f; - - private UserDataClass(string a) { _f = a; } - private UserDataClass() { _f = Private; } - - private static string _M(string s, ref string sr, in string si) => s; - private string _m(string s, ref string sr, in string si) => s; - - private static void _MVV() {} - private void _mvv() {} - } - - [StructLayout(LayoutKind.Sequential)] - struct UserDataValue - { - public const string StaticFieldName = nameof(_F); - public const string FieldName = nameof(_f); - public const string StaticMethodName = nameof(_M); - public const string MethodName = nameof(_m); - - private static string _F = PrivateStatic; - private string _f; - - public string Value => _f; - - private UserDataValue(string a) { _f = a; } - - // ValueClass are not permitted to define a private default constructor. - public UserDataValue() { _f = Private; } - - private static string _M(string s, ref string sr, in string si) => s; - private string _m(string s, ref string sr, in string si) => s; - } - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - extern static UserDataClass CallPrivateConstructorClass(); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - extern static UserDataClass CallPrivateConstructorClass(string a); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - extern static UserDataValue CallPrivateConstructorValue(string a); - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyCallDefaultCtorClass() - { - var local = CallPrivateConstructorClass(); - Assert.Equal(nameof(UserDataClass), local.GetType().Name); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyCallCtorClass() - { - var local = CallPrivateConstructorClass(PrivateArg); - Assert.Equal(nameof(UserDataClass), local.GetType().Name); - Assert.Equal(PrivateArg, local.Value); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyCallCtorValue() - { - var local = CallPrivateConstructorValue(PrivateArg); - Assert.Equal(nameof(UserDataValue), local.GetType().Name); - Assert.Equal(PrivateArg, local.Value); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyCallCtorAsMethod() - { - UserDataClass ud = (UserDataClass)RuntimeHelpers.GetUninitializedObject(typeof(UserDataClass)); - Assert.Null(ud.Value); - - CallPrivateConstructor(ud, PrivateArg); - Assert.Equal(PrivateArg, ud.Value); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=".ctor")] - extern static void CallPrivateConstructor(UserDataClass _this, string a); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessStaticFieldClass() - { - Assert.Equal(PrivateStatic, GetPrivateStaticField((UserDataClass)null)); - - [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataClass.StaticFieldName)] - extern static ref string GetPrivateStaticField(UserDataClass d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessFieldClass() - { - var local = CallPrivateConstructorClass(); - Assert.Equal(Private, GetPrivateField(local)); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataClass.FieldName)] - extern static ref string GetPrivateField(UserDataClass d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessStaticFieldValue() - { - Assert.Equal(PrivateStatic, GetPrivateStaticField(new UserDataValue())); - - [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] - extern static ref string GetPrivateStaticField(UserDataValue d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessFieldValue() - { - UserDataValue local = new(); - Assert.Equal(Private, GetPrivateField(ref local)); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] - extern static ref string GetPrivateField(ref UserDataValue d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessStaticMethodClass() - { - var sr = string.Empty; - var si = string.Empty; - Assert.Equal(PrivateStatic, GetPrivateStaticMethod((UserDataClass)null, PrivateStatic, ref sr, in si)); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodName)] - extern static string GetPrivateStaticMethod(UserDataClass d, string s, ref string sr, in string si); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessMethodClass() - { - var sr = string.Empty; - var si = string.Empty; - var local = CallPrivateConstructorClass(); - Assert.Equal(Private, GetPrivateMethod(local, Private, ref sr, in si)); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodName)] - extern static string GetPrivateMethod(UserDataClass d, string s, ref string sr, in string si); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessStaticMethodVoidClass() - { - GetPrivateStaticMethod((UserDataClass)null); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodVoidName)] - extern static void GetPrivateStaticMethod(UserDataClass d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessMethodVoidClass() - { - var local = CallPrivateConstructorClass(); - GetPrivateMethod(local); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodVoidName)] - extern static void GetPrivateMethod(UserDataClass d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessStaticMethodValue() - { - var sr = string.Empty; - var si = string.Empty; - Assert.Equal(PrivateStatic, GetPrivateStaticMethod(new UserDataValue(), PrivateStatic, ref sr, in si)); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataValue.StaticMethodName)] - extern static string GetPrivateStaticMethod(UserDataValue d, string s, ref string sr, in string si); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void VerifyAccessMethodValue() - { - var sr = string.Empty; - var si = string.Empty; - UserDataValue local = new(); - Assert.Equal(Private, GetPrivateMethod(ref local, Private, ref sr, in si)); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataValue.MethodName)] - extern static string GetPrivateMethod(ref UserDataValue d, string s, ref string sr, in string si); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static unsafe void VerifyInvalidTargetUnsafeAccessor() - { - Assert.Throws(() => MethodNotFound(null)); - Assert.Throws(() => StaticMethodNotFound(null)); - - Assert.Throws(() => FieldNotFound(null)); - Assert.Throws(() => StaticFieldNotFound(null)); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name="_DoesNotExist_")] - extern static void MethodNotFound(UserDataClass d); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="_DoesNotExist_")] - extern static void StaticMethodNotFound(UserDataClass d); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name="_DoesNotExist_")] - extern static ref string FieldNotFound(UserDataClass d); - - [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name="_DoesNotExist_")] - extern static ref string StaticFieldNotFound(UserDataClass d); - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static unsafe void VerifyInvalidUseUnsafeAccessor() - { - Assert.Throws(() => FieldReturnMustBeByRefClass((UserDataClass)null)); - Assert.Throws(() => - { - UserDataValue local = new(); - FieldReturnMustBeByRefValue(ref local); - }); - Assert.Throws(() => FieldArgumentMustBeByRef(new UserDataValue())); - Assert.Throws(() => FieldMustHaveSingleArgument((UserDataClass)null, 0)); - Assert.Throws(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0)); - Assert.Throws(() => InvalidKindValue(null)); - Assert.Throws(() => InvalidCtorSignature()); - Assert.Throws(() => InvalidCtorName()); - Assert.Throws(() => InvalidCtorType()); - Assert.Throws(() => LookUpFailsOnPointers(null)); - Assert.Throws(() => LookUpFailsOnFunctionPointers(null)); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] - extern static string FieldReturnMustBeByRefClass(UserDataClass d); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] - extern static string FieldReturnMustBeByRefValue(ref UserDataValue d); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] - extern static ref string FieldArgumentMustBeByRef(UserDataValue d); - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] - extern static ref string FieldMustHaveSingleArgument(UserDataClass d, int a); - - [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] - extern static ref string StaticFieldMustHaveSingleArgument(UserDataClass d, int a); - - [UnsafeAccessor((UnsafeAccessorKind)100, Name=UserDataClass.StaticMethodVoidName)] - extern static void InvalidKindValue(UserDataClass d); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - extern static ref UserDataClass InvalidCtorSignature(); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor, Name="_ShouldBeNull_")] - extern static UserDataClass InvalidCtorName(); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - extern static void InvalidCtorType(); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] - extern static string LookUpFailsOnPointers(void* d); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] - extern static string LookUpFailsOnFunctionPointers(delegate* fptr); - } -} \ No newline at end of file From d5ff6475416dfeee637840be05f9ac67c035d3ae Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 09:09:50 -0700 Subject: [PATCH 34/54] Defined ambiguity logic with respect to custom modifiers. - First pass ignore custom modifiers - If ambiguity detected, rerun algorithm but require precise matching of custom modifiers. - If there is no clear match throw AmbiguousImplementationException. --- src/coreclr/dlls/mscorrc/mscorrc.rc | 1 + src/coreclr/dlls/mscorrc/resource.h | 2 +- src/coreclr/vm/prestub.cpp | 76 +++++++++++++++++++++-------- src/coreclr/vm/siginfo.cpp | 65 ++++++++++++------------ 4 files changed, 90 insertions(+), 54 deletions(-) diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index c8c21bfd74790b..ed73a672ce6be4 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -608,6 +608,7 @@ BEGIN BFA_BAD_FIELD_TOKEN "Field token out of range." BFA_INVALID_FIELD_ACC_FLAGS "Invalid Field Access Flags." BFA_INVALID_UNSAFEACCESSOR "Invalid usage of UnsafeAccessorAttribute." + BFA_AMBIGUOUS_UNSAFEACCESSOR "Ambiguity in binding of UnsafeAccessorAttribute." BFA_FIELD_LITERAL_AND_INIT "Field is Literal and InitOnly." BFA_NONSTATIC_GLOBAL_FIELD "Non-Static Global Field." BFA_INSTANCE_FIELD_IN_INT "Instance Field in an Interface." diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index 878fd3c509da64..2c9dc86f0bca99 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -428,7 +428,7 @@ #define BFA_BAD_ELEM_IN_SIZEOF 0x204b #define BFA_IJW_IN_COLLECTIBLE_ALC 0x204c #define BFA_INVALID_UNSAFEACCESSOR 0x204d - +#define BFA_AMBIGUOUS_UNSAFEACCESSOR 0x204e #define IDS_CLASSLOAD_INTERFACE_NO_ACCESS 0x204f #define BFA_BAD_CA_HEADER 0x2050 diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index e883aeebfd37a0..547c51384d65f4 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1176,34 +1176,56 @@ namespace IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &declArgCount)); IfFailThrow(CorSigUncompressData_EndPtr(pSig2, pEndSig2, &methodArgCount)); - DWORD i = 0; - DWORD argCount = min(declArgCount, methodArgCount); - for (; i <= argCount; ++i) + // Validate argument count + if (cxt.Kind == UnsafeAccessorKind::Constructor) + { + // Declarations for constructor scenarios have + // matching argument counts with the target. + if (declArgCount != methodArgCount) + return false; + } + else + { + // Declarations of non-constructor scenarios have + // an additional argument to indicate target type + // and to pass an instance for non-static methods. + if (declArgCount != (methodArgCount + 1)) + return false; + } + + // Validate return and argument types + for (DWORD i = 0; i <= methodArgCount; ++i) { if (i == 0 && cxt.Kind == UnsafeAccessorKind::Constructor) { - // Skip return value (index 0) validation on constructor accessors + // Skip return value (index 0) validation on constructor + // accessor declarations. SigPointer ptr1(pSig1, (DWORD)(pEndSig1 - pSig1)); IfFailThrow(ptr1.SkipExactlyOne()); pSig1 = ptr1.GetPtr(); + CorElementType typ; SigPointer ptr2(pSig2, (DWORD)(pEndSig2 - pSig2)); - IfFailThrow(ptr2.SkipExactlyOne()); + IfFailThrow(ptr2.GetElemType(&typ)); pSig2 = ptr2.GetPtr(); + + // Validate the return value for target constructor + // candidate is void. + if (typ != ELEMENT_TYPE_VOID) + return false; + continue; } else if (i == 1 && cxt.Kind != UnsafeAccessorKind::Constructor) { - // Skip over first argument (index 1) on non-constructor accessors - // This is skipped since the first argument is used only for target - // lookup and passing an instance. + // Skip over first argument (index 1) on non-constructor accessors. + // See argument count validation above. SigPointer ptr1(pSig1, (DWORD)(pEndSig1 - pSig1)); IfFailThrow(ptr1.SkipExactlyOne()); pSig1 = ptr1.GetPtr(); - continue; } - // Compare the actual element + // Compare the types if (FALSE == MetaSig::CompareElementType( pSig1, pSig2, @@ -1219,13 +1241,13 @@ namespace } } - // If we validated all arguments, then it is a match. - return (i - 1) == methodArgCount; + return true; } bool TrySetTargetMethod( GenerationContext& cxt, - LPCUTF8 methodName) + LPCUTF8 methodName, + bool ignoreCustomModifiers = true) { STANDARD_VM_CONTRACT; _ASSERTE(methodName != NULL); @@ -1236,6 +1258,8 @@ namespace TypeHandle targetType = cxt.TargetType; _ASSERTE(!targetType.IsTypeDesc()); + MethodDesc* targetMaybe = NULL; + // Following the iteration pattern found in MemberLoader::FindMethod(). // Reverse order is recommended - see comments in MemberLoader::FindMethod(). MethodTable::MethodIterator iter(targetType.AsMethodTable()); @@ -1254,18 +1278,28 @@ namespace // Check signature MetaSig::CompareState state{}; - state.IgnoreCustomModifiers = false; + state.IgnoreCustomModifiers = ignoreCustomModifiers; if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr, state)) continue; - // - // [TODO] Collect all matching methods and compare custom modifiers count. - // - - cxt.TargetMethod = curr; - return true; + // Check if there is some ambiguity. + if (targetMaybe != NULL) + { + if (ignoreCustomModifiers) + { + // We have detected ambiguity when ignoring custom modifiers. + // Start over, but look for a match requiring custom modifiers + // to match precisely. + if (TrySetTargetMethod(cxt, methodName, false /* ignoreCustomModifiers */)) + return true; + } + COMPlusThrow(kAmbiguousImplementationException, BFA_AMBIGUOUS_UNSAFEACCESSOR); + } + targetMaybe = curr; } - return false; + + cxt.TargetMethod = targetMaybe; + return cxt.TargetMethod != NULL; } bool TrySetTargetField( diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 3a0580fcb3b63e..691fda519ca873 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3594,6 +3594,32 @@ BOOL CompareTypeTokens(mdToken tk1, mdToken tk2, ModuleBase *pModule1, ModuleBas #endif //!DACCESS_COMPILE } // CompareTypeTokens +static DWORD ConsumeCustomModifiers(PCCOR_SIGNATURE& pSig, PCCOR_SIGNATURE pEndSig) +{ + mdToken tk; + CorElementType type; + DWORD count = 0; + + PCCOR_SIGNATURE pSigTmp = pSig; + for (;;) + { + type = ELEMENT_TYPE_MAX; + IfFailThrow(CorSigUncompressElementType_EndPtr(pSigTmp, pEndSig, &type)); + + switch (type) + { + case ELEMENT_TYPE_CMOD_REQD: + case ELEMENT_TYPE_CMOD_OPT: + IfFailThrow(CorSigUncompressToken_EndPtr(pSigTmp, pEndSig, &tk)); + pSig = pSigTmp; + count++; + break; + default: + return count; + } + } +} + #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:21000) // Suppress PREFast warning about overly large function @@ -3692,6 +3718,13 @@ MetaSig::CompareElementType( state); } + // Consume custom modifiers if they are being ignored. + if (state->IgnoreCustomModifiers) + { + state->CustomModifierCount1 = ConsumeCustomModifiers(pSig1, pEndSig1); + state->CustomModifierCount2 = ConsumeCustomModifiers(pSig2, pEndSig2); + } + CorElementType Type1 = ELEMENT_TYPE_MAX; // initialize to illegal CorElementType Type2 = ELEMENT_TYPE_MAX; // initialize to illegal @@ -3785,38 +3818,6 @@ MetaSig::CompareElementType( } } } - else if (state->IgnoreCustomModifiers) - { - mdToken tk; - bool consumedCustomModifier = false; - switch (Type1) - { - case ELEMENT_TYPE_CMOD_REQD: - case ELEMENT_TYPE_CMOD_OPT: - IfFailThrow(CorSigUncompressToken_EndPtr(pSig1, pEndSig1, &tk)); - state->CustomModifierCount1++; - consumedCustomModifier = true; - break; - default: - break; - } - - switch (Type2) - { - case ELEMENT_TYPE_CMOD_REQD: - case ELEMENT_TYPE_CMOD_OPT: - IfFailThrow(CorSigUncompressToken_EndPtr(pSig2, pEndSig2, &tk)); - state->CustomModifierCount2++; - consumedCustomModifier = true; - break; - default: - break; - } - - // Custom modifiers were consumed, try again. - if (consumedCustomModifier) - goto redo; - } return FALSE; // types must be the same } From 924cf3761e5c8dd7d9debe57d53148e0e99eda8f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 12:09:23 -0700 Subject: [PATCH 35/54] Address ambiguity in NativeAOT --- .../TypeSystem/Common/ExceptionStringID.cs | 3 + .../Common/TypeSystem/Common/MethodDesc.cs | 20 +- .../Common/Properties/Resources.resx | 3 + .../Common/TypeSystem/Common/ThrowHelper.cs | 6 + .../TypeSystem/Common/TypeSystemException.cs | 8 + .../Common/TypeSystem/IL/UnsafeAccessors.cs | 182 +++++++++++++----- 6 files changed, 170 insertions(+), 52 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs index 4c55874d7999fb..ff28de2fef0b2c 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs @@ -47,5 +47,8 @@ public enum ExceptionStringID // MarshalDirectiveException MarshalDirectiveGeneric, + + // AmbiguousImplementationException + AmbiguousImplementationSpecific, } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs index c0ba423ca927ca..0bd638a369363b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using Internal.NativeFormat; @@ -183,12 +184,27 @@ public bool EmbeddedSignatureMismatchPermitted } } - public EmbeddedSignatureData[] GetEmbeddedSignatureData() + public EmbeddedSignatureData[] GetEmbeddedSignatureData(params EmbeddedSignatureDataKind[] kinds) { if ((_embeddedSignatureData == null) || (_embeddedSignatureData.Length == 0)) return null; - return (EmbeddedSignatureData[])_embeddedSignatureData.Clone(); + if (kinds == null) + return (EmbeddedSignatureData[])_embeddedSignatureData.Clone(); + + List ret = new(); + foreach (var data in _embeddedSignatureData) + { + foreach (var k in kinds) + { + if (data.kind == k) + { + ret.Add(data); + break; + } + } + } + return ret.ToArray(); } public bool Equals(MethodSignature otherSignature) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx index 6a70612fc391bf..fe7c729bc93d27 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx +++ b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx @@ -192,4 +192,7 @@ Marshaling directives are invalid + + {0} + diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs b/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs index 4e31950b40e99b..33fc2f8a635162 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs @@ -71,6 +71,12 @@ public static void ThrowMarshalDirectiveException() throw new TypeSystemException.MarshalDirectiveException(ExceptionStringID.MarshalDirectiveGeneric); } + [System.Diagnostics.DebuggerHidden] + public static void ThrowAmbiguousImplementationException(string message) + { + throw new TypeSystemException.AmbiguousImplementationException(message); + } + private static partial class Format { public static string OwningModule(TypeDesc type) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs index 6c8cd66654af31..3b356b8b921c5b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs @@ -170,5 +170,13 @@ internal MarshalDirectiveException(ExceptionStringID id) { } } + + public class AmbiguousImplementationException : TypeSystemException + { + internal AmbiguousImplementationException(string reason) + : base(ExceptionStringID.AmbiguousImplementationSpecific, reason) + { + } + } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index d7ea8852347f58..a4abf24a79f054 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -13,6 +13,7 @@ namespace Internal.IL public sealed class UnsafeAccessors { private const string InvalidUnsafeAccessorUsage = "Invalid usage of UnsafeAccessorAttribute."; + private const string AmbiguityInUnsafeAccessorUsage = "Ambiguity in binding of UnsafeAccessorAttribute."; public static MethodIL TryGetIL(EcmaMethod method) { @@ -102,7 +103,7 @@ public static MethodIL TryGetIL(EcmaMethod method) context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField; - if (!TrySetTargetField(ref context, name, retType)) + if (!TrySetTargetField(ref context, name, ((ParameterizedType)retType).GetParameterType())) { ThrowHelper.ThrowMissingFieldException(context.TargetType, name); } @@ -202,84 +203,165 @@ private static TypeDesc ValidateTargetType(TypeDesc targetTypeMaybe) return targetType; } - private static bool TrySetTargetMethod(ref GenerationContext context, string name) + private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationContext context, MethodDesc method, bool ignoreCustomModifiers) { - TypeDesc targetType = context.TargetType; + MethodSignature declSig = context.Declaration.Signature; + MethodSignature maybeSig = method.Signature; - TypeDesc declTypeDesc; - TypeDesc maybeTypeDesc; - foreach (MethodDesc md in targetType.GetMethods()) + // Check if we need to also validate custom modifiers. + // If we are, do it first. + if (!ignoreCustomModifiers) { - // Check the target and current method match static/instance state. - if (context.IsTargetStatic != md.Signature.IsStatic) + // One signature has custom modifiers the other doesn't, no match. + if (declSig.HasEmbeddedSignatureData != maybeSig.HasEmbeddedSignatureData) { - continue; + return false; } - // Check for matching name - if (!md.Name.Equals(name)) + // Get all custom modifiers on the signatures. + var declData = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier); + var maybeData = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier); + if (declData.Length != maybeData.Length) { - continue; + return false; } - // Validate calling convention. - if ((MethodSignatureFlags.UnmanagedCallingConventionMask & md.Signature.Flags) - != (MethodSignatureFlags.UnmanagedCallingConventionMask & context.Declaration.Signature.Flags)) + // Validate the custom modifiers match precisely. + for (int i = 0; i < declData.Length; ++i) { - continue; - } + EmbeddedSignatureData dd = declData[i]; + EmbeddedSignatureData md = maybeData[i]; + if (dd.kind != md.kind || dd.type != md.type) + { + return false; + } - // Validate the return type and prepare for validating - // the current signature's argument list. - int sigCount = context.Declaration.Signature.Length; - if (context.Kind == UnsafeAccessorKind.Constructor) - { - if (!md.Signature.ReturnType.IsVoid) + // The indices on non-constructor declarations require + // some slight modification since there is always an extra + // argument in the declaration compared to the target. + string declIndex = dd.index; + if (context.Kind != UnsafeAccessorKind.Constructor) { - continue; + // Decrement the second to last index by one to + // account for the difference in declarations. + string[] tmp = declIndex.Split('.'); + int toUpdate = tmp.Length < 2 ? 0 : tmp.Length - 2; + int idx = int.Parse(tmp[toUpdate]); + idx--; + tmp[toUpdate] = idx.ToString(); + declIndex = string.Join(".", tmp); } - } - else - { - declTypeDesc = context.Declaration.Signature.ReturnType; - maybeTypeDesc = md.Signature.ReturnType; - if (declTypeDesc != maybeTypeDesc) + + if (declIndex != md.index) { - continue; + return false; } + } + } + + // Validate calling convention. + if ((declSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) + != (maybeSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask)) + { + return false; + } + + // Validate argument count and return type + if (context.Kind == UnsafeAccessorKind.Constructor) + { + // Declarations for constructor scenarios have + // matching argument counts with the target. + if (declSig.Length != maybeSig.Length) + { + return false; + } - // Non-constructor accessors skip the first argument - // when validating the target argument list. - sigCount--; + // Validate the return value for target constructor + // candidate is void. + if (!maybeSig.ReturnType.IsVoid) + { + return false; + } + } + else + { + // Declarations of non-constructor scenarios have + // an additional argument to indicate target type + // and to pass an instance for non-static methods. + if (declSig.Length != (maybeSig.Length + 1)) + { + return false; } - // Validate argument count matches. - if (sigCount != md.Signature.Length) + if (declSig.ReturnType != maybeSig.ReturnType) + { + return false; + } + } + + // Validate argument types + for (int i = 0; i < maybeSig.Length; ++i) + { + // Skip over first argument (index 0) on non-constructor accessors. + // See argument count validation above. + TypeDesc declType = context.Kind == UnsafeAccessorKind.Constructor ? declSig[i] : declSig[i + 1]; + TypeDesc maybeType = maybeSig[i]; + + // Compare the types + if (declType != maybeType) + { + return false; + } + } + + return true; + } + + private static bool TrySetTargetMethod(ref GenerationContext context, string name, bool ignoreCustomModifiers = true) + { + TypeDesc targetType = context.TargetType; + + MethodDesc targetMaybe = null; + foreach (MethodDesc md in targetType.GetMethods()) + { + // Check the target and current method match static/instance state. + if (context.IsTargetStatic != md.Signature.IsStatic) { continue; } - // Validate arguments match - reverse order - for (; sigCount > 0; --sigCount) + // Check for matching name + if (!md.Name.Equals(name)) { - declTypeDesc = context.Declaration.Signature[sigCount]; - maybeTypeDesc = md.Signature[sigCount]; - if (declTypeDesc != maybeTypeDesc) - { - break; - } + continue; } - // If we validated all arguments, we have a match. - if (sigCount != 0) + // Check signature + if (!DoesMethodMatchUnsafeAccessorDeclaration(ref context, md, ignoreCustomModifiers)) { continue; } - context.TargetMethod = md; - return true; + // Check if there is some ambiguity. + if (targetMaybe != null) + { + if (ignoreCustomModifiers) + { + // We have detected ambiguity when ignoring custom modifiers. + // Start over, but look for a match requiring custom modifiers + // to match precisely. + if (TrySetTargetMethod(ref context, name, ignoreCustomModifiers: false)) + return true; + } + + ThrowHelper.ThrowAmbiguousImplementationException(AmbiguityInUnsafeAccessorUsage); + } + + targetMaybe = md; } - return false; + + context.TargetMethod = targetMaybe; + return context.TargetMethod != null; } private static bool TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType) @@ -350,7 +432,7 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) // Return from the generated stub codeStream.Emit(ILOpcode.ret); - return emit.Link(context.TargetMethod); + return emit.Link(context.TargetMethod ?? context.Declaration); } } } From 305aeff77e0229d764a3a9c4d826f6276d8cf9dc Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 12:09:49 -0700 Subject: [PATCH 36/54] Add all previous tests in libraries into runtime --- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 400 ++++++++++++++++-- .../UnsafeAccessorsTests.csproj | 1 + 2 files changed, 371 insertions(+), 30 deletions(-) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index 61027aa91bc9ed..8a7fdf7ee853f0 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -2,63 +2,403 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Xunit; -// These runtime tests are a minimum set to help validate -// runtime related test passes (e.g., CrossGen, IL round trip, etc). -// A complete set of testing can be found in the -// libraries System.Runtime.CompilerServices test suite. -static class UnsafeAccessorsTests +static unsafe class UnsafeAccessorsTests { - class UserData + const string PrivateStatic = nameof(PrivateStatic); + const string Private = nameof(Private); + const string PrivateArg = nameof(PrivateArg); + + class UserDataClass { + public const string StaticFieldName = nameof(_F); public const string FieldName = nameof(_f); - public const string MethodName = nameof(GetF); - public const string FieldValue = "Field"; + public const string StaticMethodName = nameof(_M); + public const string MethodName = nameof(_m); + public const string StaticMethodVoidName = nameof(_MVV); + public const string MethodVoidName = nameof(_mvv); + public const string MethodNameAmbiguous = nameof(_Ambiguous); + private static string _F = PrivateStatic; private string _f; - private string GetF() => _f; - private UserData() - { - _f = FieldValue; - } + + public string Value => _f; + + private UserDataClass(string a) { _f = a; } + private UserDataClass() { _f = Private; Prop = Private; } + + private static string _M(string s, ref string sr, in string si) => s; + private string _m(string s, ref string sr, in string si) => s; + + private static void _MVV() {} + private void _mvv() {} + + // The "init" is important to have here - custom modifier test. + private string Prop { get; init; } + + // Used to validate ambiguity is handled via custom modifiers. + private string _Ambiguous(delegate* unmanaged[Cdecl, MemberFunction] fptr) { return nameof(CallConvCdecl); } + private string _Ambiguous(delegate* unmanaged[Stdcall, MemberFunction] fptr) { return nameof(CallConvStdcall); } + } + + [StructLayout(LayoutKind.Sequential)] + struct UserDataValue + { + public const string StaticFieldName = nameof(_F); + public const string FieldName = nameof(_f); + public const string StaticMethodName = nameof(_M); + public const string MethodName = nameof(_m); + + private static string _F = PrivateStatic; + private string _f; + + public string Value => _f; + + private UserDataValue(string a) { _f = a; } + + // ValueClass are not permitted to define a private default constructor. + public UserDataValue() { _f = Private; } + + private static string _M(string s, ref string sr, in string si) => s; + private string _m(string s, ref string sr, in string si) => s; } [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - extern static UserData CallPrivateConstructor(); + extern static UserDataClass CallPrivateConstructorClass(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static UserDataClass CallPrivateConstructorClass(string a); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static UserDataValue CallPrivateConstructorValue(string a); + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_CallDefaultCtorClass() + { + Console.WriteLine($"Running {nameof(Verify_CallDefaultCtorClass)}"); + + var local = CallPrivateConstructorClass(); + Assert.Equal(nameof(UserDataClass), local.GetType().Name); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_CallCtorClass() + { + Console.WriteLine($"Running {nameof(Verify_CallCtorClass)}"); + + var local = CallPrivateConstructorClass(PrivateArg); + Assert.Equal(nameof(UserDataClass), local.GetType().Name); + Assert.Equal(PrivateArg, local.Value); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_CallCtorValue() + { + Console.WriteLine($"Running {nameof(Verify_CallCtorValue)}"); + + var local = CallPrivateConstructorValue(PrivateArg); + Assert.Equal(nameof(UserDataValue), local.GetType().Name); + Assert.Equal(PrivateArg, local.Value); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_CallCtorAsMethod() + { + Console.WriteLine($"Running {nameof(Verify_CallCtorAsMethod)}"); + + UserDataClass ud = (UserDataClass)RuntimeHelpers.GetUninitializedObject(typeof(UserDataClass)); + Assert.Null(ud.Value); + + CallPrivateConstructor(ud, PrivateArg); + Assert.Equal(PrivateArg, ud.Value); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=".ctor")] + extern static void CallPrivateConstructor(UserDataClass _this, string a); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_CallCtorAsMethodValue() + { + Console.WriteLine($"Running {nameof(Verify_CallCtorAsMethodValue)}"); + + UserDataValue ud = new(); + Assert.Equal(Private, ud.Value); + + CallPrivateConstructor(ref ud, PrivateArg); + Assert.Equal(PrivateArg, ud.Value); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=".ctor")] + extern static void CallPrivateConstructor(ref UserDataValue _this, string a); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessStaticFieldClass() + { + Console.WriteLine($"Running {nameof(Verify_AccessStaticFieldClass)}"); + + Assert.Equal(PrivateStatic, GetPrivateStaticField((UserDataClass)null)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataClass.StaticFieldName)] + extern static ref string GetPrivateStaticField(UserDataClass d); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessFieldClass() + { + Console.WriteLine($"Running {nameof(Verify_AccessFieldClass)}"); + + var local = CallPrivateConstructorClass(); + Assert.Equal(Private, GetPrivateField(local)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataClass.FieldName)] + extern static ref string GetPrivateField(UserDataClass d); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessStaticFieldValue() + { + Console.WriteLine($"Running {nameof(Verify_AccessStaticFieldValue)}"); + + Assert.Equal(PrivateStatic, GetPrivateStaticField(new UserDataValue())); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] + extern static ref string GetPrivateStaticField(UserDataValue d); + } [Fact] - public static void ValidateUnsafeAccess_Constructor() + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessFieldValue() { - Console.WriteLine($"Running {nameof(ValidateUnsafeAccess_Constructor)}"); + Console.WriteLine($"Running {nameof(Verify_AccessFieldValue)}"); - var ud = CallPrivateConstructor(); - Assert.Equal(typeof(UserData), ud.GetType()); + UserDataValue local = new(); + Assert.Equal(Private, GetPrivateField(ref local)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static ref string GetPrivateField(ref UserDataValue d); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessStaticMethodClass() + { + Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodClass)}"); + + var sr = string.Empty; + var si = string.Empty; + Assert.Equal(PrivateStatic, GetPrivateStaticMethod((UserDataClass)null, PrivateStatic, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodName)] + extern static string GetPrivateStaticMethod(UserDataClass d, string s, ref string sr, in string si); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessMethodClass() + { + Console.WriteLine($"Running {nameof(Verify_AccessMethodClass)}"); + + var sr = string.Empty; + var si = string.Empty; + var local = CallPrivateConstructorClass(); + Assert.Equal(Private, GetPrivateMethod(local, Private, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodName)] + extern static string GetPrivateMethod(UserDataClass d, string s, ref string sr, in string si); } [Fact] - public static void ValidateUnsafeAccess_Field() + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessStaticMethodVoidClass() { - Console.WriteLine($"Running {nameof(ValidateUnsafeAccess_Field)}"); + Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodVoidClass)}"); - var ud = CallPrivateConstructor(); - Assert.Equal(UserData.FieldValue, AccessPrivateField(ud)); + GetPrivateStaticMethod((UserDataClass)null); - [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserData.FieldName)] - extern static ref string AccessPrivateField(UserData d); + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodVoidName)] + extern static void GetPrivateStaticMethod(UserDataClass d); } [Fact] - public static void ValidateUnsafeAccess_Method() + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessMethodVoidClass() { - Console.WriteLine($"Running {nameof(ValidateUnsafeAccess_Method)}"); + Console.WriteLine($"Running {nameof(Verify_AccessMethodVoidClass)}"); + + var local = CallPrivateConstructorClass(); + GetPrivateMethod(local); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodVoidName)] + extern static void GetPrivateMethod(UserDataClass d); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessStaticMethodValue() + { + Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodValue)}"); + + var sr = string.Empty; + var si = string.Empty; + Assert.Equal(PrivateStatic, GetPrivateStaticMethod(new UserDataValue(), PrivateStatic, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataValue.StaticMethodName)] + extern static string GetPrivateStaticMethod(UserDataValue d, string s, ref string sr, in string si); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_AccessMethodValue() + { + Console.WriteLine($"Running {nameof(Verify_AccessMethodValue)}"); + + var sr = string.Empty; + var si = string.Empty; + UserDataValue local = new(); + Assert.Equal(Private, GetPrivateMethod(ref local, Private, ref sr, in si)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataValue.MethodName)] + extern static string GetPrivateMethod(ref UserDataValue d, string s, ref string sr, in string si); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_IgnoreCustomModifier() + { + Console.WriteLine($"Running {nameof(Verify_IgnoreCustomModifier)}"); + + var ud = CallPrivateConstructorClass(); + Assert.Equal(Private, CallPrivateGetter(ud)); + + const string newValue = "NewPropValue"; + CallPrivateSetter(ud, newValue); + Assert.Equal(newValue, CallPrivateGetter(ud)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name="get_Prop")] + extern static string CallPrivateGetter(UserDataClass d); + + // Private setter used with "init" to validate default "ignore custom modifier" logic + [UnsafeAccessor(UnsafeAccessorKind.Method, Name="set_Prop")] + extern static void CallPrivateSetter(UserDataClass d, string v); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_PreciseMatchCustomModifier() + { + Console.WriteLine($"Running {nameof(Verify_PreciseMatchCustomModifier)}"); + + var ud = CallPrivateConstructorClass(); + Assert.Equal(nameof(CallConvStdcall), CallPrivateMethod(ud, null)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodNameAmbiguous)] + extern static string CallPrivateMethod(UserDataClass d, delegate* unmanaged[Stdcall, MemberFunction] fptr); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_InvalidTargetUnsafeAccessor() + { + Console.WriteLine($"Running {nameof(Verify_InvalidTargetUnsafeAccessor)}"); + + Assert.Throws(() => MethodNotFound(null)); + Assert.Throws(() => StaticMethodNotFound(null)); + + Assert.Throws(() => FieldNotFound(null)); + Assert.Throws(() => StaticFieldNotFound(null)); + + Assert.Throws( + () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name="_DoesNotExist_")] + extern static void MethodNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="_DoesNotExist_")] + extern static void StaticMethodNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name="_DoesNotExist_")] + extern static ref string FieldNotFound(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name="_DoesNotExist_")] + extern static ref string StaticFieldNotFound(UserDataClass d); + + // This is an ambiguous match since there are two methods each with two custom modifiers. + // Therefore the default "ignore custom modifiers" logic fails. The fallback is for a + // precise match and that also fails because the custom modifiers don't match precisely. + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodNameAmbiguous)] + extern static string CallAmbiguousMethod(UserDataClass d, delegate* unmanaged[Stdcall, SuppressGCTransition] fptr); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_InvalidUseUnsafeAccessor() + { + Console.WriteLine($"Running {nameof(Verify_InvalidUseUnsafeAccessor)}"); + + Assert.Throws(() => FieldReturnMustBeByRefClass((UserDataClass)null)); + Assert.Throws(() => + { + UserDataValue local = new(); + FieldReturnMustBeByRefValue(ref local); + }); + Assert.Throws(() => FieldArgumentMustBeByRef(new UserDataValue())); + Assert.Throws(() => FieldMustHaveSingleArgument((UserDataClass)null, 0)); + Assert.Throws(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0)); + Assert.Throws(() => InvalidKindValue(null)); + Assert.Throws(() => InvalidCtorSignatureClass()); + Assert.Throws(() => InvalidCtorSignatureValue()); + Assert.Throws(() => InvalidCtorName()); + Assert.Throws(() => InvalidCtorType()); + Assert.Throws(() => LookUpFailsOnPointers(null)); + Assert.Throws(() => LookUpFailsOnFunctionPointers(null)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static string FieldReturnMustBeByRefClass(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static string FieldReturnMustBeByRefValue(ref UserDataValue d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static ref string FieldArgumentMustBeByRef(UserDataValue d); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] + extern static ref string FieldMustHaveSingleArgument(UserDataClass d, int a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)] + extern static ref string StaticFieldMustHaveSingleArgument(UserDataClass d, int a); + + [UnsafeAccessor((UnsafeAccessorKind)100, Name=UserDataClass.StaticMethodVoidName)] + extern static void InvalidKindValue(UserDataClass d); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static ref UserDataClass InvalidCtorSignatureClass(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static ref UserDataValue InvalidCtorSignatureValue(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor, Name="_ShouldBeNull_")] + extern static UserDataClass InvalidCtorName(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static void InvalidCtorType(); - var ud = CallPrivateConstructor(); - Assert.Equal(UserData.FieldValue, CallPrivateMethod(ud)); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] + extern static string LookUpFailsOnPointers(void* d); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserData.MethodName)] - extern static string CallPrivateMethod(UserData d); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] + extern static string LookUpFailsOnFunctionPointers(delegate* fptr); } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj index 804eae426209da..136522a99eb8b6 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj @@ -2,6 +2,7 @@ Exe 1 + true From c1699692516c4061c28edad9089a8e6d0ea70c80 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 14:03:22 -0700 Subject: [PATCH 37/54] Use AmbiguousMatchException Update doc --- .../tools/Common/TypeSystem/Common/ExceptionStringID.cs | 4 ++-- src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs | 2 +- .../Common/TypeSystem/Common/Properties/Resources.resx | 2 +- src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs | 4 ++-- .../tools/Common/TypeSystem/Common/TypeSystemException.cs | 6 +++--- src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 2 +- src/coreclr/vm/prestub.cpp | 2 +- .../Runtime/CompilerServices/UnsafeAccessorAttribute.cs | 4 +++- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 4 ++-- 9 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs index ff28de2fef0b2c..a943062eff9494 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs @@ -48,7 +48,7 @@ public enum ExceptionStringID // MarshalDirectiveException MarshalDirectiveGeneric, - // AmbiguousImplementationException - AmbiguousImplementationSpecific, + // AmbiguousMatchException + AmbiguousMatchSpecific, } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs index 0bd638a369363b..7d84986d8922bb 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs @@ -189,7 +189,7 @@ public EmbeddedSignatureData[] GetEmbeddedSignatureData(params EmbeddedSignature if ((_embeddedSignatureData == null) || (_embeddedSignatureData.Length == 0)) return null; - if (kinds == null) + if (kinds.Length == 0) return (EmbeddedSignatureData[])_embeddedSignatureData.Clone(); List ret = new(); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx index fe7c729bc93d27..0fad0c9b964772 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx +++ b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx @@ -192,7 +192,7 @@ Marshaling directives are invalid - + {0} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs b/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs index 33fc2f8a635162..661ce730157fdd 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs @@ -72,9 +72,9 @@ public static void ThrowMarshalDirectiveException() } [System.Diagnostics.DebuggerHidden] - public static void ThrowAmbiguousImplementationException(string message) + public static void ThrowAmbiguousMatchException(string message) { - throw new TypeSystemException.AmbiguousImplementationException(message); + throw new TypeSystemException.AmbiguousMatchException(message); } private static partial class Format diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs index 3b356b8b921c5b..0ac7ebac382af9 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs @@ -171,10 +171,10 @@ internal MarshalDirectiveException(ExceptionStringID id) } } - public class AmbiguousImplementationException : TypeSystemException + public class AmbiguousMatchException : TypeSystemException { - internal AmbiguousImplementationException(string reason) - : base(ExceptionStringID.AmbiguousImplementationSpecific, reason) + internal AmbiguousMatchException(string reason) + : base(ExceptionStringID.AmbiguousMatchSpecific, reason) { } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index a4abf24a79f054..4d11f271390794 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -354,7 +354,7 @@ private static bool TrySetTargetMethod(ref GenerationContext context, string nam return true; } - ThrowHelper.ThrowAmbiguousImplementationException(AmbiguityInUnsafeAccessorUsage); + ThrowHelper.ThrowAmbiguousMatchException(AmbiguityInUnsafeAccessorUsage); } targetMaybe = md; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 547c51384d65f4..1602c360ed7262 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1293,7 +1293,7 @@ namespace if (TrySetTargetMethod(cxt, methodName, false /* ignoreCustomModifiers */)) return true; } - COMPlusThrow(kAmbiguousImplementationException, BFA_AMBIGUOUS_UNSAFEACCESSOR); + COMPlusThrow(kAmbiguousMatchException, BFA_AMBIGUOUS_UNSAFEACCESSOR); } targetMaybe = curr; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs index a86b38e22bf48d..fcba9de7587d01 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -59,7 +59,9 @@ public enum UnsafeAccessorKind /// can be used to call Class1<T1>.Method1<T2>(). The generic constraints of the /// extern static method must match generic constraints of the target type, field or method. /// - /// Return type is considered for the signature match. modreqs and modopts are not considered for the signature match. + /// Return type is considered for the signature match. modreqs and modopts are initially not considered for + /// the signature match. However, if an ambiguity exists ignoring modreqs and modopts, a precise match + /// is attempted. If an ambiguity still exists is thrown. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public sealed class UnsafeAccessorAttribute : Attribute diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index 8a7fdf7ee853f0..ec25f55bc79ab0 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Runtime; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -320,7 +320,7 @@ public static void Verify_InvalidTargetUnsafeAccessor() Assert.Throws(() => FieldNotFound(null)); Assert.Throws(() => StaticFieldNotFound(null)); - Assert.Throws( + Assert.Throws( () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); [UnsafeAccessor(UnsafeAccessorKind.Method, Name="_DoesNotExist_")] From 3b3691fa282d2e5e94e633721d0d222eeaec519f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 14:11:20 -0700 Subject: [PATCH 38/54] Move tests to be Pri0 since they are all we have. --- .../compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj index 136522a99eb8b6..9a6dd1478df4fe 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj @@ -1,7 +1,6 @@ Exe - 1 true From f0756b7f60b9c08368a5c72d93a7f16c99dee135 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 19:40:33 -0700 Subject: [PATCH 39/54] Add support in ILAsm --- src/coreclr/ilasm/assem.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/coreclr/ilasm/assem.cpp b/src/coreclr/ilasm/assem.cpp index 4d50b136754714..d7282b8f4e18d1 100644 --- a/src/coreclr/ilasm/assem.cpp +++ b/src/coreclr/ilasm/assem.cpp @@ -315,6 +315,25 @@ BOOL Assembler::AddMethod(Method *pMethod) { if(fIsImport || IsMdAbstract(pMethod->m_Attr) || IsMdPinvokeImpl(pMethod->m_Attr) || IsMiRuntime(pMethod->m_wImplAttr) || IsMiInternalCall(pMethod->m_wImplAttr)) return TRUE; + + // Check method attributes that indicate no method body is expected. + ULONG count = pMethod->m_CustomDescrList.COUNT(); + for (ULONG i = 0; i < count; ++i) + { + CustomDescr* desc = pMethod->m_CustomDescrList.PEEK(i); + mdToken parent = mdTokenNil; + if (SUCCEEDED(m_pImporter->GetMemberRefProps(desc->tkType, &parent, NULL, 0, NULL, NULL, NULL))) + { + WCHAR name[128] = {}; + ULONG nameLen = ARRAYSIZE(name); + if (SUCCEEDED(m_pImporter->GetTypeRefProps(parent, NULL, name, nameLen, &nameLen))) + { + if (u16_strcmp(name, W("System.Runtime.CompilerServices.UnsafeAccessorAttribute")) == 0) + return TRUE; + } + } + } + if(OnErrGo) { report->error("Method has no body\n"); From c51e986aee5978e8f86cfe5e18c0b9972a3a1195 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 Jun 2023 19:54:11 -0700 Subject: [PATCH 40/54] Use xplat ARRAY_SIZE. --- src/coreclr/ilasm/assem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/ilasm/assem.cpp b/src/coreclr/ilasm/assem.cpp index d7282b8f4e18d1..ff0ff073cb85af 100644 --- a/src/coreclr/ilasm/assem.cpp +++ b/src/coreclr/ilasm/assem.cpp @@ -325,7 +325,7 @@ BOOL Assembler::AddMethod(Method *pMethod) if (SUCCEEDED(m_pImporter->GetMemberRefProps(desc->tkType, &parent, NULL, 0, NULL, NULL, NULL))) { WCHAR name[128] = {}; - ULONG nameLen = ARRAYSIZE(name); + ULONG nameLen = ARRAY_SIZE(name); if (SUCCEEDED(m_pImporter->GetTypeRefProps(parent, NULL, name, nameLen, &nameLen))) { if (u16_strcmp(name, W("System.Runtime.CompilerServices.UnsafeAccessorAttribute")) == 0) From 12ba4728c7cb1e0cc27f59d910aa36b172c1fcf6 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 Jun 2023 08:08:56 -0700 Subject: [PATCH 41/54] Remove special handling of empty method in ILASM --- src/coreclr/ilasm/assem.cpp | 63 +++++++------------------------------ 1 file changed, 11 insertions(+), 52 deletions(-) diff --git a/src/coreclr/ilasm/assem.cpp b/src/coreclr/ilasm/assem.cpp index ff0ff073cb85af..dd2c91ac093acb 100644 --- a/src/coreclr/ilasm/assem.cpp +++ b/src/coreclr/ilasm/assem.cpp @@ -296,60 +296,19 @@ BOOL Assembler::AddMethod(Method *pMethod) fIsInterface = IsTdInterface(pMethod->m_pClass->m_Attr); fIsImport = IsTdImport(pMethod->m_pClass->m_Attr); } - if(m_CurPC) - { - char sz[1024]; - sz[0] = 0; - if(fIsImport) strcat_s(sz,1024," imported"); - if(IsMdAbstract(pMethod->m_Attr)) strcat_s(sz,1024," abstract"); - if(IsMdPinvokeImpl(pMethod->m_Attr)) strcat_s(sz,1024," pinvoke"); - if(!IsMiIL(pMethod->m_wImplAttr)) strcat_s(sz,1024," non-IL"); - if(IsMiRuntime(pMethod->m_wImplAttr)) strcat_s(sz,1024," runtime-supplied"); - if(IsMiInternalCall(pMethod->m_wImplAttr)) strcat_s(sz,1024," an internal call"); - if(strlen(sz)) - { - report->error("Method cannot have body if it is%s\n",sz); - } - } - else // method has no body - { - if(fIsImport || IsMdAbstract(pMethod->m_Attr) || IsMdPinvokeImpl(pMethod->m_Attr) - || IsMiRuntime(pMethod->m_wImplAttr) || IsMiInternalCall(pMethod->m_wImplAttr)) return TRUE; - // Check method attributes that indicate no method body is expected. - ULONG count = pMethod->m_CustomDescrList.COUNT(); - for (ULONG i = 0; i < count; ++i) - { - CustomDescr* desc = pMethod->m_CustomDescrList.PEEK(i); - mdToken parent = mdTokenNil; - if (SUCCEEDED(m_pImporter->GetMemberRefProps(desc->tkType, &parent, NULL, 0, NULL, NULL, NULL))) - { - WCHAR name[128] = {}; - ULONG nameLen = ARRAY_SIZE(name); - if (SUCCEEDED(m_pImporter->GetTypeRefProps(parent, NULL, name, nameLen, &nameLen))) - { - if (u16_strcmp(name, W("System.Runtime.CompilerServices.UnsafeAccessorAttribute")) == 0) - return TRUE; - } - } - } + if(!m_CurPC) return TRUE; // method has no body, just emit empty method. - if(OnErrGo) - { - report->error("Method has no body\n"); - return TRUE; - } - else - { - report->warn("Method has no body, 'ret' emitted\n"); - Instr* pIns = GetInstr(); - if(pIns) - { - memset(pIns,0,sizeof(Instr)); - pIns->opcode = CEE_RET; - EmitOpcode(pIns); - } - } + char sz[1024] = {}; + if(fIsImport) strcat_s(sz,1024," imported"); + if(IsMdAbstract(pMethod->m_Attr)) strcat_s(sz,1024," abstract"); + if(IsMdPinvokeImpl(pMethod->m_Attr)) strcat_s(sz,1024," pinvoke"); + if(!IsMiIL(pMethod->m_wImplAttr)) strcat_s(sz,1024," non-IL"); + if(IsMiRuntime(pMethod->m_wImplAttr)) strcat_s(sz,1024," runtime-supplied"); + if(IsMiInternalCall(pMethod->m_wImplAttr)) strcat_s(sz,1024," an internal call"); + if(strlen(sz)) + { + report->error("Method cannot have body if it is%s\n",sz); } if(pMethod->m_Locals.COUNT()) pMethod->m_LocalsSig=0x11000001; // placeholder, the real token 2b defined in EmitMethod From efb27a47258844038d49db49c743eed1106d5fc4 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 Jun 2023 13:38:56 -0700 Subject: [PATCH 42/54] Generate accessors that throw specific failures. --- .../TypeSystem/Common/ExceptionStringID.cs | 3 - .../Common/Properties/Resources.resx | 3 - .../Common/TypeSystem/Common/ThrowHelper.cs | 6 -- .../TypeSystem/Common/TypeSystemException.cs | 8 -- .../TypeSystem/IL/NativeAotILProvider.cs | 99 ++++++++++--------- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 72 ++++++++++---- 6 files changed, 109 insertions(+), 82 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs index a943062eff9494..4c55874d7999fb 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs @@ -47,8 +47,5 @@ public enum ExceptionStringID // MarshalDirectiveException MarshalDirectiveGeneric, - - // AmbiguousMatchException - AmbiguousMatchSpecific, } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx index 0fad0c9b964772..6a70612fc391bf 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx +++ b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx @@ -192,7 +192,4 @@ Marshaling directives are invalid - - {0} - diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs b/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs index 661ce730157fdd..4e31950b40e99b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ThrowHelper.cs @@ -71,12 +71,6 @@ public static void ThrowMarshalDirectiveException() throw new TypeSystemException.MarshalDirectiveException(ExceptionStringID.MarshalDirectiveGeneric); } - [System.Diagnostics.DebuggerHidden] - public static void ThrowAmbiguousMatchException(string message) - { - throw new TypeSystemException.AmbiguousMatchException(message); - } - private static partial class Format { public static string OwningModule(TypeDesc type) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs index 0ac7ebac382af9..6c8cd66654af31 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemException.cs @@ -170,13 +170,5 @@ internal MarshalDirectiveException(ExceptionStringID id) { } } - - public class AmbiguousMatchException : TypeSystemException - { - internal AmbiguousMatchException(string reason) - : base(ExceptionStringID.AmbiguousMatchSpecific, reason) - { - } - } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs index 1760aab1218703..918b4c07f8e024 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs @@ -291,62 +291,73 @@ private static MethodIL TryGetPerInstantiationIntrinsicMethodIL(MethodDesc metho public override MethodIL GetMethodIL(MethodDesc method) { - if (method is EcmaMethod ecmaMethod) + try { - if (ecmaMethod.IsIntrinsic) + if (method is EcmaMethod ecmaMethod) { - MethodIL result = TryGetIntrinsicMethodIL(ecmaMethod); - if (result != null) - return result; - } - - if (ecmaMethod.IsRuntimeImplemented) - { - MethodIL result = TryGetRuntimeImplementedMethodIL(ecmaMethod); - if (result != null) - return result; - } + if (ecmaMethod.IsIntrinsic) + { + MethodIL result = TryGetIntrinsicMethodIL(ecmaMethod); + if (result != null) + return result; + } - MethodIL methodIL = EcmaMethodIL.Create(ecmaMethod); - if (methodIL != null) - return methodIL; + if (ecmaMethod.IsRuntimeImplemented) + { + MethodIL result = TryGetRuntimeImplementedMethodIL(ecmaMethod); + if (result != null) + return result; + } - methodIL = UnsafeAccessors.TryGetIL(ecmaMethod); - if (methodIL != null) - return methodIL; + MethodIL methodIL = EcmaMethodIL.Create(ecmaMethod); + if (methodIL != null) + return methodIL; - return null; - } - else - if (method is MethodForInstantiatedType || method is InstantiatedMethod) - { - // Intrinsics specialized per instantiation - if (method.IsIntrinsic) - { - MethodIL methodIL = TryGetPerInstantiationIntrinsicMethodIL(method); + methodIL = UnsafeAccessors.TryGetIL(ecmaMethod); if (methodIL != null) return methodIL; + + return null; } + else + if (method is MethodForInstantiatedType || method is InstantiatedMethod) + { + // Intrinsics specialized per instantiation + if (method.IsIntrinsic) + { + MethodIL methodIL = TryGetPerInstantiationIntrinsicMethodIL(method); + if (methodIL != null) + return methodIL; + } - var methodDefinitionIL = GetMethodIL(method.GetTypicalMethodDefinition()); - if (methodDefinitionIL == null) + var methodDefinitionIL = GetMethodIL(method.GetTypicalMethodDefinition()); + if (methodDefinitionIL == null) + return null; + return new InstantiatedMethodIL(method, methodDefinitionIL); + } + else + if (method is ILStubMethod) + { + return ((ILStubMethod)method).EmitIL(); + } + else + if (method is ArrayMethod) + { + return ArrayMethodILEmitter.EmitIL((ArrayMethod)method); + } + else + { + Debug.Assert(!(method is PInvokeTargetNativeMethod), "Who is asking for IL of PInvokeTargetNativeMethod?"); return null; - return new InstantiatedMethodIL(method, methodDefinitionIL); - } - else - if (method is ILStubMethod) - { - return ((ILStubMethod)method).EmitIL(); - } - else - if (method is ArrayMethod) - { - return ArrayMethodILEmitter.EmitIL((ArrayMethod)method); + } } - else + catch { - Debug.Assert(!(method is PInvokeTargetNativeMethod), "Who is asking for IL of PInvokeTargetNativeMethod?"); - return null; + ILEmitter emit = new ILEmitter(); + ILCodeStream codeStream = emit.NewCodeStream(); + MethodDesc throwHelper = method.Context.GetHelperEntryPoint("ThrowHelpers", "ThrowBadImageFormatException"); + codeStream.EmitCallThrowHelper(emit, throwHelper); + return emit.Link(method); } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 4d11f271390794..fadfddbe106f3b 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -1,6 +1,7 @@ // 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.Diagnostics; using System.Reflection.Metadata; using System.Runtime.InteropServices; @@ -12,9 +13,6 @@ namespace Internal.IL { public sealed class UnsafeAccessors { - private const string InvalidUnsafeAccessorUsage = "Invalid usage of UnsafeAccessorAttribute."; - private const string AmbiguityInUnsafeAccessorUsage = "Ambiguity in binding of UnsafeAccessorAttribute."; - public static MethodIL TryGetIL(EcmaMethod method) { Debug.Assert(method != null); @@ -27,7 +25,7 @@ public static MethodIL TryGetIL(EcmaMethod method) if (!TryParseUnsafeAccessorAttribute(method, decodedAttribute.Value, out UnsafeAccessorKind kind, out string name)) { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); } GenerationContext context = new() @@ -44,6 +42,8 @@ public static MethodIL TryGetIL(EcmaMethod method) firstArgType = sig[0]; } + bool isAmbiguous = false; + // Using the kind type, perform the following: // 1) Validate the basic type information from the signature. // 2) Resolve the name to the appropriate member. @@ -56,14 +56,14 @@ public static MethodIL TryGetIL(EcmaMethod method) // The name is defined by the runtime and should be empty. if (retType.IsVoid || retType.IsByRef || !string.IsNullOrEmpty(name)) { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); } const string ctorName = ".ctor"; context.TargetType = ValidateTargetType(retType); - if (!TrySetTargetMethod(ref context, ctorName)) + if (!TrySetTargetMethod(ref context, ctorName, out isAmbiguous)) { - ThrowHelper.ThrowMissingMethodException(context.TargetType, ctorName, null); + return GenerateAccessorSpecificFailure(ref context, ctorName, isAmbiguous); } break; case UnsafeAccessorKind.Method: @@ -71,14 +71,14 @@ public static MethodIL TryGetIL(EcmaMethod method) // Method access requires a target type. if (firstArgType == null) { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); } context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind.StaticMethod; - if (!TrySetTargetMethod(ref context, name)) + if (!TrySetTargetMethod(ref context, name, out isAmbiguous)) { - ThrowHelper.ThrowMissingMethodException(context.TargetType, name, null); + return GenerateAccessorSpecificFailure(ref context, name, isAmbiguous); } break; @@ -87,7 +87,7 @@ public static MethodIL TryGetIL(EcmaMethod method) // Field access requires a single argument for target type and a return type. if (sig.Length != 1 || retType.IsVoid) { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); } // The return type must be byref. @@ -98,19 +98,19 @@ public static MethodIL TryGetIL(EcmaMethod method) && firstArgType.IsValueType && !firstArgType.IsByRef)) { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); } context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField; if (!TrySetTargetField(ref context, name, ((ParameterizedType)retType).GetParameterType())) { - ThrowHelper.ThrowMissingFieldException(context.TargetType, name); + return GenerateAccessorSpecificFailure(ref context, name, isAmbiguous); } break; default: - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); break; } @@ -197,7 +197,7 @@ private static TypeDesc ValidateTargetType(TypeDesc targetTypeMaybe) if ((targetType.IsParameterizedType && !targetType.IsArray) || targetType.IsFunctionPointer) { - ThrowHelper.ThrowBadImageFormatException(InvalidUnsafeAccessorUsage); + ThrowHelper.ThrowBadImageFormatException(); } return targetType; @@ -317,7 +317,7 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte return true; } - private static bool TrySetTargetMethod(ref GenerationContext context, string name, bool ignoreCustomModifiers = true) + private static bool TrySetTargetMethod(ref GenerationContext context, string name, out bool isAmbiguous, bool ignoreCustomModifiers = true) { TypeDesc targetType = context.TargetType; @@ -350,16 +350,18 @@ private static bool TrySetTargetMethod(ref GenerationContext context, string nam // We have detected ambiguity when ignoring custom modifiers. // Start over, but look for a match requiring custom modifiers // to match precisely. - if (TrySetTargetMethod(ref context, name, ignoreCustomModifiers: false)) + if (TrySetTargetMethod(ref context, name, out isAmbiguous, ignoreCustomModifiers: false)) return true; } - ThrowHelper.ThrowAmbiguousMatchException(AmbiguityInUnsafeAccessorUsage); + isAmbiguous = true; + return false; } targetMaybe = md; } + isAmbiguous = false; context.TargetMethod = targetMaybe; return context.TargetMethod != null; } @@ -434,5 +436,39 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) codeStream.Emit(ILOpcode.ret); return emit.Link(context.TargetMethod ?? context.Declaration); } + + private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, bool ambiguous) + { + ILEmitter emit = new ILEmitter(); + ILCodeStream codeStream = emit.NewCodeStream(); + + MethodDesc md; + TypeSystemContext typeSysContext = context.Declaration.Context; + if (ambiguous) + { + codeStream.Emit(ILOpcode.ldstr, emit.NewToken("Ambiguity in binding of UnsafeAccessorAttribute.")); + + var type = context.Declaration.Context.SystemModule.GetType("System.Reflection", "AmbiguousMatchException"); + MethodSignature ctorString = new(MethodSignatureFlags.None, 0, typeSysContext.GetWellKnownType(WellKnownType.Void), new[] { typeSysContext.GetWellKnownType(WellKnownType.String) }); + md = type.GetMethod(".ctor", ctorString); + } + else + { + codeStream.Emit(ILOpcode.ldnull); // Not supplying class name + codeStream.Emit(ILOpcode.ldstr, emit.NewToken(name)); + + string exceptName = (context.Kind == UnsafeAccessorKind.Field || context.Kind == UnsafeAccessorKind.StaticField) + ? nameof(MissingFieldException) + : nameof(MissingMethodException); + + var type = context.Declaration.Context.SystemModule.GetType("System", exceptName); + MethodSignature ctorStringString = new(MethodSignatureFlags.None, 0, typeSysContext.GetWellKnownType(WellKnownType.Void), new[] { typeSysContext.GetWellKnownType(WellKnownType.String), typeSysContext.GetWellKnownType(WellKnownType.String) }); + md = type.GetMethod(".ctor", ctorStringString); + } + + codeStream.Emit(ILOpcode.newobj, emit.NewToken(md)); + codeStream.Emit(ILOpcode.throw_); + return emit.Link(context.Declaration); + } } } From d3b0726d5874c75587db92ddc1f427534062d48c Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 Jun 2023 15:07:50 -0700 Subject: [PATCH 43/54] NativeAOT error handling was throwing asserts. --- .../tools/Common/TypeSystem/IL/NativeAotILProvider.cs | 5 +++-- .../tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs index 918b4c07f8e024..eec6758397050d 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs @@ -355,8 +355,9 @@ public override MethodIL GetMethodIL(MethodDesc method) { ILEmitter emit = new ILEmitter(); ILCodeStream codeStream = emit.NewCodeStream(); - MethodDesc throwHelper = method.Context.GetHelperEntryPoint("ThrowHelpers", "ThrowBadImageFormatException"); - codeStream.EmitCallThrowHelper(emit, throwHelper); + var type = method.Context.SystemModule.GetType("System", "BadImageFormatException"); + codeStream.Emit(ILOpcode.newobj, emit.NewToken(type.GetDefaultConstructor())); + codeStream.Emit(ILOpcode.throw_); return emit.Link(method); } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index fadfddbe106f3b..a4ff4e727a855e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -405,36 +405,43 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) } // Provide access to the target member + MethodDesc owningMethod; switch (context.Kind) { case UnsafeAccessorKind.Constructor: Debug.Assert(context.TargetMethod != null); codeStream.Emit(ILOpcode.newobj, emit.NewToken(context.TargetMethod)); + owningMethod = context.Declaration; break; case UnsafeAccessorKind.Method: Debug.Assert(context.TargetMethod != null); codeStream.Emit(ILOpcode.callvirt, emit.NewToken(context.TargetMethod)); + owningMethod = context.TargetMethod; break; case UnsafeAccessorKind.StaticMethod: Debug.Assert(context.TargetMethod != null); codeStream.Emit(ILOpcode.call, emit.NewToken(context.TargetMethod)); + owningMethod = context.TargetMethod; break; case UnsafeAccessorKind.Field: Debug.Assert(context.TargetField != null); codeStream.Emit(ILOpcode.ldflda, emit.NewToken(context.TargetField)); + owningMethod = context.Declaration; break; case UnsafeAccessorKind.StaticField: Debug.Assert(context.TargetField != null); codeStream.Emit(ILOpcode.ldsflda, emit.NewToken(context.TargetField)); + owningMethod = context.Declaration; break; default: Debug.Fail("Unknown UnsafeAccessorKind"); + owningMethod = null; break; } // Return from the generated stub codeStream.Emit(ILOpcode.ret); - return emit.Link(context.TargetMethod ?? context.Declaration); + return emit.Link(owningMethod); } private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, bool ambiguous) From 56d733eecbea8aaeb17256e8e52dfd0688561d56 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 Jun 2023 20:24:03 -0700 Subject: [PATCH 44/54] Remove catch all for any exception. This code path has evolved to require exceptions to propagate for generation of typeload exception stub. --- .../TypeSystem/IL/NativeAotILProvider.cs | 100 ++++++++---------- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 38 +++++-- 2 files changed, 75 insertions(+), 63 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs index eec6758397050d..1760aab1218703 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs @@ -291,74 +291,62 @@ private static MethodIL TryGetPerInstantiationIntrinsicMethodIL(MethodDesc metho public override MethodIL GetMethodIL(MethodDesc method) { - try + if (method is EcmaMethod ecmaMethod) { - if (method is EcmaMethod ecmaMethod) + if (ecmaMethod.IsIntrinsic) { - if (ecmaMethod.IsIntrinsic) - { - MethodIL result = TryGetIntrinsicMethodIL(ecmaMethod); - if (result != null) - return result; - } + MethodIL result = TryGetIntrinsicMethodIL(ecmaMethod); + if (result != null) + return result; + } - if (ecmaMethod.IsRuntimeImplemented) - { - MethodIL result = TryGetRuntimeImplementedMethodIL(ecmaMethod); - if (result != null) - return result; - } + if (ecmaMethod.IsRuntimeImplemented) + { + MethodIL result = TryGetRuntimeImplementedMethodIL(ecmaMethod); + if (result != null) + return result; + } - MethodIL methodIL = EcmaMethodIL.Create(ecmaMethod); - if (methodIL != null) - return methodIL; + MethodIL methodIL = EcmaMethodIL.Create(ecmaMethod); + if (methodIL != null) + return methodIL; + + methodIL = UnsafeAccessors.TryGetIL(ecmaMethod); + if (methodIL != null) + return methodIL; - methodIL = UnsafeAccessors.TryGetIL(ecmaMethod); + return null; + } + else + if (method is MethodForInstantiatedType || method is InstantiatedMethod) + { + // Intrinsics specialized per instantiation + if (method.IsIntrinsic) + { + MethodIL methodIL = TryGetPerInstantiationIntrinsicMethodIL(method); if (methodIL != null) return methodIL; - - return null; } - else - if (method is MethodForInstantiatedType || method is InstantiatedMethod) - { - // Intrinsics specialized per instantiation - if (method.IsIntrinsic) - { - MethodIL methodIL = TryGetPerInstantiationIntrinsicMethodIL(method); - if (methodIL != null) - return methodIL; - } - var methodDefinitionIL = GetMethodIL(method.GetTypicalMethodDefinition()); - if (methodDefinitionIL == null) - return null; - return new InstantiatedMethodIL(method, methodDefinitionIL); - } - else - if (method is ILStubMethod) - { - return ((ILStubMethod)method).EmitIL(); - } - else - if (method is ArrayMethod) - { - return ArrayMethodILEmitter.EmitIL((ArrayMethod)method); - } - else - { - Debug.Assert(!(method is PInvokeTargetNativeMethod), "Who is asking for IL of PInvokeTargetNativeMethod?"); + var methodDefinitionIL = GetMethodIL(method.GetTypicalMethodDefinition()); + if (methodDefinitionIL == null) return null; - } + return new InstantiatedMethodIL(method, methodDefinitionIL); + } + else + if (method is ILStubMethod) + { + return ((ILStubMethod)method).EmitIL(); } - catch + else + if (method is ArrayMethod) { - ILEmitter emit = new ILEmitter(); - ILCodeStream codeStream = emit.NewCodeStream(); - var type = method.Context.SystemModule.GetType("System", "BadImageFormatException"); - codeStream.Emit(ILOpcode.newobj, emit.NewToken(type.GetDefaultConstructor())); - codeStream.Emit(ILOpcode.throw_); - return emit.Link(method); + return ArrayMethodILEmitter.EmitIL((ArrayMethod)method); + } + else + { + Debug.Assert(!(method is PInvokeTargetNativeMethod), "Who is asking for IL of PInvokeTargetNativeMethod?"); + return null; } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index a4ff4e727a855e..5de87a8b941ed3 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -25,7 +25,7 @@ public static MethodIL TryGetIL(EcmaMethod method) if (!TryParseUnsafeAccessorAttribute(method, decodedAttribute.Value, out UnsafeAccessorKind kind, out string name)) { - ThrowHelper.ThrowBadImageFormatException(); + return GenerateAccessorBadImageFailure(method); } GenerationContext context = new() @@ -56,11 +56,16 @@ public static MethodIL TryGetIL(EcmaMethod method) // The name is defined by the runtime and should be empty. if (retType.IsVoid || retType.IsByRef || !string.IsNullOrEmpty(name)) { - ThrowHelper.ThrowBadImageFormatException(); + return GenerateAccessorBadImageFailure(method); } const string ctorName = ".ctor"; context.TargetType = ValidateTargetType(retType); + if (context.TargetType == null) + { + return GenerateAccessorBadImageFailure(method); + } + if (!TrySetTargetMethod(ref context, ctorName, out isAmbiguous)) { return GenerateAccessorSpecificFailure(ref context, ctorName, isAmbiguous); @@ -71,10 +76,15 @@ public static MethodIL TryGetIL(EcmaMethod method) // Method access requires a target type. if (firstArgType == null) { - ThrowHelper.ThrowBadImageFormatException(); + return GenerateAccessorBadImageFailure(method); } context.TargetType = ValidateTargetType(firstArgType); + if (context.TargetType == null) + { + return GenerateAccessorBadImageFailure(method); + } + context.IsTargetStatic = kind == UnsafeAccessorKind.StaticMethod; if (!TrySetTargetMethod(ref context, name, out isAmbiguous)) { @@ -87,7 +97,7 @@ public static MethodIL TryGetIL(EcmaMethod method) // Field access requires a single argument for target type and a return type. if (sig.Length != 1 || retType.IsVoid) { - ThrowHelper.ThrowBadImageFormatException(); + return GenerateAccessorBadImageFailure(method); } // The return type must be byref. @@ -98,10 +108,15 @@ public static MethodIL TryGetIL(EcmaMethod method) && firstArgType.IsValueType && !firstArgType.IsByRef)) { - ThrowHelper.ThrowBadImageFormatException(); + return GenerateAccessorBadImageFailure(method); } context.TargetType = ValidateTargetType(firstArgType); + if (context.TargetType == null) + { + return GenerateAccessorBadImageFailure(method); + } + context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField; if (!TrySetTargetField(ref context, name, ((ParameterizedType)retType).GetParameterType())) { @@ -110,8 +125,7 @@ public static MethodIL TryGetIL(EcmaMethod method) break; default: - ThrowHelper.ThrowBadImageFormatException(); - break; + return GenerateAccessorBadImageFailure(method); } // Generate the IL for the accessor. @@ -477,5 +491,15 @@ private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext co codeStream.Emit(ILOpcode.throw_); return emit.Link(context.Declaration); } + + private static MethodIL GenerateAccessorBadImageFailure(MethodDesc method) + { + ILEmitter emit = new ILEmitter(); + ILCodeStream codeStream = emit.NewCodeStream(); + var type = method.Context.SystemModule.GetType("System", "BadImageFormatException"); + codeStream.Emit(ILOpcode.newobj, emit.NewToken(type.GetDefaultConstructor())); + codeStream.Emit(ILOpcode.throw_); + return emit.Link(method); + } } } From facc48ea6fb091b981947be7f3204888c0c2dd5e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 Jun 2023 20:32:35 -0700 Subject: [PATCH 45/54] Use declaration method as owner for stubs --- .../tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 5de87a8b941ed3..c7cf571aab7148 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -419,43 +419,36 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) } // Provide access to the target member - MethodDesc owningMethod; switch (context.Kind) { case UnsafeAccessorKind.Constructor: Debug.Assert(context.TargetMethod != null); codeStream.Emit(ILOpcode.newobj, emit.NewToken(context.TargetMethod)); - owningMethod = context.Declaration; break; case UnsafeAccessorKind.Method: Debug.Assert(context.TargetMethod != null); codeStream.Emit(ILOpcode.callvirt, emit.NewToken(context.TargetMethod)); - owningMethod = context.TargetMethod; break; case UnsafeAccessorKind.StaticMethod: Debug.Assert(context.TargetMethod != null); codeStream.Emit(ILOpcode.call, emit.NewToken(context.TargetMethod)); - owningMethod = context.TargetMethod; break; case UnsafeAccessorKind.Field: Debug.Assert(context.TargetField != null); codeStream.Emit(ILOpcode.ldflda, emit.NewToken(context.TargetField)); - owningMethod = context.Declaration; break; case UnsafeAccessorKind.StaticField: Debug.Assert(context.TargetField != null); codeStream.Emit(ILOpcode.ldsflda, emit.NewToken(context.TargetField)); - owningMethod = context.Declaration; break; default: Debug.Fail("Unknown UnsafeAccessorKind"); - owningMethod = null; break; } // Return from the generated stub codeStream.Emit(ILOpcode.ret); - return emit.Link(owningMethod); + return emit.Link(context.Declaration); } private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, bool ambiguous) From 8d92e7ee3a4c36d8410a94c557f50696d9340446 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 10 Jun 2023 07:54:23 -0700 Subject: [PATCH 46/54] Review feedback --- .../tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 3 ++- src/coreclr/vm/siginfo.cpp | 13 ++++--------- src/coreclr/vm/siginfo.hpp | 6 ------ 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index c7cf571aab7148..024f9c954d9354 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Globalization; using System.Reflection.Metadata; using System.Runtime.InteropServices; using Internal.IL.Stubs; @@ -260,7 +261,7 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte // account for the difference in declarations. string[] tmp = declIndex.Split('.'); int toUpdate = tmp.Length < 2 ? 0 : tmp.Length - 2; - int idx = int.Parse(tmp[toUpdate]); + int idx = int.Parse(tmp[toUpdate], CultureInfo.InvariantCulture); idx--; tmp[toUpdate] = idx.ToString(); declIndex = string.Join(".", tmp); diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 691fda519ca873..7d5a72e127c870 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3594,11 +3594,10 @@ BOOL CompareTypeTokens(mdToken tk1, mdToken tk2, ModuleBase *pModule1, ModuleBas #endif //!DACCESS_COMPILE } // CompareTypeTokens -static DWORD ConsumeCustomModifiers(PCCOR_SIGNATURE& pSig, PCCOR_SIGNATURE pEndSig) +static void ConsumeCustomModifiers(PCCOR_SIGNATURE& pSig, PCCOR_SIGNATURE pEndSig) { mdToken tk; CorElementType type; - DWORD count = 0; PCCOR_SIGNATURE pSigTmp = pSig; for (;;) @@ -3612,10 +3611,9 @@ static DWORD ConsumeCustomModifiers(PCCOR_SIGNATURE& pSig, PCCOR_SIGNATURE pEndS case ELEMENT_TYPE_CMOD_OPT: IfFailThrow(CorSigUncompressToken_EndPtr(pSigTmp, pEndSig, &tk)); pSig = pSigTmp; - count++; break; default: - return count; + return; } } } @@ -3721,8 +3719,8 @@ MetaSig::CompareElementType( // Consume custom modifiers if they are being ignored. if (state->IgnoreCustomModifiers) { - state->CustomModifierCount1 = ConsumeCustomModifiers(pSig1, pEndSig1); - state->CustomModifierCount2 = ConsumeCustomModifiers(pSig2, pEndSig2); + ConsumeCustomModifiers(pSig1, pEndSig1); + ConsumeCustomModifiers(pSig2, pEndSig2); } CorElementType Type1 = ELEMENT_TYPE_MAX; // initialize to illegal @@ -3871,9 +3869,6 @@ MetaSig::CompareElementType( IfFailThrow(CorSigUncompressToken_EndPtr(pSig1, pEndSig1, &tk1)); IfFailThrow(CorSigUncompressToken_EndPtr(pSig2, pEndSig2, &tk2)); - state->CustomModifierCount1++; - state->CustomModifierCount2++; - #ifndef DACCESS_COMPILE if (!CompareTypeDefOrRefOrSpec( pModule1, diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 54e2619524ddf2..7ea7ada7cb9e2f 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -952,10 +952,6 @@ class MetaSig // See TokenPairList for more use details. TokenPairList* Visited; - // Custom modifiers found during the comparison. - DWORD CustomModifierCount1; - DWORD CustomModifierCount2; - // Boolean indicating if custom modifiers should // be compared. bool IgnoreCustomModifiers; @@ -964,8 +960,6 @@ class MetaSig CompareState(TokenPairList* list) : Visited{ list } - , CustomModifierCount1{} - , CustomModifierCount2{} , IgnoreCustomModifiers{ false } { } }; From b446a20cf4f468dad09d81ddb2c7bebbf1342da3 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 12 Jun 2023 08:07:13 -0700 Subject: [PATCH 47/54] Improve MissingMethodException Add test for validation --- src/coreclr/vm/memberload.cpp | 11 ++++-- .../CoreCLRTestLibrary/AssertExtensions.cs | 39 ++++++++++++++++--- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 29 ++++++++++---- .../UnsafeAccessorsTests.csproj | 3 ++ 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/coreclr/vm/memberload.cpp b/src/coreclr/vm/memberload.cpp index 57c9072db3a4b4..1cb98fd00b3ed9 100644 --- a/src/coreclr/vm/memberload.cpp +++ b/src/coreclr/vm/memberload.cpp @@ -97,7 +97,7 @@ void DECLSPEC_NORETURN MemberLoader::ThrowMissingMethodException(MethodTable* pM LPCUTF8 szClassName; DefineFullyQualifiedNameForClass(); - if (pMT) + if (pMT != NULL) { szClassName = GetFullyQualifiedNameForClass(pMT); } @@ -106,16 +106,21 @@ void DECLSPEC_NORETURN MemberLoader::ThrowMissingMethodException(MethodTable* pM szClassName = "?"; }; + if (szMember == NULL) + szMember = "?"; + if (pSig && cSig && pModule && pModule->IsFullModule()) { MetaSig tmp(pSig, cSig, static_cast(pModule), pTypeContext); - SigFormat sf(tmp, szMember ? szMember : "?", szClassName, NULL); + SigFormat sf(tmp, szMember, szClassName, NULL); MAKE_WIDEPTR_FROMUTF8(szwFullName, sf.GetCString()); EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, szwFullName)); } else { - EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, W("?"))); + SString typeName; + typeName.Printf("%s.%s", szClassName, szMember); + EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, typeName.GetUnicode())); } } diff --git a/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs b/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs index 4da7bf9ceb0a00..7de166e6108404 100644 --- a/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs +++ b/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs @@ -41,15 +41,12 @@ public static ArgumentException ThrowsArgumentException(string parameterName, Ac /// /// Asserts that the given delegate throws an of type with the given parameter name. /// - /// - /// The delegate of type to execute. - /// - /// - /// A containing additional information for when the assertion fails. - /// /// /// A containing the parameter of name to check, to skip parameter validation. /// + /// + /// The delegate of type to execute. + /// /// /// The thrown . /// @@ -74,6 +71,36 @@ public static T ThrowsArgumentException(string parameterName, Action action) return exception; } + /// + /// Asserts that the given delegate throws an of type with the given parameter name. + /// + /// + /// A containing the parameter of name to check, to skip parameter validation. + /// + /// + /// The delegate of type to execute. + /// + /// + /// The thrown . + /// + /// + /// of type was not thrown. + /// + /// -or- + /// + /// does not contain . + /// + public static T ThrowsMissingMemberException(string memberName, Action action) + where T : MissingMemberException + { + T exception = Assert.Throws(action); + + if (memberName != null) + Assert.True(exception.Message.Contains(memberName)); + + return exception; + } + /// /// Asserts that the given async delegate throws an of type and /// returns an of type . diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index ec25f55bc79ab0..6892afab41dc4c 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -23,6 +23,7 @@ class UserDataClass public const string StaticMethodVoidName = nameof(_MVV); public const string MethodVoidName = nameof(_mvv); public const string MethodNameAmbiguous = nameof(_Ambiguous); + public const string MethodPointerName = nameof(_Pointer); private static string _F = PrivateStatic; private string _f; @@ -44,6 +45,9 @@ private void _mvv() {} // Used to validate ambiguity is handled via custom modifiers. private string _Ambiguous(delegate* unmanaged[Cdecl, MemberFunction] fptr) { return nameof(CallConvCdecl); } private string _Ambiguous(delegate* unmanaged[Stdcall, MemberFunction] fptr) { return nameof(CallConvStdcall); } + + // Used to validate pointer values. + private static string _Pointer(void* ptr) => "void*"; } [StructLayout(LayoutKind.Sequential)] @@ -314,25 +318,30 @@ public static void Verify_InvalidTargetUnsafeAccessor() { Console.WriteLine($"Running {nameof(Verify_InvalidTargetUnsafeAccessor)}"); - Assert.Throws(() => MethodNotFound(null)); - Assert.Throws(() => StaticMethodNotFound(null)); + const string DoesNotExist = "_DoesNotExist_"; + AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => MethodNotFound(null)); + AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => StaticMethodNotFound(null)); - Assert.Throws(() => FieldNotFound(null)); - Assert.Throws(() => StaticFieldNotFound(null)); + AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => FieldNotFound(null)); + AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => StaticFieldNotFound(null)); Assert.Throws( () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name="_DoesNotExist_")] + AssertExtensions.ThrowsMissingMemberException( + UserDataClass.MethodPointerName, + () => CallPointerMethod(null, null)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=DoesNotExist)] extern static void MethodNotFound(UserDataClass d); - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="_DoesNotExist_")] + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=DoesNotExist)] extern static void StaticMethodNotFound(UserDataClass d); - [UnsafeAccessor(UnsafeAccessorKind.Field, Name="_DoesNotExist_")] + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=DoesNotExist)] extern static ref string FieldNotFound(UserDataClass d); - [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name="_DoesNotExist_")] + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=DoesNotExist)] extern static ref string StaticFieldNotFound(UserDataClass d); // This is an ambiguous match since there are two methods each with two custom modifiers. @@ -340,6 +349,10 @@ public static void Verify_InvalidTargetUnsafeAccessor() // precise match and that also fails because the custom modifiers don't match precisely. [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodNameAmbiguous)] extern static string CallAmbiguousMethod(UserDataClass d, delegate* unmanaged[Stdcall, SuppressGCTransition] fptr); + + // Pointers generally degrade to `void*`, but that isn't true for UnsafeAccessor signature validation. + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodPointerName)] + extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall, SuppressGCTransition] fptr); } [Fact] diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj index 9a6dd1478df4fe..98cadb5e8d61d6 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj @@ -6,4 +6,7 @@ + + + From 5b67d04971d86e59909ffe4256262e5fc6525e36 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 12 Jun 2023 10:06:47 -0700 Subject: [PATCH 48/54] Missed fallback name lookup on NativeAOT. Added tests for pointer types and declaration name lookup. --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 2 +- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 42 +++++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 024f9c954d9354..50fb4e70895f3d 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -183,7 +183,7 @@ private static bool TryParseUnsafeAccessorAttribute(MethodDesc method, CustomAtt // as empty at the use site. if (kind is not UnsafeAccessorKind.Constructor) { - name = nameMaybe; + name = method.Name; } } diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index 6892afab41dc4c..7dfd885333fd0d 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -224,6 +224,13 @@ public static void Verify_AccessMethodClass() extern static string GetPrivateMethod(UserDataClass d, string s, ref string sr, in string si); } + // These are defined outside of the test to validate lookup using the name of + // the declaration as opposed to the Name field. + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod)] + extern static void _MVV(UserDataClass d); + [UnsafeAccessor(UnsafeAccessorKind.Method)] + extern static void _mvv(UserDataClass d); + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void Verify_AccessStaticMethodVoidClass() @@ -231,6 +238,7 @@ public static void Verify_AccessStaticMethodVoidClass() Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodVoidClass)}"); GetPrivateStaticMethod((UserDataClass)null); + _MVV((UserDataClass)null); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodVoidName)] extern static void GetPrivateStaticMethod(UserDataClass d); @@ -244,6 +252,7 @@ public static void Verify_AccessMethodVoidClass() var local = CallPrivateConstructorClass(); GetPrivateMethod(local); + _mvv(local); [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodVoidName)] extern static void GetPrivateMethod(UserDataClass d); @@ -318,20 +327,29 @@ public static void Verify_InvalidTargetUnsafeAccessor() { Console.WriteLine($"Running {nameof(Verify_InvalidTargetUnsafeAccessor)}"); + bool isNativeAot = TestLibrary.Utilities.IsNativeAot; const string DoesNotExist = "_DoesNotExist_"; - AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => MethodNotFound(null)); - AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => StaticMethodNotFound(null)); + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : DoesNotExist, + () => MethodNotFound(null)); + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : DoesNotExist, + () => StaticMethodNotFound(null)); - AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => FieldNotFound(null)); - AssertExtensions.ThrowsMissingMemberException(DoesNotExist, () => StaticFieldNotFound(null)); - - Assert.Throws( - () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : DoesNotExist, + () => FieldNotFound(null)); + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : DoesNotExist, + () => StaticFieldNotFound(null)); AssertExtensions.ThrowsMissingMemberException( - UserDataClass.MethodPointerName, + isNativeAot ? null : UserDataClass.MethodPointerName, () => CallPointerMethod(null, null)); + Assert.Throws( + () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=DoesNotExist)] extern static void MethodNotFound(UserDataClass d); @@ -344,15 +362,15 @@ public static void Verify_InvalidTargetUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=DoesNotExist)] extern static ref string StaticFieldNotFound(UserDataClass d); + // Pointers generally degrade to `void*`, but that isn't true for UnsafeAccessor signature validation. + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.MethodPointerName)] + extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall] fptr); + // This is an ambiguous match since there are two methods each with two custom modifiers. // Therefore the default "ignore custom modifiers" logic fails. The fallback is for a // precise match and that also fails because the custom modifiers don't match precisely. [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodNameAmbiguous)] extern static string CallAmbiguousMethod(UserDataClass d, delegate* unmanaged[Stdcall, SuppressGCTransition] fptr); - - // Pointers generally degrade to `void*`, but that isn't true for UnsafeAccessor signature validation. - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodPointerName)] - extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall, SuppressGCTransition] fptr); } [Fact] From 3663bad75c7e642862739de9432c349ed7c8f90a Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 12 Jun 2023 15:53:09 -0700 Subject: [PATCH 49/54] Tests for calling convention bits on NativeAOT. --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 112 +++++++++++------- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 19 ++- 2 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 50fb4e70895f3d..dc3e1322b19fea 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -227,51 +227,13 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte // If we are, do it first. if (!ignoreCustomModifiers) { - // One signature has custom modifiers the other doesn't, no match. - if (declSig.HasEmbeddedSignatureData != maybeSig.HasEmbeddedSignatureData) + // Compare custom modifiers on the signatures. + var declCustomMod = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier) ?? Array.Empty(); + var maybeCustomMod = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier) ?? Array.Empty(); + if (!CompareEmbeddedData(context.Kind, declCustomMod, maybeCustomMod)) { return false; } - - // Get all custom modifiers on the signatures. - var declData = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier); - var maybeData = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier); - if (declData.Length != maybeData.Length) - { - return false; - } - - // Validate the custom modifiers match precisely. - for (int i = 0; i < declData.Length; ++i) - { - EmbeddedSignatureData dd = declData[i]; - EmbeddedSignatureData md = maybeData[i]; - if (dd.kind != md.kind || dd.type != md.type) - { - return false; - } - - // The indices on non-constructor declarations require - // some slight modification since there is always an extra - // argument in the declaration compared to the target. - string declIndex = dd.index; - if (context.Kind != UnsafeAccessorKind.Constructor) - { - // Decrement the second to last index by one to - // account for the difference in declarations. - string[] tmp = declIndex.Split('.'); - int toUpdate = tmp.Length < 2 ? 0 : tmp.Length - 2; - int idx = int.Parse(tmp[toUpdate], CultureInfo.InvariantCulture); - idx--; - tmp[toUpdate] = idx.ToString(); - declIndex = string.Join(".", tmp); - } - - if (declIndex != md.index) - { - return false; - } - } } // Validate calling convention. @@ -281,6 +243,14 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte return false; } + // Compare unmanaged callconv on the signatures. + var declUnmanaged = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.UnmanagedCallConv) ?? Array.Empty(); + var maybeUnmanaged = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.UnmanagedCallConv) ?? Array.Empty(); + if (!CompareEmbeddedData(context.Kind, declUnmanaged, maybeUnmanaged)) + { + return false; + } + // Validate argument count and return type if (context.Kind == UnsafeAccessorKind.Constructor) { @@ -330,6 +300,64 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte } return true; + + static bool CompareEmbeddedData( + UnsafeAccessorKind kind, + EmbeddedSignatureData[] declData, + EmbeddedSignatureData[] maybeData) + { + if (declData.Length != maybeData.Length) + { + return false; + } + + // Validate the custom modifiers match precisely. + for (int i = 0; i < declData.Length; ++i) + { + EmbeddedSignatureData dd = declData[i]; + EmbeddedSignatureData md = maybeData[i]; + if (dd.kind != md.kind || dd.type != md.type) + { + return false; + } + + // The indices on non-constructor declarations require + // some slight modification since there is always an extra + // argument in the declaration compared to the target. + string declIndex = dd.index; + if (kind != UnsafeAccessorKind.Constructor) + { + string unmanagedCallConvMaybe = string.Empty; + + // Check for and drop the unmanaged calling convention + // value suffix to add it back after updating below. + if (declIndex.Contains('|')) + { + Debug.Assert(dd.kind == EmbeddedSignatureDataKind.UnmanagedCallConv); + var tmp = declIndex.Split('|'); + Debug.Assert(tmp.Length == 2); + declIndex = tmp[0]; + unmanagedCallConvMaybe = "|" + tmp[1]; + } + + // Decrement the second to last index by one to + // account for the difference in declarations. + string[] lvls = declIndex.Split('.'); + int toUpdate = lvls.Length < 2 ? 0 : lvls.Length - 2; + int idx = int.Parse(lvls[toUpdate], CultureInfo.InvariantCulture); + idx--; + lvls[toUpdate] = idx.ToString(); + declIndex = string.Join(".", lvls) + unmanagedCallConvMaybe; + } + + if (declIndex != md.index) + { + return false; + } + } + + return true; + } } private static bool TrySetTargetMethod(ref GenerationContext context, string name, out bool isAmbiguous, bool ignoreCustomModifiers = true) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index 7dfd885333fd0d..d5c849e9aba22a 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -24,6 +24,7 @@ class UserDataClass public const string MethodVoidName = nameof(_mvv); public const string MethodNameAmbiguous = nameof(_Ambiguous); public const string MethodPointerName = nameof(_Pointer); + public const string MethodCallConvBitsName = nameof(_CallConvBits); private static string _F = PrivateStatic; private string _f; @@ -43,11 +44,15 @@ private void _mvv() {} private string Prop { get; init; } // Used to validate ambiguity is handled via custom modifiers. - private string _Ambiguous(delegate* unmanaged[Cdecl, MemberFunction] fptr) { return nameof(CallConvCdecl); } - private string _Ambiguous(delegate* unmanaged[Stdcall, MemberFunction] fptr) { return nameof(CallConvStdcall); } + private string _Ambiguous(delegate* unmanaged[Cdecl, MemberFunction] fptr) => nameof(CallConvCdecl); + private string _Ambiguous(delegate* unmanaged[Stdcall, MemberFunction] fptr) => nameof(CallConvStdcall); // Used to validate pointer values. private static string _Pointer(void* ptr) => "void*"; + + // Used to validate the embedded callconv bits (non-unmanaged bit) in + // ECMA-335 signatures for methods. + private static string _CallConvBits(delegate* unmanaged[Cdecl] fptr) => nameof(CallConvCdecl); } [StructLayout(LayoutKind.Sequential)] @@ -347,6 +352,10 @@ public static void Verify_InvalidTargetUnsafeAccessor() isNativeAot ? null : UserDataClass.MethodPointerName, () => CallPointerMethod(null, null)); + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : UserDataClass.MethodCallConvBitsName, + () => CallCallConvBitsMethod(null, null)); + Assert.Throws( () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); @@ -366,6 +375,12 @@ public static void Verify_InvalidTargetUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.MethodPointerName)] extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall] fptr); + // This is used to validate the ECMA-335 calling convention bits are checked + // before checking the custom modifiers that encode the calling conventions + // when there is more than one or one option doesn't have an existing bit. + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCallConvBitsName)] + extern static string CallCallConvBitsMethod(UserDataClass d, delegate* unmanaged[Cdecl, SuppressGCTransition] fptr); + // This is an ambiguous match since there are two methods each with two custom modifiers. // Therefore the default "ignore custom modifiers" logic fails. The fallback is for a // precise match and that also fails because the custom modifiers don't match precisely. From c2be6fc2b84a87b29a02d6b93384431a48459886 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 12 Jun 2023 15:53:38 -0700 Subject: [PATCH 50/54] Remove block of doc text for generics. Will add back when supported is added. --- .../CompilerServices/UnsafeAccessorAttribute.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs index fcba9de7587d01..dfff035b9d7e07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -53,12 +53,6 @@ public enum UnsafeAccessorKind /// The first argument must be passed as ref for instance fields and methods on structs. /// The value of the first argument is not used by the implementation for static fields and methods. /// - /// The generic parameters of the extern static method are a concatenation of the type and - /// method generic arguments of the target method. For example, - /// extern static void Method1<T1, T2>(Class1<T1> @this) - /// can be used to call Class1<T1>.Method1<T2>(). The generic constraints of the - /// extern static method must match generic constraints of the target type, field or method. - /// /// Return type is considered for the signature match. modreqs and modopts are initially not considered for /// the signature match. However, if an ambiguity exists ignoring modreqs and modopts, a precise match /// is attempted. If an ambiguity still exists is thrown. @@ -66,6 +60,14 @@ public enum UnsafeAccessorKind [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public sealed class UnsafeAccessorAttribute : Attribute { + // Block of text to include above when Generics support is added: + // + // The generic parameters of the extern static method are a concatenation of the type and + // method generic arguments of the target method. For example, + // extern static void Method1<T1, T2>(Class1<T1> @this) + // can be used to call Class1<T1>.Method1<T2>(). The generic constraints of the + // extern static method must match generic constraints of the target type, field or method. + /// /// Instantiates an providing access to a member of kind . /// From 63f975c2a0cd8108a97fc61f3f419ec3675232cc Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 12 Jun 2023 17:18:27 -0700 Subject: [PATCH 51/54] Treat signature calling convention bits like we treat custom modifiers during method lookup. --- .../Common/TypeSystem/Common/MethodDesc.cs | 9 +- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 130 ++++++++---------- src/coreclr/vm/siginfo.cpp | 9 +- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 38 +++-- 4 files changed, 100 insertions(+), 86 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs index 7d84986d8922bb..6e122538dcceea 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs @@ -184,12 +184,17 @@ public bool EmbeddedSignatureMismatchPermitted } } - public EmbeddedSignatureData[] GetEmbeddedSignatureData(params EmbeddedSignatureDataKind[] kinds) + public EmbeddedSignatureData[] GetEmbeddedSignatureData() + { + return GetEmbeddedSignatureData(default); + } + + public EmbeddedSignatureData[] GetEmbeddedSignatureData(ReadOnlySpan kinds) { if ((_embeddedSignatureData == null) || (_embeddedSignatureData.Length == 0)) return null; - if (kinds.Length == 0) + if (kinds.IsEmpty) return (EmbeddedSignatureData[])_embeddedSignatureData.Clone(); List ret = new(); diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index dc3e1322b19fea..348dad5293af29 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -227,30 +227,76 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte // If we are, do it first. if (!ignoreCustomModifiers) { - // Compare custom modifiers on the signatures. - var declCustomMod = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier) ?? Array.Empty(); - var maybeCustomMod = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier) ?? Array.Empty(); - if (!CompareEmbeddedData(context.Kind, declCustomMod, maybeCustomMod)) + // Compare any unmanaged callconv and custom modifiers on the signatures. + // We treat unmanaged calling conventions at the same level of precedance + // as custom modifiers, eventhough they are normally bits in a signature. + ReadOnlySpan kinds = new EmbeddedSignatureDataKind[] + { + EmbeddedSignatureDataKind.UnmanagedCallConv, + EmbeddedSignatureDataKind.RequiredCustomModifier, + EmbeddedSignatureDataKind.OptionalCustomModifier + }; + + var declData = declSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty(); + var maybeData = maybeSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty(); + if (declData.Length != maybeData.Length) { return false; } + + // Validate the custom modifiers match precisely. + for (int i = 0; i < declData.Length; ++i) + { + EmbeddedSignatureData dd = declData[i]; + EmbeddedSignatureData md = maybeData[i]; + if (dd.kind != md.kind || dd.type != md.type) + { + return false; + } + + // The indices on non-constructor declarations require + // some slight modification since there is always an extra + // argument in the declaration compared to the target. + string declIndex = dd.index; + if (context.Kind != UnsafeAccessorKind.Constructor) + { + string unmanagedCallConvMaybe = string.Empty; + + // Check for and drop the unmanaged calling convention + // value suffix to add it back after updating below. + if (declIndex.Contains('|')) + { + Debug.Assert(dd.kind == EmbeddedSignatureDataKind.UnmanagedCallConv); + var tmp = declIndex.Split('|'); + Debug.Assert(tmp.Length == 2); + declIndex = tmp[0]; + unmanagedCallConvMaybe = "|" + tmp[1]; + } + + // Decrement the second to last index by one to + // account for the difference in declarations. + string[] lvls = declIndex.Split('.'); + int toUpdate = lvls.Length < 2 ? 0 : lvls.Length - 2; + int idx = int.Parse(lvls[toUpdate], CultureInfo.InvariantCulture); + idx--; + lvls[toUpdate] = idx.ToString(); + declIndex = string.Join(".", lvls) + unmanagedCallConvMaybe; + } + + if (declIndex != md.index) + { + return false; + } + } } - // Validate calling convention. + // Validate calling convention of declaration. if ((declSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) != (maybeSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask)) { return false; } - // Compare unmanaged callconv on the signatures. - var declUnmanaged = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.UnmanagedCallConv) ?? Array.Empty(); - var maybeUnmanaged = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.UnmanagedCallConv) ?? Array.Empty(); - if (!CompareEmbeddedData(context.Kind, declUnmanaged, maybeUnmanaged)) - { - return false; - } - // Validate argument count and return type if (context.Kind == UnsafeAccessorKind.Constructor) { @@ -300,64 +346,6 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte } return true; - - static bool CompareEmbeddedData( - UnsafeAccessorKind kind, - EmbeddedSignatureData[] declData, - EmbeddedSignatureData[] maybeData) - { - if (declData.Length != maybeData.Length) - { - return false; - } - - // Validate the custom modifiers match precisely. - for (int i = 0; i < declData.Length; ++i) - { - EmbeddedSignatureData dd = declData[i]; - EmbeddedSignatureData md = maybeData[i]; - if (dd.kind != md.kind || dd.type != md.type) - { - return false; - } - - // The indices on non-constructor declarations require - // some slight modification since there is always an extra - // argument in the declaration compared to the target. - string declIndex = dd.index; - if (kind != UnsafeAccessorKind.Constructor) - { - string unmanagedCallConvMaybe = string.Empty; - - // Check for and drop the unmanaged calling convention - // value suffix to add it back after updating below. - if (declIndex.Contains('|')) - { - Debug.Assert(dd.kind == EmbeddedSignatureDataKind.UnmanagedCallConv); - var tmp = declIndex.Split('|'); - Debug.Assert(tmp.Length == 2); - declIndex = tmp[0]; - unmanagedCallConvMaybe = "|" + tmp[1]; - } - - // Decrement the second to last index by one to - // account for the difference in declarations. - string[] lvls = declIndex.Split('.'); - int toUpdate = lvls.Length < 2 ? 0 : lvls.Length - 2; - int idx = int.Parse(lvls[toUpdate], CultureInfo.InvariantCulture); - idx--; - lvls[toUpdate] = idx.ToString(); - declIndex = string.Join(".", lvls) + unmanagedCallConvMaybe; - } - - if (declIndex != md.index) - { - return false; - } - } - - return true; - } } private static bool TrySetTargetMethod(ref GenerationContext context, string name, out bool isAmbiguous, bool ignoreCustomModifiers = true) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 7d5a72e127c870..3d13fcbc9b1207 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3927,7 +3927,14 @@ MetaSig::CompareElementType( IfFailThrow(CorSigUncompressElementType_EndPtr(pSig1, pEndSig1, &callingConvention1)); CorElementType callingConvention2 = ELEMENT_TYPE_MAX; // initialize to illegal IfFailThrow(CorSigUncompressElementType_EndPtr(pSig2, pEndSig2, &callingConvention2)); - if (callingConvention1 != callingConvention2) + + // Calling conventions are generally treated as custom modifiers. + // When callers request calling conventions to be ignored, we also ignore + // calling conventions and this is okay. It is okay because calling conventions, + // when more than one is defined (for example, SuppressGCTransition), all + // become encoded as custom modifiers. + if (!state->IgnoreCustomModifiers + && callingConvention1 != callingConvention2) { return FALSE; } diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index d5c849e9aba22a..d489ec35910ca6 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -24,7 +24,8 @@ class UserDataClass public const string MethodVoidName = nameof(_mvv); public const string MethodNameAmbiguous = nameof(_Ambiguous); public const string MethodPointerName = nameof(_Pointer); - public const string MethodCallConvBitsName = nameof(_CallConvBits); + public const string MethodCdeclCallConvBitName = nameof(_CdeclCallConvBit); + public const string MethodStdcallCallConvBitName = nameof(_StdcallCallConvBit); private static string _F = PrivateStatic; private string _f; @@ -52,7 +53,8 @@ private void _mvv() {} // Used to validate the embedded callconv bits (non-unmanaged bit) in // ECMA-335 signatures for methods. - private static string _CallConvBits(delegate* unmanaged[Cdecl] fptr) => nameof(CallConvCdecl); + private string _CdeclCallConvBit(delegate* unmanaged[Cdecl] fptr) => nameof(CallConvCdecl); + private string _StdcallCallConvBit(delegate* unmanaged[Stdcall] fptr) => nameof(CallConvStdcall); } [StructLayout(LayoutKind.Sequential)] @@ -326,6 +328,28 @@ public static void Verify_PreciseMatchCustomModifier() extern static string CallPrivateMethod(UserDataClass d, delegate* unmanaged[Stdcall, MemberFunction] fptr); } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored() + { + Console.WriteLine($"Running {nameof(Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored)}"); + + var ud = CallPrivateConstructorClass(); + Assert.Equal(nameof(CallConvCdecl), CallCdeclMethod(ud, null)); + Assert.Equal(nameof(CallConvStdcall), CallStdcallMethod(ud, null)); + + // The names of the declarations don't match the calling conventions in the function + // pointer signature, this is by design for this test. The intent here is to validate that + // calling conventions, when encoded in the ECMA-335 bits, are ignored on the first pass + // in the same way custom modifiers are ignored. + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCdeclCallConvBitName)] + extern static string CallCdeclMethod(UserDataClass d, delegate* unmanaged[Stdcall] fptr); + + // See comment above regarding naming. + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodStdcallCallConvBitName)] + extern static string CallStdcallMethod(UserDataClass d, delegate* unmanaged[Cdecl] fptr); + } + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void Verify_InvalidTargetUnsafeAccessor() @@ -352,10 +376,6 @@ public static void Verify_InvalidTargetUnsafeAccessor() isNativeAot ? null : UserDataClass.MethodPointerName, () => CallPointerMethod(null, null)); - AssertExtensions.ThrowsMissingMemberException( - isNativeAot ? null : UserDataClass.MethodCallConvBitsName, - () => CallCallConvBitsMethod(null, null)); - Assert.Throws( () => CallAmbiguousMethod(CallPrivateConstructorClass(), null)); @@ -375,12 +395,6 @@ public static void Verify_InvalidTargetUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.MethodPointerName)] extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall] fptr); - // This is used to validate the ECMA-335 calling convention bits are checked - // before checking the custom modifiers that encode the calling conventions - // when there is more than one or one option doesn't have an existing bit. - [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCallConvBitsName)] - extern static string CallCallConvBitsMethod(UserDataClass d, delegate* unmanaged[Cdecl, SuppressGCTransition] fptr); - // This is an ambiguous match since there are two methods each with two custom modifiers. // Therefore the default "ignore custom modifiers" logic fails. The fallback is for a // precise match and that also fails because the custom modifiers don't match precisely. From 869a5f07cc4cc50e1edef2828223d975a23c405a Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 13 Jun 2023 14:35:37 -0700 Subject: [PATCH 52/54] Managed and unmanaged function pointers never signature match. --- src/coreclr/vm/siginfo.cpp | 55 ++++++++++++------- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 27 ++++++++- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 3d13fcbc9b1207..dd1570145c9627 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -963,6 +963,30 @@ TypeHandle SigPointer::GetTypeHandleNT(Module* pModule, #endif // #ifndef DACCESS_COMPILE +// Normalizing function pointer calling convention means +// simply treating it as either "managed" or "unmanaged". +static uint32_t NormalizeFnPtrCallingConvention(uint32_t callConv) +{ + LIMITED_METHOD_CONTRACT; + + // Only have an unmanaged\managed status, and not the unmanaged CALLCONV_ value. + switch (callConv & IMAGE_CEE_CS_CALLCONV_MASK) + { + case IMAGE_CEE_CS_CALLCONV_C: + case IMAGE_CEE_CS_CALLCONV_STDCALL: + case IMAGE_CEE_CS_CALLCONV_THISCALL: + case IMAGE_CEE_CS_CALLCONV_FASTCALL: + // Strip the calling convention. + callConv &= ~IMAGE_CEE_CS_CALLCONV_MASK; + // Normalize to unmanaged. + callConv |= IMAGE_CEE_CS_CALLCONV_UNMANAGED; + break; + default: + break; + } + + return callConv; +} #ifdef _PREFAST_ #pragma warning(push) @@ -1739,7 +1763,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( TypeHandle *retAndArgTypes = (TypeHandle*) _alloca(cAllocaSize); bool fReturnTypeOrParameterNotLoaded = false; - for (unsigned i = 0; i <= cArgs; i++) + for (uint32_t i = 0; i <= cArgs; i++) { // Lookup type handle. retAndArgTypes[i] = psig.GetTypeHandleThrowing(pOrigModule, @@ -1765,18 +1789,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( break; } - // Only have an unmanaged\managed status, and not the unmanaged CALLCONV_ value. - switch (uCallConv & IMAGE_CEE_CS_CALLCONV_MASK) - { - case IMAGE_CEE_CS_CALLCONV_C: - case IMAGE_CEE_CS_CALLCONV_STDCALL: - case IMAGE_CEE_CS_CALLCONV_THISCALL: - case IMAGE_CEE_CS_CALLCONV_FASTCALL: - // Strip the calling convention. - uCallConv &= ~IMAGE_CEE_CS_CALLCONV_MASK; - // Normalize to unmanaged. - uCallConv |= IMAGE_CEE_CS_CALLCONV_UNMANAGED; - } + uCallConv = NormalizeFnPtrCallingConvention(uCallConv); // Find an existing function pointer or make a new one thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, fLoadTypes, level); @@ -3929,15 +3942,15 @@ MetaSig::CompareElementType( IfFailThrow(CorSigUncompressElementType_EndPtr(pSig2, pEndSig2, &callingConvention2)); // Calling conventions are generally treated as custom modifiers. - // When callers request calling conventions to be ignored, we also ignore - // calling conventions and this is okay. It is okay because calling conventions, - // when more than one is defined (for example, SuppressGCTransition), all - // become encoded as custom modifiers. - if (!state->IgnoreCustomModifiers - && callingConvention1 != callingConvention2) - { + // When callers request custom modifiers to be ignored, we also ignore + // specific unmanaged calling conventions and this is okay. It is okay + // because unmanaged calling conventions, when more than one is defined + // (for example, SuppressGCTransition), all become encoded as custom modifiers. + bool callConvMismatch = state->IgnoreCustomModifiers + ? NormalizeFnPtrCallingConvention(callingConvention1) != NormalizeFnPtrCallingConvention(callingConvention2) + : callingConvention1 != callingConvention2; + if (callConvMismatch) return FALSE; - } DWORD argCnt1; IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &argCnt1)); diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index d489ec35910ca6..e8dec8c36a0725 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -26,6 +26,7 @@ class UserDataClass public const string MethodPointerName = nameof(_Pointer); public const string MethodCdeclCallConvBitName = nameof(_CdeclCallConvBit); public const string MethodStdcallCallConvBitName = nameof(_StdcallCallConvBit); + public const string MethodManagedCallConvBitName = nameof(_ManagedCallConvBit); private static string _F = PrivateStatic; private string _f; @@ -51,10 +52,11 @@ private void _mvv() {} // Used to validate pointer values. private static string _Pointer(void* ptr) => "void*"; - // Used to validate the embedded callconv bits (non-unmanaged bit) in + // Used to validate the embedded callconv bits in // ECMA-335 signatures for methods. private string _CdeclCallConvBit(delegate* unmanaged[Cdecl] fptr) => nameof(CallConvCdecl); private string _StdcallCallConvBit(delegate* unmanaged[Stdcall] fptr) => nameof(CallConvStdcall); + private string _ManagedCallConvBit(delegate* fptr) => "Managed"; } [StructLayout(LayoutKind.Sequential)] @@ -330,9 +332,9 @@ public static void Verify_PreciseMatchCustomModifier() [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] - public static void Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored() + public static void Verify_UnmanagedCallConvBitAreTreatedAsCustomModifiersAndIgnored() { - Console.WriteLine($"Running {nameof(Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored)}"); + Console.WriteLine($"Running {nameof(Verify_UnmanagedCallConvBitAreTreatedAsCustomModifiersAndIgnored)}"); var ud = CallPrivateConstructorClass(); Assert.Equal(nameof(CallConvCdecl), CallCdeclMethod(ud, null)); @@ -350,6 +352,25 @@ public static void Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored() extern static string CallStdcallMethod(UserDataClass d, delegate* unmanaged[Cdecl] fptr); } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] + public static void Verify_ManagedUnmanagedFunctionPointersDontMatch() + { + Console.WriteLine($"Running {nameof(Verify_ManagedUnmanagedFunctionPointersDontMatch)}"); + + var ud = CallPrivateConstructorClass(); + Assert.Throws(() => CallCdeclMethod(ud, null)); + Assert.Throws(() => CallManagedMethod(ud, null)); + + // Managed calling conventions don't match on unmanaged function pointers + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCdeclCallConvBitName)] + extern static string CallCdeclMethod(UserDataClass d, delegate* fptr); + + // Unmanaged calling conventions don't match on managed function pointers + [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodManagedCallConvBitName)] + extern static string CallManagedMethod(UserDataClass d, delegate* unmanaged[Cdecl] fptr); + } + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)] public static void Verify_InvalidTargetUnsafeAccessor() From c03dc9e55fe71c2c0e10e14cb63ebda1bc10ef56 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 13 Jun 2023 14:39:34 -0700 Subject: [PATCH 53/54] Feedback for throwing exceptions on NativeAOT Remove unmanage resource string and move it to SPCL Share the AmbiguousMatchException message between NativeAOT and CoreCLR. --- src/coreclr/dlls/mscorrc/mscorrc.rc | 1 - src/coreclr/dlls/mscorrc/resource.h | 2 +- .../Runtime/CompilerHelpers/ThrowHelpers.cs | 5 ++ .../Runtime/TypeLoaderExceptionHelper.cs | 8 +++ .../TypeSystem/Common/ExceptionStringID.cs | 3 ++ .../Common/TypeSystem/IL/UnsafeAccessors.cs | 50 +++++++++++-------- .../ILCompiler.TypeSystem.csproj | 3 ++ src/coreclr/vm/prestub.cpp | 2 +- .../src/Resources/Strings.resx | 3 ++ 9 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index ed73a672ce6be4..c8c21bfd74790b 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -608,7 +608,6 @@ BEGIN BFA_BAD_FIELD_TOKEN "Field token out of range." BFA_INVALID_FIELD_ACC_FLAGS "Invalid Field Access Flags." BFA_INVALID_UNSAFEACCESSOR "Invalid usage of UnsafeAccessorAttribute." - BFA_AMBIGUOUS_UNSAFEACCESSOR "Ambiguity in binding of UnsafeAccessorAttribute." BFA_FIELD_LITERAL_AND_INIT "Field is Literal and InitOnly." BFA_NONSTATIC_GLOBAL_FIELD "Non-Static Global Field." BFA_INSTANCE_FIELD_IN_INT "Instance Field in an Interface." diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index 2c9dc86f0bca99..878fd3c509da64 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -428,7 +428,7 @@ #define BFA_BAD_ELEM_IN_SIZEOF 0x204b #define BFA_IJW_IN_COLLECTIBLE_ALC 0x204c #define BFA_INVALID_UNSAFEACCESSOR 0x204d -#define BFA_AMBIGUOUS_UNSAFEACCESSOR 0x204e + #define IDS_CLASSLOAD_INTERFACE_NO_ACCESS 0x204f #define BFA_BAD_CA_HEADER 0x2050 diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs index 17aa21e370bc2e..55adc4b8968246 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs @@ -118,6 +118,11 @@ public static void ThrowMarshalDirectiveException(ExceptionStringID id) throw TypeLoaderExceptionHelper.CreateMarshalDirectiveException(id); } + public static void ThrowAmbiguousMatchException(ExceptionStringID id) + { + throw TypeLoaderExceptionHelper.CreateAmbiguousMatchException(id); + } + public static void ThrowArgumentException() { throw new ArgumentException(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs index 76b714c11a3946..70d17bb96989d7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection; using System.Runtime.InteropServices; using Internal.TypeSystem; @@ -63,6 +64,11 @@ public static Exception CreateMarshalDirectiveException(ExceptionStringID id) throw new MarshalDirectiveException(GetFormatString(id)); } + public static Exception CreateAmbiguousMatchException(ExceptionStringID id) + { + return new AmbiguousMatchException(GetFormatString(id)); + } + // TODO: move to a place where we can share this with the compiler private static string GetFormatString(ExceptionStringID id) { @@ -112,6 +118,8 @@ private static string GetFormatString(ExceptionStringID id) return SR.Arg_BadImageFormatException; case ExceptionStringID.MarshalDirectiveGeneric: return SR.Arg_MarshalDirectiveException; + case ExceptionStringID.AmbiguousMatchUnsafeAccessor: + return SR.Arg_AmbiguousMatchException_UnsafeAccessor; default: Debug.Assert(false); return ""; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs index 4c55874d7999fb..dd53c1b47aaaf8 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs @@ -47,5 +47,8 @@ public enum ExceptionStringID // MarshalDirectiveException MarshalDirectiveGeneric, + + // AmbiguousMatchException + AmbiguousMatchUnsafeAccessor, } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 348dad5293af29..0bb3eb40af698c 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -17,7 +17,6 @@ public sealed class UnsafeAccessors public static MethodIL TryGetIL(EcmaMethod method) { Debug.Assert(method != null); - CustomAttributeValue? decodedAttribute = method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "UnsafeAccessorAttribute"); if (!decodedAttribute.HasValue) { @@ -473,32 +472,38 @@ private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext co ILEmitter emit = new ILEmitter(); ILCodeStream codeStream = emit.NewCodeStream(); - MethodDesc md; + ILCodeLabel label = emit.NewCodeLabel(); + codeStream.EmitLabel(label); + + MethodDesc thrower; TypeSystemContext typeSysContext = context.Declaration.Context; if (ambiguous) { - codeStream.Emit(ILOpcode.ldstr, emit.NewToken("Ambiguity in binding of UnsafeAccessorAttribute.")); - - var type = context.Declaration.Context.SystemModule.GetType("System.Reflection", "AmbiguousMatchException"); - MethodSignature ctorString = new(MethodSignatureFlags.None, 0, typeSysContext.GetWellKnownType(WellKnownType.Void), new[] { typeSysContext.GetWellKnownType(WellKnownType.String) }); - md = type.GetMethod(".ctor", ctorString); + codeStream.EmitLdc((int)ExceptionStringID.AmbiguousMatchUnsafeAccessor); + thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowAmbiguousMatchException"); } else { - codeStream.Emit(ILOpcode.ldnull); // Not supplying class name - codeStream.Emit(ILOpcode.ldstr, emit.NewToken(name)); - string exceptName = (context.Kind == UnsafeAccessorKind.Field || context.Kind == UnsafeAccessorKind.StaticField) - ? nameof(MissingFieldException) - : nameof(MissingMethodException); + ExceptionStringID id; + if (context.Kind == UnsafeAccessorKind.Field || context.Kind == UnsafeAccessorKind.StaticField) + { + id = ExceptionStringID.MissingField; + thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowMissingFieldException"); + } + else + { + id = ExceptionStringID.MissingMethod; + thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowMissingMethodException"); + } - var type = context.Declaration.Context.SystemModule.GetType("System", exceptName); - MethodSignature ctorStringString = new(MethodSignatureFlags.None, 0, typeSysContext.GetWellKnownType(WellKnownType.Void), new[] { typeSysContext.GetWellKnownType(WellKnownType.String), typeSysContext.GetWellKnownType(WellKnownType.String) }); - md = type.GetMethod(".ctor", ctorStringString); + codeStream.EmitLdc((int)id); + codeStream.Emit(ILOpcode.ldstr, emit.NewToken(name)); } - codeStream.Emit(ILOpcode.newobj, emit.NewToken(md)); - codeStream.Emit(ILOpcode.throw_); + Debug.Assert(thrower != null); + codeStream.Emit(ILOpcode.call, emit.NewToken(thrower)); + codeStream.Emit(ILOpcode.br, label); return emit.Link(context.Declaration); } @@ -506,9 +511,14 @@ private static MethodIL GenerateAccessorBadImageFailure(MethodDesc method) { ILEmitter emit = new ILEmitter(); ILCodeStream codeStream = emit.NewCodeStream(); - var type = method.Context.SystemModule.GetType("System", "BadImageFormatException"); - codeStream.Emit(ILOpcode.newobj, emit.NewToken(type.GetDefaultConstructor())); - codeStream.Emit(ILOpcode.throw_); + + ILCodeLabel label = emit.NewCodeLabel(); + codeStream.EmitLabel(label); + codeStream.EmitLdc((int)ExceptionStringID.BadImageFormatGeneric); + MethodDesc thrower = method.Context.GetHelperEntryPoint("ThrowHelpers", "ThrowBadImageFormatException"); + codeStream.Emit(ILOpcode.call, emit.NewToken(thrower)); + codeStream.Emit(ILOpcode.br, label); + return emit.Link(method); } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj index 7f0248c02e6d40..51e9d7bdae4d9b 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -477,6 +477,9 @@ IL\EcmaMethodIL.Symbols.cs + + IL\HelperExtensions.cs + IL\MethodIL.cs diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 1602c360ed7262..a9b56f611744fb 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1293,7 +1293,7 @@ namespace if (TrySetTargetMethod(cxt, methodName, false /* ignoreCustomModifiers */)) return true; } - COMPlusThrow(kAmbiguousMatchException, BFA_AMBIGUOUS_UNSAFEACCESSOR); + COMPlusThrow(kAmbiguousMatchException, W("Arg_AmbiguousMatchException_UnsafeAccessor")); } targetMaybe = curr; } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index dbc8fca4de5679..981d4e2d5bad5c 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -193,6 +193,9 @@ Ambiguous match found for '{0} {1}'. + + Ambiguity in binding of UnsafeAccessorAttribute. + Error in the application. From b95741651ce29ec51ce7ee027916e9059d18acf0 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 14 Jun 2023 07:53:55 -0700 Subject: [PATCH 54/54] Missing NAOT version of error string --- .../tools/Common/TypeSystem/Common/Properties/Resources.resx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx index 6a70612fc391bf..319bb25ffd7f00 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx +++ b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx @@ -192,4 +192,7 @@ Marshaling directives are invalid + + Ambiguity in binding of UnsafeAccessorAttribute. +