diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index 6a8aaf6898b834..9bd2298eb9357a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -18,46 +18,18 @@ internal sealed unsafe class RtFieldInfo : RuntimeFieldInfo, IRuntimeFieldInfo // lazy caching private string? m_name; private RuntimeType? m_fieldType; - private InvocationFlags m_invocationFlags; - internal InvocationFlags InvocationFlags - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (m_invocationFlags & InvocationFlags.Initialized) != 0 ? - m_invocationFlags : InitializeInvocationFlags(); - } + private FieldAccessor? m_fieldAccessor; - [MethodImpl(MethodImplOptions.NoInlining)] - private InvocationFlags InitializeInvocationFlags() + internal FieldAccessor FieldAccessor { - Type? declaringType = DeclaringType; - - InvocationFlags invocationFlags = 0; - - // first take care of all the NO_INVOKE cases - if (declaringType != null && declaringType.ContainsGenericParameters) - { - invocationFlags |= InvocationFlags.NoInvoke; - } - - // If the invocationFlags are still 0, then - // this should be an usable field, determine the other flags - if (invocationFlags == 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - if ((m_fieldAttributes & FieldAttributes.InitOnly) != 0) - invocationFlags |= InvocationFlags.SpecialField; - - if ((m_fieldAttributes & FieldAttributes.HasFieldRVA) != 0) - invocationFlags |= InvocationFlags.SpecialField; - - // find out if the field type is one of the following: Primitive, Enum or Pointer - Type fieldType = FieldType; - if (fieldType.IsPointer || fieldType.IsEnum || fieldType.IsPrimitive) - invocationFlags |= InvocationFlags.FieldSpecialCast; + m_fieldAccessor ??= new FieldAccessor(this); + return m_fieldAccessor; } - - // must be last to avoid threading problems - return m_invocationFlags = invocationFlags | InvocationFlags.Initialized; } + #endregion #region Constructor @@ -75,28 +47,6 @@ internal RtFieldInfo( #endregion #region Internal Members - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void CheckConsistency(object? target) - { - // only test instance fields - if ((m_fieldAttributes & FieldAttributes.Static) != FieldAttributes.Static) - { - if (!m_declaringType.IsInstanceOfType(target)) - { - if (target == null) - { - throw new TargetException(SR.RFLCT_Targ_StatFldReqTarg); - } - else - { - throw new ArgumentException( - SR.Format(SR.Arg_FieldDeclTarget, - Name, m_declaringType, target.GetType())); - } - } - } - } - internal override bool CacheEquals(object? o) { return o is RtFieldInfo m && m.m_fieldHandle == m_fieldHandle; @@ -131,36 +81,7 @@ public override int GetHashCode() => #region FieldInfo Overrides [DebuggerStepThrough] [DebuggerHidden] - public override object? GetValue(object? obj) - { - InvocationFlags invocationFlags = InvocationFlags; - RuntimeType? declaringType = DeclaringType as RuntimeType; - - if ((invocationFlags & InvocationFlags.NoInvoke) != 0) - { - if (declaringType != null && DeclaringType!.ContainsGenericParameters) - throw new InvalidOperationException(SR.Arg_UnboundGenField); - - throw new FieldAccessException(); - } - - CheckConsistency(obj); - - RuntimeType fieldType = (RuntimeType)FieldType; - - bool domainInitialized = false; - if (declaringType == null) - { - return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - return retVal; - } - } + public override object? GetValue(object? obj) => FieldAccessor.GetValue(obj); public override object GetRawConstantValue() { throw new InvalidOperationException(); } @@ -180,45 +101,7 @@ public override int GetHashCode() => [DebuggerStepThrough] [DebuggerHidden] public override void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) - { - InvocationFlags invocationFlags = InvocationFlags; - RuntimeType? declaringType = DeclaringType as RuntimeType; - - if ((invocationFlags & InvocationFlags.NoInvoke) != 0) - { - if (declaringType != null && declaringType.ContainsGenericParameters) - throw new InvalidOperationException(SR.Arg_UnboundGenField); - - throw new FieldAccessException(); - } - - CheckConsistency(obj); - - RuntimeType fieldType = (RuntimeType)FieldType; - if (value is null) - { - if (fieldType.IsActualValueType) - { - fieldType.CheckValue(ref value, binder, culture, invokeAttr); - } - } - else if (!ReferenceEquals(value.GetType(), fieldType)) - { - fieldType.CheckValue(ref value, binder, culture, invokeAttr); - } - - bool domainInitialized = false; - if (declaringType is null) - { - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - } - } + => FieldAccessor.SetValue(obj, value, invokeAttr, binder, culture); [DebuggerStepThrough] [DebuggerHidden] diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index dab1d07145dbd9..a314edaab2a15f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -11,7 +11,7 @@ internal abstract class RuntimeFieldInfo : FieldInfo #region Private Data Members private readonly BindingFlags m_bindingFlags; protected readonly RuntimeTypeCache m_reflectedTypeCache; - protected readonly RuntimeType m_declaringType; + protected internal readonly RuntimeType m_declaringType; #endregion #region Constructor diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 73b9bb167f7a7f..244312cf59809c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1087,6 +1087,7 @@ public RuntimeFieldInfoStub(RuntimeFieldHandleInternal fieldHandle, object keepa private object? m_d; private int m_b; private object? m_e; + private object? m_f; private RuntimeFieldHandleInternal m_fieldHandle; #pragma warning restore 414, 169, IDE0044 @@ -1189,17 +1190,26 @@ internal static RuntimeType GetApproxDeclaringType(IRuntimeFieldInfo field) return type; } + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool IsFastPathSupported(RtFieldInfo field); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern int GetInstanceFieldOffset(RtFieldInfo field); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern IntPtr GetStaticFieldAddress(RtFieldInfo field); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int GetToken(RtFieldInfo field); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object? GetValue(RtFieldInfo field, object? instance, RuntimeType fieldType, RuntimeType? declaringType, ref bool domainInitialized); + internal static extern object? GetValue(RtFieldInfo field, object? instance, RuntimeType fieldType, RuntimeType? declaringType, ref bool isClassInitialized); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern object? GetValueDirect(RtFieldInfo field, RuntimeType fieldType, void* pTypedRef, RuntimeType? contextType); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void SetValue(RtFieldInfo field, object? obj, object? value, RuntimeType fieldType, FieldAttributes fieldAttr, RuntimeType? declaringType, ref bool domainInitialized); + internal static extern void SetValue(RtFieldInfo field, object? obj, object? value, RuntimeType fieldType, RuntimeType? declaringType, ref bool isClassInitialized); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void SetValueDirect(RtFieldInfo field, RuntimeType fieldType, void* pTypedRef, object? value, RuntimeType? contextType); 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 1329f1f7140b43..c74d76388b91a9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1438,7 +1438,6 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach private string? m_toString; private string? m_namespace; private readonly bool m_isGlobal; - private bool m_bIsDomainInitialized; private MemberInfoCache? m_methodInfoCache; private MemberInfoCache? m_constructorInfoCache; private MemberInfoCache? m_fieldInfoCache; @@ -1523,12 +1522,6 @@ internal Type[] FunctionPointerReturnAndParameterTypes } } - internal bool DomainInitialized - { - get => m_bIsDomainInitialized; - set => m_bIsDomainInitialized = value; - } - internal string? GetName(TypeNameKind kind) { switch (kind) @@ -1935,12 +1928,6 @@ internal object? GenericCache set => Cache.GenericCache = value; } - internal bool DomainInitialized - { - get => Cache.DomainInitialized; - set => Cache.DomainInitialized = value; - } - internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle) { return GetFieldInfo(RuntimeFieldHandle.GetApproxDeclaringType(fieldHandle), fieldHandle); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index ffc2badf3e802d..bd4a2090166522 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -337,13 +337,13 @@ DEFINE_FIELD(RT_TYPE_HANDLE, M_TYPE, m_type) DEFINE_CLASS(TYPE_NAME_PARSER, Reflection, TypeNameParser) DEFINE_METHOD(TYPE_NAME_PARSER, GET_TYPE_HELPER, GetTypeHelper, SM_Type_CharPtr_RuntimeAssembly_Bool_Bool_RetRuntimeType) -DEFINE_CLASS_U(Reflection, RtFieldInfo, NoClass) -DEFINE_FIELD_U(m_fieldHandle, ReflectFieldObject, m_pFD) +DEFINE_CLASS_U(Reflection, RtFieldInfo, NoClass) +DEFINE_FIELD_U(m_fieldHandle, ReflectFieldObject, m_pFD) DEFINE_CLASS(RT_FIELD_INFO, Reflection, RtFieldInfo) DEFINE_FIELD(RT_FIELD_INFO, HANDLE, m_fieldHandle) -DEFINE_CLASS_U(System, RuntimeFieldInfoStub, ReflectFieldObject) -DEFINE_FIELD_U(m_fieldHandle, ReflectFieldObject, m_pFD) +DEFINE_CLASS_U(System, RuntimeFieldInfoStub, ReflectFieldObject) +DEFINE_FIELD_U(m_fieldHandle, ReflectFieldObject, m_pFD) DEFINE_CLASS(STUBFIELDINFO, System, RuntimeFieldInfoStub) #if FOR_ILLINK DEFINE_METHOD(STUBFIELDINFO, CTOR, .ctor, IM_RetVoid) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 1ff1218e42944d..1af6a5055e6c33 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -242,6 +242,9 @@ FCFuncStart(gCOMFieldHandleNewFuncs) FCFuncElement("GetStaticFieldForGenericType", RuntimeFieldHandle::GetStaticFieldForGenericType) FCFuncElement("AcquiresContextFromThis", RuntimeFieldHandle::AcquiresContextFromThis) FCFuncElement("GetLoaderAllocator", RuntimeFieldHandle::GetLoaderAllocator) + FCFuncElement("IsFastPathSupported", RuntimeFieldHandle::IsFastPathSupported) + FCFuncElement("GetInstanceFieldOffset", RuntimeFieldHandle::GetInstanceFieldOffset) + FCFuncElement("GetStaticFieldAddress", RuntimeFieldHandle::GetStaticFieldAddress) FCFuncEnd() FCFuncStart(gCOMModuleHandleFuncs) diff --git a/src/coreclr/vm/field.cpp b/src/coreclr/vm/field.cpp index b2973d8b4c66fd..c2eab291cc42b0 100644 --- a/src/coreclr/vm/field.cpp +++ b/src/coreclr/vm/field.cpp @@ -180,8 +180,8 @@ void* FieldDesc::GetStaticAddress(void *base) void* ret = GetStaticAddressHandle(base); // Get the handle - // For value classes, the handle points at an OBJECTREF - // which holds the boxed value class, so dereference and unbox. + // For value classes, the handle points at an OBJECTREF + // which holds the boxed value class, so dereference and unbox. if (GetFieldType() == ELEMENT_TYPE_VALUETYPE && !IsRVA()) { OBJECTREF obj = ObjectToOBJECTREF(*(Object**) ret); @@ -211,11 +211,10 @@ MethodTable * FieldDesc::GetExactDeclaringType(MethodTable * ownerOrSubType) #endif // #ifndef DACCESS_COMPILE - // static value classes are actually stored in their boxed form. - // this means that their address moves. +// Static value classes are actually stored in their boxed form. +// This means that their address moves. PTR_VOID FieldDesc::GetStaticAddressHandle(PTR_VOID base) { - CONTRACTL { INSTANCE_CHECK; @@ -255,7 +254,6 @@ PTR_VOID FieldDesc::GetStaticAddressHandle(PTR_VOID base) } #endif // FEATURE_METADATA_UPDATER - if (IsRVA()) { Module* pModule = GetModule(); @@ -270,12 +268,10 @@ PTR_VOID FieldDesc::GetStaticAddressHandle(PTR_VOID base) PTR_VOID ret = PTR_VOID(dac_cast(base) + GetOffset()); - return ret; } - // These routines encapsulate the operation of getting and setting // fields. void FieldDesc::GetInstanceField(OBJECTREF o, VOID * pOutVal) diff --git a/src/coreclr/vm/field.h b/src/coreclr/vm/field.h index e2324787febaf5..c37fa4244dadf9 100644 --- a/src/coreclr/vm/field.h +++ b/src/coreclr/vm/field.h @@ -285,6 +285,14 @@ class FieldDesc SetOffset(FIELD_OFFSET_NEW_ENC); } + BOOL IsCollectible() + { + LIMITED_METHOD_DAC_CONTRACT; + + LoaderAllocator *pLoaderAllocator = GetApproxEnclosingMethodTable()->GetLoaderAllocator(); + return pLoaderAllocator->IsCollectible(); + } + // Was this field added by EnC? // If this is true, then this object is an instance of EnCFieldDesc BOOL IsEnCNew() @@ -518,7 +526,7 @@ class FieldDesc } } - VOID CheckRunClassInitThrowing() + void CheckRunClassInitThrowing() { CONTRACTL { diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index eb8462ed16f245..7d0c8f80becdb7 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -741,14 +741,14 @@ void InvokeUtil::ValidateObjectTarget(FieldDesc *pField, TypeHandle enclosingTyp // SetValidField // Given an target object, a value object and a field this method will set the field -// on the target object. The field must be validate before calling this. +// on the target object. The field must be validated before calling this. void InvokeUtil::SetValidField(CorElementType fldType, TypeHandle fldTH, FieldDesc *pField, OBJECTREF *target, OBJECTREF *valueObj, TypeHandle declaringType, - CLR_BOOL *pDomainInitialized) { + CLR_BOOL *pIsClassInitialized) { CONTRACTL { THROWS; GC_TRIGGERS; @@ -786,19 +786,18 @@ void InvokeUtil::SetValidField(CorElementType fldType, pDeclMT = pField->GetModule()->GetGlobalMethodTable(); } - if (*pDomainInitialized == FALSE) + if (*pIsClassInitialized == FALSE) { EX_TRY { pDeclMT->EnsureInstanceActive(); pDeclMT->CheckRunClassInitThrowing(); - - *pDomainInitialized = TRUE; + *pIsClassInitialized = pDeclMT->IsClassInited(); } EX_CATCH_THROWABLE(&Throwable); } #ifdef _DEBUG - else if (*pDomainInitialized == TRUE && !declaringType.IsNull()) + else if (*pIsClassInitialized == TRUE && !declaringType.IsNull()) CONSISTENCY_CHECK(declaringType.GetMethodTable()->CheckActivated()); #endif @@ -973,9 +972,7 @@ void InvokeUtil::SetValidField(CorElementType fldType, // GetFieldValue // This method will return an ARG_SLOT containing the value of the field. -// GetFieldValue -// This method will return an ARG_SLOT containing the value of the field. -OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJECTREF* target, TypeHandle declaringType, CLR_BOOL *pDomainInitialized) { +OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJECTREF* target, TypeHandle declaringType, CLR_BOOL *pIsClassInitialized) { CONTRACTL { THROWS; GC_TRIGGERS; @@ -999,7 +996,7 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ { pDeclMT = declaringType.GetMethodTable(); - // We don't allow getting the field just so we don't have more specical + // We don't allow getting the field just so we don't have more special // cases than we need to. Then we need at least the throw check to ensure // we don't allow data corruption. if (Nullable::IsNullableType(pDeclMT)) @@ -1013,23 +1010,21 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ pDeclMT = pField->GetModule()->GetGlobalMethodTable(); } - if (*pDomainInitialized == FALSE) + if (*pIsClassInitialized == FALSE) { EX_TRY { pDeclMT->EnsureInstanceActive(); pDeclMT->CheckRunClassInitThrowing(); - - *pDomainInitialized = TRUE; + *pIsClassInitialized = pDeclMT->IsClassInited(); } EX_CATCH_THROWABLE(&Throwable); } #ifdef _DEBUG - else if (*pDomainInitialized == TRUE && !declaringType.IsNull()) + else if (*pIsClassInitialized == TRUE && !declaringType.IsNull()) CONSISTENCY_CHECK(declaringType.GetMethodTable()->CheckActivated()); #endif - if(Throwable != NULL) { GCPROTECT_BEGIN(Throwable); @@ -1084,7 +1079,7 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ case ELEMENT_TYPE_VALUETYPE: { - // Value classes require createing a boxed version of the field and then + // Value classes require creating a boxed version of the field and then // copying from the source... // Allocate an object to return... _ASSERTE(!fieldType.IsTypeDesc()); diff --git a/src/coreclr/vm/invokeutil.h b/src/coreclr/vm/invokeutil.h index 0bd1577c7a19d3..b288c475aae07e 100644 --- a/src/coreclr/vm/invokeutil.h +++ b/src/coreclr/vm/invokeutil.h @@ -138,9 +138,9 @@ class InvokeUtil // SetValidField // Given an target object, a value object and a field this method will set the field // on the target object. The field must be validate before calling this. - static void SetValidField(CorElementType fldType, TypeHandle fldTH, FieldDesc* pField, OBJECTREF* target, OBJECTREF* value, TypeHandle declaringType, CLR_BOOL *pDomainInitialized); + static void SetValidField(CorElementType fldType, TypeHandle fldTH, FieldDesc* pField, OBJECTREF* target, OBJECTREF* value, TypeHandle declaringType, CLR_BOOL *pIsClassInitialized); - static OBJECTREF GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJECTREF* target, TypeHandle declaringType, CLR_BOOL *pDomainInitialized); + static OBJECTREF GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJECTREF* target, TypeHandle declaringType, CLR_BOOL *pIsClassInitialized); // ValidateObjectTarget // This method will validate the Object/Target relationship diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 91bbc95304ce1e..915e45deca0636 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1130,6 +1130,7 @@ class ReflectFieldObject : public BaseObjectWithCachedData INT32 m_empty2; OBJECTREF m_empty3; OBJECTREF m_empty4; + OBJECTREF m_empty5; FieldDesc * m_pFD; public: diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index e12ade403c6b5c..fb5cd978b85fdf 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -25,8 +25,9 @@ #include "dbginterface.h" #include "argdestination.h" -FCIMPL5(Object*, RuntimeFieldHandle::GetValue, ReflectFieldObject *pFieldUNSAFE, Object *instanceUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pDomainInitialized) { - CONTRACTL { +FCIMPL5(Object*, RuntimeFieldHandle::GetValue, ReflectFieldObject *pFieldUNSAFE, Object *instanceUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pIsClassInitialized) { + CONTRACTL + { FCALL_CHECK; } CONTRACTL_END; @@ -50,22 +51,11 @@ FCIMPL5(Object*, RuntimeFieldHandle::GetValue, ReflectFieldObject *pFieldUNSAFE, TypeHandle fieldType = gc.pFieldType->GetType(); TypeHandle declaringType = (gc.pDeclaringType != NULL) ? gc.pDeclaringType->GetType() : TypeHandle(); - Assembly *pAssem; - if (declaringType.IsNull()) - { - // global field - pAssem = gc.refField->GetField()->GetModule()->GetAssembly(); - } - else - { - pAssem = declaringType.GetAssembly(); - } - OBJECTREF rv = NULL; // not protected HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); // There can be no GC after this until the Object is returned. - rv = InvokeUtil::GetFieldValue(gc.refField->GetField(), fieldType, &gc.target, declaringType, pDomainInitialized); + rv = InvokeUtil::GetFieldValue(gc.refField->GetField(), fieldType, &gc.target, declaringType, pIsClassInitialized); HELPER_METHOD_FRAME_END(); return OBJECTREFToObject(rv); @@ -73,7 +63,8 @@ FCIMPL5(Object*, RuntimeFieldHandle::GetValue, ReflectFieldObject *pFieldUNSAFE, FCIMPLEND FCIMPL2(FC_BOOL_RET, ReflectionInvocation::CanValueSpecialCast, ReflectClassBaseObject *pValueTypeUNSAFE, ReflectClassBaseObject *pTargetTypeUNSAFE) { - CONTRACTL { + CONTRACTL + { FCALL_CHECK; PRECONDITION(CheckPointer(pValueTypeUNSAFE)); PRECONDITION(CheckPointer(pTargetTypeUNSAFE)); @@ -126,7 +117,8 @@ FCIMPLEND /// Allocate the value type and copy the optional value into it. /// FCIMPL2(Object*, ReflectionInvocation::AllocateValueType, ReflectClassBaseObject *pTargetTypeUNSAFE, Object *valueUNSAFE) { - CONTRACTL { + CONTRACTL + { FCALL_CHECK; PRECONDITION(CheckPointer(pTargetTypeUNSAFE)); PRECONDITION(CheckPointer(valueUNSAFE, NULL_OK)); @@ -169,8 +161,9 @@ FCIMPL2(Object*, ReflectionInvocation::AllocateValueType, ReflectClassBaseObject } FCIMPLEND -FCIMPL7(void, RuntimeFieldHandle::SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, DWORD attr, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pDomainInitialized) { - CONTRACTL { +FCIMPL6(void, RuntimeFieldHandle::SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pIsClassInitialized) { + CONTRACTL + { FCALL_CHECK; } CONTRACTL_END; @@ -195,24 +188,13 @@ FCIMPL7(void, RuntimeFieldHandle::SetValue, ReflectFieldObject *pFieldUNSAFE, Ob TypeHandle fieldType = gc.fieldType->GetType(); TypeHandle declaringType = gc.declaringType != NULL ? gc.declaringType->GetType() : TypeHandle(); - Assembly *pAssem; - if (declaringType.IsNull()) - { - // global field - pAssem = gc.refField->GetField()->GetModule()->GetAssembly(); - } - else - { - pAssem = declaringType.GetAssembly(); - } - FC_GC_POLL_NOT_NEEDED(); FieldDesc* pFieldDesc = gc.refField->GetField(); HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); - InvokeUtil::SetValidField(fieldType.GetVerifierCorElementType(), fieldType, pFieldDesc, &gc.target, &gc.value, declaringType, pDomainInitialized); + InvokeUtil::SetValidField(fieldType.GetVerifierCorElementType(), fieldType, pFieldDesc, &gc.target, &gc.value, declaringType, pIsClassInitialized); HELPER_METHOD_FRAME_END(); } @@ -225,7 +207,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_CreateInstanceForAnotherGenericParam QCall::ObjectHandleOnStack pInstantiatedObject ) { - CONTRACTL{ + CONTRACTL + { QCALL_CHECK; PRECONDITION(!pTypeHandle.AsTypeHandle().IsNull()); PRECONDITION(cInstArray >= 0); @@ -310,7 +293,8 @@ FCIMPLEND static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) { - CONTRACTL { + CONTRACTL + { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; @@ -344,7 +328,8 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) static BOOL IsActivationNeededForMethodInvoke(MethodDesc * pMD) { - CONTRACTL { + CONTRACTL + { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; @@ -875,7 +860,8 @@ struct SkipStruct { // This method is called by the GetMethod function and will crawl backward // up the stack for integer methods. static StackWalkAction SkipMethods(CrawlFrame* frame, VOID* data) { - CONTRACTL { + CONTRACTL + { NOTHROW; GC_NOTRIGGER; MODE_ANY; @@ -936,8 +922,9 @@ FCIMPL1(ReflectMethodObject*, RuntimeMethodHandle::GetCurrentMethod, StackCrawlM } FCIMPLEND -static OBJECTREF DirectObjectFieldGet(FieldDesc *pField, TypeHandle fieldType, TypeHandle enclosingType, TypedByRef *pTarget, CLR_BOOL *pDomainInitialized) { - CONTRACTL { +static OBJECTREF DirectObjectFieldGet(FieldDesc *pField, TypeHandle fieldType, TypeHandle enclosingType, TypedByRef *pTarget, CLR_BOOL *pIsClassInitialized) { + CONTRACTL + { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; @@ -954,13 +941,14 @@ static OBJECTREF DirectObjectFieldGet(FieldDesc *pField, TypeHandle fieldType, T } InvokeUtil::ValidateObjectTarget(pField, enclosingType, &objref); - refRet = InvokeUtil::GetFieldValue(pField, fieldType, &objref, enclosingType, pDomainInitialized); + refRet = InvokeUtil::GetFieldValue(pField, fieldType, &objref, enclosingType, pIsClassInitialized); GCPROTECT_END(); return refRet; } FCIMPL4(Object*, RuntimeFieldHandle::GetValueDirect, ReflectFieldObject *pFieldUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, TypedByRef *pTarget, ReflectClassBaseObject *pDeclaringTypeUNSAFE) { - CONTRACTL { + CONTRACTL + { FCALL_CHECK; } CONTRACTL_END; @@ -994,9 +982,9 @@ FCIMPL4(Object*, RuntimeFieldHandle::GetValueDirect, ReflectFieldObject *pFieldU _ASSERTE(gc.refDeclaringType == NULL || !gc.refDeclaringType->GetType().IsTypeDesc()); MethodTable *pEnclosingMT = (gc.refDeclaringType != NULL ? gc.refDeclaringType->GetType() : TypeHandle()).AsMethodTable(); - CLR_BOOL domainInitialized = FALSE; + CLR_BOOL isClassInitialized = FALSE; if (pField->IsStatic() || !targetType.IsValueType()) { - refRet = DirectObjectFieldGet(pField, fieldType, TypeHandle(pEnclosingMT), pTarget, &domainInitialized); + refRet = DirectObjectFieldGet(pField, fieldType, TypeHandle(pEnclosingMT), pTarget, &isClassInitialized); goto lExit; } @@ -1059,8 +1047,9 @@ lExit: ; } FCIMPLEND -static void DirectObjectFieldSet(FieldDesc *pField, TypeHandle fieldType, TypeHandle enclosingType, TypedByRef *pTarget, OBJECTREF *pValue, CLR_BOOL *pDomainInitialized) { - CONTRACTL { +static void DirectObjectFieldSet(FieldDesc *pField, TypeHandle fieldType, TypeHandle enclosingType, TypedByRef *pTarget, OBJECTREF *pValue, CLR_BOOL *pIsClassInitialized) { + CONTRACTL + { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; @@ -1078,12 +1067,13 @@ static void DirectObjectFieldSet(FieldDesc *pField, TypeHandle fieldType, TypeHa // Validate the target/fld type relationship InvokeUtil::ValidateObjectTarget(pField, enclosingType, &objref); - InvokeUtil::SetValidField(pField->GetFieldType(), fieldType, pField, &objref, pValue, enclosingType, pDomainInitialized); + InvokeUtil::SetValidField(pField->GetFieldType(), fieldType, pField, &objref, pValue, enclosingType, pIsClassInitialized); GCPROTECT_END(); } FCIMPL5(void, RuntimeFieldHandle::SetValueDirect, ReflectFieldObject *pFieldUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, TypedByRef *pTarget, Object *valueUNSAFE, ReflectClassBaseObject *pContextTypeUNSAFE) { - CONTRACTL { + CONTRACTL + { FCALL_CHECK; } CONTRACTL_END; @@ -1124,9 +1114,9 @@ FCIMPL5(void, RuntimeFieldHandle::SetValueDirect, ReflectFieldObject *pFieldUNSA // Verify that the value passed can be widened into the target InvokeUtil::ValidField(fieldType, &gc.oValue); - CLR_BOOL domainInitialized = FALSE; + CLR_BOOL isClassInitialized = FALSE; if (pField->IsStatic() || !targetType.IsValueType()) { - DirectObjectFieldSet(pField, fieldType, TypeHandle(pEnclosingMT), pTarget, &gc.oValue, &domainInitialized); + DirectObjectFieldSet(pField, fieldType, TypeHandle(pEnclosingMT), pTarget, &gc.oValue, &isClassInitialized); goto lExit; } @@ -1243,6 +1233,85 @@ lExit: ; } FCIMPLEND +static bool IsFastPathSupportedHelper(FieldDesc* pFieldDesc) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pFieldDesc)); + } + CONTRACTL_END; + + return !pFieldDesc->IsThreadStatic() && + !pFieldDesc->IsEnCNew() && + !(pFieldDesc->IsCollectible() && pFieldDesc->IsStatic()); +} + +FCIMPL1(FC_BOOL_RET, RuntimeFieldHandle::IsFastPathSupported, ReflectFieldObject *pFieldUNSAFE) +{ + FCALL_CONTRACT; + + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(pFieldUNSAFE); + _ASSERTE(refField != NULL); + + FieldDesc* pFieldDesc = refField->GetField(); + return IsFastPathSupportedHelper(pFieldDesc) ? TRUE : FALSE; +} +FCIMPLEND + +FCIMPL1(INT32, RuntimeFieldHandle::GetInstanceFieldOffset, ReflectFieldObject *pFieldUNSAFE) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(pFieldUNSAFE)); + } + CONTRACTL_END; + + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(pFieldUNSAFE); + _ASSERTE(refField != NULL); + + FieldDesc* pFieldDesc = refField->GetField(); + _ASSERTE(!pFieldDesc->IsStatic()); + + // IsFastPathSupported needs to checked before calling this method. + _ASSERTE(IsFastPathSupportedHelper(pFieldDesc)); + + return pFieldDesc->GetOffset(); +} +FCIMPLEND + +FCIMPL1(void*, RuntimeFieldHandle::GetStaticFieldAddress, ReflectFieldObject *pFieldUNSAFE) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(pFieldUNSAFE)); + } + CONTRACTL_END; + + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(pFieldUNSAFE); + _ASSERTE(refField != NULL); + + FieldDesc* pFieldDesc = refField->GetField(); + _ASSERTE(pFieldDesc->IsStatic()); + + // IsFastPathSupported needs to checked before calling this method. + _ASSERTE(IsFastPathSupportedHelper(pFieldDesc)); + + PTR_BYTE base = 0; + if (!pFieldDesc->IsRVA()) + { + // For RVA the base is ignored and offset is used. + base = pFieldDesc->GetBase(); + } + + return PTR_VOID(base + pFieldDesc->GetOffset()); +} +FCIMPLEND + extern "C" void QCALLTYPE ReflectionInvocation_CompileMethod(MethodDesc * pMD) { QCALL_CONTRACT; @@ -1313,7 +1382,8 @@ static void PrepareMethodHelper(MethodDesc * pMD) // It does not walk a subset of callgraph to provide CER guarantees. extern "C" void QCALLTYPE ReflectionInvocation_PrepareMethod(MethodDesc *pMD, TypeHandle *pInstantiation, UINT32 cInstantiation) { - CONTRACTL { + CONTRACTL + { QCALL_CHECK; PRECONDITION(pMD != NULL); PRECONDITION(CheckPointer(pInstantiation, NULL_OK)); @@ -1366,7 +1436,8 @@ extern "C" void QCALLTYPE ReflectionInvocation_PrepareMethod(MethodDesc *pMD, Ty // was prepared prior to the Combine. FCIMPL1(void, ReflectionInvocation::PrepareDelegate, Object* delegateUNSAFE) { - CONTRACTL { + CONTRACTL + { FCALL_CHECK; PRECONDITION(CheckPointer(delegateUNSAFE, NULL_OK)); } @@ -1427,7 +1498,8 @@ FCIMPLEND FCIMPL4(void, ReflectionInvocation::MakeTypedReference, TypedByRef * value, Object* targetUNSAFE, ArrayBase* fldsUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE) { - CONTRACTL { + CONTRACTL + { FCALL_CHECK; PRECONDITION(CheckPointer(targetUNSAFE)); PRECONDITION(CheckPointer(fldsUNSAFE)); @@ -1699,7 +1771,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo( BOOL* pfCtorIsPublic ) { - CONTRACTL{ + CONTRACTL + { QCALL_CHECK; PRECONDITION(CheckPointer(ppfnAllocator)); PRECONDITION(CheckPointer(pvAllocatorFirstArg)); @@ -1818,7 +1891,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo( FCIMPL1(Object*, RuntimeTypeHandle::AllocateComObject, void* pClassFactory) { - CONTRACTL{ + CONTRACTL + { FCALL_CHECK; PRECONDITION(CheckPointer(pClassFactory)); } @@ -1867,7 +1941,8 @@ extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectIn PCODE* ppfnAllocator, void** pvAllocatorFirstArg) { - CONTRACTL{ + CONTRACTL + { QCALL_CHECK; PRECONDITION(CheckPointer(ppfnAllocator)); PRECONDITION(CheckPointer(pvAllocatorFirstArg)); diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 694a25a30623da..ec7864ad2bc499 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -292,10 +292,13 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_Destroy(MethodDesc * pMethod); class RuntimeFieldHandle { public: - static FCDECL5(Object*, GetValue, ReflectFieldObject *pFieldUNSAFE, Object *instanceUNSAFE, ReflectClassBaseObject *pFieldType, ReflectClassBaseObject *pDeclaringType, CLR_BOOL *pDomainInitialized); - static FCDECL7(void, SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldType, DWORD attr, ReflectClassBaseObject *pDeclaringType, CLR_BOOL *pDomainInitialized); + static FCDECL5(Object*, GetValue, ReflectFieldObject *pFieldUNSAFE, Object *instanceUNSAFE, ReflectClassBaseObject *pFieldType, ReflectClassBaseObject *pDeclaringType, CLR_BOOL *pIsClassInitialized); + static FCDECL6(void, SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldType, ReflectClassBaseObject *pDeclaringType, CLR_BOOL *pIsClassInitialized); static FCDECL4(Object*, GetValueDirect, ReflectFieldObject *pFieldUNSAFE, ReflectClassBaseObject *pFieldType, TypedByRef *pTarget, ReflectClassBaseObject *pDeclaringType); static FCDECL5(void, SetValueDirect, ReflectFieldObject *pFieldUNSAFE, ReflectClassBaseObject *pFieldType, TypedByRef *pTarget, Object *valueUNSAFE, ReflectClassBaseObject *pContextType); + static FCDECL1(FC_BOOL_RET, IsFastPathSupported, ReflectFieldObject *pField); + static FCDECL1(INT32, GetInstanceFieldOffset, ReflectFieldObject *pField); + static FCDECL1(void*, GetStaticFieldAddress, ReflectFieldObject *pField); static FCDECL1(StringObject*, GetName, ReflectFieldObject *pFieldUNSAFE); static FCDECL1(LPCUTF8, GetUtf8Name, FieldDesc *pField); diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index bf27c2f870db02..09348ec9d3ce43 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4298,6 +4298,9 @@ This operation is not available because the reflection support was disabled at compile time. + + Cannot set initonly static field '{0}' after type '{1}' is initialized. + This AssemblyBuilder instance doesn't support saving. Use AssemblyBuilder.DefinePersistedAssembly to create an AssemblyBuilder instance that supports saving. 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 94089d1018d8d1..c9e500d22af6b6 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 @@ -702,6 +702,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs new file mode 100644 index 00000000000000..2370d8fff263a7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs @@ -0,0 +1,508 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Reflection +{ + internal sealed class FieldAccessor + { + private readonly RtFieldInfo _fieldInfo; + private IntPtr _addressOrOffset; + private unsafe MethodTable* _methodTable; + private FieldAccessorType _fieldAccessType; + + internal FieldAccessor(FieldInfo fieldInfo) + { + _fieldInfo = (RtFieldInfo)fieldInfo; + Debug.Assert(_fieldInfo.m_declaringType != null); + + if (_fieldInfo.m_declaringType.ContainsGenericParameters || + _fieldInfo.m_declaringType.IsNullableOfT) + { + _fieldAccessType = FieldAccessorType.NoInvoke; + } + else + { + _fieldAccessType = FieldAccessorType.SlowPathUntilClassInitialized; + } + } + + private void Initialize() + { + if (!RuntimeFieldHandle.IsFastPathSupported(_fieldInfo)) + { + // Currently this is true for [ThreadStatic] cases, for fields added from EnC, and for fields on unloadable types. + _fieldAccessType = FieldAccessorType.SlowPath; + return; + } + + RuntimeType fieldType = (RuntimeType)_fieldInfo.FieldType; + + unsafe + { + if (_fieldInfo.IsStatic) + { + _addressOrOffset = RuntimeFieldHandle.GetStaticFieldAddress(_fieldInfo); + + if (fieldType.IsValueType) + { + if (fieldType.IsEnum) + { + _fieldAccessType = GetPrimitiveAccessorTypeForStatic(fieldType.GetEnumUnderlyingType()); + _methodTable = (MethodTable*)fieldType.TypeHandle.Value; + } + else if (RuntimeTypeHandle.GetCorElementType(fieldType) == CorElementType.ELEMENT_TYPE_VALUETYPE) + { + // The runtime stores non-primitive value types as a boxed value. + _fieldAccessType = FieldAccessorType.StaticValueTypeBoxed; + _methodTable = (MethodTable*)fieldType.TypeHandle.Value; + } + else + { + _fieldAccessType = GetPrimitiveAccessorTypeForStatic(fieldType); + _methodTable = (MethodTable*)fieldType.TypeHandle.Value; + } + } + else if (fieldType.IsPointer) + { + _fieldAccessType = FieldAccessorType.StaticPointerType; + } + else if (fieldType.IsFunctionPointer) + { + _fieldAccessType = GetIntPtrAccessorTypeForStatic(); + _methodTable = (MethodTable*)typeof(IntPtr).TypeHandle.Value; + } + else + { + _fieldAccessType = FieldAccessorType.StaticReferenceType; + } + } + else + { + _addressOrOffset = RuntimeFieldHandle.GetInstanceFieldOffset(_fieldInfo); + + if (fieldType.IsEnum) + { + _fieldAccessType = GetPrimitiveAccessorTypeForInstance(fieldType.GetEnumUnderlyingType()); + _methodTable = (MethodTable*)fieldType.TypeHandle.Value; + } + else if (fieldType.IsValueType) + { + _fieldAccessType = GetPrimitiveAccessorTypeForInstance(fieldType); + _methodTable = (MethodTable*)fieldType.TypeHandle.Value; + } + else if (fieldType.IsPointer) + { + _fieldAccessType = FieldAccessorType.InstancePointerType; + } + else if (fieldType.IsFunctionPointer) + { + _fieldAccessType = GetIntPtrAccessorTypeForInstance(); + _methodTable = (MethodTable*)typeof(IntPtr).TypeHandle.Value; + } + else + { + _fieldAccessType = FieldAccessorType.InstanceReferenceType; + } + } + } + } + + public object? GetValue(object? obj) + { + bool isClassInitialized; + + unsafe + { + switch (_fieldAccessType) + { + case FieldAccessorType.InstanceReferenceType: + VerifyTarget(obj); + Debug.Assert(obj != null); + return Volatile.Read(ref Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset))); + + case FieldAccessorType.InstanceValueType: + case FieldAccessorType.InstanceValueTypeSize1: + case FieldAccessorType.InstanceValueTypeSize2: + case FieldAccessorType.InstanceValueTypeSize4: + case FieldAccessorType.InstanceValueTypeSize8: + VerifyTarget(obj); + Debug.Assert(obj != null); + return RuntimeHelpers.Box( + _methodTable, + ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)); + + case FieldAccessorType.InstancePointerType: + VerifyTarget(obj); + Debug.Assert(obj != null); + return Pointer.Box( + (void*)Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)), + _fieldInfo.FieldType); + + case FieldAccessorType.StaticReferenceType: + return Volatile.Read(ref Unsafe.As(ref *(IntPtr*)_addressOrOffset)); + + case FieldAccessorType.StaticValueType: + case FieldAccessorType.StaticValueTypeSize1: + case FieldAccessorType.StaticValueTypeSize2: + case FieldAccessorType.StaticValueTypeSize4: + case FieldAccessorType.StaticValueTypeSize8: + return RuntimeHelpers.Box(_methodTable, ref Unsafe.AsRef(_addressOrOffset.ToPointer())); + + case FieldAccessorType.StaticValueTypeBoxed: + // Re-box the value. + return RuntimeHelpers.Box( + _methodTable, + ref Unsafe.As(ref *(IntPtr*)_addressOrOffset).GetRawData()); + + case FieldAccessorType.StaticPointerType: + return Pointer.Box((void*)Unsafe.As( + ref Unsafe.AsRef(_addressOrOffset.ToPointer())), _fieldInfo.FieldType); + + case FieldAccessorType.SlowPathUntilClassInitialized: + if (!IsStatic()) + { + VerifyTarget(obj); + } + + isClassInitialized = false; + object? ret = RuntimeFieldHandle.GetValue(_fieldInfo, obj, (RuntimeType)_fieldInfo.FieldType, _fieldInfo.m_declaringType, ref isClassInitialized); + if (isClassInitialized) + { + Initialize(); + } + + return ret; + + case FieldAccessorType.SlowPath: + if (!IsStatic()) + { + VerifyTarget(obj); + } + + isClassInitialized = true; + return RuntimeFieldHandle.GetValue(_fieldInfo, obj, (RuntimeType)_fieldInfo.FieldType, _fieldInfo.m_declaringType, ref isClassInitialized); + + case FieldAccessorType.NoInvoke: + if (_fieldInfo.DeclaringType is not null && _fieldInfo.DeclaringType.ContainsGenericParameters) + throw new InvalidOperationException(SR.Arg_UnboundGenField); + + if (_fieldInfo.DeclaringType is not null && ((RuntimeType)_fieldInfo.FieldType).IsNullableOfT) + throw new NotSupportedException(); + + throw new FieldAccessException(); + + default: + Debug.Assert(false, "Unknown enum value"); + return null; + } + } + } + + public void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) + { + bool isClassInitialized; + + unsafe + { + switch (_fieldAccessType) + { + case FieldAccessorType.InstanceReferenceType: + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + Debug.Assert(obj != null); + Volatile.Write( + ref Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)), + value); + return; + + case FieldAccessorType.InstanceValueTypeSize1: + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + Debug.Assert(obj != null); + Volatile.Write( + ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset), + value!.GetRawData()); + return; + + case FieldAccessorType.InstanceValueTypeSize2: + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + Debug.Assert(obj != null); + Volatile.Write( + ref Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)), + Unsafe.As(ref value!.GetRawData())); + return; + + case FieldAccessorType.InstanceValueTypeSize4: + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + Debug.Assert(obj != null); + Volatile.Write( + ref Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)), + Unsafe.As(ref value!.GetRawData())); + return; + + case FieldAccessorType.InstanceValueTypeSize8: + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + Debug.Assert(obj != null); + Volatile.Write( + ref Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)), + Unsafe.As(ref value!.GetRawData())); + return; + + case FieldAccessorType.StaticReferenceType: + VerifyStaticField(ref value, invokeAttr, binder, culture); + Volatile.Write(ref Unsafe.As(ref *(IntPtr*)_addressOrOffset), value); + return; + + case FieldAccessorType.StaticValueTypeSize1: + VerifyStaticField(ref value, invokeAttr, binder, culture); + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + value!.GetRawData()); + return; + + case FieldAccessorType.StaticValueTypeSize2: + VerifyStaticField(ref value, invokeAttr, binder, culture); + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + Unsafe.As(ref value!.GetRawData())); + return; + + case FieldAccessorType.StaticValueTypeSize4: + VerifyStaticField(ref value, invokeAttr, binder, culture); + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + Unsafe.As(ref value!.GetRawData())); + return; + + case FieldAccessorType.StaticValueTypeSize8: + VerifyStaticField(ref value, invokeAttr, binder, culture); + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + Unsafe.As(ref value!.GetRawData())); + return; + + case FieldAccessorType.SlowPathUntilClassInitialized: + if (IsStatic()) + { + VerifyStaticField(ref value, invokeAttr, binder, culture); + } + else + { + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + } + + isClassInitialized = false; + RuntimeFieldHandle.SetValue(_fieldInfo, obj, value, (RuntimeType)_fieldInfo.FieldType, _fieldInfo.m_declaringType, ref isClassInitialized); + if (isClassInitialized) + { + Initialize(); + } + + return; + + case FieldAccessorType.NoInvoke: + if (_fieldInfo.DeclaringType is not null && _fieldInfo.DeclaringType.ContainsGenericParameters) + throw new InvalidOperationException(SR.Arg_UnboundGenField); + + throw new FieldAccessException(); + } + } + + // All other cases use the slow path. + if (IsStatic()) + { + VerifyStaticField(ref value, invokeAttr, binder, culture); + } + else + { + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + } + + isClassInitialized = true; + RuntimeFieldHandle.SetValue(_fieldInfo, obj, value, (RuntimeType)_fieldInfo.FieldType, _fieldInfo.m_declaringType, ref isClassInitialized); + } + + private bool IsStatic() => (_fieldInfo.Attributes & FieldAttributes.Static) == FieldAttributes.Static; + + private void VerifyStaticField(ref object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) + { + VerifyInitOnly(); + CheckValue(ref value, invokeAttr, binder, culture); + } + + private void VerifyInstanceField(object? obj, ref object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) + { + VerifyTarget(obj); + CheckValue(ref value, invokeAttr, binder, culture); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void VerifyTarget(object? target) + { + Debug.Assert(!IsStatic()); + + if (!_fieldInfo.m_declaringType.IsInstanceOfType(target)) + { + if (target == null) + { + ThrowHelperTargetException(); + } + else + { + ThrowHelperArgumentException(target, _fieldInfo); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckValue(ref object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) + { + if (value is null) + { + if (((RuntimeType)_fieldInfo.FieldType).IsActualValueType) + { + ((RuntimeType)_fieldInfo.FieldType).CheckValue(ref value, binder, culture, invokeAttr); + } + } + else if (!ReferenceEquals(value.GetType(), _fieldInfo.FieldType)) + { + ((RuntimeType)_fieldInfo.FieldType).CheckValue(ref value, binder, culture, invokeAttr); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void VerifyInitOnly() + { + Debug.Assert(IsStatic()); + + if ((_fieldInfo.Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly && + _fieldAccessType != FieldAccessorType.SlowPathUntilClassInitialized) + { + ThrowHelperFieldAccessException(_fieldInfo.Name, _fieldInfo.DeclaringType?.FullName); + } + } + + /// + /// Currently we only optimize for primitive types and not all value types. Primitive types support atomic write operations, are + /// not boxed by the runtime when stored as a static field, and don't need special nullable, GC or alignment checks. + /// + private static FieldAccessorType GetPrimitiveAccessorTypeForInstance(Type fieldType) + { + FieldAccessorType accessorType = FieldAccessorType.InstanceValueType; + + if (fieldType == typeof(byte) || + fieldType == typeof(sbyte) || + fieldType == typeof(bool)) + accessorType = FieldAccessorType.InstanceValueTypeSize1; + else if (fieldType == typeof(short) || + fieldType == typeof(ushort) || + fieldType == typeof(char)) + accessorType = FieldAccessorType.InstanceValueTypeSize2; + else if (fieldType == typeof(int) || + fieldType == typeof(uint) || + fieldType == typeof(float)) + accessorType = FieldAccessorType.InstanceValueTypeSize4; + else if (fieldType == typeof(long) || + fieldType == typeof(ulong) || + fieldType == typeof(double)) + accessorType = FieldAccessorType.InstanceValueTypeSize8; + else if (fieldType == typeof(IntPtr) || + fieldType == typeof(UIntPtr)) + accessorType = GetIntPtrAccessorTypeForInstance(); + + return accessorType; + } + + private static FieldAccessorType GetPrimitiveAccessorTypeForStatic(Type fieldType) + { + FieldAccessorType accessorType = FieldAccessorType.StaticValueType; + + if (fieldType == typeof(byte) || + fieldType == typeof(sbyte) || + fieldType == typeof(bool)) + accessorType = FieldAccessorType.StaticValueTypeSize1; + else if (fieldType == typeof(short) || + fieldType == typeof(ushort) || + fieldType == typeof(char)) + accessorType = FieldAccessorType.StaticValueTypeSize2; + else if (fieldType == typeof(int) || + fieldType == typeof(uint) || + fieldType == typeof(float)) + accessorType = FieldAccessorType.StaticValueTypeSize4; + else if (fieldType == typeof(long) || + fieldType == typeof(ulong) || + fieldType == typeof(double)) + accessorType = FieldAccessorType.StaticValueTypeSize8; + else if (fieldType == typeof(IntPtr) || + fieldType == typeof(UIntPtr)) + accessorType = GetIntPtrAccessorTypeForStatic(); + + return accessorType; + } + + private static FieldAccessorType GetIntPtrAccessorTypeForInstance() + { + FieldAccessorType accessorType = FieldAccessorType.InstanceValueType; + + if (IntPtr.Size == 4) + { + accessorType = FieldAccessorType.InstanceValueTypeSize4; + } + else if (IntPtr.Size == 8) + { + accessorType = FieldAccessorType.InstanceValueTypeSize8; + } + + return accessorType; + } + + private static FieldAccessorType GetIntPtrAccessorTypeForStatic() + { + FieldAccessorType accessorType = FieldAccessorType.StaticValueType; + + if (IntPtr.Size == 4) + { + accessorType = FieldAccessorType.StaticValueTypeSize4; + } + else if (IntPtr.Size == 8) + { + accessorType = FieldAccessorType.StaticValueTypeSize8; + } + + return accessorType; + } + + private static void ThrowHelperTargetException() => throw new TargetException(SR.RFLCT_Targ_StatFldReqTarg); + + private static void ThrowHelperArgumentException(object target, FieldInfo fieldInfo) => + throw new ArgumentException(SR.Format(SR.Arg_FieldDeclTarget, fieldInfo.Name, fieldInfo.DeclaringType, target.GetType())); + + private static void ThrowHelperFieldAccessException(string fieldName, string? declaringTypeName) => + throw new FieldAccessException(SR.Format(SR.RFLCT_CannotSetInitonlyStaticField, fieldName, declaringTypeName)); + + private enum FieldAccessorType + { + InstanceReferenceType, + InstanceValueType, + InstanceValueTypeSize1, + InstanceValueTypeSize2, + InstanceValueTypeSize4, + InstanceValueTypeSize8, + InstancePointerType, + StaticReferenceType, + StaticValueType, + StaticValueTypeSize1, + StaticValueTypeSize2, + StaticValueTypeSize4, + StaticValueTypeSize8, + StaticValueTypeBoxed, + StaticPointerType, + SlowPathUntilClassInitialized, + SlowPath, + NoInvoke, + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/FieldInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/FieldInfoTests.cs index 5f107d9b44e585..d854e19c71ade7 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/FieldInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/FieldInfoTests.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Xunit; @@ -29,7 +31,7 @@ public void SetValue_ConstantField_ThrowsFieldAccessException(string field, obje [Fact] public void SetValue_ReadonlyField() { - FieldInfo fieldInfo = typeof(FieldInfoTests).GetTypeInfo().GetDeclaredField("readonlyIntField"); + FieldInfo fieldInfo = typeof(FieldInfoTests).GetTypeInfo().GetDeclaredField(nameof(readonlyIntField)); FieldInfoTests myInstance = new FieldInfoTests(); object current = fieldInfo.GetValue(myInstance); @@ -55,11 +57,39 @@ public static void CustomAttributes(Type type, string expectedToString) public static IEnumerable GetValue_TestData() { - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.s_intField), new FieldInfoTests(), 100 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.s_intField), null, 100 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.intField), new FieldInfoTests(), 101 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.s_stringField), new FieldInfoTests(), "static" }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.stringField), new FieldInfoTests(), "non static" }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_boolField), null, true }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_intField), new FieldInfoTests(), 100 }; // Non-null 'obj' ignored. + yield return new object[] { typeof(FieldInfoTests), nameof(s_intField), null, 100 }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_stringField), null, "static" }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_myStruct), null, new MyStruct() }; + + yield return new object[] { typeof(FieldInfoTests), nameof(boolField), new FieldInfoTests(), true }; + yield return new object[] { typeof(FieldInfoTests), nameof(intField), new FieldInfoTests(), 101 }; + yield return new object[] { typeof(FieldInfoTests), nameof(stringField), new FieldInfoTests(), "non static" }; + yield return new object[] { typeof(FieldInfoTests), nameof(_myStruct), new FieldInfoTests(), new MyStruct() }; + + yield return new object[] { typeof(FieldInfoTests), nameof(shortEnumField), new FieldInfoTests(), default(ShortEnum) }; + yield return new object[] { typeof(FieldInfoTests), nameof(intEnumField), new FieldInfoTests(), default(IntEnum) }; + yield return new object[] { typeof(FieldInfoTests), nameof(longEnumField), new FieldInfoTests(), default(LongEnum) }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_shortEnumField), null, default(ShortEnum) }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_intEnumField), null, default(IntEnum) }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_longEnumField), null, default(LongEnum) }; + + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_boolField), null, true }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_intField), null, 100 }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_intField), null, 100 }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_constIntField), null, 102 }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_stringField), null, "static" }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_readonlyStringField), null, "readonlyStatic" }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_objectField), null, MyStruct.s_objectField }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_intPtr), null, MyStruct.s_intPtrForComparison }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_rvaIntField), null, new int[] { 1, 2, 3 } }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.threadStatic_intField), null, 100 }; + + yield return new object[] { typeof(MyStruct), nameof(MyStruct.stringField), new MyStruct(), "non static" }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.intField), new MyStruct(), 101 }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.intPtr), new MyStruct(), MyStruct.intPtrForComparison }; + yield return new object[] { typeof(MyStruct_OnlyPrimitiveTypes), nameof(MyStruct_OnlyPrimitiveTypes.intField), new MyStruct_OnlyPrimitiveTypes(), 101 }; } [Theory] @@ -68,6 +98,76 @@ public void GetValue(Type type, string name, object obj, object expected) { FieldInfo fieldInfo = GetField(type, name); Assert.Equal(expected, fieldInfo.GetValue(obj)); + + // Perform a second time to rule out cases of slow-path vs. fast-path. + Assert.Equal(expected, fieldInfo.GetValue(obj)); + } + + public static IEnumerable GetValue_TestData_WithFunctionPointers() + { + yield return new object[] { typeof(MyStructWithFunctionPointers), nameof(MyStructWithFunctionPointers.s_fcnPtr), null, (IntPtr)45 }; + yield return new object[] { typeof(MyStructWithFunctionPointers), nameof(MyStructWithFunctionPointers.fcnPtr), new MyStructWithFunctionPointers(), (IntPtr)44 }; + } + + [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/97833", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [MemberData(nameof(GetValue_TestData_WithFunctionPointers))] + public void GetValueWithFunctionPointers(Type type, string name, object obj, object expected) + { + FieldInfo fieldInfo = GetField(type, name); + Assert.Equal(expected, fieldInfo.GetValue(obj)); + + // Perform a second time to rule out cases of slow-path vs. fast-path. + Assert.Equal(expected, fieldInfo.GetValue(obj)); + } + + [Fact] + public void GetAndSetValueTypeFromStatic() + { + FieldInfo fieldInfo = GetField(typeof(FieldInfoTests), nameof(s_myStruct_GetAndSet)); + s_myStruct_GetAndSet.intField = 10; + object obj = fieldInfo.GetValue(null); + Assert.Equal(10, ((MyStruct)obj).intField); + s_myStruct_GetAndSet.intField = 11; + + // Make sure the previously boxed value didn't change. The runtime boxes non-primitive value types internally. + Assert.Equal(10, ((MyStruct)obj).intField); + + obj = fieldInfo.GetValue(null); + Assert.Equal(11, ((MyStruct)obj).intField); + } + + [Fact] + public void ClassInitializerCalledOnceOnException() + { + TestThrows(); + for (int j = 0; j < 100; j++) GC.Collect(); // Encourage the type to unload. + TestThrows(); + + for (int j = 0; j < 100; j++) GC.Collect(); // Encourage the type to unload. + InitializerNotCalledAfterThrow(); + + static void TestThrows() + { + FieldInfo fi = typeof(MyTypeThatThrowsInClassInitializer).GetField(nameof(MyTypeThatThrowsInClassInitializer.s_field)); + for (int i = 0; i < 3; i++) + { + // The actual exception may be TargetInvocationException or TypeInitializationException; there is no guarantee on when + // exactly the class initializer is called (e.g. it could happen before the GetValue\SetValue operation actually runs). + Assert.ThrowsAny(() => fi.GetValue(null)); + Assert.ThrowsAny(() => fi.SetValue(null, 100)); + } + } + + static void InitializerNotCalledAfterThrow() + { + // Setting this stops the class initializer's code from throwing, but the runtime caches the previous exception so it never runs. + SettingsForMyTypeThatThrowsInClassInitializer.s_shouldThrow = false; + + FieldInfo fi = typeof(MyTypeThatThrowsInClassInitializer).GetField(nameof(MyTypeThatThrowsInClassInitializer.s_field)); + Assert.ThrowsAny(() => fi.GetValue(null)); + Assert.ThrowsAny(() => fi.SetValue(null, 100)); + } } public static IEnumerable GetValue_Invalid_TestData() @@ -82,18 +182,42 @@ public void GetValue_Invalid(Type type, string name, object obj, Type exceptionT { FieldInfo fieldInfo = GetField(type, name); Assert.Throws(exceptionType, () => fieldInfo.GetValue(obj)); + + // Perform a second time to rule out cases of slow-path vs. fast-path. + Assert.Throws(exceptionType, () => fieldInfo.GetValue(obj)); } public static IEnumerable SetValue_TestData() { - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.s_intField), new FieldInfoTests(), 1000, 1000 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.s_intField), null, 1000, 1000 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.intField), new FieldInfoTests(), 1000, 1000 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.s_stringField), new FieldInfoTests(), "new", "new" }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.stringField), new FieldInfoTests(), "new", "new" }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.shortEnumField), new FieldInfoTests(), (byte)1, (ShortEnum)1 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.intEnumField), new FieldInfoTests(), (short)2, (IntEnum)2 }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.longEnumField), new FieldInfoTests(), (int)3, (LongEnum)3 }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_boolField_Set), null, true, true }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_boolField_Set), null, false, false }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_intField_Set), new FieldInfoTests(), 1000, 1000 }; // Non-null 'obj' ignored. + yield return new object[] { typeof(FieldInfoTests), nameof(s_intField_Set), null, 1001, 1001 }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_stringField_Set), null, "new", "new" }; + yield return new object[] { typeof(FieldInfoTests), nameof(s_myStruct_Set), null, s_myStruct_Set, s_myStruct_Set }; + + yield return new object[] { typeof(FieldInfoTests), nameof(boolField), new FieldInfoTests(), true, true }; + yield return new object[] { typeof(FieldInfoTests), nameof(boolField), new FieldInfoTests(), false, false }; + yield return new object[] { typeof(FieldInfoTests), nameof(stringField), new FieldInfoTests(), "new", "new" }; + yield return new object[] { typeof(FieldInfoTests), nameof(shortEnumField), new FieldInfoTests(), (byte)1, (ShortEnum)1 }; + yield return new object[] { typeof(FieldInfoTests), nameof(intEnumField), new FieldInfoTests(), (short)2, (IntEnum)2 }; + yield return new object[] { typeof(FieldInfoTests), nameof(longEnumField), new FieldInfoTests(), (int)3, (LongEnum)3 }; + yield return new object[] { typeof(FieldInfoTests), nameof(_myStruct), new FieldInfoTests(), s_myStruct_Set, s_myStruct_Set }; + + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_boolField_Set), null, true, true }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_boolField_Set), null, false, false }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_intField_Set), null, 1001, 1001 }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_stringField_Set), null, "new", "new" }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_objectField_Set), null, MyStruct.s_objectField, MyStruct.s_objectField }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_intPtr_Set), null, MyStruct.s_intPtrForComparison, MyStruct.s_intPtrForComparison }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.threadStatic_intField_Set), null, 100, 100 }; + + yield return new object[] { typeof(MyStruct), nameof(MyStruct.boolField), new MyStruct(), true, true }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.boolField), new MyStruct(), false, false }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.intField), new MyStruct(), 1002, 1002 }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.stringField), new MyStruct(), "new", "new" }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.objectField), new MyStruct(), MyStruct.s_objectField, MyStruct.s_objectField }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.intPtr), new MyStruct(), MyStruct.s_intPtrForComparison, MyStruct.s_intPtrForComparison }; } [Theory] @@ -106,6 +230,38 @@ public void SetValue(Type type, string name, object obj, object value, object ex { fieldInfo.SetValue(obj, value); Assert.Equal(expected, fieldInfo.GetValue(obj)); + + // Perform a second time to rule out cases of slow-path vs. fast-path. + fieldInfo.SetValue(obj, value); + Assert.Equal(expected, fieldInfo.GetValue(obj)); + } + finally + { + fieldInfo.SetValue(obj, original); + } + } + + public static IEnumerable SetValue_TestData_FunctionPointers() + { + yield return new object[] { typeof(MyStructWithFunctionPointers), nameof(MyStructWithFunctionPointers.s_fcnPtr_Set), null, (IntPtr)201, (IntPtr)201 }; + yield return new object[] { typeof(MyStructWithFunctionPointers), nameof(MyStructWithFunctionPointers.fcnPtr), new MyStructWithFunctionPointers(), (IntPtr)200, (IntPtr)200 }; + } + + [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/97833", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [MemberData(nameof(SetValue_TestData_FunctionPointers))] + public void SetValueWithFunctionPointers(Type type, string name, object obj, object value, object expected) + { + FieldInfo fieldInfo = GetField(type, name); + object original = fieldInfo.GetValue(obj); + try + { + fieldInfo.SetValue(obj, value); + Assert.Equal(expected, fieldInfo.GetValue(obj)); + + // Perform a second time to rule out cases of slow-path vs. fast-path. + fieldInfo.SetValue(obj, value); + Assert.Equal(expected, fieldInfo.GetValue(obj)); } finally { @@ -115,17 +271,23 @@ public void SetValue(Type type, string name, object obj, object value, object ex public static IEnumerable SetValue_Invalid_TestData() { - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.stringField), null, "new", typeof(TargetException) }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.stringField), new object(), "new", typeof(ArgumentException) }; - yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.stringField), new FieldInfoTests(), 100, typeof(ArgumentException) }; + yield return new object[] { typeof(FieldInfoTests), nameof(stringField), null, "new", typeof(TargetException) }; + yield return new object[] { typeof(FieldInfoTests), nameof(stringField), new object(), "new", typeof(ArgumentException) }; + yield return new object[] { typeof(FieldInfoTests), nameof(stringField), new FieldInfoTests(), 100, typeof(ArgumentException) }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_constIntField), null, 100, typeof(FieldAccessException) }; + yield return new object[] { typeof(MyStruct), nameof(MyStruct.s_rvaIntField), null, new int[] { 3, 4, 5 }, typeof(FieldAccessException) }; } [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/97829", TestRuntimes.Mono)] [MemberData(nameof(SetValue_Invalid_TestData))] public void SetValue_Invalid(Type type, string name, object obj, object value, Type exceptionType) { FieldInfo fieldInfo = GetField(type, name); Assert.Throws(exceptionType, () => fieldInfo.SetValue(obj, value)); + + // Perform a second time to rule out cases of slow-path vs. fast-path. + Assert.Throws(exceptionType, () => fieldInfo.SetValue(obj, value)); } [Theory] @@ -239,6 +401,7 @@ public void IsPrivate(Type type, string name, bool expected) [Theory] [InlineData(typeof(FieldInfoTests), nameof(FieldInfoTests.readonlyIntField), true)] + [InlineData(typeof(FieldInfoTests), nameof(FieldInfoTests.s_readonlyIntField), true)] [InlineData(typeof(FieldInfoTests), nameof(FieldInfoTests.intField), false)] public void IsInitOnly(Type type, string name, bool expected) { @@ -462,18 +625,32 @@ private static FieldInfo GetField(Type type, string name) public const long ConstInt64Field = 1000; public const byte ConstByteField = 0; + public static bool s_boolField = true; + public static bool s_boolField_Set = false; public static int s_intField = 100; + public static int s_intField_Set = 0; public static string s_stringField = "static"; + public static string s_stringField_Set = "static"; + public static readonly int s_readonlyIntField = 100; + public bool boolField = true; public int intField = 101; public string stringField = "non static"; - public enum ShortEnum : short {} - public enum IntEnum {} - public enum LongEnum : long {} + public MyStruct _myStruct = new MyStruct(); + public static MyStruct s_myStruct = new MyStruct(); + public static MyStruct s_myStruct_Set = new MyStruct(); + public static MyStruct s_myStruct_GetAndSet = new MyStruct(); + + public enum ShortEnum : short { } + public enum IntEnum { } + public enum LongEnum : long { } public ShortEnum shortEnumField; public IntEnum intEnumField; public LongEnum longEnumField; + public static ShortEnum s_shortEnumField; + public static IntEnum s_intEnumField; + public static LongEnum s_longEnumField; private int privateIntField = 1; private string privateStringField = "privateStringField"; @@ -586,5 +763,81 @@ public static void SetValueDirect_GetValueDirectRoundDataTest(object value) Assert.Equal(value, result); } + + + public struct MyStruct_OnlyPrimitiveTypes + { + public int intField = 101; + + public MyStruct_OnlyPrimitiveTypes() + { + } + } + + public struct MyStruct + { + public static bool s_boolField = true; + public static bool s_boolField_Set = false; + public static int s_intField = 100; + public static int s_intField_Set = 0; + [ThreadStatic] public static int threadStatic_intField = 100; + [ThreadStatic] public static int threadStatic_intField_Set = 0; + public static string s_stringField = "static"; + public static readonly string s_readonlyStringField = "readonlyStatic"; + public static string s_stringField_Set = null; + public static object s_objectField = new MyClass1(); + public static object s_objectField_Set = null; + + // This does not report FieldAttributes.HasFieldRVA since Roslyn wraps the .data with generated helper class. + public static readonly int[] s_rvaIntField = [1, 2, 3]; + + public unsafe static object intPtrForComparison = Pointer.Box((void*)42, typeof(int*)); + public unsafe static int* s_intPtr = (int*)43; + public unsafe static int* s_intPtr_Set = (int*)0; + public unsafe static object s_intPtrForComparison = Pointer.Box((void*)43, typeof(int*)); + public bool boolField = true; + public int intField = 101; + public object objectField = null; + public string stringField = "non static"; + public const int s_constIntField = 102; + public unsafe int* intPtr = (int*)42; + + public MyStruct() { } + } + + public struct MyStructWithFunctionPointers + { + public unsafe static delegate* s_fcnPtr = (delegate*)45; + public unsafe static delegate* s_fcnPtr_Set = (delegate*)0; + public unsafe delegate* fcnPtr = (delegate*)44; + + public MyStructWithFunctionPointers() { } + } + + public class MyTypeThatThrowsInClassInitializer + { + public static int s_field; + + static MyTypeThatThrowsInClassInitializer() + { + FieldInfo fi = typeof(MyTypeThatThrowsInClassInitializer).GetField(nameof(s_field)); + + // Ensure that the runtime doesn't treat this type has having been initialized due to successful GetValue(). + for (int i = 0; i < 3; i++) + { + fi.GetValue(null); + } + + if (SettingsForMyTypeThatThrowsInClassInitializer.s_shouldThrow) + { + throw new Exception(); + } + } + } + + public static class SettingsForMyTypeThatThrowsInClassInitializer + { + public static bool s_shouldThrow = true; + } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index 33162acb597819..7811adcf631c05 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -97,8 +97,8 @@ internal override void CheckConsistency(object target) [DebuggerHidden] internal override void UnsafeSetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) { - bool domainInitialized = false; - RuntimeFieldHandle.SetValue(this, obj, value, null, Attributes, null, ref domainInitialized); + bool isClassInitialized = false; + RuntimeFieldHandle.SetValue(this, obj, value, null, Attributes, null, ref isClassInitialized); } [DebuggerStepThrough]