From 1b0d06e1f32ac140e404625e10054e864f10ca88 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 18 Apr 2025 20:40:24 -0700 Subject: [PATCH 01/62] Add support for UnsafeAccessorTypeAttribute This commit currently only contains support for CoreCLR. --- .../src/System/RuntimeType.CoreCLR.cs | 10 + src/coreclr/dlls/mscorrc/mscorrc.rc | 2 + src/coreclr/dlls/mscorrc/resource.h | 9 +- src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/metasig.h | 1 + src/coreclr/vm/siginfo.cpp | 73 ++++++ src/coreclr/vm/siginfo.hpp | 2 + src/coreclr/vm/unsafeaccessors.cpp | 219 ++++++++++++++++-- src/coreclr/vm/wellknownattributes.h | 4 + .../System.Private.CoreLib.Shared.projitems | 1 + .../UnsafeAccessorTypeAttribute.cs | 39 ++++ .../System.Runtime/ref/System.Runtime.cs | 6 + .../UnsafeAccessorsTests.Types.cs | 161 +++++++++++++ .../UnsafeAccessorsTests.csproj | 1 + 14 files changed, 509 insertions(+), 20 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs create mode 100644 src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 5e872581a02f60..741cf209a1d0b6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1747,6 +1747,16 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field) #region Internal + [RequiresUnreferencedCode("The target type might not exist")] + internal static unsafe IntPtr GetMethodTableFromTypeString(void* utf8TypeString, int utf8TypeStringLen) + { + string typeNameMaybe = new((sbyte*)utf8TypeString, 0, utf8TypeStringLen); + RuntimeType type = (RuntimeType)Type.GetType(typeNameMaybe, throwOnError: true)!; + // [TODO] Figured out how to ensure the lifetime of the type is long enough + // to use the method table pointer. + return (IntPtr)type.GetNativeTypeHandle().AsMethodTable(); + } + // Returns the type from which the current type directly inherits from (without reflection quirks). // The parent type is null for interfaces, pointers, byrefs and generic parameters. internal unsafe RuntimeType? GetParentType() diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index cb371763691e65..aa9c0464a05507 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -596,6 +596,8 @@ 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_INVALID_UNSAFEACCESSORTYPE "Invalid usage of UnsafeAccessorTypeAttribute." + BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE "ValueTypes are not supported with UnsafeAccessorTypeAttribute." 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 fc103677bcd4b2..c81cad38f84b2b 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -415,10 +415,11 @@ #define BFA_BAD_TYPEREF_TOKEN 0x2046 #define BFA_BAD_CLASS_INT_CA_FORMAT 0x2048 #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 BFA_BAD_ELEM_IN_SIZEOF 0x204a +#define BFA_IJW_IN_COLLECTIBLE_ALC 0x204b +#define BFA_INVALID_UNSAFEACCESSOR 0x204c +#define BFA_INVALID_UNSAFEACCESSORTYPE 0x204d +#define BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE 0x204e #define IDS_CLASSLOAD_INTERFACE_NO_ACCESS 0x204f #define BFA_BAD_CA_HEADER 0x2050 diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 48404f7c10af57..a7ca6f52d962da 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -156,6 +156,7 @@ DEFINE_METHOD(CLASS, GET_METHODS, GetMethods, DEFINE_METHOD(CLASS, INVOKE_MEMBER, InvokeMember, IM_Str_BindingFlags_Binder_Obj_ArrObj_ArrParameterModifier_CultureInfo_ArrStr_RetObj) DEFINE_METHOD(CLASS, GET_METHOD_BASE, GetMethodBase, SM_RuntimeType_RuntimeMethodHandleInternal_RetMethodBase) DEFINE_METHOD(CLASS, GET_FIELD_INFO, GetFieldInfo, SM_RuntimeType_IRuntimeFieldInfo_RetFieldInfo) +DEFINE_METHOD(CLASS, GET_METHODTABLE_FROM_TYPE_STRING, GetMethodTableFromTypeString, SM_VoidPtr_Int_RetIntPtr) #ifdef FOR_ILLINK DEFINE_METHOD(CLASS, CTOR, .ctor, IM_RetVoid) #endif // FOR_ILLINK diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 2626a3237bed57..611c7c189a4b80 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -176,6 +176,7 @@ DEFINE_METASIG(SM(RefIntPtr_IntPtr_IntPtr_Int_RetObj, r(I) I I i, j)) DEFINE_METASIG(SM(IntPtr_UInt_VoidPtr_RetObj, I K P(v), j)) DEFINE_METASIG(SM(Obj_IntPtr_RetIntPtr, j I, I)) DEFINE_METASIG(SM(VoidPtr_RetVoidPtr, P(v), P(v))) +DEFINE_METASIG(SM(VoidPtr_Int_RetIntPtr, P(v) i, I)) DEFINE_METASIG(SM(Obj_VoidPtr_RetVoidPtr, j P(v), P(v))) DEFINE_METASIG(SM(Obj_IntPtr_RetObj, j I, j)) DEFINE_METASIG(SM(Obj_RefIntPtr_RetVoid, j r(I), v)) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 1333e99755eb34..355f3e60f886ef 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -387,6 +387,47 @@ void SigPointer::ConvertToInternalSignature(Module* pSigModule, SigTypeContext * } } +void SigPointer::CopyModOptsReqs(SigBuilder * pSigBuilder) +{ + CONTRACTL + { + INSTANCE_CHECK; + STANDARD_VM_CHECK; + } + CONTRACTL_END + + CorElementType typ; + IfFailThrow(PeekElemType(&typ)); + if (typ == ELEMENT_TYPE_CMOD_REQD || typ == ELEMENT_TYPE_CMOD_OPT) + { + // Skip the custom modifier + IfFailThrow(GetByte(NULL)); + + // Get the encoded token. + uint32_t token; + IfFailThrow(GetToken(&token)); + + // Append the custom modifier and encoded token to the signature. + pSigBuilder->AppendElementType(typ); + pSigBuilder->AppendToken(token); + } +} + +void SigPointer::CopyExactlyOne(SigBuilder* pSigBuilder) +{ + CONTRACTL + { + INSTANCE_CHECK; + STANDARD_VM_CHECK; + } + CONTRACTL_END + + intptr_t beginExactlyOne = (intptr_t)m_ptr; + IfFailThrow(SkipExactlyOne()); + intptr_t endExactlyOne = (intptr_t)m_ptr; + pSigBuilder->AppendBlob((const PVOID)(beginExactlyOne), endExactlyOne - beginExactlyOne); +} + void SigPointer::CopySignature(Module* pSigModule, SigBuilder* pSigBuilder, BYTE additionalCallConv) { CONTRACTL @@ -3908,6 +3949,38 @@ MetaSig::CompareElementType( return (hInternal == hOtherType); } + case ELEMENT_TYPE_GENERICINST: + { + // Due to how SigPointer works, we need to fiddle with the signature pointer + // to get the ELEMENT_TYPE_GENERICINST element type back in the stream. + // Since we know the size of the element type, we can just subtract one byte from the + // signature pointer to get the correct value. + PCCOR_SIGNATURE genericInstSig; + uint32_t genericInstSigLen; + if (Type1 == ELEMENT_TYPE_INTERNAL) + { + const BYTE* sigRaw = (const BYTE*)pSig2; + genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); + genericInstSigLen = (uint32_t)((intptr_t)pEndSig2 - (intptr_t)genericInstSig); + } + else + { + const BYTE* sigRaw = (const BYTE*)pSig1; + genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); + genericInstSigLen = (uint32_t)((intptr_t)pEndSig1 - (intptr_t)genericInstSig); + } + // Assert we are in the same state as before. + _ASSERTE(*genericInstSig == ELEMENT_TYPE_GENERICINST); + + SigPointer inst{ genericInstSig, genericInstSigLen }; + + SigTypeContext dummyContext; + TypeHandle hOtherType = inst.GetTypeHandleThrowing( + pOtherModule, + &dummyContext); + + return (hInternal == hOtherType); + } default: { return FALSE; diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 99256bea5072f8..c214d3e9820b1a 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -129,6 +129,8 @@ class SigPointer : public SigParser void ConvertToInternalExactlyOne(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); void ConvertToInternalSignature(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); + void CopyModOptsReqs(SigBuilder * pSigBuilder); + void CopyExactlyOne(SigBuilder * pSigBuilder); void CopySignature(Module* pSigModule, SigBuilder * pSigBuilder, BYTE additionalCallConv); //========================================================================= diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index b740e8b642fb20..7e7280dbcdfadc 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -65,7 +65,8 @@ namespace GenerationContext(UnsafeAccessorKind kind, MethodDesc* pMD) : Kind{ kind } , Declaration{ pMD } - , DeclarationSig{ pMD } + , DeclarationSig{ pMD->GetSigPointer() } + , DeclarationMetaSig{ pMD } , TargetTypeSig{} , TargetType{} , IsTargetStatic{ false } @@ -75,7 +76,9 @@ namespace UnsafeAccessorKind Kind; MethodDesc* Declaration; - MetaSig DeclarationSig; + SigPointer DeclarationSig; // This is the official declaration signature. It may be modified + // to include the UnsafeAccessorTypeAttribute types. + MetaSig DeclarationMetaSig; SigPointer TargetTypeSig; TypeHandle TargetType; bool IsTargetStatic; @@ -83,8 +86,189 @@ namespace FieldDesc* TargetField; }; + void UpdateDeclarationSigWithTypes(GenerationContext& cxt, uint32_t translationsCount, MethodTable** translations) + { + STANDARD_VM_CONTRACT; + _ASSERTE(cxt.Declaration != NULL); + _ASSERTE(translationsCount != 0); + _ASSERTE(translations != NULL); + + // + // Parsing the signature follows details defined in ECMA-335 - II.23.2.1 + // + + // Read the current signature and copy it, updating the + // types for the parameters that had UnsafeAccessorTypeAttribute. + SigPointer origSig = cxt.Declaration->GetSigPointer(); + + // We're going to be modifying the signature and we will be inserting + // ELEMENT_TYPE_INTERNAL instances. This will add an additional pointer + // size for each discovered attribute. + SigBuilder newSig; + + uint32_t callConvDecl; + IfFailThrow(origSig.GetCallingConvInfo(&callConvDecl)); + newSig.AppendByte((BYTE)(callConvDecl & IMAGE_CEE_CS_CALLCONV_MASK)); + + uint32_t declGenericCount = 0; + if (callConvDecl & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + IfFailThrow(origSig.GetData(&declGenericCount)); + newSig.AppendData(declGenericCount); + } + + uint32_t declArgCount; + IfFailThrow(origSig.GetData(&declArgCount)); + newSig.AppendData(declArgCount); + + _ASSERTE(declArgCount + 1 == translationsCount); + + // Now we can copy over the return type and arguments. + // The format for the return type is the same as the arguments, + // except return parameters can be ELEMENT_TYPE_VOID. + for (uint32_t i = 0; i < translationsCount; ++i) + { + // Copy over any modopts or modreqs. + origSig.CopyModOptsReqs(&newSig); + + MethodTable* newType = translations[i]; + if (newType == NULL) + { + // Copy the original parameter and continue. + origSig.CopyExactlyOne(&newSig); + continue; + } + + // We have a new type to insert. We need to update this parameter. + CorElementType currTypeElem; + IfFailThrow(origSig.GetElemType(&currTypeElem)); + if (currTypeElem == ELEMENT_TYPE_BYREF) + { + newSig.AppendElementType(currTypeElem); + IfFailThrow(origSig.GetElemType(&currTypeElem)); + } + + // Validate the parameter resolves correctly. There is only one acceptable mappings: + // ELEMENT_TYPE_OBJECT - if new type is a reference type. + _ASSERTE(!newType->IsValueType()); + CorElementType expectedType = ELEMENT_TYPE_OBJECT; + + if (expectedType != currTypeElem) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + + // Append the new type to the signature. + newSig.AppendElementType(ELEMENT_TYPE_INTERNAL); + newSig.AppendPointer(newType); + } + + // Create a copy of the new signature and store it on the context. + DWORD newSigLen; + void* newSigRaw = newSig.GetSignature((DWORD*)&newSigLen); + + // Allocate the signature memory on the loader allocator associated + // with the declaration method. + void* newSigAlloc = cxt.Declaration->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(newSigLen)); + memcpy(newSigAlloc, newSigRaw, newSigLen); + + // Update the declaration signature with the new signature. + cxt.DeclarationSig = SigPointer{ (PCCOR_SIGNATURE)newSigAlloc, newSigLen }; + SigTypeContext tmpContext( cxt.Declaration ); + cxt.DeclarationMetaSig = MetaSig{ (PCCOR_SIGNATURE)newSigAlloc, newSigLen, cxt.Declaration->GetModule(), &tmpContext }; + } + + void ProcessUnsafeAccessorTypeAttributes(GenerationContext& cxt) + { + STANDARD_VM_CONTRACT; + + // Acquire attribute name to search for. + const char* typeAttrName = GetWellKnownAttributeName(WellKnownAttribute::UnsafeAccessorTypeAttribute); + + uint32_t attrCount = 0; + + // Allocate memory to store the updated types. + NewArrayHolder heapAlloc; + MethodTable* stackAlloc[12]; // Use the stack alloc the majority of the time. + MethodTable** translations = stackAlloc; + + // Determine the max parameter count. +1 for the return value, which is always index zero. + const uint32_t totalParamCount = cxt.DeclarationMetaSig.NumFixedArgs() + 1; + + // Inspect all parameters to the declaration method for UnsafeAccessorTypeAttribute. + IMDInternalImport *pInternalImport = cxt.Declaration->GetModule()->GetMDImport(); + HENUMInternalHolder hEnumParams(pInternalImport); + hEnumParams.EnumInit(mdtParamDef, cxt.Declaration->GetMemberDef()); + mdParamDef currParamDef = mdParamDefNil; + while (hEnumParams.EnumNext(&currParamDef)) + { + const void *pData; + ULONG cbData; + HRESULT hr = IfFailThrow(pInternalImport->GetCustomAttributeByName(currParamDef, typeAttrName, &pData, &cbData)); + if (hr != S_OK) + continue; + + // The first time we find an attribute, we need to initialize + // the translations array and might need to allocate a larger array + // to store the translations. + if (attrCount == 0) + { + if (totalParamCount > ARRAY_SIZE(stackAlloc)) + { + heapAlloc = new MethodTable*[totalParamCount]; + translations = heapAlloc; + } + memset(translations, 0, sizeof(translations[0]) * totalParamCount); + } + + // Parse the attribute data. + CustomAttributeParser cap(pData, cbData); + IfFailThrow(cap.ValidateProlog()); + LPCUTF8 typeString; + ULONG typeStringLen; + IfFailThrow(cap.GetNonNullString(&typeString, &typeStringLen)); + + // Pass the string in the attribute to Type.GetType(String) and attempt to load the type. + MethodTable* targetType = NULL; + { + GCX_COOP(); + MethodDescCallSite getMethodTableFromTypeString(METHOD__CLASS__GET_METHODTABLE_FROM_TYPE_STRING); + + ARG_SLOT args[] = + { + PtrToArgSlot(typeString), + PtrToArgSlot(typeStringLen) + }; + targetType = (MethodTable*)getMethodTableFromTypeString.Call_RetI(args); + } + + if (targetType == NULL) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + if (targetType->IsValueType()) + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE); + + USHORT seq; + DWORD attr; + LPCSTR paramName; + IfFailThrow(pInternalImport->GetParamDefProps(currParamDef, &seq, &attr, ¶mName)); + + if (seq >= totalParamCount) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + + // Store the MethodTable for the loaded type at the sequence number for the parameter. + translations[seq] = targetType; + attrCount++; + } + + // Update the declaration signatures if any instances of UnsafeAccessorTypeAttribute were found. + if (attrCount != 0) + UpdateDeclarationSigWithTypes(cxt, totalParamCount, translations); + } + TypeHandle ValidateTargetType(TypeHandle targetTypeMaybe, CorElementType targetFromSig) { + STANDARD_VM_CONTRACT; TypeHandle targetType = targetTypeMaybe.IsByRef() ? targetTypeMaybe.GetTypeParam() : targetTypeMaybe; @@ -113,7 +297,7 @@ namespace PCCOR_SIGNATURE pSig1; DWORD cSig1; - cxt.Declaration->GetSig(&pSig1, &cSig1); + cxt.DeclarationSig.GetSignature(&pSig1, &cSig1); PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1; ModuleBase* pModule1 = cxt.Declaration->GetModule(); const Substitution* pSubst1 = NULL; @@ -364,7 +548,7 @@ namespace PCCOR_SIGNATURE pSig1; DWORD cSig1; - cxt.Declaration->GetSig(&pSig1, &cSig1); + cxt.DeclarationSig.GetSignature(&pSig1, &cSig1); PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1; ModuleBase* pModule1 = cxt.Declaration->GetModule(); const Substitution* pSubst1 = NULL; @@ -496,7 +680,7 @@ namespace ILStubLinker sl( cxt.Declaration->GetModule(), - cxt.Declaration->GetSignature(), + cxt.Declaration->GetSignature(), // Must be the MethodDesc's declaration signature, not the DeclarationSig field. &genericContext, cxt.TargetMethod, (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); @@ -508,13 +692,13 @@ namespace // used to look up the target member to access and ignored // during dispatch. UINT beginIndex = cxt.IsTargetStatic ? 1 : 0; - UINT stubArgCount = cxt.DeclarationSig.NumFixedArgs(); + UINT stubArgCount = cxt.DeclarationMetaSig.NumFixedArgs(); for (UINT i = beginIndex; i < stubArgCount; ++i) pCode->EmitLDARG(i); // Provide access to the target member UINT targetArgCount = stubArgCount - beginIndex; - UINT targetRetCount = cxt.DeclarationSig.IsReturnTypeVoid() ? 0 : 1; + UINT targetRetCount = cxt.DeclarationMetaSig.IsReturnTypeVoid() ? 0 : 1; switch (cxt.Kind) { case UnsafeAccessorKind::Constructor: @@ -701,6 +885,9 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET GenerationContext context{ kind, this }; + // Parse the signature and check for instances of UnsafeAccessorTypeAttribute. + ProcessUnsafeAccessorTypeAttributes(context); + // Parse the signature to determine the type to use: // * Constructor access - examine the return type // * Instance member access - examine type of first parameter @@ -709,17 +896,17 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET CorElementType retCorType; TypeHandle firstArgType; CorElementType firstArgCorType = ELEMENT_TYPE_END; - retCorType = context.DeclarationSig.GetReturnType(); - retType = context.DeclarationSig.GetRetTypeHandleThrowing(); - UINT argCount = context.DeclarationSig.NumFixedArgs(); + retCorType = context.DeclarationMetaSig.GetReturnType(); + retType = context.DeclarationMetaSig.GetRetTypeHandleThrowing(); + UINT argCount = context.DeclarationMetaSig.NumFixedArgs(); if (argCount > 0) { - context.DeclarationSig.NextArg(); + context.DeclarationMetaSig.NextArg(); // Get the target type signature and resolve to a type handle. - context.TargetTypeSig = context.DeclarationSig.GetArgProps(); + context.TargetTypeSig = context.DeclarationMetaSig.GetArgProps(); (void)context.TargetTypeSig.PeekElemType(&firstArgCorType); - firstArgType = context.DeclarationSig.GetLastTypeHandleThrowing(); + firstArgType = context.DeclarationMetaSig.GetLastTypeHandleThrowing(); } // Using the kind type, perform the following: @@ -732,13 +919,13 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET // 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() || retType.IsByRef() || !name.IsEmpty()) + if (context.DeclarationMetaSig.IsReturnTypeVoid() || retType.IsByRef() || !name.IsEmpty()) { ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } // Get the target type signature from the return type. - context.TargetTypeSig = context.DeclarationSig.GetReturnProps(); + context.TargetTypeSig = context.DeclarationMetaSig.GetReturnProps(); context.TargetType = ValidateTargetType(retType, retCorType); if (!TrySetTargetMethod(context, ".ctor")) MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), ".ctor"); @@ -768,7 +955,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()) + if (argCount != 1 || firstArgType.IsNull() || context.DeclarationMetaSig.IsReturnTypeVoid()) { ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } diff --git a/src/coreclr/vm/wellknownattributes.h b/src/coreclr/vm/wellknownattributes.h index 0659f736beee54..3f7496ac1b68a9 100644 --- a/src/coreclr/vm/wellknownattributes.h +++ b/src/coreclr/vm/wellknownattributes.h @@ -36,6 +36,7 @@ enum class WellKnownAttribute : DWORD ObjectiveCTrackedTypeAttribute, InlineArrayAttribute, UnsafeAccessorAttribute, + UnsafeAccessorTypeAttribute, CountOfWellKnownAttributes }; @@ -137,6 +138,9 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute) case WellKnownAttribute::UnsafeAccessorAttribute: ret = "System.Runtime.CompilerServices.UnsafeAccessorAttribute"; break; + case WellKnownAttribute::UnsafeAccessorTypeAttribute: + ret = "System.Runtime.CompilerServices.UnsafeAccessorTypeAttribute"; + break; case WellKnownAttribute::CountOfWellKnownAttributes: default: ret = nullptr; 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 a13ce9957e434e..04c24a1bfbf6f8 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 @@ -908,6 +908,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs new file mode 100644 index 00000000000000..7876389e110142 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs @@ -0,0 +1,39 @@ +// 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 +{ + /// + /// Provides access to an inaccessible type. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = false)] + public sealed class UnsafeAccessorTypeAttribute : Attribute + { + /// + /// Instantiates an providing access to a type supplied by . + /// + /// A fully qualified or partially qualified type name. + /// + /// is expected to follow the same rules as if it were being + /// passed to . + /// + /// This attribute only has behavior on parameters or return values of methods marked with . + /// + /// This attribute should only be applied to parameters or return types of methods that are + /// typed as . Modifiers such as , , + /// , and are supported. + /// + /// Only reference types are supported to be looked up by this attribute. + /// Value types are not supported. + /// + public UnsafeAccessorTypeAttribute(string typeName) + { + TypeName = typeName; + } + + /// + /// Fully qualified or partially qualified type name to target. + /// + public string TypeName { get; } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7b4ed3169e9e7e..d5c5393756edfd 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14009,6 +14009,12 @@ public enum UnsafeAccessorKind Field = 3, StaticField = 4, } + [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue, AllowMultiple=false, Inherited=false)] + public sealed partial class UnsafeAccessorTypeAttribute : System.Attribute + { + public UnsafeAccessorTypeAttribute(string typeName) { } + public string TypeName { get { throw null; } } + } [System.AttributeUsageAttribute(System.AttributeTargets.Struct)] public sealed partial class UnsafeValueTypeAttribute : System.Attribute { diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs new file mode 100644 index 00000000000000..01aec411a495ce --- /dev/null +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -0,0 +1,161 @@ +// 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.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Xunit; + +public static class StaticClass +{ + static StaticClass() + { + StaticField = 123; + } + + public static int StaticField; + public static int StaticMethod() => StaticField; +} + +class C1 { } +class C2 { } + +struct S1 { } + +class TargetClass +{ + private C2 _f1; + private readonly C2 _f2; + private TargetClass(C2 c2) + { + _f1 = c2; + _f2 = c2; + } + private C2 M_C1(C1 a) => _f1; + private C2 M_RC1(ref C1 a) => _f1; + private C2 M_RROC1(ref readonly C1 a) => _f1; + + private C2 M_ListC1(List a) => _f1; +} + +public static unsafe class UnsafeAccessorsTestsTypes +{ + [Fact] + public static void Verify_Type_CallDefaultCtorClass() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallDefaultCtorClass)}"); + + var local = CallPrivateConstructorClassByName(); + Assert.Equal("UserDataClass", local.GetType().Name); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("UnsafeAccessorsTests+UserDataClass")] + extern static object CallPrivateConstructorClassByName(); + } + + [Fact] + public static void Verify_Type_CallCtorClass() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallCtorClass)}"); + + var local = CallPrivateConstructorClassByName(string.Empty); + Assert.Equal("UserDataClass", local.GetType().Name); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("UnsafeAccessorsTests+UserDataClass")] + extern static object CallPrivateConstructorClassByName(string a); + } + + [Fact] + public static void Verify_Type_InvalidArgument() + { + Console.WriteLine($"Running {nameof(Verify_Type_InvalidArgument)}"); + + Assert.Throws(() => CallStaticMethod1(null)); + Assert.Throws(() => CallStaticMethod2(null)); + Assert.Throws(() => CallStaticMethod3(null)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] + extern static ref int CallStaticMethod1([UnsafeAccessorType(null!)] object a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] + extern static ref int CallStaticMethod2([UnsafeAccessorType("_DoesNotExist_")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] + extern static ref int CallStaticMethod3([UnsafeAccessorType("S1")] object a); + } + + [Fact] + public static void Verify_Type_StaticClass() + { + Console.WriteLine($"Running {nameof(Verify_Type_StaticClass)}"); + + var f = GetStaticClassField(null); + Assert.Equal(StaticClass.StaticField, f); + Assert.Equal(StaticClass.StaticField, CallStaticClassMethod(null)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "StaticField")] + extern static ref int GetStaticClassField([UnsafeAccessorType("StaticClass")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "StaticMethod")] + extern static int CallStaticClassMethod([UnsafeAccessorType("StaticClass")] object a); + } + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static TargetClass CreateTargetClass([UnsafeAccessorType("C2")] object a); + + [Fact] + public static void Verify_Type_CallInstanceMethods() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallInstanceMethods)}"); + + C2 c2 = new(); + TargetClass tgt = CreateTargetClass(c2); + + C1 c1 = new(); + object oc1 = c1; + Assert.Equal(c2, CallM_C1(tgt, c1)); + Assert.Equal(c2, CallM_RC1(tgt, ref oc1)); + Assert.Equal(c2, CallM_RROC1(tgt, ref oc1)); + Assert.Equal(c2, CallM_ListC1(tgt, null)); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] + [return: UnsafeAccessorType("C2")] + extern static object CallM_C1(TargetClass tgt, [UnsafeAccessorType("C1")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_RC1")] + [return: UnsafeAccessorType("C2")] + extern static object CallM_RC1(TargetClass tgt, [UnsafeAccessorType("C1")] ref object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_RROC1")] + [return: UnsafeAccessorType("C2")] + extern static object CallM_RROC1(TargetClass tgt, [UnsafeAccessorType("C1")] ref readonly object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_ListC1")] + [return: UnsafeAccessorType("C2")] + extern static object CallM_ListC1(TargetClass tgt, [UnsafeAccessorType("System.Collections.Generic.List`1[C1]")] object? a); + } + + [Fact] + public static void Verify_Type_GetInstanceFields() + { + Console.WriteLine($"Running {nameof(Verify_Type_GetInstanceFields)}"); + + C2 c2 = new(); + TargetClass tgt = CreateTargetClass(c2); + + Assert.Equal(c2, CallField1(tgt)); + Assert.Equal(c2, CallField2(tgt)); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_f1")] + [return: UnsafeAccessorType("C2")] + extern static ref object CallField1(TargetClass tgt); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_f2")] + [return: UnsafeAccessorType("C2")] + extern static ref readonly object CallField2(TargetClass tgt); + } +} \ 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 f551f9b48c2495..caf51f8e6cff22 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj @@ -7,6 +7,7 @@ + From e1f1d6dcc5aecdca29ac7b18b070a8a3e47cbc27 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 19 Apr 2025 07:39:18 -0700 Subject: [PATCH 02/62] Add lookup from private assembly. --- .../UnsafeAccessors/PrivateLib.cs | 23 +++++++++++++++++++ .../UnsafeAccessors/PrivateLib.csproj | 9 ++++++++ .../UnsafeAccessorsTests.Types.cs | 19 ++++++++++++++- .../UnsafeAccessorsTests.csproj | 1 + 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs create mode 100644 src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.csproj diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs new file mode 100644 index 00000000000000..c73eb62b0b048c --- /dev/null +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -0,0 +1,23 @@ +// 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.Collections.Generic; + +using Xunit; + +namespace PrivateLib +{ + class Class1 + { + public static Class1 GetClass() + { + return new Class1(); + } + + public static List GetListOfClass() + { + return new List(); + } + } +} \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.csproj b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.csproj new file mode 100644 index 00000000000000..418f0402fb3411 --- /dev/null +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.csproj @@ -0,0 +1,9 @@ + + + library + + + + + + diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 01aec411a495ce..bcc18386fe96cc 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -136,7 +136,7 @@ public static void Verify_Type_CallInstanceMethods() [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_ListC1")] [return: UnsafeAccessorType("C2")] - extern static object CallM_ListC1(TargetClass tgt, [UnsafeAccessorType("System.Collections.Generic.List`1[C1]")] object? a); + extern static object CallM_ListC1(TargetClass tgt, [UnsafeAccessorType("System.Collections.Generic.List`1[[C1]]")] object? a); } [Fact] @@ -158,4 +158,21 @@ public static void Verify_Type_GetInstanceFields() [return: UnsafeAccessorType("C2")] extern static ref readonly object CallField2(TargetClass tgt); } + + [Fact] + public static void Verify_Type_FromPrivateLib() + { + Console.WriteLine($"Running {nameof(Verify_Type_FromPrivateLib)}"); + + Assert.Equal("PrivateLib.Class1", CallGetClass(null).GetType().FullName); + Assert.Equal("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]", CallGetListOfClass(null).GetType().FullName); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetClass")] + [return: UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] + extern static object CallGetClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetListOfClass")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib]]")] + extern static object CallGetListOfClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + } } \ 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 caf51f8e6cff22..71db192cc50085 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj @@ -11,5 +11,6 @@ + From a9334327c9949be6d46f386bc71407c92384fe22 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 19 Apr 2025 16:20:00 -0700 Subject: [PATCH 03/62] Address the collectible assembly issue. --- .../src/System/RuntimeType.CoreCLR.cs | 6 +++-- src/coreclr/vm/unsafeaccessors.cpp | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 741cf209a1d0b6..295aa668668a97 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1752,8 +1752,10 @@ internal static unsafe IntPtr GetMethodTableFromTypeString(void* utf8TypeString, { string typeNameMaybe = new((sbyte*)utf8TypeString, 0, utf8TypeStringLen); RuntimeType type = (RuntimeType)Type.GetType(typeNameMaybe, throwOnError: true)!; - // [TODO] Figured out how to ensure the lifetime of the type is long enough - // to use the method table pointer. + + // See VM logic for UnsafeAccessorTypeAttribute. If this type is collectible, a connection + // will be made between this type and the assembly declaring the UnsafeAccessorAttribute + // method. return (IntPtr)type.GetNativeTypeHandle().AsMethodTable(); } diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 7e7280dbcdfadc..6f06d041b524e1 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -238,15 +238,26 @@ namespace PtrToArgSlot(typeStringLen) }; targetType = (MethodTable*)getMethodTableFromTypeString.Call_RetI(args); - } - if (targetType == NULL) - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + if (targetType == NULL) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // Future versions of the runtime may support - // UnsafeAccessorTypeAttribute on value types. - if (targetType->IsValueType()) - ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE); + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + if (targetType->IsValueType()) + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE); + + // If the type is collectible create a reference between the loader + // allocators. See RuntimeType.GetMethodTableFromTypeString(). + if (targetType->Collectible()) + { + if (!cxt.Declaration->GetAssembly()->IsCollectible()) + COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleBoundNonCollectible")); + + PTR_LoaderAllocator typeLoaderAllocator = targetType->GetLoaderAllocator(); + cxt.Declaration->GetLoaderAllocator()->EnsureReference(typeLoaderAllocator); + } + } USHORT seq; DWORD attr; From 38481018fc11f86f799d519d9734335eef346be6 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 19 Apr 2025 16:20:09 -0700 Subject: [PATCH 04/62] Testing. --- .../UnsafeAccessors/PrivateLib.cs | 14 +++++- .../UnsafeAccessorsTests.Types.cs | 45 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index c73eb62b0b048c..b49f7cea169ed0 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -10,12 +10,22 @@ namespace PrivateLib { class Class1 { - public static Class1 GetClass() + static int StaticField; + int InstanceField = 456; + + static Class1() + { + StaticField = 123; + } + + Class1() { } + + static Class1 GetClass() { return new Class1(); } - public static List GetListOfClass() + List GetListOfClass() { return new List(); } diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index bcc18386fe96cc..98a5f565b57d9c 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -159,20 +159,51 @@ public static void Verify_Type_GetInstanceFields() extern static ref readonly object CallField2(TargetClass tgt); } + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetClass")] + [return: UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] + extern static object CallGetClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + [Fact] - public static void Verify_Type_FromPrivateLib() + public static void Verify_Type_CallPrivateLibMethods() { - Console.WriteLine($"Running {nameof(Verify_Type_FromPrivateLib)}"); + Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibMethods)}"); + + { + object class1 = CreateClass(); + Assert.Equal("PrivateLib.Class1", class1.GetType().FullName); + } - Assert.Equal("PrivateLib.Class1", CallGetClass(null).GetType().FullName); - Assert.Equal("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]", CallGetListOfClass(null).GetType().FullName); + { + object class1 = CallGetClass(null); + Assert.Equal("PrivateLib.Class1", class1.GetType().FullName); + object listClass1 = CallGetListOfClass(class1); + Assert.Equal("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]", listClass1.GetType().FullName); + } - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetClass")] + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] [return: UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] - extern static object CallGetClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + extern static object CreateClass(); - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetListOfClass")] + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetListOfClass")] [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib]]")] extern static object CallGetListOfClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); } + + [Fact] + public static void Verify_Type_GetPrivateLibFields() + { + Console.WriteLine($"Running {nameof(Verify_Type_GetPrivateLibFields)}"); + + object class1 = CallGetClass(null); + Assert.Equal("PrivateLib.Class1", class1.GetType().FullName); + + Assert.Equal(123, GetStaticField(null)); + Assert.Equal(456, GetInstanceField(class1)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "StaticField")] + extern static ref int GetStaticField([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "InstanceField")] + extern static ref int GetInstanceField([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + } } \ No newline at end of file From 925e8625237b744867d151c2c2e5ba3302ac042a Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 19 Apr 2025 18:40:16 -0700 Subject: [PATCH 05/62] Offline feedback. --- .../src/System/RuntimeType.CoreCLR.cs | 12 ----- src/coreclr/vm/corelib.h | 1 - src/coreclr/vm/metasig.h | 1 - src/coreclr/vm/unsafeaccessors.cpp | 47 +++++++------------ 4 files changed, 17 insertions(+), 44 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 295aa668668a97..5e872581a02f60 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1747,18 +1747,6 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field) #region Internal - [RequiresUnreferencedCode("The target type might not exist")] - internal static unsafe IntPtr GetMethodTableFromTypeString(void* utf8TypeString, int utf8TypeStringLen) - { - string typeNameMaybe = new((sbyte*)utf8TypeString, 0, utf8TypeStringLen); - RuntimeType type = (RuntimeType)Type.GetType(typeNameMaybe, throwOnError: true)!; - - // See VM logic for UnsafeAccessorTypeAttribute. If this type is collectible, a connection - // will be made between this type and the assembly declaring the UnsafeAccessorAttribute - // method. - return (IntPtr)type.GetNativeTypeHandle().AsMethodTable(); - } - // Returns the type from which the current type directly inherits from (without reflection quirks). // The parent type is null for interfaces, pointers, byrefs and generic parameters. internal unsafe RuntimeType? GetParentType() diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index a7ca6f52d962da..48404f7c10af57 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -156,7 +156,6 @@ DEFINE_METHOD(CLASS, GET_METHODS, GetMethods, DEFINE_METHOD(CLASS, INVOKE_MEMBER, InvokeMember, IM_Str_BindingFlags_Binder_Obj_ArrObj_ArrParameterModifier_CultureInfo_ArrStr_RetObj) DEFINE_METHOD(CLASS, GET_METHOD_BASE, GetMethodBase, SM_RuntimeType_RuntimeMethodHandleInternal_RetMethodBase) DEFINE_METHOD(CLASS, GET_FIELD_INFO, GetFieldInfo, SM_RuntimeType_IRuntimeFieldInfo_RetFieldInfo) -DEFINE_METHOD(CLASS, GET_METHODTABLE_FROM_TYPE_STRING, GetMethodTableFromTypeString, SM_VoidPtr_Int_RetIntPtr) #ifdef FOR_ILLINK DEFINE_METHOD(CLASS, CTOR, .ctor, IM_RetVoid) #endif // FOR_ILLINK diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 611c7c189a4b80..2626a3237bed57 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -176,7 +176,6 @@ DEFINE_METASIG(SM(RefIntPtr_IntPtr_IntPtr_Int_RetObj, r(I) I I i, j)) DEFINE_METASIG(SM(IntPtr_UInt_VoidPtr_RetObj, I K P(v), j)) DEFINE_METASIG(SM(Obj_IntPtr_RetIntPtr, j I, I)) DEFINE_METASIG(SM(VoidPtr_RetVoidPtr, P(v), P(v))) -DEFINE_METASIG(SM(VoidPtr_Int_RetIntPtr, P(v) i, I)) DEFINE_METASIG(SM(Obj_VoidPtr_RetVoidPtr, j P(v), P(v))) DEFINE_METASIG(SM(Obj_IntPtr_RetObj, j I, j)) DEFINE_METASIG(SM(Obj_RefIntPtr_RetVoid, j r(I), v)) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 6f06d041b524e1..7a89a4bb2e0896 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -3,6 +3,7 @@ #include "common.h" #include "customattribute.h" +#include "typeparse.h" namespace { @@ -226,38 +227,24 @@ namespace ULONG typeStringLen; IfFailThrow(cap.GetNonNullString(&typeString, &typeStringLen)); - // Pass the string in the attribute to Type.GetType(String) and attempt to load the type. - MethodTable* targetType = NULL; - { - GCX_COOP(); - MethodDescCallSite getMethodTableFromTypeString(METHOD__CLASS__GET_METHODTABLE_FROM_TYPE_STRING); + StackSString typeStringUtf8{ SString::Utf8, typeString, typeStringLen }; - ARG_SLOT args[] = - { - PtrToArgSlot(typeString), - PtrToArgSlot(typeStringLen) - }; - targetType = (MethodTable*)getMethodTableFromTypeString.Call_RetI(args); - - if (targetType == NULL) - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - - // Future versions of the runtime may support - // UnsafeAccessorTypeAttribute on value types. - if (targetType->IsValueType()) - ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE); - - // If the type is collectible create a reference between the loader - // allocators. See RuntimeType.GetMethodTableFromTypeString(). - if (targetType->Collectible()) - { - if (!cxt.Declaration->GetAssembly()->IsCollectible()) - COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleBoundNonCollectible")); + // Pass the string in the attribute to similar logic as Type.GetType(String). + // If the type is collectible we need to create a reference between the requesting + // assembly and type. See RuntimeType.GetMethodTableFromTypeString(). + // The below API creates a dependency between the returned type and the requesting + // assembly for the purposes of lifetime tracking of collectible types. + TypeHandle typeHandle = TypeName::GetTypeReferencedByCustomAttribute( + typeStringUtf8.GetUnicode(), + cxt.Declaration->GetAssembly()); + _ASSERTE(!typeHandle.IsNull()); - PTR_LoaderAllocator typeLoaderAllocator = targetType->GetLoaderAllocator(); - cxt.Declaration->GetLoaderAllocator()->EnsureReference(typeLoaderAllocator); - } - } + MethodTable* targetType = typeHandle.AsMethodTable(); + + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + if (targetType->IsValueType()) + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE); USHORT seq; DWORD attr; From 840a54b39c06b8e0ae9c263783179d615d5a601a Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 19 Apr 2025 18:45:02 -0700 Subject: [PATCH 06/62] Update comment. --- src/coreclr/vm/unsafeaccessors.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 7a89a4bb2e0896..f5f53c47642bda 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -230,8 +230,6 @@ namespace StackSString typeStringUtf8{ SString::Utf8, typeString, typeStringLen }; // Pass the string in the attribute to similar logic as Type.GetType(String). - // If the type is collectible we need to create a reference between the requesting - // assembly and type. See RuntimeType.GetMethodTableFromTypeString(). // The below API creates a dependency between the returned type and the requesting // assembly for the purposes of lifetime tracking of collectible types. TypeHandle typeHandle = TypeName::GetTypeReferencedByCustomAttribute( From d352abd636d9009939637a92aee0d6476d631768 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 11:49:19 -0700 Subject: [PATCH 07/62] Add mono runtime support --- src/mono/mono/metadata/custom-attrs.c | 53 +++++++++++++++++++ src/mono/mono/metadata/marshal.c | 52 ++++++++++++++++++ src/mono/mono/metadata/reflection-internals.h | 2 + .../UnsafeAccessorsTests.Types.cs | 3 +- 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/metadata/custom-attrs.c b/src/mono/mono/metadata/custom-attrs.c index e0e485dd3e4a44..8bb0fb414a235d 100644 --- a/src/mono/mono/metadata/custom-attrs.c +++ b/src/mono/mono/metadata/custom-attrs.c @@ -51,6 +51,7 @@ static GENERATE_GET_CLASS_WITH_CACHE (custom_attribute_typed_argument, "System.R static GENERATE_GET_CLASS_WITH_CACHE (custom_attribute_named_argument, "System.Reflection", "CustomAttributeNamedArgument"); static GENERATE_TRY_GET_CLASS_WITH_CACHE (customattribute_data, "System.Reflection", "RuntimeCustomAttributeData"); static GENERATE_TRY_GET_CLASS_WITH_CACHE (unsafe_accessor_attribute, "System.Runtime.CompilerServices", "UnsafeAccessorAttribute"); +static GENERATE_TRY_GET_CLASS_WITH_CACHE (unsafe_accessor_type_attribute, "System.Runtime.CompilerServices", "UnsafeAccessorTypeAttribute"); static MonoCustomAttrInfo* mono_custom_attrs_from_builders_handle (MonoImage *alloc_img, MonoImage *image, MonoArrayHandle cattrs, gboolean respect_cattr_visibility); @@ -2099,6 +2100,58 @@ mono_method_get_unsafe_accessor_attr_data (MonoMethod *method, int *accessor_kin return TRUE; } +gboolean +mono_method_param_get_unsafe_accessor_type_attr_data (MonoMethod *method, int param_seq, char **type_name, MonoError *error) +{ + MonoCustomAttrInfo *cinfo = mono_custom_attrs_from_param_checked (method, param_seq, error); + + if (!cinfo || !is_ok (error)) { + mono_error_cleanup (error); + return FALSE; + } + + MonoClass *unsafeAccessorType = mono_class_try_get_unsafe_accessor_type_attribute_class (); + MonoCustomAttrEntry *attr = NULL; + + for (int idx = 0; idx < cinfo->num_attrs; ++idx) { + MonoClass *ctor_class = cinfo->attrs [idx].ctor->klass; + if (ctor_class == unsafeAccessorType) { + attr = &cinfo->attrs [idx]; + break; + } + } + + if (!attr){ + if (!cinfo->cached) + mono_custom_attrs_free(cinfo); + return FALSE; + } + + MonoDecodeCustomAttr *decoded_args = mono_reflection_create_custom_attr_data_args_noalloc (m_class_get_image (attr->ctor->klass), attr->ctor, attr->data, attr->data_size, error); + + if (!is_ok (error)) { + mono_error_cleanup (error); + mono_reflection_free_custom_attr_data_args_noalloc (decoded_args); + if (!cinfo->cached) + mono_custom_attrs_free(cinfo); + return FALSE; + } + + g_assert (decoded_args->typed_args_num == 1); + const char *ptr = (const char*)decoded_args->typed_args [0]->value.primitive; + uint32_t len = mono_metadata_decode_value (ptr, &ptr); + char *name = m_method_alloc0 (method, len + 1); + memcpy (name, ptr, len); + name[len] = 0; + *type_name = (char*)name; + + mono_reflection_free_custom_attr_data_args_noalloc (decoded_args); + if (!cinfo->cached) + mono_custom_attrs_free(cinfo); + + return TRUE; +} + /** * mono_custom_attrs_from_class: */ diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index e68fba963e70e6..2eb9356b552555 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -5241,6 +5241,55 @@ mono_marshal_get_array_accessor_wrapper (MonoMethod *method) return res; } +static void process_unsafe_accessor_type (MonoMethod *accessor_method, MonoMethodSignature *tgt_sig) +{ + g_assert (accessor_method); + g_assert (tgt_sig); + + char *type_name; + MonoAssemblyLoadContext *alc = mono_alc_get_ambient (); + MonoImage *image = m_class_get_image (accessor_method->klass); + MonoType *type; + + // Iterate through all parameters. Zero is the return value, the arguments are 1-based. + for (guint16 seq = 0; seq <= tgt_sig->param_count; ++seq) { + + ERROR_DECL (error); + + type_name = NULL; + if (!mono_method_param_get_unsafe_accessor_type_attr_data (accessor_method, seq, &type_name, error)) + continue; + mono_error_assert_ok (error); + g_assert (type_name); + + type = mono_reflection_type_from_name_checked (type_name, alc, image, error); + if (!type) + continue; + mono_error_assert_ok (error); + + if (type->type == MONO_TYPE_VALUETYPE) + continue; + + // Check the target signature for cmods and byref state. This information + // is not contained with in the type itself, so we need to check the target signature + // and retain it on the new type. + MonoType *current_param = (seq == 0) ? tgt_sig->ret : tgt_sig->params [seq - 1]; + if (m_type_is_byref(current_param) || current_param->has_cmods) { + type = mono_metadata_type_dup_with_cmods(image, type, current_param); + type->byref__ = m_type_is_byref(current_param) ? 1 : 0; + } + + // Update the target signature with the new type + if (seq == 0) { + // The return value + tgt_sig->ret = type; + } else { + // The arguments + tgt_sig->params [seq - 1] = type; + } + } +} + /* * mono_marshal_get_unsafe_accessor_wrapper: * @@ -5389,6 +5438,9 @@ mono_marshal_get_unsafe_accessor_wrapper (MonoMethod *accessor_method, MonoUnsaf } sig->pinvoke = 0; + // Parse the signature and check for instances of UnsafeAccessorTypeAttribute. + process_unsafe_accessor_type (accessor_method, sig); + get_marshal_cb ()->mb_skip_visibility (mb); if (generic_wrapper || is_inflated) { diff --git a/src/mono/mono/metadata/reflection-internals.h b/src/mono/mono/metadata/reflection-internals.h index 01e3d136e0aab7..160850ae24ed5a 100644 --- a/src/mono/mono/metadata/reflection-internals.h +++ b/src/mono/mono/metadata/reflection-internals.h @@ -67,6 +67,8 @@ MONO_COMPONENT_API MonoCustomAttrInfo* mono_custom_attrs_from_method_checked (MonoMethod *method, MonoError *error); gboolean mono_method_get_unsafe_accessor_attr_data (MonoMethod *method, int *accessor_kind, char **member_name, MonoError *error); +gboolean +mono_method_param_get_unsafe_accessor_type_attr_data (MonoMethod *method, int param_seq, char **type_name, MonoError *error); MONO_COMPONENT_API MonoCustomAttrInfo* mono_custom_attrs_from_class_checked (MonoClass *klass, MonoError *error); MONO_COMPONENT_API MonoCustomAttrInfo* diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 98a5f565b57d9c..362283b400996d 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -69,7 +69,8 @@ public static void Verify_Type_CallCtorClass() extern static object CallPrivateConstructorClassByName(string a); } - [Fact] + // Skip validating error cases on Mono runtime + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] public static void Verify_Type_InvalidArgument() { Console.WriteLine($"Running {nameof(Verify_Type_InvalidArgument)}"); From 6476d4346f4385b3b47ca02ad5865df68780951f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 12:55:57 -0700 Subject: [PATCH 08/62] Mono Convert check to assert. Include attributes when deciding to make a copy. --- src/mono/mono/metadata/marshal.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 2eb9356b552555..8fbadb28abc766 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -5267,16 +5267,18 @@ static void process_unsafe_accessor_type (MonoMethod *accessor_method, MonoMetho continue; mono_error_assert_ok (error); - if (type->type == MONO_TYPE_VALUETYPE) - continue; + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + g_assert (type->type != MONO_TYPE_VALUETYPE); - // Check the target signature for cmods and byref state. This information - // is not contained with in the type itself, so we need to check the target signature - // and retain it on the new type. + // Check the target signature for attribute, byref and cmods state. This information + // is not contained with in the type name itself, so we may need to check the target + // signature and retain it on the new type. MonoType *current_param = (seq == 0) ? tgt_sig->ret : tgt_sig->params [seq - 1]; - if (m_type_is_byref(current_param) || current_param->has_cmods) { + if (current_param->attrs != 0 || m_type_is_byref(current_param) || current_param->has_cmods) { type = mono_metadata_type_dup_with_cmods(image, type, current_param); - type->byref__ = m_type_is_byref(current_param) ? 1 : 0; + type->byref__ = current_param->byref__; + type->attrs = current_param->attrs; } // Update the target signature with the new type From 728c3361dc977f1b6b6734dde8e0f42037700a5a Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 14:37:14 -0700 Subject: [PATCH 09/62] Remove unused local. --- src/coreclr/vm/unsafeaccessors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index f5f53c47642bda..b651312a044e3e 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -743,7 +743,7 @@ namespace sigBuilder.AppendElementType(ELEMENT_TYPE_MVAR); sigBuilder.AppendData(i); } - sigLen; + sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen); methodSpecSigToken = pCode->GetSigToken(sig, sigLen); } From 6e316b6046d12e8b24670b1fe1de7e4843c1fc1a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Apr 2025 15:20:14 -0700 Subject: [PATCH 10/62] Add NativeAOT support --- .../Runtime/CompilerHelpers/ThrowHelpers.cs | 7 + .../Common/TypeSystem/IL/UnsafeAccessors.cs | 142 ++++++++++++++++-- .../CoreCLRTestLibrary/AssertExtensions.cs | 41 +++++ .../UnsafeAccessorsTests.Types.cs | 4 +- 4 files changed, 178 insertions(+), 16 deletions(-) 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 21c68337bcfe85..5fd69bfa9cf9fe 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 @@ -115,5 +115,12 @@ internal static void ThrowNotSupportedInlineArrayEqualsGetHashCode() { throw new NotSupportedException(SR.NotSupported_InlineArrayEqualsGetHashCode); } + + [DoesNotReturn] + [DebuggerHidden] + internal static void ThrowNotSupportedException() + { + throw new NotSupportedException(SR.NotSupported_InlineArrayEqualsGetHashCode); + } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index e8e97f2eb31973..53676b15b4ddcf 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -40,15 +40,21 @@ public static MethodIL TryGetIL(EcmaMethod method) Declaration = method }; - MethodSignature sig = method.Signature; - TypeDesc retType = sig.ReturnType; - TypeDesc firstArgType = null; - if (sig.Length > 0) + SetTargetResult result; + + result = TrySetTargetMethodSignature(ref context); + if (result is not SetTargetResult.Success) { - firstArgType = sig[0]; + return GenerateAccessorSpecificFailure(ref context, name, result, isSignatureLoad: true); } - SetTargetResult result; + TypeDesc retType = context.DeclarationSignature.ReturnType; + + TypeDesc firstArgType = null; + if (context.DeclarationSignature.Length > 0) + { + firstArgType = context.DeclarationSignature[0]; + } // Using the kind type, perform the following: // 1) Validate the basic type information from the signature. @@ -110,7 +116,7 @@ public static MethodIL TryGetIL(EcmaMethod method) case UnsafeAccessorKind.Field: case UnsafeAccessorKind.StaticField: // Field access requires a single argument for target type and a return type. - if (sig.Length != 1 || retType.IsVoid) + if (context.DeclarationSignature.Length != 1 || retType.IsVoid) { return GenerateAccessorBadImageFailure(method); } @@ -209,6 +215,7 @@ private struct GenerationContext { public UnsafeAccessorKind Kind; public EcmaMethod Declaration; + public MethodSignature DeclarationSignature; public TypeDesc TargetType; public bool IsTargetStatic; public MethodDesc TargetMethod; @@ -241,7 +248,7 @@ private static bool ValidateTargetType(TypeDesc targetTypeMaybe, out TypeDesc va private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationContext context, MethodDesc method, bool ignoreCustomModifiers) { - MethodSignature declSig = context.Declaration.Signature; + MethodSignature declSig = context.DeclarationSignature; MethodSignature maybeSig = method.Signature; // Check if we need to also validate custom modifiers. @@ -249,14 +256,14 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte if (!ignoreCustomModifiers) { // Compare any unmanaged callconv and custom modifiers on the signatures. - // We treat unmanaged calling conventions at the same level of precedance + // We treat unmanaged calling conventions at the same level of precedence // as custom modifiers, eventhough they are normally bits in a signature. - ReadOnlySpan kinds = new EmbeddedSignatureDataKind[] - { + ReadOnlySpan kinds = + [ EmbeddedSignatureDataKind.UnmanagedCallConv, EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier - }; + ]; var declData = declSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty(); var maybeData = maybeSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty(); @@ -405,6 +412,7 @@ private enum SetTargetResult Missing, Ambiguous, Invalid, + NotSupported } private static SetTargetResult TrySetTargetMethod(ref GenerationContext context, string name, bool ignoreCustomModifiers = true) @@ -494,6 +502,103 @@ private static SetTargetResult TrySetTargetField(ref GenerationContext context, return SetTargetResult.Missing; } + private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext context) + { + EcmaMethod method = context.Declaration; + MetadataReader reader = method.MetadataReader; + MethodDefinition methodDef = reader.GetMethodDefinition(method.Handle); + ParameterHandleCollection parameters = methodDef.GetParameters(); + + MethodSignature originalSignature = method.Signature; + + MethodSignatureBuilder updatedSignature = new MethodSignatureBuilder(originalSignature); + + foreach (ParameterHandle parameterHandle in parameters) + { + Parameter parameter = reader.GetParameter(parameterHandle); + CustomAttribute? unsafeAccessorTypeAttribute = null; + foreach (CustomAttributeHandle customAttributeHandle in parameter.GetCustomAttributes()) + { + reader.GetAttributeNamespaceAndName(customAttributeHandle, out StringHandle namespaceName, out StringHandle name); + if (reader.StringComparer.Equals(namespaceName, "System.Runtime.CompilerServices") + && reader.StringComparer.Equals(name, "UnsafeAccessorTypeAttribute")) + { + unsafeAccessorTypeAttribute = reader.GetCustomAttribute(customAttributeHandle); + break; + } + } + + if (unsafeAccessorTypeAttribute is null) + { + continue; + } + + if (parameter.SequenceNumber > originalSignature.Length) + { + return SetTargetResult.Invalid; + } + + bool isReturnValue = parameter.SequenceNumber == 0; + + TypeDesc initialType = isReturnValue ? originalSignature.ReturnType : originalSignature[parameter.SequenceNumber - 1]; + + bool initialTypeIsByRef = initialType.IsByRef; + + if (initialTypeIsByRef) + { + // We need to strip the byref off the type to get the + // underlying type for the parameter. + initialType = ((ParameterizedType)initialType).ParameterType; + } + + if (initialType != method.Context.GetWellKnownType(WellKnownType.Object)) + { + // Illegal argument type for UnsafeAccessorTypeAttribute target + return SetTargetResult.Invalid; + } + + CustomAttributeValue decoded = unsafeAccessorTypeAttribute.Value.DecodeValue( + new CustomAttributeTypeProvider(method.Module)); + + if (decoded.FixedArguments[0].Value is not string replacementTypeName) + { + return SetTargetResult.Invalid; + } + + TypeDesc replacementType = method.Module.GetTypeByCustomAttributeTypeName(replacementTypeName, throwIfNotFound: false); + + if (replacementType is null) + { + return SetTargetResult.Missing; + } + + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + if (replacementType.IsValueType) + { + return SetTargetResult.NotSupported; + } + + if (initialTypeIsByRef) + { + // We need to reapply the byref to the type. + replacementType = method.Context.GetByRefType(replacementType); + } + + if (isReturnValue) + { + updatedSignature.ReturnType = replacementType; + } + else + { + updatedSignature[parameter.SequenceNumber - 1] = replacementType; + } + } + + context.DeclarationSignature = updatedSignature.ToSignature(); + return SetTargetResult.Success; + } + private static MethodIL GenerateAccessor(ref GenerationContext context) { ILEmitter emit = new ILEmitter(); @@ -504,7 +609,7 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) // 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; + int stubArgCount = context.DeclarationSignature.Length; for (int i = beginIndex; i < stubArgCount; ++i) { codeStream.EmitLdArg(i); @@ -543,7 +648,7 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) return emit.Link(context.Declaration); } - private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, SetTargetResult result) + private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, SetTargetResult result, bool isSignatureLoad = false) { ILEmitter emit = new ILEmitter(); ILCodeStream codeStream = emit.NewCodeStream(); @@ -563,6 +668,15 @@ private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext co codeStream.EmitLdc((int)ExceptionStringID.InvalidProgramDefault); thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowInvalidProgramException"); } + else if (result is SetTargetResult.NotSupported) + { + thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowNotSupportedException"); + } + else if (isSignatureLoad) + { + Debug.Assert(result is SetTargetResult.Missing); + thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowUnavailableType"); + } else { Debug.Assert(result is SetTargetResult.Missing); diff --git a/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs b/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs index 09f97637f90d66..4963ed0e1ededf 100644 --- a/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs +++ b/src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs @@ -5,8 +5,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Xunit.Sdk; + namespace Xunit { public static class AssertExtensions @@ -139,6 +142,44 @@ public static TInner ThrowsWithInnerException(Action action) return (TInner)outerException.InnerException; } + public static void ThrowsAny(Type firstExceptionType, Type secondExceptionType, Action action) + { + ThrowsAnyInternal(action, firstExceptionType, secondExceptionType); + } + + private static void ThrowsAnyInternal(Action action, params Type[] exceptionTypes) + { + try + { + action(); + } + catch (Exception e) + { + Type exceptionType = e.GetType(); + if (exceptionTypes.Any(t => t.Equals(exceptionType))) + return; + + throw new XunitException($"Expected one of: ({string.Join(", ", exceptionTypes)}) -> Actual: ({exceptionType}): {e}"); // Log message and callstack to help diagnosis + } + + throw new XunitException($"Expected one of: ({string.Join(", ", exceptionTypes)}) -> Actual: No exception thrown"); + } + + public static void ThrowsAny(Action action) + where TFirstExceptionType : Exception + where TSecondExceptionType : Exception + { + ThrowsAnyInternal(action, typeof(TFirstExceptionType), typeof(TSecondExceptionType)); + } + + public static void ThrowsAny(Action action) + where TFirstExceptionType : Exception + where TSecondExceptionType : Exception + where TThirdExceptionType : Exception + { + ThrowsAnyInternal(action, typeof(TFirstExceptionType), typeof(TSecondExceptionType), typeof(TThirdExceptionType)); + } + /// /// Tests whether the two lists are the same length and contain the same objects (using Object.Equals()) in the same order and diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 98a5f565b57d9c..accd93f5a8d8ad 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -74,7 +74,7 @@ public static void Verify_Type_InvalidArgument() { Console.WriteLine($"Running {nameof(Verify_Type_InvalidArgument)}"); - Assert.Throws(() => CallStaticMethod1(null)); + AssertExtensions.ThrowsAny(() => CallStaticMethod1(null)); Assert.Throws(() => CallStaticMethod2(null)); Assert.Throws(() => CallStaticMethod3(null)); @@ -206,4 +206,4 @@ public static void Verify_Type_GetPrivateLibFields() [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "InstanceField")] extern static ref int GetInstanceField([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); } -} \ No newline at end of file +} From 3a8436057853786eca039bc13633d8f7299bc9d9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Apr 2025 15:35:48 -0700 Subject: [PATCH 11/62] Add linker support --- .../Linker.Steps/UnsafeAccessorMarker.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs b/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs index d6155b8a50b5e1..38a8f101d1bf17 100644 --- a/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs +++ b/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Mono.Cecil; @@ -67,6 +68,29 @@ public void ProcessUnsafeAccessorMethod (MethodDefinition method) } } + bool TryResolveTargetType(TypeReference targetTypeReference, ICustomAttributeProvider unsafeAccessorTypeProvider, AssemblyDefinition assembly, [NotNullWhen(true)] out TypeDefinition? targetType) + { + targetType = null; + if (_context.TryResolve (targetTypeReference) is not TypeDefinition initialTargetType) + return false; + + targetType = initialTargetType; + + foreach (CustomAttribute attr in unsafeAccessorTypeProvider.CustomAttributes) { + if (attr.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.UnsafeAccessorTypeAttribute") { + if (attr.HasConstructorArguments && attr.ConstructorArguments[0].Value is string typeName) { + TypeDefinition? newTargetType = _context.TryResolve (assembly, typeName); + if (newTargetType is null) + return false; // We can't find the target type, so there's nothing to mark. + + targetType = newTargetType; + } + } + } + + return true; + } + void ProcessConstructorAccessor (MethodDefinition method, string? name) { // A return type is required for a constructor, otherwise @@ -76,7 +100,7 @@ void ProcessConstructorAccessor (MethodDefinition method, string? name) if (method.ReturnsVoid () || method.ReturnType.IsByRefOrPointer () || !string.IsNullOrEmpty (name)) return; - if (_context.TryResolve (method.ReturnType) is not TypeDefinition targetType) + if (!TryResolveTargetType(method.ReturnType, method.MethodReturnType, method.Module.Assembly, out TypeDefinition? targetType)) return; foreach (MethodDefinition targetMethod in targetType.Methods) { @@ -97,7 +121,7 @@ void ProcessMethodAccessor (MethodDefinition method, string? name, bool isStatic name = method.Name; TypeReference targetTypeReference = method.Parameters[0].ParameterType; - if (_context.TryResolve (targetTypeReference) is not TypeDefinition targetType) + if (!TryResolveTargetType (targetTypeReference, method.Parameters[0], method.Module.Assembly, out TypeDefinition? targetType)) return; if (!isStatic && targetType.IsValueType && !targetTypeReference.IsByReference) @@ -124,7 +148,7 @@ void ProcessFieldAccessor (MethodDefinition method, string? name, bool isStatic) return; TypeReference targetTypeReference = method.Parameters[0].ParameterType; - if (_context.TryResolve (targetTypeReference) is not TypeDefinition targetType) + if (!TryResolveTargetType (targetTypeReference, method.Parameters[0], method.Module.Assembly, out TypeDefinition? targetType)) return; if (!isStatic && targetType.IsValueType && !targetTypeReference.IsByReference) From e824981f14f5a5502f90a815cd27f14de4eb8303 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 16:39:27 -0700 Subject: [PATCH 12/62] Copy custom modifiers should work on N. --- src/coreclr/vm/siginfo.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 355f3e60f886ef..57e2b667fc5b5b 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -398,7 +398,7 @@ void SigPointer::CopyModOptsReqs(SigBuilder * pSigBuilder) CorElementType typ; IfFailThrow(PeekElemType(&typ)); - if (typ == ELEMENT_TYPE_CMOD_REQD || typ == ELEMENT_TYPE_CMOD_OPT) + while (typ == ELEMENT_TYPE_CMOD_REQD || typ == ELEMENT_TYPE_CMOD_OPT) { // Skip the custom modifier IfFailThrow(GetByte(NULL)); @@ -410,6 +410,9 @@ void SigPointer::CopyModOptsReqs(SigBuilder * pSigBuilder) // Append the custom modifier and encoded token to the signature. pSigBuilder->AppendElementType(typ); pSigBuilder->AppendToken(token); + + typ = ELEMENT_TYPE_END; + IfFailThrow(PeekElemType(&typ)); } } From c6df90be97ad4cd8420a736c4a65a17ac486ad13 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 17:00:45 -0700 Subject: [PATCH 13/62] Add ref return test. --- .../UnsafeAccessors/UnsafeAccessorsTests.Types.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 362283b400996d..6a4d333610d1b6 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -39,6 +39,8 @@ private TargetClass(C2 c2) private C2 M_RROC1(ref readonly C1 a) => _f1; private C2 M_ListC1(List a) => _f1; + + private ref C2 M_C1_RC2(C1 a) => ref _f1; } public static unsafe class UnsafeAccessorsTestsTypes @@ -122,6 +124,7 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(c2, CallM_RC1(tgt, ref oc1)); Assert.Equal(c2, CallM_RROC1(tgt, ref oc1)); Assert.Equal(c2, CallM_ListC1(tgt, null)); + Assert.Equal(c2, CallM_C1_RC2(tgt, c1)); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] [return: UnsafeAccessorType("C2")] @@ -138,6 +141,10 @@ public static void Verify_Type_CallInstanceMethods() [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_ListC1")] [return: UnsafeAccessorType("C2")] extern static object CallM_ListC1(TargetClass tgt, [UnsafeAccessorType("System.Collections.Generic.List`1[[C1]]")] object? a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1_RC2")] + [return: UnsafeAccessorType("C2")] + extern static ref object CallM_C1_RC2(TargetClass tgt, [UnsafeAccessorType("C1")] object a); } [Fact] From 63bfc45a4ebc3290b785fee6f4ab1ae2bbf0dccd Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 17:12:57 -0700 Subject: [PATCH 14/62] Additional tests. --- .../UnsafeAccessors/PrivateLib.cs | 6 ++++-- .../UnsafeAccessorsTests.Generics.cs | 21 +++++++++++++------ .../UnsafeAccessorsTests.Types.cs | 10 ++++----- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index b49f7cea169ed0..ceb356e27d5a99 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -25,9 +25,11 @@ static Class1 GetClass() return new Class1(); } - List GetListOfClass() + List GetListOfClass2() { - return new List(); + return new List(); } } + + class Class2 { } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Generics.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Generics.cs index 311550810224c8..ee112b3dae2c66 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Generics.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Generics.cs @@ -421,10 +421,15 @@ public static void Verify_Generic_MethodConstraintEnforcement() Assert.Equal($"{typeof(ClassWithI1I2)}|{typeof(I1)}", CallMethod(new MethodWithConstraints())); Assert.Equal($"{typeof(ClassWithI1I2)}|{typeof(I1)}", CallStaticMethod(null)); - Assert.Throws(() => CallMethod_NoConstraints(new MethodWithConstraints())); - Assert.Throws(() => CallMethod_MissingConstraint(new MethodWithConstraints())); - Assert.Throws(() => CallStaticMethod_NoConstraints(null)); - Assert.Throws(() => CallStaticMethod_MissingConstraint(null)); + + // Skip validating error cases on Mono runtime + if (TestLibrary.Utilities.IsNotMonoRuntime) + { + Assert.Throws(() => CallMethod_NoConstraints(new MethodWithConstraints())); + Assert.Throws(() => CallMethod_MissingConstraint(new MethodWithConstraints())); + Assert.Throws(() => CallStaticMethod_NoConstraints(null)); + Assert.Throws(() => CallStaticMethod_MissingConstraint(null)); + } [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")] extern static string CallMethod(MethodWithConstraints c) where V : W, I2; @@ -478,8 +483,12 @@ public static void Verify_Generic_ClassConstraintEnforcement() Assert.Equal($"{typeof(ClassWithI1I2)}|{typeof(I1)}|{typeof(ClassWithI1)}", AccessorsWithConstraints.CallMethod(new ClassWithConstraints())); Assert.Equal($"{typeof(ClassWithI1I2)}|{typeof(I1)}|{typeof(ClassWithI1)}", AccessorsWithConstraints.CallStaticMethod(null)); - Assert.Throws(() => AccessorsWithConstraints.CallMethod_MissingMethodConstraint(new ClassWithConstraints())); - Assert.Throws(() => AccessorsWithConstraints.CallStaticMethod_MissingMethodConstraint(null)); + // Skip validating error cases on Mono runtime + if (TestLibrary.Utilities.IsNotMonoRuntime) + { + Assert.Throws(() => AccessorsWithConstraints.CallMethod_MissingMethodConstraint(new ClassWithConstraints())); + Assert.Throws(() => AccessorsWithConstraints.CallStaticMethod_MissingMethodConstraint(null)); + } } class Invalid diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 6a4d333610d1b6..5dc700ee8417d1 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -184,17 +184,17 @@ public static void Verify_Type_CallPrivateLibMethods() { object class1 = CallGetClass(null); Assert.Equal("PrivateLib.Class1", class1.GetType().FullName); - object listClass1 = CallGetListOfClass(class1); - Assert.Equal("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]", listClass1.GetType().FullName); + object listClass2 = CallGetListOfClass2(class1); + Assert.Equal("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]", listClass2.GetType().FullName); } [UnsafeAccessor(UnsafeAccessorKind.Constructor)] [return: UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] extern static object CreateClass(); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetListOfClass")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class1, PrivateLib]]")] - extern static object CallGetListOfClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetListOfClass2")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] + extern static object CallGetListOfClass2([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); } [Fact] From 23bb9b8d05970c68c4e96fafe169cfb96c672ecd Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 17:18:37 -0700 Subject: [PATCH 15/62] Remove calling convention mask. --- src/coreclr/vm/unsafeaccessors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index b651312a044e3e..59a79c77ea9776 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -109,7 +109,7 @@ namespace uint32_t callConvDecl; IfFailThrow(origSig.GetCallingConvInfo(&callConvDecl)); - newSig.AppendByte((BYTE)(callConvDecl & IMAGE_CEE_CS_CALLCONV_MASK)); + newSig.AppendByte((BYTE)callConvDecl); uint32_t declGenericCount = 0; if (callConvDecl & IMAGE_CEE_CS_CALLCONV_GENERIC) From 4b5a47d9f8b8bf23b84e4c0d6e3fe323ccfaa16d Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 17:35:26 -0700 Subject: [PATCH 16/62] Fix windows build break. --- src/coreclr/vm/unsafeaccessors.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 59a79c77ea9776..8b55a685f5f87c 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -164,7 +164,7 @@ namespace // Create a copy of the new signature and store it on the context. DWORD newSigLen; - void* newSigRaw = newSig.GetSignature((DWORD*)&newSigLen); + void* newSigRaw = newSig.GetSignature(&newSigLen); // Allocate the signature memory on the loader allocator associated // with the declaration method. @@ -292,7 +292,7 @@ namespace _ASSERTE(method != NULL); PCCOR_SIGNATURE pSig1; - DWORD cSig1; + uint32_t cSig1; cxt.DeclarationSig.GetSignature(&pSig1, &cSig1); PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1; ModuleBase* pModule1 = cxt.Declaration->GetModule(); @@ -543,7 +543,7 @@ namespace _ASSERTE(field != NULL); PCCOR_SIGNATURE pSig1; - DWORD cSig1; + uint32_t cSig1; cxt.DeclarationSig.GetSignature(&pSig1, &cSig1); PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1; ModuleBase* pModule1 = cxt.Declaration->GetModule(); From c543349971406bfa282bcd678a9667a82c0e8e28 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 21 Apr 2025 18:14:56 -0700 Subject: [PATCH 17/62] Make SigPointer's Copy functions consistent. --- src/coreclr/vm/siginfo.cpp | 24 ++++++++++++------------ src/coreclr/vm/siginfo.hpp | 9 ++++++--- src/coreclr/vm/unsafeaccessors.cpp | 8 +++++--- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 57e2b667fc5b5b..2f96dc72b40f5c 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -387,7 +387,7 @@ void SigPointer::ConvertToInternalSignature(Module* pSigModule, SigTypeContext * } } -void SigPointer::CopyModOptsReqs(SigBuilder * pSigBuilder) +void SigPointer::CopyModOptsReqs(Module* pSigModule, SigBuilder* pSigBuilder) { CONTRACTL { @@ -397,26 +397,26 @@ void SigPointer::CopyModOptsReqs(SigBuilder * pSigBuilder) CONTRACTL_END CorElementType typ; - IfFailThrow(PeekElemType(&typ)); + IfFailThrowBF(PeekElemType(&typ), BFA_BAD_COMPLUS_SIG, pSigModule); while (typ == ELEMENT_TYPE_CMOD_REQD || typ == ELEMENT_TYPE_CMOD_OPT) { // Skip the custom modifier - IfFailThrow(GetByte(NULL)); + IfFailThrowBF(GetByte(NULL), BFA_BAD_COMPLUS_SIG, pSigModule); // Get the encoded token. uint32_t token; - IfFailThrow(GetToken(&token)); + IfFailThrowBF(GetToken(&token), BFA_BAD_COMPLUS_SIG, pSigModule); // Append the custom modifier and encoded token to the signature. pSigBuilder->AppendElementType(typ); pSigBuilder->AppendToken(token); typ = ELEMENT_TYPE_END; - IfFailThrow(PeekElemType(&typ)); + IfFailThrowBF(PeekElemType(&typ), BFA_BAD_COMPLUS_SIG, pSigModule); } } -void SigPointer::CopyExactlyOne(SigBuilder* pSigBuilder) +void SigPointer::CopyExactlyOne(Module* pSigModule, SigBuilder* pSigBuilder) { CONTRACTL { @@ -426,9 +426,9 @@ void SigPointer::CopyExactlyOne(SigBuilder* pSigBuilder) CONTRACTL_END intptr_t beginExactlyOne = (intptr_t)m_ptr; - IfFailThrow(SkipExactlyOne()); + IfFailThrowBF(SkipExactlyOne(), BFA_BAD_COMPLUS_SIG, pSigModule); intptr_t endExactlyOne = (intptr_t)m_ptr; - pSigBuilder->AppendBlob((const PVOID)(beginExactlyOne), endExactlyOne - beginExactlyOne); + pSigBuilder->AppendBlob((const PVOID)beginExactlyOne, endExactlyOne - beginExactlyOne); } void SigPointer::CopySignature(Module* pSigModule, SigBuilder* pSigBuilder, BYTE additionalCallConv) @@ -440,10 +440,10 @@ void SigPointer::CopySignature(Module* pSigModule, SigBuilder* pSigBuilder, BYTE } CONTRACTL_END - SigPointer spEnd(*this); - IfFailThrowBF(spEnd.SkipSignature(), BFA_BAD_COMPLUS_SIG, pSigModule); - pSigBuilder->AppendByte(*m_ptr | additionalCallConv); - pSigBuilder->AppendBlob((const PVOID)(m_ptr + 1), spEnd.m_ptr - (m_ptr + 1)); + PCCOR_SIGNATURE beginSignature = m_ptr; + IfFailThrowBF(SkipSignature(), BFA_BAD_COMPLUS_SIG, pSigModule); + pSigBuilder->AppendByte(*beginSignature | additionalCallConv); + pSigBuilder->AppendBlob((const PVOID)(beginSignature + 1), m_ptr - (beginSignature + 1)); } #endif // DACCESS_COMPILE diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index c214d3e9820b1a..6f0649c0b8d8f2 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -129,9 +129,12 @@ class SigPointer : public SigParser void ConvertToInternalExactlyOne(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); void ConvertToInternalSignature(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); - void CopyModOptsReqs(SigBuilder * pSigBuilder); - void CopyExactlyOne(SigBuilder * pSigBuilder); - void CopySignature(Module* pSigModule, SigBuilder * pSigBuilder, BYTE additionalCallConv); + + // Copy the current part of the signature to the SigBuilder. + // All copy methods advance internal state as if a Get was called. + void CopyModOptsReqs(Module* pSigModule, SigBuilder* pSigBuilder); + void CopyExactlyOne(Module* pSigModule, SigBuilder* pSigBuilder); + void CopySignature(Module* pSigModule, SigBuilder* pSigBuilder, BYTE additionalCallConv); //========================================================================= // The CLOSED interface for reading signatures. With the following diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 8b55a685f5f87c..0e49a8616403f0 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -95,9 +95,11 @@ namespace _ASSERTE(translations != NULL); // - // Parsing the signature follows details defined in ECMA-335 - II.23.2.1 + // Parsing and building the signature follows details defined in ECMA-335 - II.23.2.1 // + Module* pSigModule = cxt.Declaration->GetModule(); + // Read the current signature and copy it, updating the // types for the parameters that had UnsafeAccessorTypeAttribute. SigPointer origSig = cxt.Declaration->GetSigPointer(); @@ -130,13 +132,13 @@ namespace for (uint32_t i = 0; i < translationsCount; ++i) { // Copy over any modopts or modreqs. - origSig.CopyModOptsReqs(&newSig); + origSig.CopyModOptsReqs(pSigModule, &newSig); MethodTable* newType = translations[i]; if (newType == NULL) { // Copy the original parameter and continue. - origSig.CopyExactlyOne(&newSig); + origSig.CopyExactlyOne(pSigModule, &newSig); continue; } From 577489594342cb1dd0f991b4cc69e5e559c3bb6e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 22 Apr 2025 13:35:49 -0700 Subject: [PATCH 18/62] Remove duplicate throw helper. --- .../src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs | 7 ------- 1 file changed, 7 deletions(-) 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 5fd69bfa9cf9fe..21c68337bcfe85 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 @@ -115,12 +115,5 @@ internal static void ThrowNotSupportedInlineArrayEqualsGetHashCode() { throw new NotSupportedException(SR.NotSupported_InlineArrayEqualsGetHashCode); } - - [DoesNotReturn] - [DebuggerHidden] - internal static void ThrowNotSupportedException() - { - throw new NotSupportedException(SR.NotSupported_InlineArrayEqualsGetHashCode); - } } } From aab24c95703652a467cc3fe2fefdd0d45faad28d Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 22 Apr 2025 13:36:32 -0700 Subject: [PATCH 19/62] [CoreCLR] Generic support for UnsafeAccessorType --- src/coreclr/vm/siginfo.cpp | 14 ++++++++++---- src/coreclr/vm/siginfo.hpp | 5 +++++ src/coreclr/vm/unsafeaccessors.cpp | 3 ++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 2f96dc72b40f5c..5b411a19d16991 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3954,6 +3954,11 @@ MetaSig::CompareElementType( } case ELEMENT_TYPE_GENERICINST: { + // Generic instantiation types are a special case. We need + // additional context to compare them correctly. + if (state == NULL) + return FALSE; + // Due to how SigPointer works, we need to fiddle with the signature pointer // to get the ELEMENT_TYPE_GENERICINST element type back in the stream. // Since we know the size of the element type, we can just subtract one byte from the @@ -3976,13 +3981,14 @@ MetaSig::CompareElementType( _ASSERTE(*genericInstSig == ELEMENT_TYPE_GENERICINST); SigPointer inst{ genericInstSig, genericInstSigLen }; - - SigTypeContext dummyContext; TypeHandle hOtherType = inst.GetTypeHandleThrowing( pOtherModule, - &dummyContext); + &state->InternalToGenericInstContext); - return (hInternal == hOtherType); + // Compare typical method tables to handle shared generics. + MethodTable* typicalInternalMT = hInternal.AsMethodTable()->GetTypicalMethodTable(); + MethodTable* typeicalOtherMT = hOtherType.AsMethodTable()->GetTypicalMethodTable(); + return typicalInternalMT == typeicalOtherMT; } default: { diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 6f0649c0b8d8f2..f3b77be15355c0 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -972,11 +972,16 @@ class MetaSig // be compared. bool IgnoreCustomModifiers; + // Context to use when comparing generic instantiations against + // ELEMENT_TYPE_INTERNAL types. + SigTypeContext InternalToGenericInstContext; + CompareState() = default; CompareState(TokenPairList* list) : Visited{ list } , IgnoreCustomModifiers{ false } + , InternalToGenericInstContext{} { } }; diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 0e49a8616403f0..1d7a1162ae17f5 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -493,7 +493,7 @@ namespace // Following a similar iteration pattern found in MemberLoader::FindMethod(). // However, we are only operating on the current type not walking the type hierarchy. - MethodTable::IntroducedMethodIterator iter(pMT); + MethodTable::IntroducedMethodIterator iter(pMT, /* restrictToCanonicalTypes */ FALSE); for (; iter.IsValid(); iter.Next()) { MethodDesc* curr = iter.GetMethodDesc(); @@ -510,6 +510,7 @@ namespace TokenPairList list { nullptr }; MetaSig::CompareState state{ &list }; state.IgnoreCustomModifiers = ignoreCustomModifiers; + state.InternalToGenericInstContext = { cxt.Declaration->GetClassInstantiation(), cxt.Declaration->GetMethodInstantiation() }; if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr, state)) continue; From 51f36517bd9e56ad7b2aa2acba7c3757e352e947 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 22 Apr 2025 14:08:57 -0700 Subject: [PATCH 20/62] Add tests. --- .../UnsafeAccessors/PrivateLib.cs | 17 +-- .../UnsafeAccessorsTests.Types.cs | 111 ++++++++++++++++++ 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index ceb356e27d5a99..94618ed58d0f75 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -20,16 +20,17 @@ static Class1() Class1() { } - static Class1 GetClass() - { - return new Class1(); - } + static Class1 GetClass() => new Class1(); - List GetListOfClass2() - { - return new List(); - } + List GetListOfClass2() => new List(); } class Class2 { } + + class GenericClass + { + List M1() => new List(); + + List M2() => new List(); + } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 78570bcf7f9893..aede4e0951de81 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -214,4 +215,114 @@ public static void Verify_Type_GetPrivateLibFields() [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "InstanceField")] extern static ref int GetInstanceField([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); } + + class Accessors + { + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] + public extern static object CreateGenericClassInt(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] + public extern static object CreateGenericClassString(); + + // Class type variables + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M1")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Int32]]")] + public extern static object CallGenericClassIntM1([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M1")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.String]]")] + public extern static object CallGenericClassStringM1([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] object a); + + // Method type variables + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Int32]]")] + public extern static object CallGenericClassIntM2Int([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.String]]")] + public extern static object CallGenericClassIntM2String([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Int32]]")] + public extern static object CallGenericClassStringM2Int([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.String]]")] + public extern static object CallGenericClassStringM2String([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] object a); + } + + private static bool TypeNameEquals(TypeName typeName1, TypeName typeName2) + { + if (typeName1.Name != typeName2.Name) + { + return false; + } + + if (typeName1.IsConstructedGenericType != typeName2.IsConstructedGenericType) + { + return false; + } + + var typeArgs1 = typeName1.GetGenericArguments(); + var typeArgs2 = typeName2.GetGenericArguments(); + if (typeArgs1.Length != typeArgs2.Length) + { + return false; + } + + for (int i = 0; i < typeArgs1.Length; i++) + { + if (!TypeNameEquals(typeArgs1[i], typeArgs2[i])) + { + return false; + } + } + + return true; + } + + // Skip private types and Generic support on Mono runtime + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] + public static void Verify_Type_CallPrivateLibGenerics() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibGenerics)}"); + + { + object genericClass = Accessors.CreateGenericClassInt(); + TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); + Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.Int32]]"))); + + object genericListT = Accessors.CallGenericClassIntM1(genericClass); + TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); + Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); + + object genericListInt = Accessors.CallGenericClassIntM2Int(genericClass); + TypeName genericListIntName = TypeName.Parse(genericListInt.GetType().FullName); + Assert.True(TypeNameEquals(genericListIntName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); + + object genericListString = Accessors.CallGenericClassIntM2String(genericClass); + TypeName genericListStringName = TypeName.Parse(genericListString.GetType().FullName); + Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); + } + + { + object genericClass = Accessors.CreateGenericClassString(); + TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); + Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.String]]"))); + + object genericListT = Accessors.CallGenericClassStringM1(genericClass); + TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); + Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); + + object genericListInt = Accessors.CallGenericClassStringM2Int(genericClass); + TypeName genericListIntName = TypeName.Parse(genericListInt.GetType().FullName); + Assert.True(TypeNameEquals(genericListIntName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); + + object genericListString = Accessors.CallGenericClassStringM2String(genericClass); + TypeName genericListStringName = TypeName.Parse(genericListString.GetType().FullName); + Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); + } + } } From d71b5fd441a372b4dc934043a34736bd6652e0ef Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Apr 2025 16:53:48 -0700 Subject: [PATCH 21/62] Fix generics support in NativeAOT --- src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 53676b15b4ddcf..17c4e93b34cf8e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -352,7 +352,7 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte return false; } - if (declSig.ReturnType != maybeSig.ReturnType) + if (declSig.ReturnType.GetTypeDefinition() != maybeSig.ReturnType.GetTypeDefinition()) { return false; } @@ -367,7 +367,7 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte TypeDesc maybeType = maybeSig[i]; // Compare the types - if (declType != maybeType) + if (declType.GetTypeDefinition() != maybeType.GetTypeDefinition()) { return false; } From a3b8e892cf368bb5f3898e497a25300792429067 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 22 Apr 2025 21:02:19 -0700 Subject: [PATCH 22/62] Use canonical vs typical methodtable. --- src/coreclr/vm/siginfo.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 5b411a19d16991..a6759bbaad1289 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3985,10 +3985,10 @@ MetaSig::CompareElementType( pOtherModule, &state->InternalToGenericInstContext); - // Compare typical method tables to handle shared generics. - MethodTable* typicalInternalMT = hInternal.AsMethodTable()->GetTypicalMethodTable(); - MethodTable* typeicalOtherMT = hOtherType.AsMethodTable()->GetTypicalMethodTable(); - return typicalInternalMT == typeicalOtherMT; + // Compare canonical method tables to handle shared generics. + MethodTable* canonicalInternalMT = hInternal.AsMethodTable()->GetCanonicalMethodTable(); + MethodTable* canonicalOtherMT = hOtherType.AsMethodTable()->GetCanonicalMethodTable(); + return canonicalInternalMT == canonicalOtherMT; } default: { From d0de954693ac8c7ee924886c710fabe56853528e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sun, 27 Apr 2025 20:15:20 -0700 Subject: [PATCH 23/62] Update format to respect IL syntax for generics ("!N" and "!!N"). --- .../Reflection/TypeNameResolver.CoreCLR.cs | 49 +++++++++++++- src/coreclr/vm/corelib.h | 2 +- src/coreclr/vm/metasig.h | 2 +- src/coreclr/vm/method.hpp | 4 ++ src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/siginfo.cpp | 57 ++++++---------- src/coreclr/vm/siginfo.hpp | 5 -- src/coreclr/vm/typeparse.cpp | 11 ++-- src/coreclr/vm/typeparse.h | 2 +- src/coreclr/vm/unsafeaccessors.cpp | 66 +++++++++++++++++-- .../UnsafeAccessors/PrivateLib.cs | 2 +- .../UnsafeAccessorsTests.Types.cs | 66 ++++++------------- 12 files changed, 163 insertions(+), 104 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs index 9bf62c527ed789..fe75630c185d78 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs @@ -21,9 +21,12 @@ internal partial struct TypeNameResolver private bool _extensibleParser; private bool _requireAssemblyQualifiedName; private bool _suppressContextualReflectionContext; + private IntPtr _unsafeAccessorMethod; private Assembly? _requestingAssembly; private Assembly? _topLevelAssembly; + private bool SupportUnboundGenerics { get => _unsafeAccessorMethod != IntPtr.Zero; } + [RequiresUnreferencedCode("The type might be removed")] internal static Type? GetType( string typeName, @@ -128,14 +131,20 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, // Used by VM internal static unsafe RuntimeType? GetTypeHelper(char* pTypeName, RuntimeAssembly? requestingAssembly, - bool throwOnError, bool requireAssemblyQualifiedName) + bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod) { ReadOnlySpan typeName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pTypeName); - return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName); + return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName, unsafeAccessorMethod); } internal static unsafe RuntimeType? GetTypeHelper(ReadOnlySpan typeName, RuntimeAssembly? requestingAssembly, bool throwOnError, bool requireAssemblyQualifiedName) + { + return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName, IntPtr.Zero); + } + + private static unsafe RuntimeType? GetTypeHelper(ReadOnlySpan typeName, RuntimeAssembly? requestingAssembly, + bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod) { // Compat: Empty name throws TypeLoadException instead of // the natural ArgumentException @@ -158,6 +167,7 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, _throwOnError = throwOnError, _suppressContextualReflectionContext = true, _requireAssemblyQualifiedName = requireAssemblyQualifiedName, + _unsafeAccessorMethod = unsafeAccessorMethod, }.Resolve(parsed); if (type != null) @@ -186,6 +196,9 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, return assembly; } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "UnsafeAccessors_ResolveGenericParamToTypeHandle")] + private static partial IntPtr ResolveGenericParamToTypeHandle(IntPtr unsafeAccessorMethod, [MarshalAs(UnmanagedType.Bool)] bool isMethodParam, int paramIndex); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "TypeNameResolver.GetType is marked as RequiresUnreferencedCode.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", @@ -228,6 +241,38 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, { if (assembly is null) { + if (SupportUnboundGenerics + && !string.IsNullOrEmpty(escapedTypeName) + && escapedTypeName[0] == '!') + { + Debug.Assert(_throwOnError); // Unbound generic support currently always throws. + + // Parse the type as an unbound generic parameter. Following the common VAR/MVAR IL syntax: + // ! - Represents a zero-based index into the type's generic parameters. + // !! - Represents a zero-based index into the method's generic parameters. + + // Confirm we have at least one more character + if (escapedTypeName.Length == 1) + { + throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName); + } + + // At this point we expect either another '!' and then a number or a number. + bool isMethodParam = escapedTypeName[1] == '!'; + int paramIndex = isMethodParam + ? int.Parse(escapedTypeName.AsSpan(2)) // Skip over "!!" + : int.Parse(escapedTypeName.AsSpan(1)); // Skip over "!" + + Debug.Assert(_unsafeAccessorMethod != IntPtr.Zero); + IntPtr typeHandle = ResolveGenericParamToTypeHandle(_unsafeAccessorMethod, isMethodParam, paramIndex); + if (typeHandle == IntPtr.Zero) + { + throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName); + } + + return RuntimeTypeHandle.GetRuntimeTypeFromHandle(typeHandle); + } + if (_requireAssemblyQualifiedName) { if (_throwOnError) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 48404f7c10af57..513054096eab84 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -362,7 +362,7 @@ DEFINE_FIELD(RT_TYPE_HANDLE, M_TYPE, m_type) DEFINE_METHOD(TYPED_REFERENCE, GETREFANY, GetRefAny, NoSig) DEFINE_CLASS(TYPE_NAME_RESOLVER, Reflection, TypeNameResolver) -DEFINE_METHOD(TYPE_NAME_RESOLVER, GET_TYPE_HELPER, GetTypeHelper, SM_Type_CharPtr_RuntimeAssembly_Bool_Bool_RetRuntimeType) +DEFINE_METHOD(TYPE_NAME_RESOLVER, GET_TYPE_HELPER, GetTypeHelper, SM_Type_CharPtr_RuntimeAssembly_Bool_Bool_IntPtr_RetRuntimeType) DEFINE_CLASS_U(Reflection, RtFieldInfo, NoClass) DEFINE_FIELD_U(m_fieldHandle, ReflectFieldObject, m_pFD) diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 2626a3237bed57..44238a0b94455c 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -170,7 +170,7 @@ // static methods: DEFINE_METASIG_T(SM(Int_IntPtr_Obj_RetException, i I j, C(EXCEPTION))) -DEFINE_METASIG_T(SM(Type_CharPtr_RuntimeAssembly_Bool_Bool_RetRuntimeType, P(u) C(ASSEMBLY) F F, C(CLASS))) +DEFINE_METASIG_T(SM(Type_CharPtr_RuntimeAssembly_Bool_Bool_IntPtr_RetRuntimeType, P(u) C(ASSEMBLY) F F I, C(CLASS))) DEFINE_METASIG_T(SM(Type_RetIntPtr, C(TYPE), I)) DEFINE_METASIG(SM(RefIntPtr_IntPtr_IntPtr_Int_RetObj, r(I) I I i, j)) DEFINE_METASIG(SM(IntPtr_UInt_VoidPtr_RetObj, I K P(v), j)) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 0a9ff181416760..cb6d50ae76b25f 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1901,6 +1901,10 @@ class MethodDesc friend struct ::cdac_data; }; +#ifndef DACCESS_COMPILE +extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(MethodDesc* unsafeAccessorMethod, BOOL isMethodParam, int paramIndex); +#endif // DACCESS_COMPILE + template<> struct cdac_data { static constexpr size_t ChunkIndex = offsetof(MethodDesc, m_chunkIndex); diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 19ce7a5ffa05ab..d0f625b895d504 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -177,6 +177,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeFieldHandle_GetEnCFieldAddr) DllImportEntry(RuntimeFieldHandle_GetRVAFieldInfo) DllImportEntry(RuntimeFieldHandle_GetFieldDataReference) + DllImportEntry(UnsafeAccessors_ResolveGenericParamToTypeHandle) DllImportEntry(StackTrace_GetStackFramesInternal) DllImportEntry(StackFrame_GetMethodDescFromNativeIP) DllImportEntry(ModuleBuilder_GetStringConstant) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index a6759bbaad1289..f2ddbfd055c8c9 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3948,47 +3948,16 @@ MetaSig::CompareElementType( pOtherModule, tkOther, ClassLoader::ReturnNullIfNotFound, - ClassLoader::FailIfUninstDefOrRef); - - return (hInternal == hOtherType); - } - case ELEMENT_TYPE_GENERICINST: - { - // Generic instantiation types are a special case. We need - // additional context to compare them correctly. - if (state == NULL) - return FALSE; + ClassLoader::PermitUninstDefOrRef); - // Due to how SigPointer works, we need to fiddle with the signature pointer - // to get the ELEMENT_TYPE_GENERICINST element type back in the stream. - // Since we know the size of the element type, we can just subtract one byte from the - // signature pointer to get the correct value. - PCCOR_SIGNATURE genericInstSig; - uint32_t genericInstSigLen; - if (Type1 == ELEMENT_TYPE_INTERNAL) + // If the return type is a generic type definition, we need to + // compare it to the typical method table of the internal type. + if (!hOtherType.IsNull() && hOtherType.IsGenericTypeDefinition()) { - const BYTE* sigRaw = (const BYTE*)pSig2; - genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); - genericInstSigLen = (uint32_t)((intptr_t)pEndSig2 - (intptr_t)genericInstSig); + hInternal = TypeHandle{ hInternal.AsMethodTable()->GetTypicalMethodTable() }; } - else - { - const BYTE* sigRaw = (const BYTE*)pSig1; - genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); - genericInstSigLen = (uint32_t)((intptr_t)pEndSig1 - (intptr_t)genericInstSig); - } - // Assert we are in the same state as before. - _ASSERTE(*genericInstSig == ELEMENT_TYPE_GENERICINST); - - SigPointer inst{ genericInstSig, genericInstSigLen }; - TypeHandle hOtherType = inst.GetTypeHandleThrowing( - pOtherModule, - &state->InternalToGenericInstContext); - // Compare canonical method tables to handle shared generics. - MethodTable* canonicalInternalMT = hInternal.AsMethodTable()->GetCanonicalMethodTable(); - MethodTable* canonicalOtherMT = hOtherType.AsMethodTable()->GetCanonicalMethodTable(); - return canonicalInternalMT == canonicalOtherMT; + return (hInternal == hOtherType); } default: { @@ -5779,6 +5748,20 @@ TokenPairList TokenPairList::AdjustForTypeSpec(TokenPairList *pTemplate, ModuleB result.m_bInTypeEquivalenceForbiddenScope = !IsTdInterface(dwAttrType); } } + else if (elemType == ELEMENT_TYPE_INTERNAL) + { + TypeHandle typeHandle; + IfFailThrow(sig.GetPointer((void**)&typeHandle)); + + if (typeHandle.IsTypeDesc()) + { + result.m_bInTypeEquivalenceForbiddenScope = TRUE; + } + else + { + result.m_bInTypeEquivalenceForbiddenScope = !typeHandle.AsMethodTable()->IsInterface(); + } + } else { _ASSERTE(elemType == ELEMENT_TYPE_VALUETYPE); diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index f3b77be15355c0..6f0649c0b8d8f2 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -972,16 +972,11 @@ class MetaSig // be compared. bool IgnoreCustomModifiers; - // Context to use when comparing generic instantiations against - // ELEMENT_TYPE_INTERNAL types. - SigTypeContext InternalToGenericInstContext; - CompareState() = default; CompareState(TokenPairList* list) : Visited{ list } , IgnoreCustomModifiers{ false } - , InternalToGenericInstContext{} { } }; diff --git a/src/coreclr/vm/typeparse.cpp b/src/coreclr/vm/typeparse.cpp index e66d3baef9d240..b018bbe216df47 100644 --- a/src/coreclr/vm/typeparse.cpp +++ b/src/coreclr/vm/typeparse.cpp @@ -7,7 +7,7 @@ #include "common.h" #include "typeparse.h" -static TypeHandle GetTypeHelper(LPCWSTR szTypeName, Assembly* pRequestingAssembly, BOOL bThrowIfNotFound, BOOL bRequireAssemblyQualifiedName) +static TypeHandle GetTypeHelper(LPCWSTR szTypeName, Assembly* pRequestingAssembly, BOOL bThrowIfNotFound, BOOL bRequireAssemblyQualifiedName, MethodDesc* unsafeAccessorMethod) { CONTRACTL { @@ -37,6 +37,7 @@ static TypeHandle GetTypeHelper(LPCWSTR szTypeName, Assembly* pRequestingAssembl args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(objRequestingAssembly); args[ARGNUM_2] = BOOL_TO_ARGHOLDER(bThrowIfNotFound); args[ARGNUM_3] = BOOL_TO_ARGHOLDER(bRequireAssemblyQualifiedName); + args[ARGNUM_4] = PTR_TO_ARGHOLDER(unsafeAccessorMethod); REFLECTCLASSBASEREF objType = NULL; CALL_MANAGED_METHOD_RETREF(objType, REFLECTCLASSBASEREF, args); @@ -55,17 +56,17 @@ TypeHandle TypeName::GetTypeReferencedByCustomAttribute(LPCUTF8 szTypeName, Asse { WRAPPER_NO_CONTRACT; StackSString sszAssemblyQualifiedName(SString::Utf8, szTypeName); - return GetTypeHelper(sszAssemblyQualifiedName.GetUnicode(), pRequestingAssembly, TRUE /* bThrowIfNotFound */, FALSE /* bRequireAssemblyQualifiedName */); + return GetTypeHelper(sszAssemblyQualifiedName.GetUnicode(), pRequestingAssembly, TRUE /* bThrowIfNotFound */, FALSE /* bRequireAssemblyQualifiedName */, NULL /* unsafeAccessorMethod */); } -TypeHandle TypeName::GetTypeReferencedByCustomAttribute(LPCWSTR szTypeName, Assembly* pRequestingAssembly) +TypeHandle TypeName::GetTypeReferencedByCustomAttribute(LPCWSTR szTypeName, Assembly* pRequestingAssembly, MethodDesc* unsafeAccessorMethod) { WRAPPER_NO_CONTRACT; - return GetTypeHelper(szTypeName, pRequestingAssembly, TRUE /* bThrowIfNotFound */, FALSE /* bRequireAssemblyQualifiedName */); + return GetTypeHelper(szTypeName, pRequestingAssembly, TRUE /* bThrowIfNotFound */, FALSE /* bRequireAssemblyQualifiedName */, unsafeAccessorMethod); } TypeHandle TypeName::GetTypeFromAsmQualifiedName(LPCWSTR szFullyQualifiedName, BOOL bThrowIfNotFound) { WRAPPER_NO_CONTRACT; - return GetTypeHelper(szFullyQualifiedName, NULL, bThrowIfNotFound, TRUE /* bRequireAssemblyQualifiedName */); + return GetTypeHelper(szFullyQualifiedName, NULL, bThrowIfNotFound, TRUE /* bRequireAssemblyQualifiedName */, NULL /* unsafeAccessorMethod */); } diff --git a/src/coreclr/vm/typeparse.h b/src/coreclr/vm/typeparse.h index cbf96f40389a3f..2031bba94421ca 100644 --- a/src/coreclr/vm/typeparse.h +++ b/src/coreclr/vm/typeparse.h @@ -57,7 +57,7 @@ class TypeName // //-------------------------------------------------------------------------------------------- static TypeHandle GetTypeReferencedByCustomAttribute(LPCUTF8 szTypeName, Assembly *pRequestingAssembly); - static TypeHandle GetTypeReferencedByCustomAttribute(LPCWSTR szTypeName, Assembly *pRequestingAssembly); + static TypeHandle GetTypeReferencedByCustomAttribute(LPCWSTR szTypeName, Assembly *pRequestingAssembly, MethodDesc* unsafeAccessorMethod = NULL); }; #endif diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 1d7a1162ae17f5..a5cbd06e5ee018 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -159,9 +159,28 @@ namespace if (expectedType != currTypeElem) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + // Append the new type to the signature. + Instantiation inst = newType->GetInstantiation(); + + // We have a generic element, mark GENERICINST. + if (!inst.IsEmpty()) + newSig.AppendElementType(ELEMENT_TYPE_GENERICINST); + // Append the new type to the signature. newSig.AppendElementType(ELEMENT_TYPE_INTERNAL); newSig.AppendPointer(newType); + + // Append the instantiation types to the signature. + if (!inst.IsEmpty()) + { + newSig.AppendData(inst.GetNumArgs()); + for (DWORD i = 0; i < inst.GetNumArgs(); i++) + { + TypeVarTypeDesc* typeVar = inst[i].AsGenericVariable(); + newSig.AppendElementType(typeVar->GetInternalCorElementType()); + newSig.AppendData(typeVar->GetIndex()); + } + } } // Create a copy of the new signature and store it on the context. @@ -236,11 +255,23 @@ namespace // assembly for the purposes of lifetime tracking of collectible types. TypeHandle typeHandle = TypeName::GetTypeReferencedByCustomAttribute( typeStringUtf8.GetUnicode(), - cxt.Declaration->GetAssembly()); + cxt.Declaration->GetAssembly(), + cxt.Declaration /* unsafeAccessorMethod */); _ASSERTE(!typeHandle.IsNull()); MethodTable* targetType = typeHandle.AsMethodTable(); + // Any instantiation on the type must be a generic variable. + // We do this since the runtime will be instantiating the type + // with the generic parameters associated with the UnsafeAccessorAttribute + // definition. + Instantiation targetTypeInst = targetType->GetInstantiation(); + for (DWORD i = 0; i < targetTypeInst.GetNumArgs(); i++) + { + if (!targetTypeInst[i].IsGenericVariable()) + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); + } + // Future versions of the runtime may support // UnsafeAccessorTypeAttribute on value types. if (targetType->IsValueType()) @@ -297,14 +328,14 @@ namespace uint32_t cSig1; cxt.DeclarationSig.GetSignature(&pSig1, &cSig1); PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1; - ModuleBase* pModule1 = cxt.Declaration->GetModule(); + Module* 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(); + Module* pModule2 = method->GetModule(); const Substitution* pSubst2 = NULL; // @@ -510,7 +541,6 @@ namespace TokenPairList list { nullptr }; MetaSig::CompareState state{ &list }; state.IgnoreCustomModifiers = ignoreCustomModifiers; - state.InternalToGenericInstContext = { cxt.Declaration->GetClassInstantiation(), cxt.Declaration->GetMethodInstantiation() }; if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr, state)) continue; @@ -984,3 +1014,31 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET GenerateAccessor(context, resolver, methodILDecoder); return true; } + +extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(MethodDesc* unsafeAccessorMethod, BOOL isMethodParam, int paramIndex) +{ + QCALL_CONTRACT; + _ASSERTE(unsafeAccessorMethod != NULL); + + TypeHandle ret; + + BEGIN_QCALL; + + Instantiation genericParams; + MethodDesc* typicalMD = unsafeAccessorMethod->LoadTypicalMethodDefinition(); + if (isMethodParam) + { + genericParams = typicalMD->GetMethodInstantiation(); + } + else + { + genericParams = typicalMD->GetMethodTable()->GetInstantiation(); + } + + if (0 <= paramIndex && paramIndex < genericParams.GetNumArgs()) + ret = genericParams[paramIndex]; + + END_QCALL; + + return ret.AsPtr(); +} diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index 94618ed58d0f75..76bebc1aa15dca 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -22,7 +22,7 @@ static Class1() static Class1 GetClass() => new Class1(); - List GetListOfClass2() => new List(); + Class2 GetClass2() => new Class2(); } class Class2 { } diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index aede4e0951de81..de58f68799a790 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -38,9 +38,6 @@ private TargetClass(C2 c2) private C2 M_C1(C1 a) => _f1; private C2 M_RC1(ref C1 a) => _f1; private C2 M_RROC1(ref readonly C1 a) => _f1; - - private C2 M_ListC1(List a) => _f1; - private ref C2 M_C1_RC2(C1 a) => ref _f1; } @@ -124,7 +121,6 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(c2, CallM_C1(tgt, c1)); Assert.Equal(c2, CallM_RC1(tgt, ref oc1)); Assert.Equal(c2, CallM_RROC1(tgt, ref oc1)); - Assert.Equal(c2, CallM_ListC1(tgt, null)); Assert.Equal(c2, CallM_C1_RC2(tgt, c1)); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] @@ -139,10 +135,6 @@ public static void Verify_Type_CallInstanceMethods() [return: UnsafeAccessorType("C2")] extern static object CallM_RROC1(TargetClass tgt, [UnsafeAccessorType("C1")] ref readonly object a); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_ListC1")] - [return: UnsafeAccessorType("C2")] - extern static object CallM_ListC1(TargetClass tgt, [UnsafeAccessorType("System.Collections.Generic.List`1[[C1]]")] object? a); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1_RC2")] [return: UnsafeAccessorType("C2")] extern static ref object CallM_C1_RC2(TargetClass tgt, [UnsafeAccessorType("C1")] object a); @@ -185,17 +177,17 @@ public static void Verify_Type_CallPrivateLibMethods() { object class1 = CallGetClass(null); Assert.Equal("PrivateLib.Class1", class1.GetType().FullName); - object listClass2 = CallGetListOfClass2(class1); - Assert.Equal("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]", listClass2.GetType().FullName); + object listClass2 = CallGetClass2(class1); + Assert.Equal("PrivateLib.Class2", listClass2.GetType().FullName); } [UnsafeAccessor(UnsafeAccessorKind.Constructor)] [return: UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] extern static object CreateClass(); - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetListOfClass2")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] - extern static object CallGetListOfClass2([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetClass2")] + [return: UnsafeAccessorType("PrivateLib.Class2, PrivateLib")] + extern static object CallGetClass2([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); } [Fact] @@ -219,38 +211,18 @@ public static void Verify_Type_GetPrivateLibFields() class Accessors { [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] - public extern static object CreateGenericClassInt(); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] - public extern static object CreateGenericClassString(); + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] + public extern static object CreateGenericClass(); // Class type variables [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M1")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Int32]]")] - public extern static object CallGenericClassIntM1([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] object a); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M1")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.String]]")] - public extern static object CallGenericClassStringM1([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] object a); + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[!0]]")] + public extern static object CallGenericClassM1([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); // Method type variables [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Int32]]")] - public extern static object CallGenericClassIntM2Int([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] object a); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.String]]")] - public extern static object CallGenericClassIntM2String([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.Int32]], PrivateLib")] object a); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Int32]]")] - public extern static object CallGenericClassStringM2Int([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] object a); - - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] - [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.String]]")] - public extern static object CallGenericClassStringM2String([UnsafeAccessorType("PrivateLib.GenericClass`1[[System.String]], PrivateLib")] object a); + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[!!0]]")] + public extern static object CallGenericClassM2([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); } private static bool TypeNameEquals(TypeName typeName1, TypeName typeName2) @@ -290,37 +262,37 @@ public static void Verify_Type_CallPrivateLibGenerics() Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibGenerics)}"); { - object genericClass = Accessors.CreateGenericClassInt(); + object genericClass = Accessors.CreateGenericClass(); TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.Int32]]"))); - object genericListT = Accessors.CallGenericClassIntM1(genericClass); + object genericListT = Accessors.CallGenericClassM1(genericClass); TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); - object genericListInt = Accessors.CallGenericClassIntM2Int(genericClass); + object genericListInt = Accessors.CallGenericClassM2(genericClass); TypeName genericListIntName = TypeName.Parse(genericListInt.GetType().FullName); Assert.True(TypeNameEquals(genericListIntName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); - object genericListString = Accessors.CallGenericClassIntM2String(genericClass); + object genericListString = Accessors.CallGenericClassM2(genericClass); TypeName genericListStringName = TypeName.Parse(genericListString.GetType().FullName); Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); } { - object genericClass = Accessors.CreateGenericClassString(); + object genericClass = Accessors.CreateGenericClass(); TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.String]]"))); - object genericListT = Accessors.CallGenericClassStringM1(genericClass); + object genericListT = Accessors.CallGenericClassM1(genericClass); TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); - object genericListInt = Accessors.CallGenericClassStringM2Int(genericClass); + object genericListInt = Accessors.CallGenericClassM2(genericClass); TypeName genericListIntName = TypeName.Parse(genericListInt.GetType().FullName); Assert.True(TypeNameEquals(genericListIntName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); - object genericListString = Accessors.CallGenericClassStringM2String(genericClass); + object genericListString = Accessors.CallGenericClassM2(genericClass); TypeName genericListStringName = TypeName.Parse(genericListString.GetType().FullName); Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); } From e5d3794fe4588060d71f638c5273aa8a2b354c04 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 28 Apr 2025 08:39:58 -0700 Subject: [PATCH 24/62] Fix build breaks. --- .../src/System/Reflection/TypeNameResolver.CoreCLR.cs | 8 ++++---- src/coreclr/vm/method.hpp | 2 +- src/coreclr/vm/unsafeaccessors.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs index fe75630c185d78..ff9072d2b7005a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs @@ -197,7 +197,7 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "UnsafeAccessors_ResolveGenericParamToTypeHandle")] - private static partial IntPtr ResolveGenericParamToTypeHandle(IntPtr unsafeAccessorMethod, [MarshalAs(UnmanagedType.Bool)] bool isMethodParam, int paramIndex); + private static partial IntPtr ResolveGenericParamToTypeHandle(IntPtr unsafeAccessorMethod, [MarshalAs(UnmanagedType.Bool)] bool isMethodParam, uint paramIndex); [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "TypeNameResolver.GetType is marked as RequiresUnreferencedCode.")] @@ -259,9 +259,9 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, // At this point we expect either another '!' and then a number or a number. bool isMethodParam = escapedTypeName[1] == '!'; - int paramIndex = isMethodParam - ? int.Parse(escapedTypeName.AsSpan(2)) // Skip over "!!" - : int.Parse(escapedTypeName.AsSpan(1)); // Skip over "!" + uint paramIndex = isMethodParam + ? uint.Parse(escapedTypeName.AsSpan(2)) // Skip over "!!" + : uint.Parse(escapedTypeName.AsSpan(1)); // Skip over "!" Debug.Assert(_unsafeAccessorMethod != IntPtr.Zero); IntPtr typeHandle = ResolveGenericParamToTypeHandle(_unsafeAccessorMethod, isMethodParam, paramIndex); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index cb6d50ae76b25f..75eb6cbb0344ac 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1902,7 +1902,7 @@ class MethodDesc }; #ifndef DACCESS_COMPILE -extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(MethodDesc* unsafeAccessorMethod, BOOL isMethodParam, int paramIndex); +extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(MethodDesc* unsafeAccessorMethod, BOOL isMethodParam, DWORD paramIndex); #endif // DACCESS_COMPILE template<> struct cdac_data diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index a5cbd06e5ee018..65e33272faa28d 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -1015,7 +1015,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET return true; } -extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(MethodDesc* unsafeAccessorMethod, BOOL isMethodParam, int paramIndex) +extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(MethodDesc* unsafeAccessorMethod, BOOL isMethodParam, DWORD paramIndex) { QCALL_CONTRACT; _ASSERTE(unsafeAccessorMethod != NULL); From 7097c73c442be37f0c696f072006ea16e1ebe803 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 28 Apr 2025 10:27:55 -0700 Subject: [PATCH 25/62] Fix build break on linux --- src/coreclr/vm/typeparse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/typeparse.cpp b/src/coreclr/vm/typeparse.cpp index b018bbe216df47..ae3fdf778f76a2 100644 --- a/src/coreclr/vm/typeparse.cpp +++ b/src/coreclr/vm/typeparse.cpp @@ -32,7 +32,7 @@ static TypeHandle GetTypeHelper(LPCWSTR szTypeName, Assembly* pRequestingAssembl OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); PREPARE_NONVIRTUAL_CALLSITE(METHOD__TYPE_NAME_RESOLVER__GET_TYPE_HELPER); - DECLARE_ARGHOLDER_ARRAY(args, 4); + DECLARE_ARGHOLDER_ARRAY(args, 5); args[ARGNUM_0] = PTR_TO_ARGHOLDER(szTypeName); args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(objRequestingAssembly); args[ARGNUM_2] = BOOL_TO_ARGHOLDER(bThrowIfNotFound); From db756dc2112bb3938449c931c085b63935c53bf5 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 28 Apr 2025 14:47:36 -0700 Subject: [PATCH 26/62] Enable bound generics with private types. --- src/coreclr/vm/siginfo.cpp | 28 ++++++++++ src/coreclr/vm/typehandle.h | 12 ++++- src/coreclr/vm/unsafeaccessors.cpp | 21 +++----- .../UnsafeAccessors/PrivateLib.cs | 4 ++ .../UnsafeAccessorsTests.Types.cs | 53 ++++++++++++++++--- 5 files changed, 97 insertions(+), 21 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index f2ddbfd055c8c9..fa04e0202dba0e 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3959,6 +3959,34 @@ MetaSig::CompareElementType( return (hInternal == hOtherType); } + case ELEMENT_TYPE_GENERICINST: + { + // Due to how SigPointer works, we need to fiddle with the signature pointer + // to get the ELEMENT_TYPE_GENERICINST element type back in the stream. + // Since we know the size of the element type, we can just subtract one byte from the + // signature pointer to get the correct value. + PCCOR_SIGNATURE genericInstSig; + uint32_t genericInstSigLen; + if (Type1 == ELEMENT_TYPE_INTERNAL) + { + const BYTE* sigRaw = (const BYTE*)pSig2; + genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); + genericInstSigLen = (uint32_t)((intptr_t)pEndSig2 - (intptr_t)genericInstSig); + } + else + { + const BYTE* sigRaw = (const BYTE*)pSig1; + genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); + genericInstSigLen = (uint32_t)((intptr_t)pEndSig1 - (intptr_t)genericInstSig); + } + // Assert we are in the same state as before. + _ASSERTE(*genericInstSig == ELEMENT_TYPE_GENERICINST); + + SigPointer inst{ genericInstSig, genericInstSigLen }; + TypeHandle hOtherType = inst.GetTypeHandleThrowing(pOtherModule, NULL); + + return (hInternal == hOtherType); + } default: { return FALSE; diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 862e2fa137aeec..811c903440e884 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -719,7 +719,7 @@ class Instantiation bool ContainsAllOneType(TypeHandle th) { - for (auto i = GetNumArgs(); i > 0;) + for (DWORD i = GetNumArgs(); i > 0;) { if ((*this)[--i] != th) return false; @@ -727,6 +727,16 @@ class Instantiation return true; } + bool ContainsGenericVariables() + { + for (DWORD i = GetNumArgs(); i > 0;) + { + if ((*this)[--i].IsGenericVariable()) + return true; + } + return false; + } + private: // Note that for DAC builds, m_pArgs may be host allocated buffer, not a copy of an object marshalled by DAC. TypeHandle* m_pArgs; diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 65e33272faa28d..ebfb40edfdc954 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -162,8 +162,9 @@ namespace // Append the new type to the signature. Instantiation inst = newType->GetInstantiation(); - // We have a generic element, mark GENERICINST. - if (!inst.IsEmpty()) + // We have at least one generic variable, mark GENERICINST. + BOOL hasGenericVariables = inst.ContainsGenericVariables(); + if (hasGenericVariables) newSig.AppendElementType(ELEMENT_TYPE_GENERICINST); // Append the new type to the signature. @@ -171,11 +172,14 @@ namespace newSig.AppendPointer(newType); // Append the instantiation types to the signature. - if (!inst.IsEmpty()) + if (hasGenericVariables) { newSig.AppendData(inst.GetNumArgs()); for (DWORD i = 0; i < inst.GetNumArgs(); i++) { + if (!inst[i].IsGenericVariable()) + continue; + TypeVarTypeDesc* typeVar = inst[i].AsGenericVariable(); newSig.AppendElementType(typeVar->GetInternalCorElementType()); newSig.AppendData(typeVar->GetIndex()); @@ -261,17 +265,6 @@ namespace MethodTable* targetType = typeHandle.AsMethodTable(); - // Any instantiation on the type must be a generic variable. - // We do this since the runtime will be instantiating the type - // with the generic parameters associated with the UnsafeAccessorAttribute - // definition. - Instantiation targetTypeInst = targetType->GetInstantiation(); - for (DWORD i = 0; i < targetTypeInst.GetNumArgs(); i++) - { - if (!targetTypeInst[i].IsGenericVariable()) - ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); - } - // Future versions of the runtime may support // UnsafeAccessorTypeAttribute on value types. if (targetType->IsValueType()) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index 76bebc1aa15dca..ec4f2b09359ede 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -32,5 +32,9 @@ class GenericClass List M1() => new List(); List M2() => new List(); + + List M3() => new List(); + + List M4() => new List(); } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index de58f68799a790..98a2afe36ce582 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -223,6 +223,14 @@ class Accessors [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M2")] [return: UnsafeAccessorType("System.Collections.Generic.List`1[[!!0]]")] public extern static object CallGenericClassM2([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); + + // Bound type variables + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M3")] + public extern static List CallGenericClassM3([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M4")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] + public extern static object CallGenericClassM4([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); } private static bool TypeNameEquals(TypeName typeName1, TypeName typeName2) @@ -257,9 +265,9 @@ private static bool TypeNameEquals(TypeName typeName1, TypeName typeName2) // Skip private types and Generic support on Mono runtime [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] - public static void Verify_Type_CallPrivateLibGenerics() + public static void Verify_Type_CallPrivateLibTypeGenericParams() { - Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibGenerics)}"); + Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibTypeGenericParams)}"); { object genericClass = Accessors.CreateGenericClass(); @@ -270,6 +278,43 @@ public static void Verify_Type_CallPrivateLibGenerics() TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); + List boundListInt = Accessors.CallGenericClassM3(genericClass); + Assert.Empty(boundListInt); + + object genericListClass2 = Accessors.CallGenericClassM4(genericClass); + TypeName genericListClass2Name = TypeName.Parse(genericListClass2.GetType().FullName); + Assert.True(TypeNameEquals(genericListClass2Name, TypeName.Parse("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]"))); + } + + { + object genericClass = Accessors.CreateGenericClass(); + TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); + Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.String]]"))); + + object genericListT = Accessors.CallGenericClassM1(genericClass); + TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); + Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); + + List boundListInt = Accessors.CallGenericClassM3(genericClass); + Assert.Empty(boundListInt); + + object genericListClass2 = Accessors.CallGenericClassM4(genericClass); + TypeName genericListClass2Name = TypeName.Parse(genericListClass2.GetType().FullName); + Assert.True(TypeNameEquals(genericListClass2Name, TypeName.Parse("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]"))); + } + } + + // Skip private types and Generic support on Mono runtime + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] + public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibTypeAndMethodGenericParams)}"); + + { + object genericClass = Accessors.CreateGenericClass(); + TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); + Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.Int32]]"))); + object genericListInt = Accessors.CallGenericClassM2(genericClass); TypeName genericListIntName = TypeName.Parse(genericListInt.GetType().FullName); Assert.True(TypeNameEquals(genericListIntName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); @@ -284,10 +329,6 @@ public static void Verify_Type_CallPrivateLibGenerics() TypeName genericClassName = TypeName.Parse(genericClass.GetType().FullName); Assert.True(TypeNameEquals(genericClassName, TypeName.Parse("PrivateLib.GenericClass`1[[System.String]]"))); - object genericListT = Accessors.CallGenericClassM1(genericClass); - TypeName genericListTName = TypeName.Parse(genericListT.GetType().FullName); - Assert.True(TypeNameEquals(genericListTName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); - object genericListInt = Accessors.CallGenericClassM2(genericClass); TypeName genericListIntName = TypeName.Parse(genericListInt.GetType().FullName); Assert.True(TypeNameEquals(genericListIntName, TypeName.Parse("System.Collections.Generic.List`1[[System.Int32]]"))); From 2729f3595214cfe2c5a923566623173633d799e9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Apr 2025 10:59:43 -0700 Subject: [PATCH 27/62] Support unbound generics in NAOT. --- .../CustomAttributeTypeNameParser.cs | 39 ++++++++++++++++--- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 2 +- src/coreclr/tools/aot/ILCompiler/Program.cs | 2 +- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs index 83e74b02dd9f0c..e2e294035a5fc8 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs @@ -21,6 +21,7 @@ public static class CustomAttributeTypeNameParser /// This is the inverse of what does. /// public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module, string name, bool throwIfNotFound = true, + bool resolveUnboundGenerics = false, Func canonResolver = null) { if (!TypeName.TryParse(name.AsSpan(), out TypeName parsed, s_typeNameParseOptions)) @@ -31,7 +32,8 @@ public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module, _context = module.Context, _module = module, _throwIfNotFound = throwIfNotFound, - _canonResolver = canonResolver + _canonResolver = canonResolver, + _resolveUnboundGenerics = resolveUnboundGenerics }.Resolve(parsed); } @@ -91,6 +93,7 @@ private struct TypeNameResolver internal TypeSystemContext _context; internal ModuleDesc _module; internal bool _throwIfNotFound; + internal bool _resolveUnboundGenerics; internal Func _canonResolver; internal List _referencedModules; @@ -136,35 +139,45 @@ private TypeDesc GetSimpleType(TypeName typeName) } ModuleDesc module = _module; - if (topLevelTypeName.AssemblyName != null) + if (topLevelTypeName.AssemblyName is not null) { module = _context.ResolveAssembly(typeName.AssemblyName, throwIfNotFound: _throwIfNotFound); if (module == null) return null; } - if (module != null) + if (module is not null) { TypeDesc type = GetSimpleTypeFromModule(typeName, module); - if (type != null) + if (type is not null) { _referencedModules?.Add(module); return type; } } - // If it didn't resolve and wasn't assembly-qualified, we also try core library if (topLevelTypeName.AssemblyName == null) { + // If it didn't resolve and wasn't assembly-qualified, we also try core library if (module != _context.SystemModule) { TypeDesc type = GetSimpleTypeFromModule(typeName, _context.SystemModule); - if (type != null) + if (type is not null) { _referencedModules?.Add(_context.SystemModule); return type; } } + + // If we still haven't resolved the name, check if we have an unbound generic type. + if (_resolveUnboundGenerics && topLevelTypeName.FullName.StartsWith('!')) + { + TypeDesc type = ResolveUnboundGenericType(typeName); + if (type is not null) + { + return type; + } + } } if (_throwIfNotFound) @@ -213,6 +226,20 @@ private TypeDesc GetGenericType(TypeName typeName) } return ((MetadataType)typeDefinition).MakeInstantiatedType(instantiation); } + + private TypeDesc ResolveUnboundGenericType(TypeName typeName) + { + string name = typeName.FullName; + Debug.Assert(name.StartsWith('!')); + bool isMethodParameter = name.StartsWith("!!", StringComparison.Ordinal); + + if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), out int index)) + { + return null; + } + + return _module.Context.GetSignatureVariable(index, isMethodParameter); + } } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 17c4e93b34cf8e..2c3306491587d6 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -565,7 +565,7 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.Invalid; } - TypeDesc replacementType = method.Module.GetTypeByCustomAttributeTypeName(replacementTypeName, throwIfNotFound: false); + TypeDesc replacementType = method.Module.GetTypeByCustomAttributeTypeName(replacementTypeName, throwIfNotFound: false, resolveUnboundGenerics: true); if (replacementType is null) { diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 0420a555eb3c5f..33f6a9f2c39ad5 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -725,7 +725,7 @@ private static TypeDesc FindType(CompilerTypeSystemContext context, string typeN ModuleDesc systemModule = context.SystemModule; TypeDesc foundType = systemModule.GetTypeByCustomAttributeTypeName(typeName, false, - (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); + canonResolver: (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); if (foundType == null) throw new CommandLineException($"Type '{typeName}' not found"); From 6a6e6165ea30ed949d13fbebfddc667dc7247f5c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Apr 2025 11:07:49 -0700 Subject: [PATCH 28/62] Fix crossgen usage of updated API --- .../aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs | 2 +- src/coreclr/tools/aot/crossgen2/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs index bc79c183257bc9..e577ae53e600a7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs @@ -227,7 +227,7 @@ private MethodDesc ResolveMethodName(CompilerTypeSystemContext context, string m private MethodDesc ResolveMethodName(CompilerTypeSystemContext context, ModuleDesc module, string namespaceAndTypeName, string methodName) { TypeDesc resolvedType = module.GetTypeByCustomAttributeTypeName(namespaceAndTypeName, false, - (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); + canonResolver: (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); if (resolvedType != null) { diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index be5da5fd7ea9dd..5b0f5ef7b502e3 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -664,7 +664,7 @@ private TypeDesc FindType(CompilerTypeSystemContext context, string typeName) ModuleDesc systemModule = context.SystemModule; TypeDesc foundType = systemModule.GetTypeByCustomAttributeTypeName(typeName, false, - (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); + canonResolver: (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); if (foundType == null) throw new CommandLineException(string.Format(SR.TypeNotFound, typeName)); From d2dd033022ace41c529d7095a341a1cbe148dfd3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Apr 2025 11:41:26 -0700 Subject: [PATCH 29/62] Restructure to avoid having to deal with .NET Standard-targeting ILVerify --- .../CustomAttributeTypeNameParser.cs | 37 +++---------------- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 21 ++++++++++- .../Compiler/CallChainProfile.cs | 2 +- src/coreclr/tools/aot/ILCompiler/Program.cs | 2 +- src/coreclr/tools/aot/crossgen2/Program.cs | 2 +- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs index e2e294035a5fc8..1c2de5d1743ff5 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs @@ -21,8 +21,7 @@ public static class CustomAttributeTypeNameParser /// This is the inverse of what does. /// public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module, string name, bool throwIfNotFound = true, - bool resolveUnboundGenerics = false, - Func canonResolver = null) + Func canonGenericResolver = null) { if (!TypeName.TryParse(name.AsSpan(), out TypeName parsed, s_typeNameParseOptions)) ThrowHelper.ThrowTypeLoadException(name, module); @@ -32,8 +31,7 @@ public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module, _context = module.Context, _module = module, _throwIfNotFound = throwIfNotFound, - _canonResolver = canonResolver, - _resolveUnboundGenerics = resolveUnboundGenerics + _canonGenericResolver = canonGenericResolver }.Resolve(parsed); } @@ -93,8 +91,7 @@ private struct TypeNameResolver internal TypeSystemContext _context; internal ModuleDesc _module; internal bool _throwIfNotFound; - internal bool _resolveUnboundGenerics; - internal Func _canonResolver; + internal Func _canonGenericResolver; internal List _referencedModules; @@ -168,16 +165,6 @@ private TypeDesc GetSimpleType(TypeName typeName) return type; } } - - // If we still haven't resolved the name, check if we have an unbound generic type. - if (_resolveUnboundGenerics && topLevelTypeName.FullName.StartsWith('!')) - { - TypeDesc type = ResolveUnboundGenericType(typeName); - if (type is not null) - { - return type; - } - } } if (_throwIfNotFound) @@ -197,9 +184,9 @@ private TypeDesc GetSimpleTypeFromModule(TypeName typeName, ModuleDesc module) string fullName = TypeNameHelpers.Unescape(typeName.FullName); - if (_canonResolver != null) + if (_canonGenericResolver != null) { - MetadataType canonType = _canonResolver(module, fullName); + TypeDesc canonType = _canonGenericResolver(module, fullName); if (canonType != null) return canonType; } @@ -226,20 +213,6 @@ private TypeDesc GetGenericType(TypeName typeName) } return ((MetadataType)typeDefinition).MakeInstantiatedType(instantiation); } - - private TypeDesc ResolveUnboundGenericType(TypeName typeName) - { - string name = typeName.FullName; - Debug.Assert(name.StartsWith('!')); - bool isMethodParameter = name.StartsWith("!!", StringComparison.Ordinal); - - if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), out int index)) - { - return null; - } - - return _module.Context.GetSignatureVariable(index, isMethodParameter); - } } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 2c3306491587d6..411f73f430c51c 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Globalization; +using System.Reflection; using System.Reflection.Metadata; using System.Runtime.InteropServices; using Internal.IL.Stubs; @@ -565,7 +566,25 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.Invalid; } - TypeDesc replacementType = method.Module.GetTypeByCustomAttributeTypeName(replacementTypeName, throwIfNotFound: false, resolveUnboundGenerics: true); + TypeDesc replacementType = method.Module.GetTypeByCustomAttributeTypeName( + replacementTypeName, + throwIfNotFound: false, + canonGenericResolver: (module, name) => + { + if (!name.StartsWith('!')) + { + return null; + } + + bool isMethodParameter = name.StartsWith("!!", StringComparison.Ordinal); + + if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), out int index)) + { + return null; + } + + return module.Context.GetSignatureVariable(index, isMethodParameter); + }); if (replacementType is null) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs index e577ae53e600a7..bc79c183257bc9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs @@ -227,7 +227,7 @@ private MethodDesc ResolveMethodName(CompilerTypeSystemContext context, string m private MethodDesc ResolveMethodName(CompilerTypeSystemContext context, ModuleDesc module, string namespaceAndTypeName, string methodName) { TypeDesc resolvedType = module.GetTypeByCustomAttributeTypeName(namespaceAndTypeName, false, - canonResolver: (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); + (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); if (resolvedType != null) { diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 33f6a9f2c39ad5..0420a555eb3c5f 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -725,7 +725,7 @@ private static TypeDesc FindType(CompilerTypeSystemContext context, string typeN ModuleDesc systemModule = context.SystemModule; TypeDesc foundType = systemModule.GetTypeByCustomAttributeTypeName(typeName, false, - canonResolver: (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); + (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); if (foundType == null) throw new CommandLineException($"Type '{typeName}' not found"); diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 5b0f5ef7b502e3..be5da5fd7ea9dd 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -664,7 +664,7 @@ private TypeDesc FindType(CompilerTypeSystemContext context, string typeName) ModuleDesc systemModule = context.SystemModule; TypeDesc foundType = systemModule.GetTypeByCustomAttributeTypeName(typeName, false, - canonResolver: (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); + (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName)); if (foundType == null) throw new CommandLineException(string.Format(SR.TypeNotFound, typeName)); From baab3ea4a605072f4513bc1422de9d3d4f0de4df Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 30 Apr 2025 18:47:20 -0700 Subject: [PATCH 30/62] Handle byrefs and pointers as type name. --- .../tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 7 +++++++ src/coreclr/vm/unsafeaccessors.cpp | 3 +++ .../UnsafeAccessors/UnsafeAccessorsTests.Types.cs | 12 ++++++++++++ 3 files changed, 22 insertions(+) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 411f73f430c51c..b6e66edc8a2e9e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -591,6 +591,13 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.Missing; } + if (replacementType.IsByRef + || replacementType.IsFunctionPointer + || replacementType.IsPointer) + { + return SetTargetResult.NotSupported; + } + // Future versions of the runtime may support // UnsafeAccessorTypeAttribute on value types. if (replacementType.IsValueType) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index ebfb40edfdc954..26591760f9e250 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -263,6 +263,9 @@ namespace cxt.Declaration /* unsafeAccessorMethod */); _ASSERTE(!typeHandle.IsNull()); + if (typeHandle.IsTypeDesc()) + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); + MethodTable* targetType = typeHandle.AsMethodTable(); // Future versions of the runtime may support diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 98a2afe36ce582..0133bb1c3fd5e2 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -78,6 +78,12 @@ public static void Verify_Type_InvalidArgument() AssertExtensions.ThrowsAny(() => CallStaticMethod1(null)); Assert.Throws(() => CallStaticMethod2(null)); Assert.Throws(() => CallStaticMethod3(null)); + Assert.Throws(() => + { + object o = null; + CallStaticMethod4(ref o); + }); + Assert.Throws(() => CallStaticMethod5(null)); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] extern static ref int CallStaticMethod1([UnsafeAccessorType(null!)] object a); @@ -87,6 +93,12 @@ public static void Verify_Type_InvalidArgument() [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] extern static ref int CallStaticMethod3([UnsafeAccessorType("S1")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] + extern static ref int CallStaticMethod4([UnsafeAccessorType("C1&")] ref object a); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] + extern static ref int CallStaticMethod5([UnsafeAccessorType("S1*")] object a); } [Fact] From fca0b95e10ae9f5cf4deb738f10f37174173a277 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 3 May 2025 09:46:57 -0700 Subject: [PATCH 31/62] Require UnsafeAccessorType to define full signature as a string. Add type checks for all argument usage of UnsafeAccessorType. --- src/coreclr/vm/ilstubresolver.cpp | 12 + src/coreclr/vm/jitinterface.cpp | 21 +- src/coreclr/vm/siginfo.cpp | 32 ++- src/coreclr/vm/stubgen.cpp | 20 +- src/coreclr/vm/stubgen.h | 70 +++++- src/coreclr/vm/unsafeaccessors.cpp | 213 ++++++++++++------ .../UnsafeAccessorsTests.Types.cs | 40 ++-- 7 files changed, 301 insertions(+), 107 deletions(-) diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 7e2786baa9d82c..0d877e94e454c5 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -213,6 +213,18 @@ void ILStubResolver::ResolveToken(mdToken token, ResolvedToken* resolvedToken) resolvedToken->TypeHandle = TypeHandle(pMT); } break; + + case mdtTypeSpec: + { + TokenLookupMap::TypeSpecEntry entry = m_pCompileTimeState->m_tokenLookupMap.LookupTypeSpec(token); + _ASSERTE(entry.ClassSignatureToken != mdTokenNil); + _ASSERTE(!entry.Type.IsNull()); + resolvedToken->TypeSignature = m_pCompileTimeState->m_tokenLookupMap.LookupSig(entry.ClassSignatureToken); + + SigTypeContext typeContext{ m_pStubMD->GetClassInstantiation(), m_pStubMD->GetMethodInstantiation() }; + resolvedToken->TypeHandle = resolvedToken->TypeSignature.GetTypeHandleThrowing(m_pStubMD->GetModule(), &typeContext); + } + break; #endif // !defined(DACCESS_COMPILE) default: diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index eac05399f5bdf7..819b22f4ebe0f0 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -917,8 +917,18 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken } else { - if ((tkType != mdtTypeDef) && (tkType != mdtTypeRef)) + if (tkType == mdtTypeSpec) + { + // We have a TypeSpec, so we need to verify the signature is non-NULL + // and the typehandle has been fully instantiated. + if (pResolvedToken->pTypeSpec == NULL || th.ContainsGenericVariables()) + ThrowBadTokenException(pResolvedToken); + } + else if ((tkType != mdtTypeDef) && (tkType != mdtTypeRef)) + { ThrowBadTokenException(pResolvedToken); + } + if ((tokenType & CORINFO_TOKENKIND_Class) == 0) ThrowBadTokenException(pResolvedToken); if (th.IsNull()) @@ -14597,7 +14607,8 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, MethodTable* m sigBuilder.AppendByte(callConv); sigBuilder.AppendData(numArgs); - auto appendTypeHandle = [&](TypeHandle th) { + auto appendTypeHandle = [](SigBuilder& sigBuilder, TypeHandle th) + { _ASSERTE(!th.IsByRef()); CorElementType ty = th.GetSignatureCorElementType(); if (CorTypeInfo::IsObjRef(ty)) @@ -14614,9 +14625,9 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, MethodTable* m sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); sigBuilder.AppendPointer(th.AsPtr()); } - }; + }; - appendTypeHandle(msig.GetRetTypeHandleThrowing()); // return type + appendTypeHandle(sigBuilder, msig.GetRetTypeHandleThrowing()); // return type #ifndef TARGET_X86 if (msig.HasGenericContextArg()) { @@ -14631,7 +14642,7 @@ static Signature BuildResumptionStubCalliSignature(MetaSig& msig, MethodTable* m while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) { TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); - appendTypeHandle(tyHnd); + appendTypeHandle(sigBuilder, tyHnd); } #ifdef TARGET_X86 diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 28c7305215f181..36ac12554e3d14 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3964,31 +3964,45 @@ MetaSig::CompareElementType( return (hInternal == hOtherType); } case ELEMENT_TYPE_GENERICINST: + case ELEMENT_TYPE_BYREF: { // Due to how SigPointer works, we need to fiddle with the signature pointer - // to get the ELEMENT_TYPE_GENERICINST element type back in the stream. + // to get the current element type back in the stream. // Since we know the size of the element type, we can just subtract one byte from the // signature pointer to get the correct value. - PCCOR_SIGNATURE genericInstSig; - uint32_t genericInstSigLen; + PCCOR_SIGNATURE currInstSig; + uint32_t currInstSigLen; if (Type1 == ELEMENT_TYPE_INTERNAL) { const BYTE* sigRaw = (const BYTE*)pSig2; - genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); - genericInstSigLen = (uint32_t)((intptr_t)pEndSig2 - (intptr_t)genericInstSig); + currInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); + currInstSigLen = (uint32_t)((intptr_t)pEndSig2 - (intptr_t)currInstSig); } else { const BYTE* sigRaw = (const BYTE*)pSig1; - genericInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); - genericInstSigLen = (uint32_t)((intptr_t)pEndSig1 - (intptr_t)genericInstSig); + currInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); + currInstSigLen = (uint32_t)((intptr_t)pEndSig1 - (intptr_t)currInstSig); } // Assert we are in the same state as before. - _ASSERTE(*genericInstSig == ELEMENT_TYPE_GENERICINST); + _ASSERTE(*currInstSig == eOtherType); - SigPointer inst{ genericInstSig, genericInstSigLen }; + SigPointer inst{ currInstSig, currInstSigLen }; TypeHandle hOtherType = inst.GetTypeHandleThrowing(pOtherModule, NULL); + // SigPointer::GetTypeHandleThrowing() is non-consuming, so we need to + // consume the signature to move past the current type and update the appropriate + // 'in/out' parameter. + IfFailThrow(inst.SkipExactlyOne()); + if (Type1 == ELEMENT_TYPE_INTERNAL) + { + pSig2 = inst.GetPtr(); + } + else + { + pSig1 = inst.GetPtr(); + } + return (hInternal == hOtherType); } default: diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 6f44bae72be4ef..988211d74c2578 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -1232,6 +1232,11 @@ void ILCodeStream::EmitCALLI(int token, int numInArgs, int numRetArgs) WRAPPER_NO_CONTRACT; Emit(CEE_CALLI, (INT16)(numRetArgs - numInArgs - 1), token); } +void ILCodeStream::EmitCASTCLASS(int token) +{ + WRAPPER_NO_CONTRACT; + Emit(CEE_CASTCLASS, 0, token); +} void ILCodeStream::EmitCEQ() { WRAPPER_NO_CONTRACT; @@ -1995,7 +2000,7 @@ DWORD StubSigBuilder::Append(LocalDesc* pLoc) m_pbSigCursor += sizeof(TypeHandle); m_cbSig += sizeof(TypeHandle); break; - + case ELEMENT_TYPE_CMOD_INTERNAL: { // Nove later elements in the signature to make room for the CMOD_INTERNAL payload @@ -3232,16 +3237,16 @@ int ILStubLinker::GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken metho return m_tokenMap.GetToken(pMD, typeSignature, methodSignature); } -int ILStubLinker::GetToken(MethodTable* pMT) +int ILStubLinker::GetToken(TypeHandle th) { STANDARD_VM_CONTRACT; - return m_tokenMap.GetToken(TypeHandle(pMT)); + return m_tokenMap.GetToken(th); } -int ILStubLinker::GetToken(TypeHandle th) +int ILStubLinker::GetToken(TypeHandle th, mdToken typeSignature) { STANDARD_VM_CONTRACT; - return m_tokenMap.GetToken(th); + return m_tokenMap.GetToken(th, typeSignature); } int ILStubLinker::GetToken(FieldDesc* pFD) @@ -3352,6 +3357,11 @@ int ILCodeStream::GetToken(TypeHandle th) STANDARD_VM_CONTRACT; return m_pOwner->GetToken(th); } +int ILCodeStream::GetToken(TypeHandle th, mdToken typeSignature) +{ + STANDARD_VM_CONTRACT; + return m_pOwner->GetToken(th, typeSignature); +} int ILCodeStream::GetToken(FieldDesc* pFD) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index c848b0665008fa..d1aa1d72c9e63f 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -40,7 +40,7 @@ struct LocalDesc TypeHandle InternalToken; // only valid with ELEMENT_TYPE_INTERNAL // only valid with ELEMENT_TYPE_CMOD_INTERNAL - bool InternalModifierRequired; + bool InternalModifierRequired; TypeHandle InternalModifierToken; // used only for E_T_FNPTR and E_T_ARRAY @@ -313,12 +313,33 @@ class TokenLookupMap m_memberRefs.Set(pSrc->m_memberRefs); m_methodSpecs.Set(pSrc->m_methodSpecs); + m_typeSpecs.Set(pSrc->m_typeSpecs); } TypeHandle LookupTypeDef(mdToken token) { WRAPPER_NO_CONTRACT; - return LookupTokenWorker(token); + return LookupTokenWorker(token); + } + struct TypeSpecEntry final + { + mdToken ClassSignatureToken; + TypeHandle Type; + }; + TypeSpecEntry LookupTypeSpec(mdToken token) + { + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_NOTRIGGER; + PRECONDITION(RidFromToken(token) - 1 < m_typeSpecs.GetCount()); + PRECONDITION(RidFromToken(token) != 0); + PRECONDITION(TypeFromToken(token) == mdtTypeSpec); + } + CONTRACTL_END; + + return m_typeSpecs[static_cast(RidFromToken(token) - 1)]; } MethodDesc* LookupMethodDef(mdToken token) { @@ -398,10 +419,30 @@ class TokenLookupMap return SigPointer(pSig, cbSig); } - mdToken GetToken(TypeHandle pMT) + mdToken GetToken(TypeHandle th) { WRAPPER_NO_CONTRACT; - return GetTokenWorker(pMT); + return GetTokenWorker(th); + } + mdToken GetToken(TypeHandle th, mdToken typeSignature) + { + CONTRACTL + { + THROWS; + MODE_ANY; + GC_NOTRIGGER; + PRECONDITION(!th.IsNull()); + PRECONDITION(!th.IsTypeDesc() && th.GetMethodTable()->ContainsGenericVariables()); + PRECONDITION(typeSignature != mdTokenNil); + } + CONTRACTL_END; + + TypeSpecEntry* entry; + mdToken token = GetTypeSpecWorker(&entry); + entry->ClassSignatureToken = typeSignature; + entry->Type = th; + return token; + } mdToken GetToken(MethodDesc* pMD) { @@ -488,6 +529,22 @@ class TokenLookupMap } protected: + mdToken GetTypeSpecWorker(TypeSpecEntry** entry) + { + CONTRACTL + { + THROWS; + MODE_ANY; + GC_NOTRIGGER; + PRECONDITION(entry != NULL); + } + CONTRACTL_END; + + mdToken token = TokenFromRid(m_typeSpecs.GetCount(), mdtTypeSpec) + 1; + *entry = &*m_typeSpecs.Append(); // Dereference the iterator and then take the address + return token; + } + mdToken GetMemberRefWorker(MemberRefEntry** entry) { CONTRACTL @@ -566,6 +623,7 @@ class TokenLookupMap SArray, FALSE> m_signatures; SArray m_memberRefs; SArray m_methodSpecs; + SArray m_typeSpecs; }; class ILCodeLabel; @@ -734,8 +792,8 @@ class ILStubLinker int GetToken(MethodDesc* pMD); int GetToken(MethodDesc* pMD, mdToken typeSignature); int GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature); - int GetToken(MethodTable* pMT); int GetToken(TypeHandle th); + int GetToken(TypeHandle th, mdToken typeSignature); int GetToken(FieldDesc* pFD); int GetToken(FieldDesc* pFD, mdToken typeSignature); int GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig); @@ -859,6 +917,7 @@ class ILCodeStream void EmitCALL (int token, int numInArgs, int numRetArgs); void EmitCALLI (int token, int numInArgs, int numRetArgs); void EmitCALLVIRT (int token, int numInArgs, int numRetArgs); + void EmitCASTCLASS (int token); void EmitCEQ (); void EmitCGT (); void EmitCGT_UN (); @@ -971,6 +1030,7 @@ class ILCodeStream int GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature); int GetToken(MethodTable* pMT); int GetToken(TypeHandle th); + int GetToken(TypeHandle th, mdToken typeSignature); int GetToken(FieldDesc* pFD); int GetToken(FieldDesc* pFD, mdToken typeSignature); int GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig); diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 26591760f9e250..5d45fb01576d41 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -68,6 +68,7 @@ namespace , Declaration{ pMD } , DeclarationSig{ pMD->GetSigPointer() } , DeclarationMetaSig{ pMD } + , TranslatedTypes{} , TargetTypeSig{} , TargetType{} , IsTargetStatic{ false } @@ -80,6 +81,8 @@ namespace SigPointer DeclarationSig; // This is the official declaration signature. It may be modified // to include the UnsafeAccessorTypeAttribute types. MetaSig DeclarationMetaSig; + NewArrayHolder TranslatedTypes; // Redefined types from UnsafeAccessorTypeAttribute usage. + // Return type is at 0 index. Function arguments are 1 to N. SigPointer TargetTypeSig; TypeHandle TargetType; bool IsTargetStatic; @@ -87,12 +90,44 @@ namespace FieldDesc* TargetField; }; - void UpdateDeclarationSigWithTypes(GenerationContext& cxt, uint32_t translationsCount, MethodTable** translations) + void AppendTypeSignature( + SigBuilder& sig, + TypeHandle th, + Instantiation inst) + { + STANDARD_VM_CONTRACT; + _ASSERTE(!th.IsNull()); + + // We have at least one generic variable, mark GENERICINST. + BOOL hasGenericVariables = inst.ContainsGenericVariables(); + if (hasGenericVariables) + sig.AppendElementType(ELEMENT_TYPE_GENERICINST); + + // Append the new type to the signature. + sig.AppendElementType(ELEMENT_TYPE_INTERNAL); + sig.AppendPointer(th.AsPtr()); + + // Append the instantiation types to the signature. + if (hasGenericVariables) + { + sig.AppendData(inst.GetNumArgs()); + for (DWORD i = 0; i < inst.GetNumArgs(); i++) + { + if (!inst[i].IsGenericVariable()) + continue; + + TypeVarTypeDesc* typeVar = inst[i].AsGenericVariable(); + sig.AppendElementType(typeVar->GetInternalCorElementType()); + sig.AppendData(typeVar->GetIndex()); + } + } + } + + void UpdateDeclarationSigWithTypes(GenerationContext& cxt) { STANDARD_VM_CONTRACT; _ASSERTE(cxt.Declaration != NULL); - _ASSERTE(translationsCount != 0); - _ASSERTE(translations != NULL); + _ASSERTE(cxt.TranslatedTypes != NULL); // // Parsing and building the signature follows details defined in ECMA-335 - II.23.2.1 @@ -124,18 +159,17 @@ namespace IfFailThrow(origSig.GetData(&declArgCount)); newSig.AppendData(declArgCount); - _ASSERTE(declArgCount + 1 == translationsCount); - // Now we can copy over the return type and arguments. // The format for the return type is the same as the arguments, // except return parameters can be ELEMENT_TYPE_VOID. - for (uint32_t i = 0; i < translationsCount; ++i) + uint32_t totalParamCount = declArgCount + 1; + for (uint32_t i = 0; i < totalParamCount; ++i) { // Copy over any modopts or modreqs. origSig.CopyModOptsReqs(pSigModule, &newSig); - MethodTable* newType = translations[i]; - if (newType == NULL) + TypeHandle newTypeMaybe = cxt.TranslatedTypes[i]; + if (newTypeMaybe.IsNull()) { // Copy the original parameter and continue. origSig.CopyExactlyOne(pSigModule, &newSig); @@ -143,48 +177,47 @@ namespace } // We have a new type to insert. We need to update this parameter. - CorElementType currTypeElem; - IfFailThrow(origSig.GetElemType(&currTypeElem)); - if (currTypeElem == ELEMENT_TYPE_BYREF) - { - newSig.AppendElementType(currTypeElem); - IfFailThrow(origSig.GetElemType(&currTypeElem)); - } + TypeHandle currHandle = origSig.GetTypeHandleThrowing(pSigModule, NULL); - // Validate the parameter resolves correctly. There is only one acceptable mappings: - // ELEMENT_TYPE_OBJECT - if new type is a reference type. - _ASSERTE(!newType->IsValueType()); - CorElementType expectedType = ELEMENT_TYPE_OBJECT; + // SigPointer::GetTypeHandleThrowing() is non-consuming, so we need to + // consume the signature to move past the current type. + IfFailThrow(origSig.SkipExactlyOne()); - if (expectedType != currTypeElem) - ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + // Extract from the parameterized type from the current type. + while (currHandle.HasTypeParam()) + currHandle = currHandle.GetTypeParam(); - // Append the new type to the signature. - Instantiation inst = newType->GetInstantiation(); + // Validate the parameter resolves correctly. There is only one acceptable mapping: + // ELEMENT_TYPE_OBJECT - if new type is a reference type. + _ASSERTE(!newTypeMaybe.IsValueType()); + if (currHandle != TypeHandle(g_pObjectClass)) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // We have at least one generic variable, mark GENERICINST. - BOOL hasGenericVariables = inst.ContainsGenericVariables(); - if (hasGenericVariables) - newSig.AppendElementType(ELEMENT_TYPE_GENERICINST); + // Extract from the parameterized type from the new target. + while (newTypeMaybe.HasTypeParam()) + newTypeMaybe = newTypeMaybe.GetTypeParam(); + // // Append the new type to the signature. - newSig.AppendElementType(ELEMENT_TYPE_INTERNAL); - newSig.AppendPointer(newType); - - // Append the instantiation types to the signature. - if (hasGenericVariables) + // + + // UnsafeAccessorAttribute requires the return type to be byref for any field kind. + // This is a deviation from the normal UnsafeAccessorTypeAttribute requirements + // where the type is expected to match the signature exactly. Users aren't required to + // state the byref for field access because that isn't in the target field signature. + if (i == 0 // Return type + && (cxt.Kind == UnsafeAccessorKind::Field + || cxt.Kind == UnsafeAccessorKind::StaticField)) { - newSig.AppendData(inst.GetNumArgs()); - for (DWORD i = 0; i < inst.GetNumArgs(); i++) - { - if (!inst[i].IsGenericVariable()) - continue; - - TypeVarTypeDesc* typeVar = inst[i].AsGenericVariable(); - newSig.AppendElementType(typeVar->GetInternalCorElementType()); - newSig.AppendData(typeVar->GetIndex()); - } + newSig.AppendElementType(ELEMENT_TYPE_BYREF); } + + Instantiation inst = newTypeMaybe.GetInstantiation(); + + // Go back to the original translated type since during analysis + // the local may have been altered. + newTypeMaybe = cxt.TranslatedTypes[i]; + AppendTypeSignature(newSig, newTypeMaybe, inst); } // Create a copy of the new signature and store it on the context. @@ -211,12 +244,7 @@ namespace uint32_t attrCount = 0; - // Allocate memory to store the updated types. - NewArrayHolder heapAlloc; - MethodTable* stackAlloc[12]; // Use the stack alloc the majority of the time. - MethodTable** translations = stackAlloc; - - // Determine the max parameter count. +1 for the return value, which is always index zero. + // Determine the max parameter count. +1 for the return value, which is always index 0. const uint32_t totalParamCount = cxt.DeclarationMetaSig.NumFixedArgs() + 1; // Inspect all parameters to the declaration method for UnsafeAccessorTypeAttribute. @@ -232,17 +260,12 @@ namespace if (hr != S_OK) continue; - // The first time we find an attribute, we need to initialize - // the translations array and might need to allocate a larger array - // to store the translations. + // The first time we find an attribute, we need to allocate and + // initialize the translations array. if (attrCount == 0) { - if (totalParamCount > ARRAY_SIZE(stackAlloc)) - { - heapAlloc = new MethodTable*[totalParamCount]; - translations = heapAlloc; - } - memset(translations, 0, sizeof(translations[0]) * totalParamCount); + cxt.TranslatedTypes = new TypeHandle[totalParamCount]; + memset(cxt.TranslatedTypes, 0, sizeof(cxt.TranslatedTypes[0]) * totalParamCount); } // Parse the attribute data. @@ -257,20 +280,15 @@ namespace // Pass the string in the attribute to similar logic as Type.GetType(String). // The below API creates a dependency between the returned type and the requesting // assembly for the purposes of lifetime tracking of collectible types. - TypeHandle typeHandle = TypeName::GetTypeReferencedByCustomAttribute( + TypeHandle targetType = TypeName::GetTypeReferencedByCustomAttribute( typeStringUtf8.GetUnicode(), cxt.Declaration->GetAssembly(), cxt.Declaration /* unsafeAccessorMethod */); - _ASSERTE(!typeHandle.IsNull()); - - if (typeHandle.IsTypeDesc()) - ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); - - MethodTable* targetType = typeHandle.AsMethodTable(); + _ASSERTE(!targetType.IsNull()); // Future versions of the runtime may support // UnsafeAccessorTypeAttribute on value types. - if (targetType->IsValueType()) + if (targetType.IsValueType()) ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE); USHORT seq; @@ -281,14 +299,14 @@ namespace if (seq >= totalParamCount) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // Store the MethodTable for the loaded type at the sequence number for the parameter. - translations[seq] = targetType; + // Store the TypeHandle for the loaded type at the sequence number for the parameter. + cxt.TranslatedTypes[seq] = targetType; attrCount++; } // Update the declaration signatures if any instances of UnsafeAccessorTypeAttribute were found. if (attrCount != 0) - UpdateDeclarationSigWithTypes(cxt, totalParamCount, translations); + UpdateDeclarationSigWithTypes(cxt); } TypeHandle ValidateTargetType(TypeHandle targetTypeMaybe, CorElementType targetFromSig) @@ -686,6 +704,59 @@ namespace return false; } + void EmitTypeCheck(ILCodeStream* pCode, TypeHandle th) + { + STANDARD_VM_CONTRACT; + + if (th.IsNull()) + return; + + TypeHandle origType = th; + + // We are going to emit a type check for the UnsafeAccessorTypeAttribute + // scenario. We do this because the callable signature isn't going to enforce + // type safety, so we do it here. + // + // If the type is a byref, DUP the value so the execution stack has a + // byref value on it after the type check. Emit a load indirection of the byref + // in order to validate the type. After the typecheck, we'll pop off the object. + // This doesn't handle the null byref case. + // + // In the non-byref case, an object reference will be placed back on the stack + // through the type check so there is no need for the DUP. + if (origType.IsByRef()) + { + pCode->EmitDUP(); + + th = origType.GetTypeParam(); + pCode->EmitLDIND_REF(); + } + _ASSERTE(!th.IsTypeDesc()); + + int tk; + if (!th.GetMethodTable()->ContainsGenericVariables()) + { + tk = pCode->GetToken(th); + } + else + { + SigBuilder sigBuilder; + AppendTypeSignature(sigBuilder, th, th.GetInstantiation()); + + uint32_t sigLen; + PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen); + mdToken typeSig = pCode->GetSigToken(sig, sigLen); + tk = pCode->GetToken(th, typeSig); + } + + // Perform the type check. + pCode->EmitCASTCLASS(tk); + + // If the type was a byref, pop the object reference + if (origType.IsByRef()) + pCode->EmitPOP(); + } + void GenerateAccessor( GenerationContext& cxt, DynamicResolver** resolver, @@ -719,8 +790,14 @@ namespace UINT beginIndex = cxt.IsTargetStatic ? 1 : 0; UINT stubArgCount = cxt.DeclarationMetaSig.NumFixedArgs(); for (UINT i = beginIndex; i < stubArgCount; ++i) + { pCode->EmitLDARG(i); + // Perform a typecheck if the type was translated. + if (cxt.TranslatedTypes != NULL) + EmitTypeCheck(pCode, cxt.TranslatedTypes[i + 1]); // Index is +1 to account for the return value. + } + // Provide access to the target member UINT targetArgCount = stubArgCount - beginIndex; UINT targetRetCount = cxt.DeclarationMetaSig.IsReturnTypeVoid() ? 0 : 1; diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 0133bb1c3fd5e2..5a260582c0406c 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -35,6 +35,11 @@ private TargetClass(C2 c2) _f1 = c2; _f2 = c2; } + private TargetClass(ref C2 c2) + { + _f1 = c2; + _f2 = c2; + } private C2 M_C1(C1 a) => _f1; private C2 M_RC1(ref C1 a) => _f1; private C2 M_RROC1(ref readonly C1 a) => _f1; @@ -78,12 +83,6 @@ public static void Verify_Type_InvalidArgument() AssertExtensions.ThrowsAny(() => CallStaticMethod1(null)); Assert.Throws(() => CallStaticMethod2(null)); Assert.Throws(() => CallStaticMethod3(null)); - Assert.Throws(() => - { - object o = null; - CallStaticMethod4(ref o); - }); - Assert.Throws(() => CallStaticMethod5(null)); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] extern static ref int CallStaticMethod1([UnsafeAccessorType(null!)] object a); @@ -93,12 +92,6 @@ public static void Verify_Type_InvalidArgument() [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] extern static ref int CallStaticMethod3([UnsafeAccessorType("S1")] object a); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] - extern static ref int CallStaticMethod4([UnsafeAccessorType("C1&")] ref object a); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "MethodName")] - extern static ref int CallStaticMethod5([UnsafeAccessorType("S1*")] object a); } [Fact] @@ -120,6 +113,23 @@ public static void Verify_Type_StaticClass() [UnsafeAccessor(UnsafeAccessorKind.Constructor)] extern static TargetClass CreateTargetClass([UnsafeAccessorType("C2")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + extern static TargetClass CreateTargetClass([UnsafeAccessorType("C2&")] ref object a); + + // Skip validating error cases on Mono runtime + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] + public static void Verify_Type_TypeCheck() + { + Console.WriteLine($"Running {nameof(Verify_Type_TypeCheck)}"); + + Assert.Throws(() => CreateTargetClass(new C1())); + Assert.Throws(() => + { + object c1 = new C1(); + CreateTargetClass(ref c1); + }); + } + [Fact] public static void Verify_Type_CallInstanceMethods() { @@ -141,14 +151,14 @@ public static void Verify_Type_CallInstanceMethods() [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_RC1")] [return: UnsafeAccessorType("C2")] - extern static object CallM_RC1(TargetClass tgt, [UnsafeAccessorType("C1")] ref object a); + extern static object CallM_RC1(TargetClass tgt, [UnsafeAccessorType("C1&")] ref object a); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_RROC1")] [return: UnsafeAccessorType("C2")] - extern static object CallM_RROC1(TargetClass tgt, [UnsafeAccessorType("C1")] ref readonly object a); + extern static object CallM_RROC1(TargetClass tgt, [UnsafeAccessorType("C1&")] ref readonly object a); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1_RC2")] - [return: UnsafeAccessorType("C2")] + [return: UnsafeAccessorType("C2&")] extern static ref object CallM_C1_RC2(TargetClass tgt, [UnsafeAccessorType("C1")] object a); } From 5388fc824e7f0202c8dc23afc584289e335a6d5e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 3 May 2025 11:56:33 -0700 Subject: [PATCH 32/62] Style/nits and added tests --- src/coreclr/vm/unsafeaccessors.cpp | 43 +++++---- .../UnsafeAccessors/PrivateLib.cs | 2 + .../UnsafeAccessorsTests.Types.cs | 93 ++++++++++++++----- 3 files changed, 93 insertions(+), 45 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 5d45fb01576d41..bfc9436adb3e78 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -90,7 +90,7 @@ namespace FieldDesc* TargetField; }; - void AppendTypeSignature( + void AppendTypeToSignature( SigBuilder& sig, TypeHandle th, Instantiation inst) @@ -98,7 +98,11 @@ namespace STANDARD_VM_CONTRACT; _ASSERTE(!th.IsNull()); - // We have at least one generic variable, mark GENERICINST. + // + // Building the signature follows details defined in ECMA-335 - II.23.2.12 + // + + // We have at least one generic variable, mark as ELEMENT_TYPE_GENERICINST. BOOL hasGenericVariables = inst.ContainsGenericVariables(); if (hasGenericVariables) sig.AppendElementType(ELEMENT_TYPE_GENERICINST); @@ -148,9 +152,9 @@ namespace IfFailThrow(origSig.GetCallingConvInfo(&callConvDecl)); newSig.AppendByte((BYTE)callConvDecl); - uint32_t declGenericCount = 0; if (callConvDecl & IMAGE_CEE_CS_CALLCONV_GENERIC) { + uint32_t declGenericCount; IfFailThrow(origSig.GetData(&declGenericCount)); newSig.AppendData(declGenericCount); } @@ -162,7 +166,7 @@ namespace // Now we can copy over the return type and arguments. // The format for the return type is the same as the arguments, // except return parameters can be ELEMENT_TYPE_VOID. - uint32_t totalParamCount = declArgCount + 1; + const uint32_t totalParamCount = declArgCount + 1; for (uint32_t i = 0; i < totalParamCount; ++i) { // Copy over any modopts or modreqs. @@ -190,7 +194,7 @@ namespace // Validate the parameter resolves correctly. There is only one acceptable mapping: // ELEMENT_TYPE_OBJECT - if new type is a reference type. _ASSERTE(!newTypeMaybe.IsValueType()); - if (currHandle != TypeHandle(g_pObjectClass)) + if (currHandle != TypeHandle{ g_pObjectClass }) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); // Extract from the parameterized type from the new target. @@ -214,10 +218,10 @@ namespace Instantiation inst = newTypeMaybe.GetInstantiation(); - // Go back to the original translated type since during analysis - // the local may have been altered. + // Go back to the original translated type because + // analysis the local may have been altered. newTypeMaybe = cxt.TranslatedTypes[i]; - AppendTypeSignature(newSig, newTypeMaybe, inst); + AppendTypeToSignature(newSig, newTypeMaybe, inst); } // Create a copy of the new signature and store it on the context. @@ -231,7 +235,7 @@ namespace // Update the declaration signature with the new signature. cxt.DeclarationSig = SigPointer{ (PCCOR_SIGNATURE)newSigAlloc, newSigLen }; - SigTypeContext tmpContext( cxt.Declaration ); + SigTypeContext tmpContext{ cxt.Declaration }; cxt.DeclarationMetaSig = MetaSig{ (PCCOR_SIGNATURE)newSigAlloc, newSigLen, cxt.Declaration->GetModule(), &tmpContext }; } @@ -242,12 +246,11 @@ namespace // Acquire attribute name to search for. const char* typeAttrName = GetWellKnownAttributeName(WellKnownAttribute::UnsafeAccessorTypeAttribute); - uint32_t attrCount = 0; - // Determine the max parameter count. +1 for the return value, which is always index 0. const uint32_t totalParamCount = cxt.DeclarationMetaSig.NumFixedArgs() + 1; // Inspect all parameters to the declaration method for UnsafeAccessorTypeAttribute. + uint32_t attrCount = 0; IMDInternalImport *pInternalImport = cxt.Declaration->GetModule()->GetMDImport(); HENUMInternalHolder hEnumParams(pInternalImport); hEnumParams.EnumInit(mdtParamDef, cxt.Declaration->GetMemberDef()); @@ -278,8 +281,8 @@ namespace StackSString typeStringUtf8{ SString::Utf8, typeString, typeStringLen }; // Pass the string in the attribute to similar logic as Type.GetType(String). - // The below API creates a dependency between the returned type and the requesting - // assembly for the purposes of lifetime tracking of collectible types. + // The below API will also handle any dependency between the returned type and the + // requesting assembly for the purposes of lifetime tracking of collectible types. TypeHandle targetType = TypeName::GetTypeReferencedByCustomAttribute( typeStringUtf8.GetUnicode(), cxt.Declaration->GetAssembly(), @@ -452,14 +455,10 @@ namespace void VerifyDeclarationSatisfiesTargetConstraints(MethodDesc* declaration, MethodTable* targetType, MethodDesc* targetMethod) { - CONTRACTL - { - STANDARD_VM_CHECK; - PRECONDITION(declaration != NULL); - PRECONDITION(targetType != NULL); - PRECONDITION(targetMethod != NULL); - } - CONTRACTL_END; + STANDARD_VM_CONTRACT; + _ASSERTE(declaration != NULL); + _ASSERTE(targetType != NULL); + _ASSERTE(targetMethod != NULL); // If the target method has no generic parameters there is nothing to verify if (!targetMethod->HasClassOrMethodInstantiation()) @@ -741,7 +740,7 @@ namespace else { SigBuilder sigBuilder; - AppendTypeSignature(sigBuilder, th, th.GetInstantiation()); + AppendTypeToSignature(sigBuilder, th, th.GetInstantiation()); uint32_t sigLen; PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen); diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index ec4f2b09359ede..272a572d165eb4 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -36,5 +36,7 @@ class GenericClass List M3() => new List(); List M4() => new List(); + + bool M5(List a, List b, List c, List d) where S : T => true; } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 5a260582c0406c..3ef851f023fc4e 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -44,34 +44,36 @@ private TargetClass(ref C2 c2) private C2 M_RC1(ref C1 a) => _f1; private C2 M_RROC1(ref readonly C1 a) => _f1; private ref C2 M_C1_RC2(C1 a) => ref _f1; + + private class InnerClass + { + private InnerClass() { } + private InnerClass(string _) { } + } } public static unsafe class UnsafeAccessorsTestsTypes { [Fact] - public static void Verify_Type_CallDefaultCtorClass() + public static void Verify_Type_CallInnerCtorClass() { - Console.WriteLine($"Running {nameof(Verify_Type_CallDefaultCtorClass)}"); + Console.WriteLine($"Running {nameof(Verify_Type_CallInnerCtorClass)}"); - var local = CallPrivateConstructorClassByName(); - Assert.Equal("UserDataClass", local.GetType().Name); + object obj; - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("UnsafeAccessorsTests+UserDataClass")] - extern static object CallPrivateConstructorClassByName(); - } + obj = CreateInner(); + Assert.Equal("InnerClass", obj.GetType().Name); - [Fact] - public static void Verify_Type_CallCtorClass() - { - Console.WriteLine($"Running {nameof(Verify_Type_CallCtorClass)}"); + obj = CreateInnerString(string.Empty); + Assert.Equal("InnerClass", obj.GetType().Name); - var local = CallPrivateConstructorClassByName(string.Empty); - Assert.Equal("UserDataClass", local.GetType().Name); + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("TargetClass+InnerClass")] + extern static object CreateInner(); [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("UnsafeAccessorsTests+UserDataClass")] - extern static object CallPrivateConstructorClassByName(string a); + [return: UnsafeAccessorType("TargetClass+InnerClass")] + extern static object CreateInnerString(string a); } // Skip validating error cases on Mono runtime @@ -136,14 +138,14 @@ public static void Verify_Type_CallInstanceMethods() Console.WriteLine($"Running {nameof(Verify_Type_CallInstanceMethods)}"); C2 c2 = new(); - TargetClass tgt = CreateTargetClass(c2); + object arg = c2; + TargetClass tgt = CreateTargetClass(arg); - C1 c1 = new(); - object oc1 = c1; - Assert.Equal(c2, CallM_C1(tgt, c1)); - Assert.Equal(c2, CallM_RC1(tgt, ref oc1)); - Assert.Equal(c2, CallM_RROC1(tgt, ref oc1)); - Assert.Equal(c2, CallM_C1_RC2(tgt, c1)); + arg = new C1(); + Assert.Equal(c2, CallM_C1(tgt, arg)); + Assert.Equal(c2, CallM_RC1(tgt, ref arg)); + Assert.Equal(c2, CallM_RROC1(tgt, ref arg)); + Assert.Equal(c2, CallM_C1_RC2(tgt, arg)); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] [return: UnsafeAccessorType("C2")] @@ -253,6 +255,28 @@ class Accessors [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M4")] [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] public extern static object CallGenericClassM4([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M5")] + public extern static bool CallGenericClassM5( + [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, + [UnsafeAccessorType("System.Collections.Generic.List`1[[!0]]")] + object a, + [UnsafeAccessorType("System.Collections.Generic.List`1[[!!0]]")] + object b, + List c, + [UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] + object d) where W : T; + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M5")] + public extern static bool CallGenericClassM5_NoConstraint( + [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, + [UnsafeAccessorType("System.Collections.Generic.List`1[[!0]]")] + object a, + [UnsafeAccessorType("System.Collections.Generic.List`1[[!!0]]")] + object b, + List c, + [UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] + object d); } private static bool TypeNameEquals(TypeName typeName1, TypeName typeName2) @@ -344,6 +368,8 @@ public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() object genericListString = Accessors.CallGenericClassM2(genericClass); TypeName genericListStringName = TypeName.Parse(genericListString.GetType().FullName); Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); + + Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); } { @@ -358,6 +384,27 @@ public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() object genericListString = Accessors.CallGenericClassM2(genericClass); TypeName genericListStringName = TypeName.Parse(genericListString.GetType().FullName); Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); + + Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); + } + } + + // Skip private types and Generic support on Mono runtime + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] + public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParamsWithConstraints() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallPrivateLibTypeAndMethodGenericParamsWithConstraints)}"); + + { + object genericClass = Accessors.CreateGenericClass(); + Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); + Assert.Throws(() => Accessors.CallGenericClassM5_NoConstraint(genericClass, null, null, null, null)); + } + + { + object genericClass = Accessors.CreateGenericClass(); + Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); + Assert.Throws(() => Accessors.CallGenericClassM5_NoConstraint(genericClass, null, null, null, null)); } } } From 1008e985f009cbd0a6bdc0bc5626d655cf474493 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 5 May 2025 10:36:17 -0700 Subject: [PATCH 33/62] Adjust ref handling in NAOT and add type check for values (skipping refs for now while we decide what we're going to do) --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index b6e66edc8a2e9e..d32ed53f265db5 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -591,8 +591,7 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.Missing; } - if (replacementType.IsByRef - || replacementType.IsFunctionPointer + if (replacementType.IsFunctionPointer || replacementType.IsPointer) { return SetTargetResult.NotSupported; @@ -605,14 +604,23 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.NotSupported; } - if (initialTypeIsByRef) + if (replacementType.IsByRef != initialTypeIsByRef) { - // We need to reapply the byref to the type. - replacementType = method.Context.GetByRefType(replacementType); + // The replacement type must match the original type + // in terms of byref-ness. + return SetTargetResult.Invalid; } if (isReturnValue) { + if (context.Kind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField) + { + // UnsafeAccessorAttribute requires the return type to be byref for any field kind. + // This is a deviation from the normal UnsafeAccessorTypeAttribute requirements + // where the type is expected to match the signature exactly. Users aren't required to + // state the byref for field access because that isn't in the target field signature. + replacementType = replacementType.MakeByRefType(); + } updatedSignature.ReturnType = replacementType; } else @@ -639,6 +647,10 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) for (int i = beginIndex; i < stubArgCount; ++i) { codeStream.EmitLdArg(i); + if (context.DeclarationSignature[i] is { Category: TypeFlags.Class } classType) + { + codeStream.Emit(ILOpcode.castclass, emit.NewToken(classType)); + } } // Provide access to the target member From 3f8f22b202285aa4ac242abf3aa44038af0e4253 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 5 May 2025 11:41:30 -0700 Subject: [PATCH 34/62] Implement byref type validation with a local for NAOT --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 63 +++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index d32ed53f265db5..ac36e7c6470651 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -604,6 +604,18 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.NotSupported; } + bool isUnsafeAccessorFieldReturn = isReturnValue + && context.Kind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField; + + if (isUnsafeAccessorFieldReturn) + { + // UnsafeAccessorAttribute requires the return type to be byref for any field kind. + // This is a deviation from the normal UnsafeAccessorTypeAttribute requirements + // where the type is expected to match the signature exactly. Users aren't required to + // state the byref for field access because that isn't in the target field signature. + replacementType = replacementType.MakeByRefType(); + } + if (replacementType.IsByRef != initialTypeIsByRef) { // The replacement type must match the original type @@ -613,14 +625,6 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext if (isReturnValue) { - if (context.Kind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField) - { - // UnsafeAccessorAttribute requires the return type to be byref for any field kind. - // This is a deviation from the normal UnsafeAccessorTypeAttribute requirements - // where the type is expected to match the signature exactly. Users aren't required to - // state the byref for field access because that isn't in the target field signature. - replacementType = replacementType.MakeByRefType(); - } updatedSignature.ReturnType = replacementType; } else @@ -644,12 +648,36 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) // during dispatch. int beginIndex = context.IsTargetStatic ? 1 : 0; int stubArgCount = context.DeclarationSignature.Length; + Stubs.ILLocalVariable?[] localsToRestore = null; for (int i = beginIndex; i < stubArgCount; ++i) { - codeStream.EmitLdArg(i); - if (context.DeclarationSignature[i] is { Category: TypeFlags.Class } classType) + if (context.Declaration.Signature[i] != context.DeclarationSignature[i]) + { + if (context.DeclarationSignature[i] is { Category: TypeFlags.Class } classType) + { + codeStream.EmitLdArg(i); + codeStream.Emit(ILOpcode.castclass, emit.NewToken(classType)); + } + else if (context.DeclarationSignature[i] is ByRefType { ParameterType.Category: TypeFlags.Class } byrefType) + { + localsToRestore ??= new Stubs.ILLocalVariable?[stubArgCount]; + + TypeDesc targetType = byrefType.ParameterType; + codeStream.EmitLdArg(i); + localsToRestore[i] = emit.NewLocal(targetType); + codeStream.EmitLdInd(targetType); + codeStream.Emit(ILOpcode.castclass, emit.NewToken(targetType)); + codeStream.EmitStLoc(localsToRestore[i].Value); + codeStream.EmitLdLoca(localsToRestore[i].Value); + } + else + { + codeStream.EmitLdArg(i); + } + } + else { - codeStream.Emit(ILOpcode.castclass, emit.NewToken(classType)); + codeStream.EmitLdArg(i); } } @@ -681,6 +709,19 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) break; } + if (localsToRestore is not null) + { + for (int i = beginIndex; i < stubArgCount; ++i) + { + if (localsToRestore[i] != null) + { + codeStream.EmitLdArg(i); + codeStream.EmitLdLoc(localsToRestore[i].Value); + codeStream.EmitStInd(((ParameterizedType)context.Declaration.Signature[i]).ParameterType); + } + } + } + // Return from the generated stub codeStream.Emit(ILOpcode.ret); return emit.Link(context.Declaration); From 02ae9f3f74a8a091141addbdc44ac44862770b66 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 May 2025 13:11:32 -0700 Subject: [PATCH 35/62] Address type safety issue by passing a ref to a fully typed local in the stub. --- src/coreclr/vm/stubgen.cpp | 2 + src/coreclr/vm/stubgen.h | 2 + src/coreclr/vm/typehandle.h | 29 ++-- src/coreclr/vm/unsafeaccessors.cpp | 141 +++++++++++------- .../UnsafeAccessorsTests.Types.cs | 66 +++++--- 5 files changed, 146 insertions(+), 94 deletions(-) diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 988211d74c2578..05273e0162ccdb 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -1049,6 +1049,8 @@ LPCSTR ILCodeStream::GetStreamDescription(ILStubLinker::CodeStreamType streamTyp "ExceptionCleanup", "Cleanup", "ExceptionHandler", + "TypeCheckAndCallMethod", + "UpdateByRefsAndReturn" }; #ifdef _DEBUG diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index d1aa1d72c9e63f..cee9177d40b920 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -746,6 +746,8 @@ class ILStubLinker kExceptionCleanup, kCleanup, kExceptionHandler, + kTypeCheckDispatch, + kUpdateByRefsReturn }; ILCodeStream* NewCodeStream(CodeStreamType codeStreamType); diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 811c903440e884..538ec43f51df0c 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -84,12 +84,6 @@ class ComCallWrapperTemplate; class TypeHandle { public: - TypeHandle() { - LIMITED_METHOD_DAC_CONTRACT; - - m_asTAddr = 0; - } - static TypeHandle FromPtr(PTR_VOID aPtr) { LIMITED_METHOD_DAC_CONTRACT; @@ -104,29 +98,34 @@ class TypeHandle return TypeHandle(data); } + TypeHandle() + : m_asTAddr{ 0 } + { + LIMITED_METHOD_DAC_CONTRACT; + } + // When you ask for a class in JitInterface when all you have // is a methodDesc of an array method... // Convert from a JitInterface handle to an internal EE TypeHandle explicit TypeHandle(struct CORINFO_CLASS_STRUCT_*aPtr) + : m_asTAddr{ dac_cast(aPtr) } { LIMITED_METHOD_DAC_CONTRACT; - - m_asTAddr = dac_cast(aPtr); INDEBUGIMPL(Verify()); } - TypeHandle(MethodTable const * aMT) { + TypeHandle(MethodTable const * aMT) + : m_asTAddr{ dac_cast(aMT) } + { LIMITED_METHOD_DAC_CONTRACT; - - m_asTAddr = dac_cast(aMT); INDEBUGIMPL(Verify()); } - explicit TypeHandle(TypeDesc *aType) { + explicit TypeHandle(TypeDesc *aType) + : m_asTAddr{ dac_cast(aType) | 2 } + { LIMITED_METHOD_DAC_CONTRACT; _ASSERTE(aType); - - m_asTAddr = (dac_cast(aType) | 2); INDEBUGIMPL(Verify()); } @@ -138,9 +137,9 @@ class TypeHandle // TypeHandle::FromPtr and TypeHandle::TAddr instead of these constructors. // Allowing a public constructor that takes a "void *" or a "TADDR" is error-prone. explicit TypeHandle(TADDR aTAddr) + : m_asTAddr{ aTAddr } { LIMITED_METHOD_DAC_CONTRACT; - m_asTAddr = aTAddr; INDEBUGIMPL(Verify()); } diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index bfc9436adb3e78..d78b75721ab566 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -61,6 +61,21 @@ namespace return true; } + struct ParamDetails final + { + ParamDetails() = default; + + ParamDetails(TypeHandle type, DWORD attrs) + : Type{ type } + , Attrs{ attrs } + { } + + ParamDetails(const ParamDetails&) = default; + + TypeHandle Type; + DWORD Attrs; + }; + struct GenerationContext final { GenerationContext(UnsafeAccessorKind kind, MethodDesc* pMD) @@ -68,7 +83,7 @@ namespace , Declaration{ pMD } , DeclarationSig{ pMD->GetSigPointer() } , DeclarationMetaSig{ pMD } - , TranslatedTypes{} + , TranslatedParams{} , TargetTypeSig{} , TargetType{} , IsTargetStatic{ false } @@ -81,8 +96,8 @@ namespace SigPointer DeclarationSig; // This is the official declaration signature. It may be modified // to include the UnsafeAccessorTypeAttribute types. MetaSig DeclarationMetaSig; - NewArrayHolder TranslatedTypes; // Redefined types from UnsafeAccessorTypeAttribute usage. - // Return type is at 0 index. Function arguments are 1 to N. + NewArrayHolder TranslatedParams; // Redefined types from UnsafeAccessorTypeAttribute usage. + // Return type is at 0 index. Function arguments are 1 to N. SigPointer TargetTypeSig; TypeHandle TargetType; bool IsTargetStatic; @@ -131,7 +146,7 @@ namespace { STANDARD_VM_CONTRACT; _ASSERTE(cxt.Declaration != NULL); - _ASSERTE(cxt.TranslatedTypes != NULL); + _ASSERTE(cxt.TranslatedParams != NULL); // // Parsing and building the signature follows details defined in ECMA-335 - II.23.2.1 @@ -172,7 +187,7 @@ namespace // Copy over any modopts or modreqs. origSig.CopyModOptsReqs(pSigModule, &newSig); - TypeHandle newTypeMaybe = cxt.TranslatedTypes[i]; + TypeHandle newTypeMaybe = cxt.TranslatedParams[i].Type; if (newTypeMaybe.IsNull()) { // Copy the original parameter and continue. @@ -220,7 +235,7 @@ namespace // Go back to the original translated type because // analysis the local may have been altered. - newTypeMaybe = cxt.TranslatedTypes[i]; + newTypeMaybe = cxt.TranslatedParams[i].Type; AppendTypeToSignature(newSig, newTypeMaybe, inst); } @@ -263,13 +278,9 @@ namespace if (hr != S_OK) continue; - // The first time we find an attribute, we need to allocate and - // initialize the translations array. + // The first time we find an attribute, we allocate the translations array. if (attrCount == 0) - { - cxt.TranslatedTypes = new TypeHandle[totalParamCount]; - memset(cxt.TranslatedTypes, 0, sizeof(cxt.TranslatedTypes[0]) * totalParamCount); - } + cxt.TranslatedParams = new ParamDetails[totalParamCount]; // Parse the attribute data. CustomAttributeParser cap(pData, cbData); @@ -303,7 +314,7 @@ namespace ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); // Store the TypeHandle for the loaded type at the sequence number for the parameter. - cxt.TranslatedTypes[seq] = targetType; + cxt.TranslatedParams[seq] = { targetType, attr }; attrCount++; } @@ -703,39 +714,40 @@ namespace return false; } - void EmitTypeCheck(ILCodeStream* pCode, TypeHandle th) + void EmitTypeCheck(UINT argId, ParamDetails& param, ILCodeStream* pDispatchCode, ILCodeStream* pReturnCode) { STANDARD_VM_CONTRACT; + _ASSERTE(pDispatchCode != NULL); + _ASSERTE(pReturnCode != NULL); - if (th.IsNull()) + if (param.Type.IsNull()) return; - TypeHandle origType = th; + TypeHandle th = param.Type; + DWORD localIndex = MAXDWORD; // We are going to emit a type check for the UnsafeAccessorTypeAttribute // scenario. We do this because the callable signature isn't going to enforce // type safety, so we do it here. // - // If the type is a byref, DUP the value so the execution stack has a - // byref value on it after the type check. Emit a load indirection of the byref - // in order to validate the type. After the typecheck, we'll pop off the object. - // This doesn't handle the null byref case. - // - // In the non-byref case, an object reference will be placed back on the stack - // through the type check so there is no need for the DUP. - if (origType.IsByRef()) + // Ensuring type safety is paramount in the UnsafeAccessorTypeAttribute scenario + // so we pass a byref to a local variable after the type check as opposed + // to simply forwarding the original byref argument. This does mean the byref itself + // isn't the same as the input byref, but the byref is now verifiable. + if (th.IsByRef()) { - pCode->EmitDUP(); + th = th.GetTypeParam(); + LocalDesc typedLocal{ th }; + localIndex = pDispatchCode->NewLocal(typedLocal); - th = origType.GetTypeParam(); - pCode->EmitLDIND_REF(); + pDispatchCode->EmitLDIND_REF(); } _ASSERTE(!th.IsTypeDesc()); int tk; if (!th.GetMethodTable()->ContainsGenericVariables()) { - tk = pCode->GetToken(th); + tk = pDispatchCode->GetToken(th); } else { @@ -744,16 +756,30 @@ namespace uint32_t sigLen; PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen); - mdToken typeSig = pCode->GetSigToken(sig, sigLen); - tk = pCode->GetToken(th, typeSig); + mdToken typeSig = pDispatchCode->GetSigToken(sig, sigLen); + tk = pDispatchCode->GetToken(th, typeSig); } // Perform the type check. - pCode->EmitCASTCLASS(tk); + pDispatchCode->EmitCASTCLASS(tk); + + // If we have a local variable, we need to store the cast result + // in the local variable and load a byref to the local variable as + // the argument to the target method. Finally, we may need to update + // the byref on return. + if (localIndex != MAXDWORD) + { + pDispatchCode->EmitSTLOC(localIndex); + pDispatchCode->EmitLDLOCA(localIndex); - // If the type was a byref, pop the object reference - if (origType.IsByRef()) - pCode->EmitPOP(); + // Update the byref on return, except if it is marked "in". + if (!(param.Attrs & pdIn)) + { + pReturnCode->EmitLDARG(argId); + pReturnCode->EmitLDLOC(localIndex); + pReturnCode->EmitSTIND_REF(); + } + } } void GenerateAccessor( @@ -780,7 +806,8 @@ namespace cxt.TargetMethod, (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); - ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + ILCodeStream* pDispatchCode = sl.NewCodeStream(ILStubLinker::kTypeCheckDispatch); + ILCodeStream* pReturnCode = sl.NewCodeStream(ILStubLinker::kUpdateByRefsReturn); // Load stub arguments. // When the target is static, the first argument is only @@ -790,11 +817,11 @@ namespace UINT stubArgCount = cxt.DeclarationMetaSig.NumFixedArgs(); for (UINT i = beginIndex; i < stubArgCount; ++i) { - pCode->EmitLDARG(i); + pDispatchCode->EmitLDARG(i); // Perform a typecheck if the type was translated. - if (cxt.TranslatedTypes != NULL) - EmitTypeCheck(pCode, cxt.TranslatedTypes[i + 1]); // Index is +1 to account for the return value. + if (cxt.TranslatedParams != NULL) + EmitTypeCheck(i, cxt.TranslatedParams[i + 1], pDispatchCode, pReturnCode); // Index is +1 to account for the return value. } // Provide access to the target member @@ -808,17 +835,17 @@ namespace mdToken target; if (!cxt.TargetType.HasInstantiation()) { - target = pCode->GetToken(cxt.TargetMethod); + target = pDispatchCode->GetToken(cxt.TargetMethod); } else { PCCOR_SIGNATURE sig; uint32_t sigLen; cxt.TargetTypeSig.GetSignature(&sig, &sigLen); - mdToken targetTypeSigToken = pCode->GetSigToken(sig, sigLen); - target = pCode->GetToken(cxt.TargetMethod, targetTypeSigToken); + mdToken targetTypeSigToken = pDispatchCode->GetSigToken(sig, sigLen); + target = pDispatchCode->GetToken(cxt.TargetMethod, targetTypeSigToken); } - pCode->EmitNEWOBJ(target, targetArgCount); + pDispatchCode->EmitNEWOBJ(target, targetArgCount); break; } case UnsafeAccessorKind::Method: @@ -828,7 +855,7 @@ namespace mdToken target; if (!cxt.TargetMethod->HasClassOrMethodInstantiation()) { - target = pCode->GetToken(cxt.TargetMethod); + target = pDispatchCode->GetToken(cxt.TargetMethod); } else { @@ -850,16 +877,16 @@ namespace } sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen); - methodSpecSigToken = pCode->GetSigToken(sig, sigLen); + methodSpecSigToken = pDispatchCode->GetSigToken(sig, sigLen); } cxt.TargetTypeSig.GetSignature(&sig, &sigLen); - mdToken targetTypeSigToken = pCode->GetSigToken(sig, sigLen); + mdToken targetTypeSigToken = pDispatchCode->GetSigToken(sig, sigLen); if (methodSpecSigToken == mdTokenNil) { // Create a MemberRef - target = pCode->GetToken(cxt.TargetMethod, targetTypeSigToken); + target = pDispatchCode->GetToken(cxt.TargetMethod, targetTypeSigToken); _ASSERTE(TypeFromToken(target) == mdtMemberRef); } else @@ -869,18 +896,18 @@ namespace MethodDesc* instantiatedTarget = MethodDesc::FindOrCreateAssociatedMethodDesc(cxt.TargetMethod, cxt.TargetType.GetMethodTable(), FALSE, methodInst, TRUE); // Create a MethodSpec - target = pCode->GetToken(instantiatedTarget, targetTypeSigToken, methodSpecSigToken); + target = pDispatchCode->GetToken(instantiatedTarget, targetTypeSigToken, methodSpecSigToken); _ASSERTE(TypeFromToken(target) == mdtMethodSpec); } } if (cxt.Kind == UnsafeAccessorKind::StaticMethod) { - pCode->EmitCALL(target, targetArgCount, targetRetCount); + pDispatchCode->EmitCALL(target, targetArgCount, targetRetCount); } else { - pCode->EmitCALLVIRT(target, targetArgCount, targetRetCount); + pDispatchCode->EmitCALLVIRT(target, targetArgCount, targetRetCount); } break; } @@ -890,15 +917,15 @@ namespace mdToken target; if (!cxt.TargetType.HasInstantiation()) { - target = pCode->GetToken(cxt.TargetField); + target = pDispatchCode->GetToken(cxt.TargetField); } else { // See the static field case for why this can be mdTokenNil. mdToken targetTypeSigToken = mdTokenNil; - target = pCode->GetToken(cxt.TargetField, targetTypeSigToken); + target = pDispatchCode->GetToken(cxt.TargetField, targetTypeSigToken); } - pCode->EmitLDFLDA(target); + pDispatchCode->EmitLDFLDA(target); break; } case UnsafeAccessorKind::StaticField: @@ -906,7 +933,7 @@ namespace mdToken target; if (!cxt.TargetType.HasInstantiation()) { - target = pCode->GetToken(cxt.TargetField); + target = pDispatchCode->GetToken(cxt.TargetField); } else { @@ -919,17 +946,17 @@ namespace PCCOR_SIGNATURE sig; uint32_t sigLen; cxt.TargetTypeSig.GetSignature(&sig, &sigLen); - mdToken targetTypeSigToken = pCode->GetSigToken(sig, sigLen); - target = pCode->GetToken(cxt.TargetField, targetTypeSigToken); + mdToken targetTypeSigToken = pDispatchCode->GetSigToken(sig, sigLen); + target = pDispatchCode->GetToken(cxt.TargetField, targetTypeSigToken); } - pCode->EmitLDSFLDA(target); + pDispatchCode->EmitLDSFLDA(target); break; default: _ASSERTE(!"Unknown UnsafeAccessorKind"); } // Return from the generated stub - pCode->EmitRET(); + pReturnCode->EmitRET(); // Generate all IL associated data for JIT { diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 3ef851f023fc4e..9caf7412becfeb 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -45,6 +45,13 @@ private TargetClass(ref C2 c2) private C2 M_RROC1(ref readonly C1 a) => _f1; private ref C2 M_C1_RC2(C1 a) => ref _f1; + private void M_ByRefs(C1 c, in C1 ic, ref C1 rc, out C1 oc) + { + Assert.Null(ic); // See caller + rc = c; + oc = c; + } + private class InnerClass { private InnerClass() { } @@ -54,28 +61,6 @@ private InnerClass(string _) { } public static unsafe class UnsafeAccessorsTestsTypes { - [Fact] - public static void Verify_Type_CallInnerCtorClass() - { - Console.WriteLine($"Running {nameof(Verify_Type_CallInnerCtorClass)}"); - - object obj; - - obj = CreateInner(); - Assert.Equal("InnerClass", obj.GetType().Name); - - obj = CreateInnerString(string.Empty); - Assert.Equal("InnerClass", obj.GetType().Name); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("TargetClass+InnerClass")] - extern static object CreateInner(); - - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("TargetClass+InnerClass")] - extern static object CreateInnerString(string a); - } - // Skip validating error cases on Mono runtime [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] public static void Verify_Type_InvalidArgument() @@ -147,6 +132,14 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(c2, CallM_RROC1(tgt, ref arg)); Assert.Equal(c2, CallM_C1_RC2(tgt, arg)); + object ic = null; + object rc = null; + object oc = null; + CallM_ByRefs(tgt, arg, in ic, ref rc, out oc); + Assert.Null(ic); + Assert.Equal(arg, rc); + Assert.Equal(arg, oc); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] [return: UnsafeAccessorType("C2")] extern static object CallM_C1(TargetClass tgt, [UnsafeAccessorType("C1")] object a); @@ -162,6 +155,13 @@ public static void Verify_Type_CallInstanceMethods() [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1_RC2")] [return: UnsafeAccessorType("C2&")] extern static ref object CallM_C1_RC2(TargetClass tgt, [UnsafeAccessorType("C1")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_ByRefs")] + extern static void CallM_ByRefs(TargetClass tgt, + [UnsafeAccessorType("C1")] object c, + [UnsafeAccessorType("C1&")] in object ic, + [UnsafeAccessorType("C1&")] ref object rc, + [UnsafeAccessorType("C1&")] out object oc); } [Fact] @@ -184,6 +184,28 @@ public static void Verify_Type_GetInstanceFields() extern static ref readonly object CallField2(TargetClass tgt); } + [Fact] + public static void Verify_Type_CallInnerCtorClass() + { + Console.WriteLine($"Running {nameof(Verify_Type_CallInnerCtorClass)}"); + + object obj; + + obj = CreateInner(); + Assert.Equal("InnerClass", obj.GetType().Name); + + obj = CreateInnerString(string.Empty); + Assert.Equal("InnerClass", obj.GetType().Name); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("TargetClass+InnerClass")] + extern static object CreateInner(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("TargetClass+InnerClass")] + extern static object CreateInnerString(string a); + } + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetClass")] [return: UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] extern static object CallGetClass([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); From 9b1193e0a663eb5de48150ff92b155f6d8beaa54 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 May 2025 13:21:57 -0700 Subject: [PATCH 36/62] Use typed enum. --- src/coreclr/vm/unsafeaccessors.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index d78b75721ab566..7b687032fc6fa0 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -65,7 +65,7 @@ namespace { ParamDetails() = default; - ParamDetails(TypeHandle type, DWORD attrs) + ParamDetails(TypeHandle type, CorParamAttr attrs) : Type{ type } , Attrs{ attrs } { } @@ -73,7 +73,7 @@ namespace ParamDetails(const ParamDetails&) = default; TypeHandle Type; - DWORD Attrs; + CorParamAttr Attrs; }; struct GenerationContext final @@ -314,7 +314,7 @@ namespace ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); // Store the TypeHandle for the loaded type at the sequence number for the parameter. - cxt.TranslatedParams[seq] = { targetType, attr }; + cxt.TranslatedParams[seq] = { targetType, (CorParamAttr)attr }; attrCount++; } @@ -773,7 +773,7 @@ namespace pDispatchCode->EmitLDLOCA(localIndex); // Update the byref on return, except if it is marked "in". - if (!(param.Attrs & pdIn)) + if (!IsPdIn(param.Attrs)) { pReturnCode->EmitLDARG(argId); pReturnCode->EmitLDLOC(localIndex); From 0da4049f964ea791993bf54df3a29b16a0403f03 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 5 May 2025 13:30:28 -0700 Subject: [PATCH 37/62] Const and nits --- src/coreclr/vm/unsafeaccessors.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 7b687032fc6fa0..05775644e7494b 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -234,7 +234,7 @@ namespace Instantiation inst = newTypeMaybe.GetInstantiation(); // Go back to the original translated type because - // analysis the local may have been altered. + // analysis may have altered the local. newTypeMaybe = cxt.TranslatedParams[i].Type; AppendTypeToSignature(newSig, newTypeMaybe, inst); } @@ -714,7 +714,7 @@ namespace return false; } - void EmitTypeCheck(UINT argId, ParamDetails& param, ILCodeStream* pDispatchCode, ILCodeStream* pReturnCode) + void EmitTypeCheck(UINT argId, const ParamDetails& param, ILCodeStream* pDispatchCode, ILCodeStream* pReturnCode) { STANDARD_VM_CONTRACT; _ASSERTE(pDispatchCode != NULL); @@ -731,9 +731,10 @@ namespace // type safety, so we do it here. // // Ensuring type safety is paramount in the UnsafeAccessorTypeAttribute scenario - // so we pass a byref to a local variable after the type check as opposed - // to simply forwarding the original byref argument. This does mean the byref itself - // isn't the same as the input byref, but the byref is now verifiable. + // so when a byref is involved, we pass a byref to a local variable after the type + // check as opposed to simply forwarding the original byref argument. This does + // mean the byref itself isn't the same as the input byref, but the byref is now + // verifiable. if (th.IsByRef()) { th = th.GetTypeParam(); From 2e5676638efab942d0d04fd180b6542878896f21 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 6 May 2025 10:09:00 -0700 Subject: [PATCH 38/62] Array support. Error checking for generic string signature. --- .../Reflection/TypeNameResolver.CoreCLR.cs | 11 +++-- src/coreclr/vm/siginfo.cpp | 1 + .../UnsafeAccessorsTests.Types.cs | 47 ++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs index ff9072d2b7005a..31df393fbc2104 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Reflection.Metadata; using System.Runtime.CompilerServices; @@ -259,9 +260,13 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, // At this point we expect either another '!' and then a number or a number. bool isMethodParam = escapedTypeName[1] == '!'; - uint paramIndex = isMethodParam - ? uint.Parse(escapedTypeName.AsSpan(2)) // Skip over "!!" - : uint.Parse(escapedTypeName.AsSpan(1)); // Skip over "!" + ReadOnlySpan toParse = isMethodParam + ? escapedTypeName.AsSpan(2) // Skip over "!!" + : escapedTypeName.AsSpan(1); // Skip over "!" + if (!uint.TryParse(toParse, NumberStyles.None, null, out uint paramIndex)) + { + throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName); + } Debug.Assert(_unsafeAccessorMethod != IntPtr.Zero); IntPtr typeHandle = ResolveGenericParamToTypeHandle(_unsafeAccessorMethod, isMethodParam, paramIndex); diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 36ac12554e3d14..cb5a6a433c25f7 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3965,6 +3965,7 @@ MetaSig::CompareElementType( } case ELEMENT_TYPE_GENERICINST: case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_SZARRAY: { // Due to how SigPointer works, we need to fiddle with the signature pointer // to get the current element type back in the stream. diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index 9caf7412becfeb..ada7ba94649182 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -52,6 +52,10 @@ private void M_ByRefs(C1 c, in C1 ic, ref C1 rc, out C1 oc) oc = c; } + private Type M_C1Array(C1[] c) => typeof(C1[]); + private Type M_C1Array(C1[][] c) => typeof(C1[][]); + private Type M_C1Array(C1[][][] c) => typeof(C1[][][]); + private class InnerClass { private InnerClass() { } @@ -140,6 +144,10 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(arg, rc); Assert.Equal(arg, oc); + Assert.Equal(typeof(C1[]), CallM_C1Array(tgt, Array.Empty())); + Assert.Equal(typeof(C1[][]), CallM_C1MDArray2(tgt, new C1[0][])); + Assert.Equal(typeof(C1[][][]), CallM_C1MDArray3(tgt, new C1[0][][])); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] [return: UnsafeAccessorType("C2")] extern static object CallM_C1(TargetClass tgt, [UnsafeAccessorType("C1")] object a); @@ -162,6 +170,15 @@ extern static void CallM_ByRefs(TargetClass tgt, [UnsafeAccessorType("C1&")] in object ic, [UnsafeAccessorType("C1&")] ref object rc, [UnsafeAccessorType("C1&")] out object oc); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] + extern static Type CallM_C1Array(TargetClass tgt, [UnsafeAccessorType("C1[]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] + extern static Type CallM_C1MDArray2(TargetClass tgt, [UnsafeAccessorType("C1[][]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] + extern static Type CallM_C1MDArray3(TargetClass tgt, [UnsafeAccessorType("C1[][][]")] object a); } [Fact] @@ -254,8 +271,24 @@ public static void Verify_Type_GetPrivateLibFields() extern static ref int GetInstanceField([UnsafeAccessorType("PrivateLib.Class1, PrivateLib")] object a); } - class Accessors + partial class Accessors { + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[!-0]], PrivateLib")] + public extern static object CreateGenericClass_InvalidGenericIndex1(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[!+0]], PrivateLib")] + public extern static object CreateGenericClass_InvalidGenericIndex2(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[!-1]], PrivateLib")] + public extern static object CreateGenericClass_InvalidGenericIndex3(); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[!1]], PrivateLib")] + public extern static object CreateGenericClass_InvalidGenericIndex4(); + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] [return: UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] public extern static object CreateGenericClass(); @@ -301,6 +334,18 @@ public extern static bool CallGenericClassM5_NoConstraint( object d); } + // Skip validating error cases on Mono runtime + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime))] + public static void Verify_Type_InvalidGenericTypeString() + { + Console.WriteLine($"Running {nameof(Verify_Type_InvalidGenericTypeString)}"); + + Assert.Throws(() => Accessors.CreateGenericClass_InvalidGenericIndex1()); + Assert.Throws(() => Accessors.CreateGenericClass_InvalidGenericIndex2()); + Assert.Throws(() => Accessors.CreateGenericClass_InvalidGenericIndex3()); + Assert.Throws(() => Accessors.CreateGenericClass_InvalidGenericIndex4()); + } + private static bool TypeNameEquals(TypeName typeName1, TypeName typeName2) { if (typeName1.Name != typeName2.Name) From b46266c01b0d0da02e39eeb9b0dd75ea5a285046 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 6 May 2025 15:15:07 -0700 Subject: [PATCH 39/62] Remove now-incorrect generics handling and fix handling of invalid generic parameter reference syntax --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index ac36e7c6470651..3ebddc77793042 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -353,7 +353,7 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte return false; } - if (declSig.ReturnType.GetTypeDefinition() != maybeSig.ReturnType.GetTypeDefinition()) + if (declSig.ReturnType != maybeSig.ReturnType) { return false; } @@ -368,7 +368,7 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte TypeDesc maybeType = maybeSig[i]; // Compare the types - if (declType.GetTypeDefinition() != maybeType.GetTypeDefinition()) + if (declType != maybeType) { return false; } @@ -578,11 +578,36 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext bool isMethodParameter = name.StartsWith("!!", StringComparison.Ordinal); - if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), out int index)) + if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), NumberStyles.None, CultureInfo.InvariantCulture, out int index)) { return null; } + if (isMethodParameter) + { + if (!method.HasInstantiation) + { + return null; + } + + if (index >= method.Instantiation.Length) + { + return null; + } + } + else + { + if (!method.OwningType.HasInstantiation) + { + return null; + } + + if (index >= method.OwningType.Instantiation.Length) + { + return null; + } + } + return module.Context.GetSignatureVariable(index, isMethodParameter); }); From 0d855f8e12cd7f59c316836ae5f5651dbc166641 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 6 May 2025 15:53:04 -0700 Subject: [PATCH 40/62] Adjust handling for "ref object" parameters --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 3ebddc77793042..d90769e333a322 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -543,19 +543,14 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext TypeDesc initialType = isReturnValue ? originalSignature.ReturnType : originalSignature[parameter.SequenceNumber - 1]; - bool initialTypeIsByRef = initialType.IsByRef; - - if (initialTypeIsByRef) - { - // We need to strip the byref off the type to get the - // underlying type for the parameter. - initialType = ((ParameterizedType)initialType).ParameterType; - } - + // Only allow 'object' and 'ref object' as initial types. if (initialType != method.Context.GetWellKnownType(WellKnownType.Object)) { - // Illegal argument type for UnsafeAccessorTypeAttribute target - return SetTargetResult.Invalid; + if (initialType.IsByRef + && ((ByRefType)initialType).ParameterType != method.Context.GetWellKnownType(WellKnownType.Object)) + { + return SetTargetResult.Invalid; + } } CustomAttributeValue decoded = unsafeAccessorTypeAttribute.Value.DecodeValue( @@ -616,15 +611,15 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.Missing; } - if (replacementType.IsFunctionPointer - || replacementType.IsPointer) + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + if (replacementType.IsValueType) { return SetTargetResult.NotSupported; } - // Future versions of the runtime may support - // UnsafeAccessorTypeAttribute on value types. - if (replacementType.IsValueType) + if (replacementType.IsFunctionPointer + || replacementType.IsPointer) { return SetTargetResult.NotSupported; } @@ -641,7 +636,7 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext replacementType = replacementType.MakeByRefType(); } - if (replacementType.IsByRef != initialTypeIsByRef) + if (replacementType.IsByRef != initialType.IsByRef) { // The replacement type must match the original type // in terms of byref-ness. From b7ce59b2b81f37db577e0a61da26a525e9cad5b3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 6 May 2025 15:53:32 -0700 Subject: [PATCH 41/62] Add missing case --- src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index d90769e333a322..632f939b100f55 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -551,6 +551,10 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext { return SetTargetResult.Invalid; } + else if (!initialType.IsByRef) + { + return SetTargetResult.Invalid; + } } CustomAttributeValue decoded = unsafeAccessorTypeAttribute.Value.DecodeValue( From 2a04159ec53640bfb101e02c052ab31ded4eeb99 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 6 May 2025 17:00:06 -0700 Subject: [PATCH 42/62] Enforce signature validation explicitly for certain types. Remove support for inaccessible field types due to byref to object requirement. This was a type safety issue. Add support for array's of inaccessible value types. --- src/coreclr/vm/siginfo.cpp | 2 + src/coreclr/vm/unsafeaccessors.cpp | 64 ++++++++++------- .../UnsafeAccessorsTests.Types.cs | 70 ++++++++++++++++--- 3 files changed, 104 insertions(+), 32 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index cb5a6a433c25f7..ffa162fb2c8ac4 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3965,7 +3965,9 @@ MetaSig::CompareElementType( } case ELEMENT_TYPE_GENERICINST: case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_PTR: { // Due to how SigPointer works, we need to fiddle with the signature pointer // to get the current element type back in the stream. diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 05775644e7494b..3cb82c95d836a0 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -202,37 +202,37 @@ namespace // consume the signature to move past the current type. IfFailThrow(origSig.SkipExactlyOne()); - // Extract from the parameterized type from the current type. - while (currHandle.HasTypeParam()) - currHandle = currHandle.GetTypeParam(); - - // Validate the parameter resolves correctly. There is only one acceptable mapping: - // ELEMENT_TYPE_OBJECT - if new type is a reference type. - _ASSERTE(!newTypeMaybe.IsValueType()); - if (currHandle != TypeHandle{ g_pObjectClass }) + bool isValid; + if (newTypeMaybe.IsArray()) + { + isValid = currHandle == TypeHandle{ g_pObjectClass }; + } + else if (newTypeMaybe.IsPointer()) + { + isValid = currHandle.IsPointer() + && currHandle.GetTypeParam() == TypeHandle{ CoreLibBinder::GetClass(CLASS__VOID) }; + } + else if (newTypeMaybe.IsByRef()) + { + isValid = currHandle.IsByRef() + && currHandle.GetTypeParam() == TypeHandle{ g_pObjectClass }; + } + else + { + _ASSERTE(!newTypeMaybe.IsValueType()); + isValid = currHandle == TypeHandle{ g_pObjectClass }; + } + + if (!isValid) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // Extract from the parameterized type from the new target. + // Extract from the parameterized type the new target. while (newTypeMaybe.HasTypeParam()) newTypeMaybe = newTypeMaybe.GetTypeParam(); - // - // Append the new type to the signature. - // - - // UnsafeAccessorAttribute requires the return type to be byref for any field kind. - // This is a deviation from the normal UnsafeAccessorTypeAttribute requirements - // where the type is expected to match the signature exactly. Users aren't required to - // state the byref for field access because that isn't in the target field signature. - if (i == 0 // Return type - && (cxt.Kind == UnsafeAccessorKind::Field - || cxt.Kind == UnsafeAccessorKind::StaticField)) - { - newSig.AppendElementType(ELEMENT_TYPE_BYREF); - } - Instantiation inst = newTypeMaybe.GetInstantiation(); + // Append the new type to the signature. // Go back to the original translated type because // analysis may have altered the local. newTypeMaybe = cxt.TranslatedParams[i].Type; @@ -313,6 +313,17 @@ namespace if (seq >= totalParamCount) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); + // UnsafeAccessorAttribute requires the return type to be byref for any field kind. + // This means creates a type safety issue for the runtime. Since byrefs don't support + // contravariance, we can't permit returning a byref to a fully typed field as a byref to + // an "opaque" type (for example, "ref string" -> "ref object"). + if (seq == 0 // Return type + && (cxt.Kind == UnsafeAccessorKind::Field + || cxt.Kind == UnsafeAccessorKind::StaticField)) + { + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); + } + // Store the TypeHandle for the loaded type at the sequence number for the parameter. cxt.TranslatedParams[seq] = { targetType, (CorParamAttr)attr }; attrCount++; @@ -743,6 +754,11 @@ namespace pDispatchCode->EmitLDIND_REF(); } + else if (th.IsPointer()) + { + // Pointer types are not verifiable, so we skip the type check. + return; + } _ASSERTE(!th.IsTypeDesc()); int tk; diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index ada7ba94649182..cec5ec792c8271 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -53,9 +53,21 @@ private void M_ByRefs(C1 c, in C1 ic, ref C1 rc, out C1 oc) } private Type M_C1Array(C1[] c) => typeof(C1[]); + private Type M_C1Array(C1[,] c) => typeof(C1[,]); + private Type M_C1Array(C1[,,] c) => typeof(C1[,,]); private Type M_C1Array(C1[][] c) => typeof(C1[][]); private Type M_C1Array(C1[][][] c) => typeof(C1[][][]); + private Type M_S1Array(S1[] c) => typeof(S1[]); + private Type M_S1Array(S1[,] c) => typeof(S1[,]); + private Type M_S1Array(S1[,,] c) => typeof(S1[,,]); + private Type M_S1Array(S1[][] c) => typeof(S1[][]); + private Type M_S1Array(S1[][][] c) => typeof(S1[][][]); + +#pragma warning disable CS8500 + private unsafe void M_C1Pointer(C1* c) { } +#pragma warning restore CS8500 + private class InnerClass { private InnerClass() { } @@ -145,8 +157,18 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(arg, oc); Assert.Equal(typeof(C1[]), CallM_C1Array(tgt, Array.Empty())); - Assert.Equal(typeof(C1[][]), CallM_C1MDArray2(tgt, new C1[0][])); - Assert.Equal(typeof(C1[][][]), CallM_C1MDArray3(tgt, new C1[0][][])); + Assert.Equal(typeof(C1[,]), CallM_C1MDArray2(tgt, new C1[1,1])); + Assert.Equal(typeof(C1[,,]), CallM_C1MDArray3(tgt, new C1[1,1,1])); + Assert.Equal(typeof(C1[][]), CallM_C1JaggedArray2(tgt, new C1[0][])); + Assert.Equal(typeof(C1[][][]), CallM_C1JaggedArray3(tgt, new C1[0][][])); + + Assert.Equal(typeof(S1[]), CallM_S1Array(tgt, Array.Empty())); + Assert.Equal(typeof(S1[,]), CallM_S1MDArray2(tgt, new S1[1,1])); + Assert.Equal(typeof(S1[,,]), CallM_S1MDArray3(tgt, new S1[1,1,1])); + Assert.Equal(typeof(S1[][]), CallM_S1JaggedArray2(tgt, new S1[0][])); + Assert.Equal(typeof(S1[][][]), CallM_S1JaggedArray3(tgt, new S1[0][][])); + + CallM_C1Pointer(tgt, null); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1")] [return: UnsafeAccessorType("C2")] @@ -175,22 +197,46 @@ extern static void CallM_ByRefs(TargetClass tgt, extern static Type CallM_C1Array(TargetClass tgt, [UnsafeAccessorType("C1[]")] object a); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] - extern static Type CallM_C1MDArray2(TargetClass tgt, [UnsafeAccessorType("C1[][]")] object a); + extern static Type CallM_C1MDArray2(TargetClass tgt, [UnsafeAccessorType("C1[,]")] object a); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] - extern static Type CallM_C1MDArray3(TargetClass tgt, [UnsafeAccessorType("C1[][][]")] object a); + extern static Type CallM_C1MDArray3(TargetClass tgt, [UnsafeAccessorType("C1[,,]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] + extern static Type CallM_C1JaggedArray2(TargetClass tgt, [UnsafeAccessorType("C1[][]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] + extern static Type CallM_C1JaggedArray3(TargetClass tgt, [UnsafeAccessorType("C1[][][]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] + extern static Type CallM_S1Array(TargetClass tgt, [UnsafeAccessorType("S1[]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] + extern static Type CallM_S1MDArray2(TargetClass tgt, [UnsafeAccessorType("S1[,]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] + extern static Type CallM_S1MDArray3(TargetClass tgt, [UnsafeAccessorType("S1[,,]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] + extern static Type CallM_S1JaggedArray2(TargetClass tgt, [UnsafeAccessorType("S1[][]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] + extern static Type CallM_S1JaggedArray3(TargetClass tgt, [UnsafeAccessorType("S1[][][]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Pointer")] + extern static void CallM_C1Pointer(TargetClass tgt, [UnsafeAccessorType("C1*")] void* a); } [Fact] - public static void Verify_Type_GetInstanceFields() + public static void Verify_Type_GetInstanceFields_NotSupported() { - Console.WriteLine($"Running {nameof(Verify_Type_GetInstanceFields)}"); + Console.WriteLine($"Running {nameof(Verify_Type_GetInstanceFields_NotSupported)}"); C2 c2 = new(); TargetClass tgt = CreateTargetClass(c2); - Assert.Equal(c2, CallField1(tgt)); - Assert.Equal(c2, CallField2(tgt)); + Assert.Throws(()=> CallField1(tgt)); + Assert.Throws(()=> CallField2(tgt)); [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_f1")] [return: UnsafeAccessorType("C2")] @@ -311,6 +357,10 @@ partial class Accessors [return: UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] public extern static object CallGenericClassM4([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M4")] + [return: UnsafeAccessorType("System.Collections.Generic.List`1[[System.Object]]")] + public extern static object CallGenericClassM4_InvalidReturn([UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M5")] public extern static bool CallGenericClassM5( [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, @@ -397,6 +447,8 @@ public static void Verify_Type_CallPrivateLibTypeGenericParams() object genericListClass2 = Accessors.CallGenericClassM4(genericClass); TypeName genericListClass2Name = TypeName.Parse(genericListClass2.GetType().FullName); Assert.True(TypeNameEquals(genericListClass2Name, TypeName.Parse("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]"))); + + Assert.Throws(() => Accessors.CallGenericClassM4_InvalidReturn(genericClass)); } { @@ -414,6 +466,8 @@ public static void Verify_Type_CallPrivateLibTypeGenericParams() object genericListClass2 = Accessors.CallGenericClassM4(genericClass); TypeName genericListClass2Name = TypeName.Parse(genericListClass2.GetType().FullName); Assert.True(TypeNameEquals(genericListClass2Name, TypeName.Parse("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]"))); + + Assert.Throws(() => Accessors.CallGenericClassM4_InvalidReturn(genericClass)); } } From 149c159e741dc0d6535790edfb7b99c1ecd7a53e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 6 May 2025 17:14:50 -0700 Subject: [PATCH 43/62] Update to use unbox.any instead of castclass. The unbox.any has the same semantics as castclass when the input is a reference type. --- src/coreclr/vm/stubgen.cpp | 5 ----- src/coreclr/vm/stubgen.h | 1 - src/coreclr/vm/unsafeaccessors.cpp | 11 ++++++----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 05273e0162ccdb..7b0a97a5c92ab7 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -1234,11 +1234,6 @@ void ILCodeStream::EmitCALLI(int token, int numInArgs, int numRetArgs) WRAPPER_NO_CONTRACT; Emit(CEE_CALLI, (INT16)(numRetArgs - numInArgs - 1), token); } -void ILCodeStream::EmitCASTCLASS(int token) -{ - WRAPPER_NO_CONTRACT; - Emit(CEE_CASTCLASS, 0, token); -} void ILCodeStream::EmitCEQ() { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index cee9177d40b920..2c4e843f6b9787 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -919,7 +919,6 @@ class ILCodeStream void EmitCALL (int token, int numInArgs, int numRetArgs); void EmitCALLI (int token, int numInArgs, int numRetArgs); void EmitCALLVIRT (int token, int numInArgs, int numRetArgs); - void EmitCASTCLASS (int token); void EmitCEQ (); void EmitCGT (); void EmitCGT_UN (); diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 3cb82c95d836a0..effb7316468289 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -314,9 +314,9 @@ namespace ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); // UnsafeAccessorAttribute requires the return type to be byref for any field kind. - // This means creates a type safety issue for the runtime. Since byrefs don't support - // contravariance, we can't permit returning a byref to a fully typed field as a byref to - // an "opaque" type (for example, "ref string" -> "ref object"). + // This creates a type safety issue for the runtime. Since byrefs don't support + // contravariance, we can't permit returning a byref to a fully typed field as a + // byref to an "opaque" type (for example, "ref string" -> "ref object"). if (seq == 0 // Return type && (cxt.Kind == UnsafeAccessorKind::Field || cxt.Kind == UnsafeAccessorKind::StaticField)) @@ -778,9 +778,10 @@ namespace } // Perform the type check. - pDispatchCode->EmitCASTCLASS(tk); + // If the type is a reference type, unbox.any has the same semantics as castclass. + pDispatchCode->EmitUNBOX_ANY(tk); - // If we have a local variable, we need to store the cast result + // If we have a local variable, we need to store the result // in the local variable and load a byref to the local variable as // the argument to the target method. Finally, we may need to update // the byref on return. From 3f224dc21eb851922cc1f6181a62aeeaafb9830b Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 7 May 2025 13:19:41 -0700 Subject: [PATCH 44/62] Fix mono --- src/mono/mono/metadata/marshal.c | 11 ++++++++--- .../UnsafeAccessors/UnsafeAccessorsTests.Types.cs | 6 ++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 8fbadb28abc766..f6dd2f3bec0292 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -5241,7 +5241,7 @@ mono_marshal_get_array_accessor_wrapper (MonoMethod *method) return res; } -static void process_unsafe_accessor_type (MonoMethod *accessor_method, MonoMethodSignature *tgt_sig) +static void process_unsafe_accessor_type (MonoUnsafeAccessorKind kind, MonoMethod *accessor_method, MonoMethodSignature *tgt_sig) { g_assert (accessor_method); g_assert (tgt_sig); @@ -5271,6 +5271,11 @@ static void process_unsafe_accessor_type (MonoMethod *accessor_method, MonoMetho // UnsafeAccessorTypeAttribute on value types. g_assert (type->type != MONO_TYPE_VALUETYPE); + if (seq == 0 && (kind == MONO_UNSAFE_ACCESSOR_FIELD || kind == MONO_UNSAFE_ACCESSOR_STATIC_FIELD)) { + // [FIXME] UnsafeAccessorType is not supported on return values for any field access. + return; + } + // Check the target signature for attribute, byref and cmods state. This information // is not contained with in the type name itself, so we may need to check the target // signature and retain it on the new type. @@ -5440,8 +5445,8 @@ mono_marshal_get_unsafe_accessor_wrapper (MonoMethod *accessor_method, MonoUnsaf } sig->pinvoke = 0; - // Parse the signature and check for instances of UnsafeAccessorTypeAttribute. - process_unsafe_accessor_type (accessor_method, sig); + // Parse the signature and check for instances of UnsafeAccessorTypeAttribute. + process_unsafe_accessor_type (kind, accessor_method, sig); get_marshal_cb ()->mb_skip_visibility (mb); diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index cec5ec792c8271..d90195603e8d75 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -235,8 +235,10 @@ public static void Verify_Type_GetInstanceFields_NotSupported() C2 c2 = new(); TargetClass tgt = CreateTargetClass(c2); - Assert.Throws(()=> CallField1(tgt)); - Assert.Throws(()=> CallField2(tgt)); + // The following calls should throw NotSupportedException. + // Mono throws MissingFieldException since throwing NotSupportedException is difficult to implement. + AssertExtensions.ThrowsAny(()=> CallField1(tgt)); + AssertExtensions.ThrowsAny(()=> CallField2(tgt)); [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_f1")] [return: UnsafeAccessorType("C2")] From 174f886a835a9f8d3a944d37f9d80a2246155051 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 7 May 2025 13:20:02 -0700 Subject: [PATCH 45/62] CoreCLR nits --- src/coreclr/vm/unsafeaccessors.cpp | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index effb7316468289..c9b9e7b29be1b5 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -117,7 +117,7 @@ namespace // Building the signature follows details defined in ECMA-335 - II.23.2.12 // - // We have at least one generic variable, mark as ELEMENT_TYPE_GENERICINST. + // If we have any generic variables, mark as ELEMENT_TYPE_GENERICINST. BOOL hasGenericVariables = inst.ContainsGenericVariables(); if (hasGenericVariables) sig.AppendElementType(ELEMENT_TYPE_GENERICINST); @@ -158,9 +158,7 @@ namespace // types for the parameters that had UnsafeAccessorTypeAttribute. SigPointer origSig = cxt.Declaration->GetSigPointer(); - // We're going to be modifying the signature and we will be inserting - // ELEMENT_TYPE_INTERNAL instances. This will add an additional pointer - // size for each discovered attribute. + // We're going to be modifying the signature to include the translated types. SigBuilder newSig; uint32_t callConvDecl; @@ -195,7 +193,8 @@ namespace continue; } - // We have a new type to insert. We need to update this parameter. + // We have a new type to insert and need to update this + // parameter in the signature. TypeHandle currHandle = origSig.GetTypeHandleThrowing(pSigModule, NULL); // SigPointer::GetTypeHandleThrowing() is non-consuming, so we need to @@ -226,7 +225,7 @@ namespace if (!isValid) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // Extract from the parameterized type the new target. + // Extract the new target from the parameterized type. while (newTypeMaybe.HasTypeParam()) newTypeMaybe = newTypeMaybe.GetTypeParam(); @@ -264,7 +263,7 @@ namespace // Determine the max parameter count. +1 for the return value, which is always index 0. const uint32_t totalParamCount = cxt.DeclarationMetaSig.NumFixedArgs() + 1; - // Inspect all parameters to the declaration method for UnsafeAccessorTypeAttribute. + // Inspect all parameters on the declaration method for UnsafeAccessorTypeAttribute. uint32_t attrCount = 0; IMDInternalImport *pInternalImport = cxt.Declaration->GetModule()->GetMDImport(); HENUMInternalHolder hEnumParams(pInternalImport); @@ -292,7 +291,7 @@ namespace StackSString typeStringUtf8{ SString::Utf8, typeString, typeStringLen }; // Pass the string in the attribute to similar logic as Type.GetType(String). - // The below API will also handle any dependency between the returned type and the + // The below API will handle any dependency between the returned type and the // requesting assembly for the purposes of lifetime tracking of collectible types. TypeHandle targetType = TypeName::GetTypeReferencedByCustomAttribute( typeStringUtf8.GetUnicode(), @@ -314,9 +313,9 @@ namespace ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); // UnsafeAccessorAttribute requires the return type to be byref for any field kind. - // This creates a type safety issue for the runtime. Since byrefs don't support - // contravariance, we can't permit returning a byref to a fully typed field as a - // byref to an "opaque" type (for example, "ref string" -> "ref object"). + // Since byrefs don't support variance, we can't allow returning a byref to a fully + // typed field as a byref to an "opaque" type (for example, "ref string" -> "ref object"). + // This is blocked for type safety reasons. if (seq == 0 // Return type && (cxt.Kind == UnsafeAccessorKind::Field || cxt.Kind == UnsafeAccessorKind::StaticField)) @@ -738,7 +737,7 @@ namespace DWORD localIndex = MAXDWORD; // We are going to emit a type check for the UnsafeAccessorTypeAttribute - // scenario. We do this because the callable signature isn't going to enforce + // scenario. We do this because the declared signature isn't going to enforce // type safety, so we do it here. // // Ensuring type safety is paramount in the UnsafeAccessorTypeAttribute scenario @@ -1141,16 +1140,10 @@ extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(Metho BEGIN_QCALL; - Instantiation genericParams; MethodDesc* typicalMD = unsafeAccessorMethod->LoadTypicalMethodDefinition(); - if (isMethodParam) - { - genericParams = typicalMD->GetMethodInstantiation(); - } - else - { - genericParams = typicalMD->GetMethodTable()->GetInstantiation(); - } + Instantiation genericParams = isMethodParam + ? typicalMD->GetMethodInstantiation() + : typicalMD->GetMethodTable()->GetInstantiation(); if (0 <= paramIndex && paramIndex < genericParams.GetNumArgs()) ret = genericParams[paramIndex]; From 2eb1b4bb6c47e3172364d8b0b34fb37a5482d668 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 7 May 2025 13:44:11 -0700 Subject: [PATCH 46/62] Update NAOT handling --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 632f939b100f55..08593bd63ec043 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -503,6 +503,28 @@ private static SetTargetResult TrySetTargetField(ref GenerationContext context, return SetTargetResult.Missing; } + private static bool IsValidInitialTypeForReplacementType(TypeDesc initialType, TypeDesc replacementType) + { + if (replacementType.IsByRef) + { + if (!initialType.IsByRef) + { + return false; + } + + return IsValidInitialTypeForReplacementType(((ByRefType)initialType).ParameterType, ((ByRefType)replacementType).ParameterType); + } + + if (replacementType.IsPointer) + { + return initialType is PointerType { ParameterType.IsVoid: true }; + } + + Debug.Assert(!replacementType.IsValueType); + + return initialType.IsObject; + } + private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext context) { EcmaMethod method = context.Declaration; @@ -541,22 +563,15 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext bool isReturnValue = parameter.SequenceNumber == 0; - TypeDesc initialType = isReturnValue ? originalSignature.ReturnType : originalSignature[parameter.SequenceNumber - 1]; - - // Only allow 'object' and 'ref object' as initial types. - if (initialType != method.Context.GetWellKnownType(WellKnownType.Object)) + if (isReturnValue && context.Kind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField) { - if (initialType.IsByRef - && ((ByRefType)initialType).ParameterType != method.Context.GetWellKnownType(WellKnownType.Object)) - { - return SetTargetResult.Invalid; - } - else if (!initialType.IsByRef) - { - return SetTargetResult.Invalid; - } + // We can't support UnsafeAccessorTypeAttribute on fields + // today as it would create a type-safety hole. + return SetTargetResult.NotSupported; } + TypeDesc initialType = isReturnValue ? originalSignature.ReturnType : originalSignature[parameter.SequenceNumber - 1]; + CustomAttributeValue decoded = unsafeAccessorTypeAttribute.Value.DecodeValue( new CustomAttributeTypeProvider(method.Module)); @@ -622,22 +637,9 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.NotSupported; } - if (replacementType.IsFunctionPointer - || replacementType.IsPointer) + if (!IsValidInitialTypeForReplacementType(initialType, replacementType)) { - return SetTargetResult.NotSupported; - } - - bool isUnsafeAccessorFieldReturn = isReturnValue - && context.Kind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField; - - if (isUnsafeAccessorFieldReturn) - { - // UnsafeAccessorAttribute requires the return type to be byref for any field kind. - // This is a deviation from the normal UnsafeAccessorTypeAttribute requirements - // where the type is expected to match the signature exactly. Users aren't required to - // state the byref for field access because that isn't in the target field signature. - replacementType = replacementType.MakeByRefType(); + return SetTargetResult.Invalid; } if (replacementType.IsByRef != initialType.IsByRef) From a7d9c69519d6f7c8223b29bd470f58c157fafa0d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 8 May 2025 15:54:23 -0700 Subject: [PATCH 47/62] PR feedback --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 235 +++++++++++------- 1 file changed, 141 insertions(+), 94 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 08593bd63ec043..0808a57917656c 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Runtime.InteropServices; +using System.Threading; using Internal.IL.Stubs; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -509,11 +510,17 @@ private static bool IsValidInitialTypeForReplacementType(TypeDesc initialType, T { if (!initialType.IsByRef) { + // We can't replace a non-byref with a byref. return false; } return IsValidInitialTypeForReplacementType(((ByRefType)initialType).ParameterType, ((ByRefType)replacementType).ParameterType); } + else if (initialType.IsByRef) + { + // We can't replace a byref with a non-byref. + return false; + } if (replacementType.IsPointer) { @@ -539,128 +546,155 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext foreach (ParameterHandle parameterHandle in parameters) { Parameter parameter = reader.GetParameter(parameterHandle); - CustomAttribute? unsafeAccessorTypeAttribute = null; - foreach (CustomAttributeHandle customAttributeHandle in parameter.GetCustomAttributes()) - { - reader.GetAttributeNamespaceAndName(customAttributeHandle, out StringHandle namespaceName, out StringHandle name); - if (reader.StringComparer.Equals(namespaceName, "System.Runtime.CompilerServices") - && reader.StringComparer.Equals(name, "UnsafeAccessorTypeAttribute")) - { - unsafeAccessorTypeAttribute = reader.GetCustomAttribute(customAttributeHandle); - break; - } - } - if (unsafeAccessorTypeAttribute is null) + if (parameter.SequenceNumber > originalSignature.Length) { - continue; + // This is invalid metadata (parameter metadata for a parameter that doesn't exist in the signature). + return SetTargetResult.Invalid; } - if (parameter.SequenceNumber > originalSignature.Length) + CustomAttributeHandle unsafeAccessorTypeAttributeHandle = FindUnsafeAccessorTypeAttribute(reader, parameter); + + if (unsafeAccessorTypeAttributeHandle.IsNil) { - return SetTargetResult.Invalid; + continue; } bool isReturnValue = parameter.SequenceNumber == 0; - if (isReturnValue && context.Kind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField) + TypeDesc initialType = isReturnValue ? originalSignature.ReturnType : originalSignature[parameter.SequenceNumber - 1]; + + if (isReturnValue && initialType.IsByRef) { - // We can't support UnsafeAccessorTypeAttribute on fields + // We can't support UnsafeAccessorTypeAttribute on by-ref returns // today as it would create a type-safety hole. return SetTargetResult.NotSupported; } - TypeDesc initialType = isReturnValue ? originalSignature.ReturnType : originalSignature[parameter.SequenceNumber - 1]; + SetTargetResult decodeResult = DecodeUnsafeAccessorType(method, reader.GetCustomAttribute(unsafeAccessorTypeAttributeHandle), out TypeDesc replacementType); + if (decodeResult != SetTargetResult.Success) + { + return decodeResult; + } - CustomAttributeValue decoded = unsafeAccessorTypeAttribute.Value.DecodeValue( - new CustomAttributeTypeProvider(method.Module)); + // Future versions of the runtime may support + // UnsafeAccessorTypeAttribute on value types. + if (replacementType.IsValueType) + { + return SetTargetResult.NotSupported; + } - if (decoded.FixedArguments[0].Value is not string replacementTypeName) + if (!IsValidInitialTypeForReplacementType(initialType, replacementType)) { return SetTargetResult.Invalid; } - TypeDesc replacementType = method.Module.GetTypeByCustomAttributeTypeName( - replacementTypeName, - throwIfNotFound: false, - canonGenericResolver: (module, name) => + if (isReturnValue) + { + updatedSignature.ReturnType = replacementType; + } + else + { + updatedSignature[parameter.SequenceNumber - 1] = replacementType; + } + } + + context.DeclarationSignature = updatedSignature.ToSignature(); + return SetTargetResult.Success; + } + + private static SetTargetResult DecodeUnsafeAccessorType(EcmaMethod method, CustomAttribute unsafeAccessorTypeAttribute, out TypeDesc replacementType) + { + replacementType = null; + CustomAttributeValue decoded = unsafeAccessorTypeAttribute.DecodeValue( + new CustomAttributeTypeProvider(method.Module)); + + if (decoded.FixedArguments[0].Value is not string replacementTypeName) + { + return SetTargetResult.Invalid; + } + + replacementType = method.Module.GetTypeByCustomAttributeTypeName( + replacementTypeName, + throwIfNotFound: false, + canonGenericResolver: (module, name) => + { + if (!name.StartsWith('!')) { - if (!name.StartsWith('!')) - { - return null; - } + return null; + } + + bool isMethodParameter = name.StartsWith("!!", StringComparison.Ordinal); - bool isMethodParameter = name.StartsWith("!!", StringComparison.Ordinal); + if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), NumberStyles.None, CultureInfo.InvariantCulture, out int index)) + { + return null; + } - if (!int.TryParse(name.AsSpan(isMethodParameter ? 2 : 1), NumberStyles.None, CultureInfo.InvariantCulture, out int index)) + if (isMethodParameter) + { + if (index >= method.Instantiation.Length) { return null; } - - if (isMethodParameter) + } + else + { + if (index >= method.OwningType.Instantiation.Length) { - if (!method.HasInstantiation) - { - return null; - } - - if (index >= method.Instantiation.Length) - { - return null; - } + return null; } - else - { - if (!method.OwningType.HasInstantiation) - { - return null; - } + } - if (index >= method.OwningType.Instantiation.Length) - { - return null; - } - } + return module.Context.GetSignatureVariable(index, isMethodParameter); + }); - return module.Context.GetSignatureVariable(index, isMethodParameter); - }); + return replacementType is null + ? SetTargetResult.Missing + : SetTargetResult.Success; + } - if (replacementType is null) + private static CustomAttributeHandle FindUnsafeAccessorTypeAttribute(MetadataReader reader, Parameter parameter) + { + foreach (CustomAttributeHandle customAttributeHandle in parameter.GetCustomAttributes()) + { + reader.GetAttributeNamespaceAndName(customAttributeHandle, out StringHandle namespaceName, out StringHandle name); + if (reader.StringComparer.Equals(namespaceName, "System.Runtime.CompilerServices") + && reader.StringComparer.Equals(name, "UnsafeAccessorTypeAttribute")) { - return SetTargetResult.Missing; + return customAttributeHandle; } + } - // Future versions of the runtime may support - // UnsafeAccessorTypeAttribute on value types. - if (replacementType.IsValueType) - { - return SetTargetResult.NotSupported; - } + return default; + } - if (!IsValidInitialTypeForReplacementType(initialType, replacementType)) - { - return SetTargetResult.Invalid; - } + private static ParameterHandle FindParameterForSequenceNumber(MetadataReader reader, ref ParameterHandleCollection.Enumerator parameterEnumerator, int sequenceNumber) + { + Parameter currentParameter = reader.GetParameter(parameterEnumerator.Current); + if (currentParameter.SequenceNumber == sequenceNumber) + { + return parameterEnumerator.Current; + } - if (replacementType.IsByRef != initialType.IsByRef) + // Scan until we are either at this parameter or at the first one after it (if there is no Parameter row in the table) + while (parameterEnumerator.MoveNext()) + { + Parameter thisParameterMaybe = reader.GetParameter(parameterEnumerator.Current); + if (thisParameterMaybe.SequenceNumber > sequenceNumber) { - // The replacement type must match the original type - // in terms of byref-ness. - return SetTargetResult.Invalid; + // We've passed where it should be. + return default; } - if (isReturnValue) + if (thisParameterMaybe.SequenceNumber == sequenceNumber) { - updatedSignature.ReturnType = replacementType; - } - else - { - updatedSignature[parameter.SequenceNumber - 1] = replacementType; + // We found it. + return parameterEnumerator.Current; } } - context.DeclarationSignature = updatedSignature.ToSignature(); - return SetTargetResult.Success; + return default; } private static MethodIL GenerateAccessor(ref GenerationContext context) @@ -668,6 +702,10 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) ILEmitter emit = new ILEmitter(); ILCodeStream codeStream = emit.NewCodeStream(); + MetadataReader reader = context.Declaration.MetadataReader; + ParameterHandleCollection.Enumerator parameterEnumerator = reader.GetMethodDefinition(context.Declaration.Handle).GetParameters().GetEnumerator(); + parameterEnumerator.MoveNext(); + // Load stub arguments. // When the target is static, the first argument is only // used to look up the target member to access and ignored @@ -675,36 +713,45 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) int beginIndex = context.IsTargetStatic ? 1 : 0; int stubArgCount = context.DeclarationSignature.Length; Stubs.ILLocalVariable?[] localsToRestore = null; + for (int i = beginIndex; i < stubArgCount; ++i) { + codeStream.EmitLdArg(i); if (context.Declaration.Signature[i] != context.DeclarationSignature[i]) { if (context.DeclarationSignature[i] is { Category: TypeFlags.Class } classType) { - codeStream.EmitLdArg(i); - codeStream.Emit(ILOpcode.castclass, emit.NewToken(classType)); + codeStream.Emit(ILOpcode.unbox_any, emit.NewToken(classType)); } else if (context.DeclarationSignature[i] is ByRefType { ParameterType.Category: TypeFlags.Class } byrefType) { localsToRestore ??= new Stubs.ILLocalVariable?[stubArgCount]; TypeDesc targetType = byrefType.ParameterType; - codeStream.EmitLdArg(i); - localsToRestore[i] = emit.NewLocal(targetType); + Stubs.ILLocalVariable local = emit.NewLocal(targetType); codeStream.EmitLdInd(targetType); - codeStream.Emit(ILOpcode.castclass, emit.NewToken(targetType)); - codeStream.EmitStLoc(localsToRestore[i].Value); - codeStream.EmitLdLoca(localsToRestore[i].Value); - } - else - { - codeStream.EmitLdArg(i); + codeStream.Emit(ILOpcode.unbox_any, emit.NewToken(targetType)); + codeStream.EmitStLoc(local); + codeStream.EmitLdLoca(local); + + // Only mark the local to be restored after the call + // if the parameter is not marked as "in". + // The "sequence number" for parameters is 1-based, whereas the parameter index is 0-based. + ParameterHandle paramHandle = FindParameterForSequenceNumber(reader, ref parameterEnumerator, i + 1); + if (!paramHandle.IsNil) + { + if (!reader.GetParameter(paramHandle).Attributes.HasFlag(ParameterAttributes.In)) + { + localsToRestore[i] = local; + } + } + else + { + // If there's no metadata for the parameter, it definitely doesn't have the [In] attribute. + localsToRestore[i] = local; + } } } - else - { - codeStream.EmitLdArg(i); - } } // Provide access to the target member From 2fb3b8a2fe34fb439105f8893c984328a0a19972 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 May 2025 16:10:15 -0700 Subject: [PATCH 48/62] [CoreCLR] Encode the generic definition in signature. Encode primitive ELEMENT_TYPE in signature. --- .../Reflection/TypeNameResolver.CoreCLR.cs | 4 +- src/coreclr/vm/siginfo.cpp | 18 +------ src/coreclr/vm/typehandle.h | 10 ---- src/coreclr/vm/unsafeaccessors.cpp | 51 ++++++++++++------- .../UnsafeAccessors/PrivateLib.cs | 6 ++- .../UnsafeAccessorsTests.Types.cs | 18 ++++++- 6 files changed, 58 insertions(+), 49 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs index 31df393fbc2104..3a6ae52ed00797 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs @@ -26,7 +26,7 @@ internal partial struct TypeNameResolver private Assembly? _requestingAssembly; private Assembly? _topLevelAssembly; - private bool SupportUnboundGenerics { get => _unsafeAccessorMethod != IntPtr.Zero; } + private bool SupportsUnboundGenerics { get => _unsafeAccessorMethod != IntPtr.Zero; } [RequiresUnreferencedCode("The type might be removed")] internal static Type? GetType( @@ -242,7 +242,7 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, { if (assembly is null) { - if (SupportUnboundGenerics + if (SupportsUnboundGenerics && !string.IsNullOrEmpty(escapedTypeName) && escapedTypeName[0] == '!') { diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index ffa162fb2c8ac4..9b16cc4bf04fd1 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3924,7 +3924,6 @@ MetaSig::CompareElementType( pOtherModule = pModule1; } - // Internal types can only correspond to types or value types. switch (eOtherType) { case ELEMENT_TYPE_OBJECT: @@ -3954,13 +3953,6 @@ MetaSig::CompareElementType( ClassLoader::ReturnNullIfNotFound, ClassLoader::PermitUninstDefOrRef); - // If the return type is a generic type definition, we need to - // compare it to the typical method table of the internal type. - if (!hOtherType.IsNull() && hOtherType.IsGenericTypeDefinition()) - { - hInternal = TypeHandle{ hInternal.AsMethodTable()->GetTypicalMethodTable() }; - } - return (hInternal == hOtherType); } case ELEMENT_TYPE_GENERICINST: @@ -5808,15 +5800,7 @@ TokenPairList TokenPairList::AdjustForTypeSpec(TokenPairList *pTemplate, ModuleB { TypeHandle typeHandle; IfFailThrow(sig.GetPointer((void**)&typeHandle)); - - if (typeHandle.IsTypeDesc()) - { - result.m_bInTypeEquivalenceForbiddenScope = TRUE; - } - else - { - result.m_bInTypeEquivalenceForbiddenScope = !typeHandle.AsMethodTable()->IsInterface(); - } + result.m_bInTypeEquivalenceForbiddenScope = !typeHandle.IsInterface(); } else { diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 538ec43f51df0c..dcf1852bacab3b 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -726,16 +726,6 @@ class Instantiation return true; } - bool ContainsGenericVariables() - { - for (DWORD i = GetNumArgs(); i > 0;) - { - if ((*this)[--i].IsGenericVariable()) - return true; - } - return false; - } - private: // Note that for DAC builds, m_pArgs may be host allocated buffer, not a copy of an object marshalled by DAC. TypeHandle* m_pArgs; diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index c9b9e7b29be1b5..c2f399eaae55b5 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -118,10 +118,16 @@ namespace // // If we have any generic variables, mark as ELEMENT_TYPE_GENERICINST. - BOOL hasGenericVariables = inst.ContainsGenericVariables(); + BOOL hasGenericVariables = !inst.IsEmpty(); if (hasGenericVariables) + { sig.AppendElementType(ELEMENT_TYPE_GENERICINST); + // Embed the generic type definition in the signature. + th = TypeHandle{ th.GetMethodTableOfRootTypeParam()->GetTypicalMethodTable() }; + _ASSERTE(th.IsGenericTypeDefinition()); + } + // Append the new type to the signature. sig.AppendElementType(ELEMENT_TYPE_INTERNAL); sig.AppendPointer(th.AsPtr()); @@ -129,15 +135,26 @@ namespace // Append the instantiation types to the signature. if (hasGenericVariables) { + _ASSERTE(inst.GetNumArgs() > 0); sig.AppendData(inst.GetNumArgs()); for (DWORD i = 0; i < inst.GetNumArgs(); i++) { - if (!inst[i].IsGenericVariable()) - continue; - - TypeVarTypeDesc* typeVar = inst[i].AsGenericVariable(); - sig.AppendElementType(typeVar->GetInternalCorElementType()); - sig.AppendData(typeVar->GetIndex()); + TypeHandle& instTh = inst[i]; + if (instTh.IsGenericVariable()) + { + TypeVarTypeDesc* typeVar = instTh.AsGenericVariable(); + sig.AppendElementType(typeVar->GetInternalCorElementType()); + sig.AppendData(typeVar->GetIndex()); + } + else if (CorIsPrimitiveType(instTh.GetSignatureCorElementType())) + { + sig.AppendElementType(instTh.GetSignatureCorElementType()); + } + else + { + sig.AppendElementType(ELEMENT_TYPE_INTERNAL); + sig.AppendPointer(instTh.AsPtr()); + } } } } @@ -197,6 +214,15 @@ namespace // parameter in the signature. TypeHandle currHandle = origSig.GetTypeHandleThrowing(pSigModule, NULL); + // Since byrefs don't support variance, we can't allow returning a byref to a fully + // typed field as a byref to an "opaque" type (for example, "ref string" -> "ref object"). + // This is blocked for type safety reasons. + if (i == 0 // Return type + && currHandle.IsByRef()) + { + ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); + } + // SigPointer::GetTypeHandleThrowing() is non-consuming, so we need to // consume the signature to move past the current type. IfFailThrow(origSig.SkipExactlyOne()); @@ -312,17 +338,6 @@ namespace if (seq >= totalParamCount) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // UnsafeAccessorAttribute requires the return type to be byref for any field kind. - // Since byrefs don't support variance, we can't allow returning a byref to a fully - // typed field as a byref to an "opaque" type (for example, "ref string" -> "ref object"). - // This is blocked for type safety reasons. - if (seq == 0 // Return type - && (cxt.Kind == UnsafeAccessorKind::Field - || cxt.Kind == UnsafeAccessorKind::StaticField)) - { - ThrowHR(COR_E_NOTSUPPORTED, BFA_INVALID_UNSAFEACCESSORTYPE); - } - // Store the TypeHandle for the loaded type at the sequence number for the parameter. cxt.TranslatedParams[seq] = { targetType, (CorParamAttr)attr }; attrCount++; diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index 272a572d165eb4..81135bf7f7d1a0 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -37,6 +37,10 @@ class GenericClass List M4() => new List(); - bool M5(List a, List b, List c, List d) where S : T => true; + bool M5(List a, List b, List c, List d) where W : T => true; + + Type M6(Dictionary a) => typeof(X); + + Type M7(Dictionary a) => typeof(Y); } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index d90195603e8d75..ebb0e3e049afcf 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -146,7 +146,7 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(c2, CallM_C1(tgt, arg)); Assert.Equal(c2, CallM_RC1(tgt, ref arg)); Assert.Equal(c2, CallM_RROC1(tgt, ref arg)); - Assert.Equal(c2, CallM_C1_RC2(tgt, arg)); + AssertExtensions.ThrowsAny(()=> CallM_C1_RC2(tgt, arg)); object ic = null; object rc = null; @@ -384,6 +384,18 @@ public extern static bool CallGenericClassM5_NoConstraint( List c, [UnsafeAccessorType("System.Collections.Generic.List`1[[PrivateLib.Class2, PrivateLib]]")] object d); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M6")] + public extern static Type CallGenericClassM6( + [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, + [UnsafeAccessorType("System.Collections.Generic.Dictionary`2[[!!0],[System.Int32]]")] + object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M7")] + public extern static Type CallGenericClassM7( + [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, + [UnsafeAccessorType("System.Collections.Generic.Dictionary`2[[System.Int32],[!!0]]")] + object a); } // Skip validating error cases on Mono runtime @@ -493,6 +505,8 @@ public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); + Assert.Equal(typeof(int), Accessors.CallGenericClassM6(genericClass, null)); + Assert.Equal(typeof(int), Accessors.CallGenericClassM7(genericClass, null)); } { @@ -509,6 +523,8 @@ public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() Assert.True(TypeNameEquals(genericListStringName, TypeName.Parse("System.Collections.Generic.List`1[[System.String]]"))); Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); + Assert.Equal(typeof(string), Accessors.CallGenericClassM6(genericClass, null)); + Assert.Equal(typeof(string), Accessors.CallGenericClassM7(genericClass, null)); } } From 5fba0a3b26ca6ea3eee77d8ca811f725b8aa66f8 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 May 2025 16:15:43 -0700 Subject: [PATCH 49/62] Update mono to block ref object on method return. --- src/mono/mono/metadata/marshal.c | 4 ++-- .../UnsafeAccessors/UnsafeAccessorsTests.Types.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index f6dd2f3bec0292..e6d7f80d8e7365 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -5271,8 +5271,8 @@ static void process_unsafe_accessor_type (MonoUnsafeAccessorKind kind, MonoMetho // UnsafeAccessorTypeAttribute on value types. g_assert (type->type != MONO_TYPE_VALUETYPE); - if (seq == 0 && (kind == MONO_UNSAFE_ACCESSOR_FIELD || kind == MONO_UNSAFE_ACCESSOR_STATIC_FIELD)) { - // [FIXME] UnsafeAccessorType is not supported on return values for any field access. + if (seq == 0 && m_type_is_byref (tgt_sig->ret)) { + // [FIXME] UnsafeAccessorType is not supported on return that are byref, type safety issue. return; } diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index ebb0e3e049afcf..c6b7aa9f1f8db6 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -146,7 +146,7 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(c2, CallM_C1(tgt, arg)); Assert.Equal(c2, CallM_RC1(tgt, ref arg)); Assert.Equal(c2, CallM_RROC1(tgt, ref arg)); - AssertExtensions.ThrowsAny(()=> CallM_C1_RC2(tgt, arg)); + AssertExtensions.ThrowsAny(()=> CallM_C1_RC2(tgt, arg)); object ic = null; object rc = null; From a91035c219f85c23b144202eb18c0df9877b46f0 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 8 May 2025 16:41:14 -0700 Subject: [PATCH 50/62] Use helper function. --- src/coreclr/vm/unsafeaccessors.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index c2f399eaae55b5..7682eedf7f751f 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -252,15 +252,9 @@ namespace ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); // Extract the new target from the parameterized type. - while (newTypeMaybe.HasTypeParam()) - newTypeMaybe = newTypeMaybe.GetTypeParam(); - - Instantiation inst = newTypeMaybe.GetInstantiation(); + Instantiation inst = newTypeMaybe.GetMethodTableOfRootTypeParam()->GetInstantiation(); // Append the new type to the signature. - // Go back to the original translated type because - // analysis may have altered the local. - newTypeMaybe = cxt.TranslatedParams[i].Type; AppendTypeToSignature(newSig, newTypeMaybe, inst); } From 93f6f683c5dea102fded251eecddddf95b6f064b Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 May 2025 19:24:01 -0700 Subject: [PATCH 51/62] Address arrays --- .../Reflection/TypeNameResolver.CoreCLR.cs | 8 +- src/coreclr/vm/unsafeaccessors.cpp | 75 +++++++++++++------ .../UnsafeAccessors/PrivateLib.cs | 4 + .../UnsafeAccessorsTests.Types.cs | 30 +++++++- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs index 3a6ae52ed00797..23546a595564d8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs @@ -139,13 +139,7 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, } internal static unsafe RuntimeType? GetTypeHelper(ReadOnlySpan typeName, RuntimeAssembly? requestingAssembly, - bool throwOnError, bool requireAssemblyQualifiedName) - { - return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName, IntPtr.Zero); - } - - private static unsafe RuntimeType? GetTypeHelper(ReadOnlySpan typeName, RuntimeAssembly? requestingAssembly, - bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod) + bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod = 0) { // Compat: Empty name throws TypeLoadException instead of // the natural ArgumentException diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 7682eedf7f751f..5d1132ed64cceb 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -107,8 +107,7 @@ namespace void AppendTypeToSignature( SigBuilder& sig, - TypeHandle th, - Instantiation inst) + TypeHandle th) { STANDARD_VM_CONTRACT; _ASSERTE(!th.IsNull()); @@ -117,6 +116,52 @@ namespace // Building the signature follows details defined in ECMA-335 - II.23.2.12 // + CorElementType elemType = th.GetSignatureCorElementType(); + if (CorIsPrimitiveType(elemType)) + { + sig.AppendElementType(elemType); + return; + } + + if (th.IsGenericVariable()) + { + TypeVarTypeDesc* typeVar = th.AsGenericVariable(); + sig.AppendElementType(typeVar->GetInternalCorElementType()); + sig.AppendData(typeVar->GetIndex()); + return; + } + + if (th.IsArray()) + { + // Append the array element type. + sig.AppendElementType(elemType); + TypeHandle elemTypeHandle = th.GetArrayElementTypeHandle(); + AppendTypeToSignature(sig, elemTypeHandle); + + // Append ArrayShape for MD array + // See II.23.2.13 ArrayShape + if (elemType == ELEMENT_TYPE_ARRAY) + { + DWORD rank = th.GetRank(); + sig.AppendData(rank); + sig.AppendData(0); // Append the number of sizes. + + // Roslyn always emits the lower bounds of 0 for each dimension. + // In order to match the signature, we also need to append the number + // of lower bounds for each dimension. + // We can emit 0 for each lower bound, since UnsafeAccessors is only + // supported in C# and C# doesn't support lower bounds. + sig.AppendData(rank); + for (DWORD i = 0; i < rank; i++) + sig.AppendData(0); + } + return; + } + + // Extract the instantiation from parameterized type. + MethodTable* pMT = th.GetMethodTableOfRootTypeParam(); + Instantiation inst = pMT->GetInstantiation(); + // If we have any generic variables, mark as ELEMENT_TYPE_GENERICINST. BOOL hasGenericVariables = !inst.IsEmpty(); if (hasGenericVariables) @@ -124,7 +169,7 @@ namespace sig.AppendElementType(ELEMENT_TYPE_GENERICINST); // Embed the generic type definition in the signature. - th = TypeHandle{ th.GetMethodTableOfRootTypeParam()->GetTypicalMethodTable() }; + th = TypeHandle{ pMT->GetTypicalMethodTable() }; _ASSERTE(th.IsGenericTypeDefinition()); } @@ -139,22 +184,7 @@ namespace sig.AppendData(inst.GetNumArgs()); for (DWORD i = 0; i < inst.GetNumArgs(); i++) { - TypeHandle& instTh = inst[i]; - if (instTh.IsGenericVariable()) - { - TypeVarTypeDesc* typeVar = instTh.AsGenericVariable(); - sig.AppendElementType(typeVar->GetInternalCorElementType()); - sig.AppendData(typeVar->GetIndex()); - } - else if (CorIsPrimitiveType(instTh.GetSignatureCorElementType())) - { - sig.AppendElementType(instTh.GetSignatureCorElementType()); - } - else - { - sig.AppendElementType(ELEMENT_TYPE_INTERNAL); - sig.AppendPointer(instTh.AsPtr()); - } + AppendTypeToSignature(sig, inst[i]); } } } @@ -251,11 +281,8 @@ namespace if (!isValid) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSORTYPE); - // Extract the new target from the parameterized type. - Instantiation inst = newTypeMaybe.GetMethodTableOfRootTypeParam()->GetInstantiation(); - // Append the new type to the signature. - AppendTypeToSignature(newSig, newTypeMaybe, inst); + AppendTypeToSignature(newSig, newTypeMaybe); } // Create a copy of the new signature and store it on the context. @@ -777,7 +804,7 @@ namespace else { SigBuilder sigBuilder; - AppendTypeToSignature(sigBuilder, th, th.GetInstantiation()); + AppendTypeToSignature(sigBuilder, th); uint32_t sigLen; PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen); diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs index 81135bf7f7d1a0..eb3543cae330b2 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/PrivateLib.cs @@ -42,5 +42,9 @@ class GenericClass Type M6(Dictionary a) => typeof(X); Type M7(Dictionary a) => typeof(Y); + + Z M8() where Z : class, new() => new Z(); + + bool M9(List> a, List b, List> c) => true; } } \ No newline at end of file diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs index c6b7aa9f1f8db6..7226cb051c0d60 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.Types.cs @@ -57,12 +57,14 @@ private void M_ByRefs(C1 c, in C1 ic, ref C1 rc, out C1 oc) private Type M_C1Array(C1[,,] c) => typeof(C1[,,]); private Type M_C1Array(C1[][] c) => typeof(C1[][]); private Type M_C1Array(C1[][][] c) => typeof(C1[][][]); + private Type M_C1Array(C1[][,] c) => typeof(C1[][,]); private Type M_S1Array(S1[] c) => typeof(S1[]); private Type M_S1Array(S1[,] c) => typeof(S1[,]); private Type M_S1Array(S1[,,] c) => typeof(S1[,,]); private Type M_S1Array(S1[][] c) => typeof(S1[][]); private Type M_S1Array(S1[][][] c) => typeof(S1[][][]); + private Type M_S1Array(S1[][,] c) => typeof(S1[][,]); #pragma warning disable CS8500 private unsafe void M_C1Pointer(C1* c) { } @@ -161,12 +163,14 @@ public static void Verify_Type_CallInstanceMethods() Assert.Equal(typeof(C1[,,]), CallM_C1MDArray3(tgt, new C1[1,1,1])); Assert.Equal(typeof(C1[][]), CallM_C1JaggedArray2(tgt, new C1[0][])); Assert.Equal(typeof(C1[][][]), CallM_C1JaggedArray3(tgt, new C1[0][][])); + Assert.Equal(typeof(C1[][,]), CallM_C1MixedArrays(tgt, new C1[0][,])); Assert.Equal(typeof(S1[]), CallM_S1Array(tgt, Array.Empty())); Assert.Equal(typeof(S1[,]), CallM_S1MDArray2(tgt, new S1[1,1])); Assert.Equal(typeof(S1[,,]), CallM_S1MDArray3(tgt, new S1[1,1,1])); Assert.Equal(typeof(S1[][]), CallM_S1JaggedArray2(tgt, new S1[0][])); Assert.Equal(typeof(S1[][][]), CallM_S1JaggedArray3(tgt, new S1[0][][])); + Assert.Equal(typeof(S1[][,]), CallM_S1MixedArrays(tgt, new S1[0][,])); CallM_C1Pointer(tgt, null); @@ -208,6 +212,9 @@ extern static void CallM_ByRefs(TargetClass tgt, [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] extern static Type CallM_C1JaggedArray3(TargetClass tgt, [UnsafeAccessorType("C1[][][]")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Array")] + extern static Type CallM_C1MixedArrays(TargetClass tgt, [UnsafeAccessorType("C1[,][]")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] extern static Type CallM_S1Array(TargetClass tgt, [UnsafeAccessorType("S1[]")] object a); @@ -223,6 +230,9 @@ extern static void CallM_ByRefs(TargetClass tgt, [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] extern static Type CallM_S1JaggedArray3(TargetClass tgt, [UnsafeAccessorType("S1[][][]")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_S1Array")] + extern static Type CallM_S1MixedArrays(TargetClass tgt, [UnsafeAccessorType("S1[,][]")] object a); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M_C1Pointer")] extern static void CallM_C1Pointer(TargetClass tgt, [UnsafeAccessorType("C1*")] void* a); } @@ -392,10 +402,25 @@ public extern static Type CallGenericClassM6( object a); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M7")] - public extern static Type CallGenericClassM7( + public extern static Type CallGenericClassM7( [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, [UnsafeAccessorType("System.Collections.Generic.Dictionary`2[[System.Int32],[!!0]]")] object a); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M8")] + [return: UnsafeAccessorType("!!0")] + public extern static object CallGenericClassM8( + [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt) where Z : class, new(); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M9")] + public extern static bool CallGenericClassM9( + [UnsafeAccessorType("PrivateLib.GenericClass`1[[!0]], PrivateLib")] object tgt, + [UnsafeAccessorType("System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[!!0]]]]")] + object a, + [UnsafeAccessorType("System.Collections.Generic.List`1[[!!0[,,]]]")] + object b, + [UnsafeAccessorType("System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[!0[][,]]]]]")] + object c); } // Skip validating error cases on Mono runtime @@ -507,6 +532,7 @@ public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); Assert.Equal(typeof(int), Accessors.CallGenericClassM6(genericClass, null)); Assert.Equal(typeof(int), Accessors.CallGenericClassM7(genericClass, null)); + Assert.True(Accessors.CallGenericClassM9(genericClass, null, null, null)); } { @@ -525,6 +551,8 @@ public static void Verify_Type_CallPrivateLibTypeAndMethodGenericParams() Assert.True(Accessors.CallGenericClassM5(genericClass, null, null, null, null)); Assert.Equal(typeof(string), Accessors.CallGenericClassM6(genericClass, null)); Assert.Equal(typeof(string), Accessors.CallGenericClassM7(genericClass, null)); + Assert.Equal(typeof(C1), Accessors.CallGenericClassM8(genericClass).GetType()); + Assert.True(Accessors.CallGenericClassM9(genericClass, null, null, null)); } } From 482f5174d42641f7743f69ab8d782739eb721a0b Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Fri, 9 May 2025 19:39:27 -0700 Subject: [PATCH 52/62] Remove unnecessary compare logic. --- src/coreclr/vm/siginfo.cpp | 45 ------------------------------ src/coreclr/vm/unsafeaccessors.cpp | 10 +++---- 2 files changed, 5 insertions(+), 50 deletions(-) diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 9b16cc4bf04fd1..ee39fbf5cbe676 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -3955,51 +3955,6 @@ MetaSig::CompareElementType( return (hInternal == hOtherType); } - case ELEMENT_TYPE_GENERICINST: - case ELEMENT_TYPE_BYREF: - case ELEMENT_TYPE_ARRAY: - case ELEMENT_TYPE_SZARRAY: - case ELEMENT_TYPE_PTR: - { - // Due to how SigPointer works, we need to fiddle with the signature pointer - // to get the current element type back in the stream. - // Since we know the size of the element type, we can just subtract one byte from the - // signature pointer to get the correct value. - PCCOR_SIGNATURE currInstSig; - uint32_t currInstSigLen; - if (Type1 == ELEMENT_TYPE_INTERNAL) - { - const BYTE* sigRaw = (const BYTE*)pSig2; - currInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); - currInstSigLen = (uint32_t)((intptr_t)pEndSig2 - (intptr_t)currInstSig); - } - else - { - const BYTE* sigRaw = (const BYTE*)pSig1; - currInstSig = (PCCOR_SIGNATURE)(sigRaw - 1); - currInstSigLen = (uint32_t)((intptr_t)pEndSig1 - (intptr_t)currInstSig); - } - // Assert we are in the same state as before. - _ASSERTE(*currInstSig == eOtherType); - - SigPointer inst{ currInstSig, currInstSigLen }; - TypeHandle hOtherType = inst.GetTypeHandleThrowing(pOtherModule, NULL); - - // SigPointer::GetTypeHandleThrowing() is non-consuming, so we need to - // consume the signature to move past the current type and update the appropriate - // 'in/out' parameter. - IfFailThrow(inst.SkipExactlyOne()); - if (Type1 == ELEMENT_TYPE_INTERNAL) - { - pSig2 = inst.GetPtr(); - } - else - { - pSig1 = inst.GetPtr(); - } - - return (hInternal == hOtherType); - } default: { return FALSE; diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 5d1132ed64cceb..c36509b1f1223e 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -131,14 +131,14 @@ namespace return; } - if (th.IsArray()) + if (th.HasTypeParam()) { - // Append the array element type. + // Append the element type. sig.AppendElementType(elemType); - TypeHandle elemTypeHandle = th.GetArrayElementTypeHandle(); - AppendTypeToSignature(sig, elemTypeHandle); + TypeHandle typeParam = th.GetTypeParam(); + AppendTypeToSignature(sig, typeParam); - // Append ArrayShape for MD array + // Append ArrayShape for MD arrays // See II.23.2.13 ArrayShape if (elemType == ELEMENT_TYPE_ARRAY) { From e8ca051d522e831de398673611e8db679d9c7eac Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 10 May 2025 07:58:06 -0700 Subject: [PATCH 53/62] Remove unnecessary changes. --- src/coreclr/vm/unsafeaccessors.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index c36509b1f1223e..fc88b2703cac43 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -158,8 +158,7 @@ namespace return; } - // Extract the instantiation from parameterized type. - MethodTable* pMT = th.GetMethodTableOfRootTypeParam(); + MethodTable* pMT = th.GetMethodTable(); Instantiation inst = pMT->GetInstantiation(); // If we have any generic variables, mark as ELEMENT_TYPE_GENERICINST. @@ -594,7 +593,7 @@ namespace // Following a similar iteration pattern found in MemberLoader::FindMethod(). // However, we are only operating on the current type not walking the type hierarchy. - MethodTable::IntroducedMethodIterator iter(pMT, /* restrictToCanonicalTypes */ FALSE); + MethodTable::IntroducedMethodIterator iter(pMT); for (; iter.IsValid(); iter.Next()) { MethodDesc* curr = iter.GetMethodDesc(); From 4c26d4ea2ff0639586e1bf04df34cbc7a734d50e Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 10 May 2025 08:13:47 -0700 Subject: [PATCH 54/62] Nits --- src/coreclr/vm/unsafeaccessors.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index fc88b2703cac43..80b8f16426f71b 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -144,13 +144,13 @@ namespace { DWORD rank = th.GetRank(); sig.AppendData(rank); - sig.AppendData(0); // Append the number of sizes. - // Roslyn always emits the lower bounds of 0 for each dimension. + // Roslyn always emits size and lower bounds of 0 in C# signatures. // In order to match the signature, we also need to append the number // of lower bounds for each dimension. // We can emit 0 for each lower bound, since UnsafeAccessors is only // supported in C# and C# doesn't support lower bounds. + sig.AppendData(0); // Append the number of sizes. sig.AppendData(rank); for (DWORD i = 0; i < rank; i++) sig.AppendData(0); @@ -243,8 +243,8 @@ namespace // parameter in the signature. TypeHandle currHandle = origSig.GetTypeHandleThrowing(pSigModule, NULL); - // Since byrefs don't support variance, we can't allow returning a byref to a fully - // typed field as a byref to an "opaque" type (for example, "ref string" -> "ref object"). + // Since byrefs don't support variance, we can't allow returning a + // fully typed byref as a byref to an "opaque" type (for example, "ref string" -> "ref object"). // This is blocked for type safety reasons. if (i == 0 // Return type && currHandle.IsByRef()) @@ -1178,7 +1178,7 @@ extern "C" void* QCALLTYPE UnsafeAccessors_ResolveGenericParamToTypeHandle(Metho MethodDesc* typicalMD = unsafeAccessorMethod->LoadTypicalMethodDefinition(); Instantiation genericParams = isMethodParam ? typicalMD->GetMethodInstantiation() - : typicalMD->GetMethodTable()->GetInstantiation(); + : typicalMD->GetClassInstantiation(); if (0 <= paramIndex && paramIndex < genericParams.GetNumArgs()) ret = genericParams[paramIndex]; From 3cfee4c8840f2fa85a2ac4149edb1cb3b9ff85e8 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 10 May 2025 16:26:40 -0700 Subject: [PATCH 55/62] Remove specific Array branch. --- src/coreclr/vm/unsafeaccessors.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/coreclr/vm/unsafeaccessors.cpp b/src/coreclr/vm/unsafeaccessors.cpp index 80b8f16426f71b..0badb1570bee2e 100644 --- a/src/coreclr/vm/unsafeaccessors.cpp +++ b/src/coreclr/vm/unsafeaccessors.cpp @@ -257,20 +257,16 @@ namespace IfFailThrow(origSig.SkipExactlyOne()); bool isValid; - if (newTypeMaybe.IsArray()) + if (newTypeMaybe.IsByRef()) { - isValid = currHandle == TypeHandle{ g_pObjectClass }; + isValid = currHandle.IsByRef() + && currHandle.GetTypeParam() == TypeHandle{ g_pObjectClass }; } else if (newTypeMaybe.IsPointer()) { isValid = currHandle.IsPointer() && currHandle.GetTypeParam() == TypeHandle{ CoreLibBinder::GetClass(CLASS__VOID) }; } - else if (newTypeMaybe.IsByRef()) - { - isValid = currHandle.IsByRef() - && currHandle.GetTypeParam() == TypeHandle{ g_pObjectClass }; - } else { _ASSERTE(!newTypeMaybe.IsValueType()); From 27f9eb2c4d4bb14338ba3f1c6c277d7e8f4831c9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 12 May 2025 14:38:41 -0700 Subject: [PATCH 56/62] PR feedback --- .../Common/TypeSystem/IL/UnsafeAccessors.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 0808a57917656c..822c8fbb89730b 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Diagnostics; using System.Globalization; using System.Reflection; @@ -47,7 +48,7 @@ public static MethodIL TryGetIL(EcmaMethod method) result = TrySetTargetMethodSignature(ref context); if (result is not SetTargetResult.Success) { - return GenerateAccessorSpecificFailure(ref context, name, result, isSignatureLoad: true); + return GenerateAccessorSpecificFailure(ref context, name, result); } TypeDesc retType = context.DeclarationSignature.ReturnType; @@ -218,6 +219,7 @@ private struct GenerationContext public UnsafeAccessorKind Kind; public EcmaMethod Declaration; public MethodSignature DeclarationSignature; + public BitArray ReplacedSignatureElements; public TypeDesc TargetType; public bool IsTargetStatic; public MethodDesc TargetMethod; @@ -412,6 +414,7 @@ private enum SetTargetResult { Success, Missing, + MissingType, Ambiguous, Invalid, NotSupported @@ -589,6 +592,9 @@ private static SetTargetResult TrySetTargetMethodSignature(ref GenerationContext return SetTargetResult.Invalid; } + context.ReplacedSignatureElements ??= new BitArray(originalSignature.Length + 1, false); + context.ReplacedSignatureElements[parameter.SequenceNumber] = true; + if (isReturnValue) { updatedSignature.ReturnType = replacementType; @@ -650,7 +656,7 @@ private static SetTargetResult DecodeUnsafeAccessorType(EcmaMethod method, Custo }); return replacementType is null - ? SetTargetResult.Missing + ? SetTargetResult.MissingType : SetTargetResult.Success; } @@ -717,7 +723,7 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) for (int i = beginIndex; i < stubArgCount; ++i) { codeStream.EmitLdArg(i); - if (context.Declaration.Signature[i] != context.DeclarationSignature[i]) + if (context.ReplacedSignatureElements[i + 1]) { if (context.DeclarationSignature[i] is { Category: TypeFlags.Class } classType) { @@ -738,16 +744,9 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) // if the parameter is not marked as "in". // The "sequence number" for parameters is 1-based, whereas the parameter index is 0-based. ParameterHandle paramHandle = FindParameterForSequenceNumber(reader, ref parameterEnumerator, i + 1); - if (!paramHandle.IsNil) - { - if (!reader.GetParameter(paramHandle).Attributes.HasFlag(ParameterAttributes.In)) - { - localsToRestore[i] = local; - } - } - else + if (paramHandle.IsNil + || !reader.GetParameter(paramHandle).Attributes.HasFlag(ParameterAttributes.In)) { - // If there's no metadata for the parameter, it definitely doesn't have the [In] attribute. localsToRestore[i] = local; } } @@ -800,7 +799,7 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) return emit.Link(context.Declaration); } - private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, SetTargetResult result, bool isSignatureLoad = false) + private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, SetTargetResult result) { ILEmitter emit = new ILEmitter(); ILCodeStream codeStream = emit.NewCodeStream(); @@ -824,9 +823,8 @@ private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext co { thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowNotSupportedException"); } - else if (isSignatureLoad) + else if (result is SetTargetResult.MissingType) { - Debug.Assert(result is SetTargetResult.Missing); thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowUnavailableType"); } else From 68f807028c4674d59c2bafd52c478f8380bc79fc Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 12 May 2025 15:58:43 -0700 Subject: [PATCH 57/62] Add UnsafeAccessorType tests --- .../Reflection/UnsafeAccessor.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs index 2d9aadff272fc4..4c4a0f3fbc4e68 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs @@ -21,6 +21,7 @@ public static void Main () StaticFieldAccess.Test (); InstanceFieldAccess.Test (); InheritanceTest.Test (); + PrivateTypeTest.Test (); } // Trimmer doesn't use method overload resolution for UnsafeAccessor and instead marks entire method groups (by name) @@ -892,6 +893,41 @@ public static void Test () } } + [Kept] + class PrivateTypeTest + { + class ExternalType + { + [Kept] + [KeptMember (".ctor()")] + class PrivateType + { + [Kept] + private void TargetMethod () + { + } + } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + [return: KeptAttributeAttribute (typeof (UnsafeAccessorTypeAttribute))] + [return: UnsafeAccessorType ("Mono.Linker.Test.Cases.Reflection.UnsafeAccessor+PrivateTypeTest+ExternalType+PrivateType")] + extern static object TargetConstructor (); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void TargetMethod ([KeptAttributeAttribute(typeof(UnsafeAccessorTypeAttribute)), UnsafeAccessorType ("Mono.Linker.Test.Cases.Reflection.UnsafeAccessor+PrivateTypeTest+ExternalType+PrivateType")] object target); + + [Kept] + public static void Test () + { + TargetMethod (TargetConstructor()); + } + } + [Kept (By = Tool.Trimmer)] // NativeAOT doesn't preserve base type if it's not used anywhere class SuperBase { } @@ -904,3 +940,19 @@ class Base : SuperBase { } class Derived : Base { } } } + +// Polyfill for UnsafeAccessorTypeAttribute until we use an LKG runtime that has it. +namespace System.Runtime.CompilerServices +{ + [Kept] + [KeptBaseType(typeof(Attribute))] + [KeptAttributeAttribute(typeof(AttributeUsageAttribute))] + [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = false)] + public sealed class UnsafeAccessorTypeAttribute : Attribute + { + [Kept] + public UnsafeAccessorTypeAttribute (string typeName) + { + } + } +} From 393cb4a68551a919cb1d04c9262882d55ccc1dae Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 12 May 2025 16:02:44 -0700 Subject: [PATCH 58/62] Handle null ReplacedSignatureElements --- src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index 822c8fbb89730b..b6be413b9baa14 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -723,7 +723,7 @@ private static MethodIL GenerateAccessor(ref GenerationContext context) for (int i = beginIndex; i < stubArgCount; ++i) { codeStream.EmitLdArg(i); - if (context.ReplacedSignatureElements[i + 1]) + if (context.ReplacedSignatureElements?[i + 1] == true) { if (context.DeclarationSignature[i] is { Category: TypeFlags.Class } classType) { From f609f6238930d09bc5f8c2ac310ddf098cfb1f49 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 13 May 2025 08:28:34 -0700 Subject: [PATCH 59/62] Fix NAOT trimming test --- .../Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs index 4c4a0f3fbc4e68..58e56f120edbf2 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs @@ -944,13 +944,13 @@ class Derived : Base { } // Polyfill for UnsafeAccessorTypeAttribute until we use an LKG runtime that has it. namespace System.Runtime.CompilerServices { - [Kept] - [KeptBaseType(typeof(Attribute))] - [KeptAttributeAttribute(typeof(AttributeUsageAttribute))] + [Kept(By = Tool.Trimmer)] + [KeptBaseType(typeof(Attribute), By = Tool.Trimmer)] + [KeptAttributeAttribute(typeof(AttributeUsageAttribute), By = Tool.Trimmer)] [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = false)] public sealed class UnsafeAccessorTypeAttribute : Attribute { - [Kept] + [Kept(By = Tool.Trimmer)] public UnsafeAccessorTypeAttribute (string typeName) { } From 5f3129e18d8433ad46fb24a614fa72b757b71d60 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 13 May 2025 13:14:11 -0700 Subject: [PATCH 60/62] Update API documentation. --- .../UnsafeAccessorTypeAttribute.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs index 7876389e110142..759fd873ef0271 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs @@ -15,16 +15,29 @@ public sealed class UnsafeAccessorTypeAttribute : Attribute /// A fully qualified or partially qualified type name. /// /// is expected to follow the same rules as if it were being - /// passed to . + /// passed to . When unbound generics are involved they + /// should follow the IL syntax of referencing an ELEMENT_TYPE_VAR or ELEMENT_TYPE_MVAR using + /// the syntax of !N or !!N respectively, where N is the zero-based index of the + /// generic parameters. The generic rules defined for + /// apply to this attribute as well, meaning the arity and type of generic parameter must match + /// the target type. /// /// This attribute only has behavior on parameters or return values of methods marked with . /// /// This attribute should only be applied to parameters or return types of methods that are - /// typed as . Modifiers such as , , - /// , and are supported. + /// typed as follows: + /// + ///
    + ///
  • References should be typed as object.
  • + ///
  • Arrays should be typed as object.
  • + ///
  • Byref arguments should be typed with in, ref, or out to object.
  • + ///
  • Pointers should be typed as void*.
  • + ///
