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..6e6217bd33b5ac 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,6 +1190,15 @@ 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, out bool isBoxed); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int GetToken(RtFieldInfo field); @@ -1199,7 +1209,7 @@ internal static RuntimeType GetApproxDeclaringType(IRuntimeFieldInfo field) 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 domainInitialized); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void SetValueDirect(RtFieldInfo field, RuntimeType fieldType, void* pTypedRef, object? value, RuntimeType? contextType); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 491265645b8e3a..0cb59f82fc05d0 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 64142b1fb69d13..cbee36613dcccf 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..1bc8885c9faabf 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 *pLoaderAllocatorOfMethod = GetApproxEnclosingMethodTable()->GetLoaderAllocator(); + return pLoaderAllocatorOfMethod->IsCollectible(); + } + // Was this field added by EnC? // If this is true, then this object is an instance of EnCFieldDesc BOOL IsEnCNew() diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index eb8462ed16f245..0d7994a330ca75 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -741,7 +741,7 @@ 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, @@ -971,8 +971,6 @@ 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) { @@ -999,7 +997,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)) @@ -1084,7 +1082,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/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 72cf39758b1fdb..d6e1d98b609435 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -50,17 +50,6 @@ 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); @@ -169,7 +158,7 @@ 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) { +FCIMPL6(void, RuntimeFieldHandle::SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pDomainInitialized) { CONTRACTL { FCALL_CHECK; } @@ -195,17 +184,6 @@ 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(); @@ -1243,6 +1221,95 @@ lExit: ; } FCIMPLEND +static FC_BOOL_RET IsFastPathSupportedHelper(FieldDesc* pFieldDesc) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pFieldDesc)); + } + CONTRACTL_END; + + return pFieldDesc->IsThreadStatic() || + pFieldDesc->IsEnCNew() || + pFieldDesc->IsCollectible() ? FALSE : TRUE; +} + +FCIMPL1(FC_BOOL_RET, RuntimeFieldHandle::IsFastPathSupported, ReflectFieldObject *pFieldUNSAFE) +{ + CONTRACTL { + FCALL_CHECK; + } + CONTRACTL_END; + + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(pFieldUNSAFE); + if (refField == NULL) + FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); + + FieldDesc* pFieldDesc = refField->GetField(); + return IsFastPathSupportedHelper(pFieldDesc); +} +FCIMPLEND + +FCIMPL1(INT32, RuntimeFieldHandle::GetInstanceFieldOffset, ReflectFieldObject *pFieldUNSAFE) +{ + CONTRACTL { + FCALL_CHECK; + PRECONDITION(CheckPointer(pFieldUNSAFE)); + } + CONTRACTL_END; + + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(pFieldUNSAFE); + if (refField == NULL) + FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); + + FieldDesc* pFieldDesc = refField->GetField(); + _ASSERTE(!pFieldDesc->IsStatic()); + + // IsFastPathSupported needs to checked before calling this method. + _ASSERTE(IsFastPathSupportedHelper(pFieldDesc)); + + return pFieldDesc->GetOffset(); +} +FCIMPLEND + +FCIMPL2(void*, RuntimeFieldHandle::GetStaticFieldAddress, ReflectFieldObject *pFieldUNSAFE, CLR_BOOL *isBoxed) +{ + CONTRACTL { + FCALL_CHECK; + PRECONDITION(CheckPointer(pFieldUNSAFE)); + } + CONTRACTL_END; + + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(pFieldUNSAFE); + if (refField == NULL) + FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); + + 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. + *isBoxed = FALSE; + } + else + { + // Non-primitive value types need to be unboxed to get the base address. + *isBoxed = (pFieldDesc->GetFieldType() == ELEMENT_TYPE_VALUETYPE) ? TRUE : FALSE; + + base = pFieldDesc->GetBase(); + } + + return PTR_VOID(base + pFieldDesc->GetOffset()); +} +FCIMPLEND + extern "C" void QCALLTYPE ReflectionInvocation_CompileMethod(MethodDesc * pMD) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 694a25a30623da..c27b6c6f2aaae9 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -293,9 +293,12 @@ 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 FCDECL6(void, SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldType, ReflectClassBaseObject *pDeclaringType, CLR_BOOL *pDomainInitialized); 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 FCDECL2(void*, GetStaticFieldAddress, ReflectFieldObject *pField, CLR_BOOL *isBoxed); 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 089418e78f4c36..2d2957f9263f05 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4295,6 +4295,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 9fc91793b70654..85859f83b5cad3 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 @@ -701,6 +701,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..08c3189deab997 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs @@ -0,0 +1,556 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Reflection +{ + internal sealed class FieldAccessor + { + private readonly IntPtr _addressOrOffset; + private readonly RtFieldInfo _fieldInfo; + private readonly unsafe MethodTable* _methodTable; + private readonly FieldAccessorType _fieldAccessType; + + internal FieldAccessor(FieldInfo fieldInfo) + { + _fieldInfo = (RtFieldInfo)fieldInfo; + + InitializeClass(); + + // Cached the method table for performance. + unsafe + { + _methodTable = (MethodTable*)_fieldInfo.FieldType.TypeHandle.Value; + } + + // Initialize for the type of field. + Debug.Assert(_fieldInfo.m_declaringType != null); + if (_fieldInfo.m_declaringType.ContainsGenericParameters) + { + _fieldAccessType = FieldAccessorType.NoInvoke; + } + else if (_fieldInfo.m_declaringType.IsNullableOfT) + { + _fieldAccessType = FieldAccessorType.NoInvoke; + } + else 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; + } + else + { + Type fieldType = _fieldInfo.FieldType; + + if (fieldInfo.IsStatic) + { + _addressOrOffset = RuntimeFieldHandle.GetStaticFieldAddress(_fieldInfo, out bool isBoxed); + + if (fieldType.IsValueType) + { + // The runtime stores non-primitive value types as a boxed value. + if (isBoxed) + { + _fieldAccessType = FieldAccessorType.StaticValueTypeBoxed; + } + else + { + _fieldAccessType = GetPrimitiveAccessorTypeForStatic(fieldType); + } + } + else if (fieldType.IsPointer) + { + Debug.Assert(!isBoxed); + _fieldAccessType = FieldAccessorType.StaticPointerType; + } + else if (fieldType.IsFunctionPointer) + { + Debug.Assert(!isBoxed); + _fieldAccessType = GetFunctionPointerAccessorTypeForStatic(); + unsafe + { + _methodTable = (MethodTable*)typeof(IntPtr).TypeHandle.Value; + } + } + else + { + Debug.Assert(!isBoxed); + _fieldAccessType = FieldAccessorType.StaticReferenceType; + } + } + else + { + _addressOrOffset = RuntimeFieldHandle.GetInstanceFieldOffset(_fieldInfo); + + if (fieldType.IsValueType) + { + _fieldAccessType = GetPrimitiveAccessorTypeForInstance(fieldType); + } + else if (fieldType.IsPointer) + { + _fieldAccessType = FieldAccessorType.InstancePointerType; + } + else if (fieldType.IsFunctionPointer) + { + _fieldAccessType = GetFunctionPointerAccessorTypeForInstance(); + unsafe + { + _methodTable = (MethodTable*)typeof(IntPtr).TypeHandle.Value; + } + } + else + { + _fieldAccessType = FieldAccessorType.InstanceReferenceType; + } + } + } + } + + public object? GetValue(object? obj) + { + unsafe + { + switch (_fieldAccessType) + { + case FieldAccessorType.InstanceReferenceType: + VerifyTarget(obj); + Debug.Assert(obj != null); + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + 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); + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + return RuntimeHelpers.Box( + _methodTable, + ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)); + + case FieldAccessorType.InstancePointerType: + VerifyTarget(obj); + Debug.Assert(obj != null); + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + return Pointer.Box( + (void*)Unsafe.As(ref Unsafe.AddByteOffset(ref obj.GetRawData(), _addressOrOffset)), + _fieldInfo.FieldType); + + case FieldAccessorType.StaticReferenceType: + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + return Volatile.Read(ref Unsafe.As(ref *(IntPtr*)_addressOrOffset)); + + case FieldAccessorType.StaticValueType: + case FieldAccessorType.StaticValueTypeSize1: + case FieldAccessorType.StaticValueTypeSize2: + case FieldAccessorType.StaticValueTypeSize4: + case FieldAccessorType.StaticValueTypeSize8: + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + return RuntimeHelpers.Box(_methodTable, ref Unsafe.AsRef(_addressOrOffset.ToPointer())); + + case FieldAccessorType.StaticValueTypeBoxed: + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + // Re-box the value. + return RuntimeHelpers.Box( + _methodTable, + ref Unsafe.As(ref *(IntPtr*)_addressOrOffset).GetRawData()); + + case FieldAccessorType.StaticPointerType: + if (!_fieldInfo.m_declaringType.DomainInitialized) + { + return SlowPath(); + } + return Pointer.Box((void*)Unsafe.As( + ref Unsafe.AsRef(_addressOrOffset.ToPointer())), _fieldInfo.FieldType); + + 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(); + } + + // Slow path + if (!IsStatic()) + { + VerifyTarget(obj); + } + + return SlowPath(); + + object? SlowPath() + { + object? ret; + RuntimeType declaringType = _fieldInfo.m_declaringType; + bool domainInitialized = declaringType.DomainInitialized; + ret = RuntimeFieldHandle.GetValue(_fieldInfo, obj, (RuntimeType)_fieldInfo.FieldType, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + return ret; + } + } + } + + public void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) + { + 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.StaticReferenceType: + if (!VerifyStaticField(ref value, invokeAttr, binder, culture)) + { + SlowPath(); + } + else + { + Volatile.Write(ref Unsafe.As(ref *(IntPtr*)_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.StaticValueTypeSize1: + if (!VerifyStaticField(ref value, invokeAttr, binder, culture)) + { + SlowPath(); + } + else + { + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + value!.GetRawData()); + } + return; + + case FieldAccessorType.StaticValueTypeSize2: + if (!VerifyStaticField(ref value, invokeAttr, binder, culture)) + { + SlowPath(); + } + else + { + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + Unsafe.As(ref value!.GetRawData())); + } + return; + + case FieldAccessorType.StaticValueTypeSize4: + if (!VerifyStaticField(ref value, invokeAttr, binder, culture)) + { + SlowPath(); + } + else + { + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + Unsafe.As(ref value!.GetRawData())); + } + return; + + case FieldAccessorType.StaticValueTypeSize8: + if (!VerifyStaticField(ref value, invokeAttr, binder, culture)) + { + SlowPath(); + } + else + { + Volatile.Write( + ref Unsafe.AsRef(_addressOrOffset.ToPointer()), + Unsafe.As(ref value!.GetRawData())); + } + return; + + case FieldAccessorType.NoInvoke: + if (_fieldInfo.DeclaringType is not null && _fieldInfo.DeclaringType.ContainsGenericParameters) + throw new InvalidOperationException(SR.Arg_UnboundGenField); + + throw new FieldAccessException(); + } + } + + // Slow path + if (IsStatic()) + { + VerifyStaticField(ref value, invokeAttr, binder, culture); + } + else + { + VerifyInstanceField(obj, ref value, invokeAttr, binder, culture); + } + + SlowPath(); + + void SlowPath() + { + RuntimeType declaringType = _fieldInfo.m_declaringType; + bool domainInitialized = declaringType.DomainInitialized; + RuntimeFieldHandle.SetValue(_fieldInfo, obj, value, (RuntimeType)_fieldInfo.FieldType, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + } + } + + private void InitializeClass() + { + // Call the class constructor if not already. + if (_fieldInfo.DeclaringType is null) + { + RunModuleConstructor(_fieldInfo.Module); + } + else + { + RunClassConstructor(_fieldInfo); + } + } + + private bool IsStatic() => (_fieldInfo.Attributes & FieldAttributes.Static) == FieldAttributes.Static; + + private bool VerifyStaticField(ref object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) + { + VerifyInitOnly(); + CheckValue(ref value, invokeAttr, binder, culture); + + return _fieldInfo.m_declaringType.DomainInitialized; + } + + 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 && _fieldInfo.m_declaringType.DomainInitialized) + { + ThrowHelperFieldAccessException(_fieldInfo.Name, _fieldInfo.DeclaringType?.FullName); + } + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2059:RunClassConstructor", + Justification = "This represents the static constructor, so if this object was created, the static constructor exists.")] + private static void RunClassConstructor(FieldInfo fieldInfo) + { + RuntimeHelpers.RunClassConstructor(fieldInfo.DeclaringType!.TypeHandle); + } + + private static void RunModuleConstructor(Module module) + { + RuntimeHelpers.RunModuleConstructor(module.ModuleHandle); + } + + /// + /// 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; + + 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; + + return accessorType; + } + + private static FieldAccessorType GetFunctionPointerAccessorTypeForInstance() + { + FieldAccessorType accessorType = FieldAccessorType.InstanceValueType; + + if (IntPtr.Size == 4) + { + accessorType = FieldAccessorType.InstanceValueTypeSize4; + } + else if (IntPtr.Size == 8) + { + accessorType = FieldAccessorType.InstanceValueTypeSize8; + } + + return accessorType; + } + + private static FieldAccessorType GetFunctionPointerAccessorTypeForStatic() + { + 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, + NoInvoke, + SlowPath, + } + } +} 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..9b6951e185c978 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,31 @@ 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(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_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] @@ -70,6 +92,37 @@ public void GetValue(Type type, string name, object obj, object expected) 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)); + } + + [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); + } + public static IEnumerable GetValue_Invalid_TestData() { yield return new object[] { typeof(FieldInfoTests), nameof(FieldInfoTests.stringField), null, typeof(TargetException) }; @@ -86,14 +139,35 @@ public void GetValue_Invalid(Type type, string name, object obj, Type exceptionT 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] @@ -113,14 +187,41 @@ public void SetValue(Type type, string name, object obj, object value, object ex } } + 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)); + } + finally + { + fieldInfo.SetValue(obj, original); + } + } + 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) { @@ -239,6 +340,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,15 +564,26 @@ 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; @@ -586,5 +699,54 @@ 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 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 thd .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() { } + } } }