/// - /// Only reference types are supported to be looked up by this attribute. /// Value types are not supported. + /// + /// Due to lack of variance for byrefs, returns involving byrefs are not supported. This + /// specifically means that accessors for fields of inaccessible types are not supported. ///
public UnsafeAccessorTypeAttribute(string typeName) { From d516333c9b9fd1a0e3a49cd157fee1e98a83ca7d Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 13 May 2025 13:30:20 -0700 Subject: [PATCH 61/62] Update comments. --- .../Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs index 759fd873ef0271..149d303700ff5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs @@ -32,6 +32,8 @@ public sealed class UnsafeAccessorTypeAttribute : Attribute ///
  • Arrays should be typed as object.
  • ///
  • Byref arguments should be typed with in, ref, or out to object.
  • ///
  • Pointers should be typed as void*.
  • + ///
  • Byref arguments to reference types or arrays should be typed with in, ref, or out to object.
  • + ///
  • Byref arguments to pointer types should be typed with in, ref, or out to void*.
  • /// /// /// Value types are not supported. From be088860effba34b21a5f441385f37baa96650f0 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 13 May 2025 14:03:22 -0700 Subject: [PATCH 62/62] Feedback --- .../CompilerServices/UnsafeAccessorTypeAttribute.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs index 149d303700ff5d..41a4f47c60bdab 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorTypeAttribute.cs @@ -16,7 +16,7 @@ public sealed class UnsafeAccessorTypeAttribute : Attribute /// /// is expected to follow the same rules as if it were being /// passed to . When unbound generics are involved they - /// should follow the IL syntax of referencing an ELEMENT_TYPE_VAR or ELEMENT_TYPE_MVAR using + /// should follow the IL syntax of referencing a type or method generic variables using /// the syntax of !N or !!N respectively, where N is the zero-based index of the /// generic parameters. The generic rules defined for /// apply to this attribute as well, meaning the arity and type of generic parameter must match @@ -29,11 +29,10 @@ public sealed class UnsafeAccessorTypeAttribute : Attribute /// ///
      ///
    • References should be typed as object.
    • - ///
    • Arrays should be typed as object.
    • ///
    • Byref arguments should be typed with in, ref, or out to object.
    • - ///
    • Pointers should be typed as void*.
    • + ///
    • Unmanaged pointers should be typed as void*.
    • ///
    • Byref arguments to reference types or arrays should be typed with in, ref, or out to object.
    • - ///
    • Byref arguments to pointer types should be typed with in, ref, or out to void*.
    • + ///
    • Byref arguments to unmanaged pointer types should be typed with in, ref, or out to void*.
    • ///
    /// /// Value types are not supported.