diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
index 2efcb649844afc..f05992c2352cf9 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
@@ -62,7 +62,10 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de
if ((uint)(destinationIndex + length) > destinationArray.NativeLength)
throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray));
- if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray))
+ ArrayAssignType assignType = ArrayAssignType.WrongType;
+
+ if (sourceArray.GetType() == destinationArray.GetType()
+ || (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy)
{
MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
@@ -86,44 +89,50 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de
throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy);
// Rare
- CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
+ CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType);
}
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern bool IsSimpleCopy(Array sourceArray, Array destinationArray);
+ private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType)
+ {
+ Debug.Assert(elementType.IsPrimitiveType());
+
+ // Array Primitive types such as E_T_I4 and E_T_U4 are interchangeable
+ // Enums with interchangeable underlying types are interchangeable
+ // BOOL is NOT interchangeable with I1/U1, neither CHAR -- with I2/U2
+
+ // U1/U2/U4/U8/U
+ int shift = (0b0010_0000_0000_0000_1010_1010_0000 >> (int)elementType) & 1;
+ return (CorElementType)((int)elementType - shift);
+ }
// Reliability-wise, this method will either possibly corrupt your
// instance & might fail when called from within a CER, or if the
// reliable flag is true, it will either always succeed or always
// throw an exception with no side effects.
- private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
+ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType)
{
Debug.Assert(sourceArray.Rank == destinationArray.Rank);
- void* srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType;
- void* destTH = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType;
- AssignArrayEnum r = CanAssignArrayType(srcTH, destTH);
-
- if (r == AssignArrayEnum.AssignWrongType)
+ if (assignType == ArrayAssignType.WrongType)
throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType);
if (length > 0)
{
- switch (r)
+ switch (assignType)
{
- case AssignArrayEnum.AssignUnboxValueClass:
+ case ArrayAssignType.UnboxValueClass:
CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;
- case AssignArrayEnum.AssignBoxValueClassOrPrimitive:
+ case ArrayAssignType.BoxValueClassOrPrimitive:
CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;
- case AssignArrayEnum.AssignMustCast:
+ case ArrayAssignType.MustCast:
CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;
- case AssignArrayEnum.AssignPrimitiveWiden:
+ case ArrayAssignType.PrimitiveWiden:
CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;
@@ -134,18 +143,90 @@ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array de
}
}
- // Must match the definition in arraynative.cpp
- private enum AssignArrayEnum
+ private enum ArrayAssignType
{
- AssignWrongType,
- AssignMustCast,
- AssignBoxValueClassOrPrimitive,
- AssignUnboxValueClass,
- AssignPrimitiveWiden,
+ SimpleCopy,
+ WrongType,
+ MustCast,
+ BoxValueClassOrPrimitive,
+ UnboxValueClass,
+ PrimitiveWiden,
}
- [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_CanAssignArrayType")]
- private static unsafe partial AssignArrayEnum CanAssignArrayType(void* srcTH, void* dstTH);
+ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray)
+ {
+ TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle();
+ TypeHandle destTH = RuntimeHelpers.GetMethodTable(destinationArray)->GetArrayElementTypeHandle();
+
+ if (TypeHandle.AreSameType(srcTH, destTH)) // This check kicks for different array kind or dimensions
+ return ArrayAssignType.SimpleCopy;
+
+ if (!srcTH.IsTypeDesc && !destTH.IsTypeDesc)
+ {
+ MethodTable* pMTsrc = srcTH.AsMethodTable();
+ MethodTable* pMTdest = destTH.AsMethodTable();
+
+ // Value class boxing
+ if (pMTsrc->IsValueType && !pMTdest->IsValueType)
+ {
+ if (srcTH.CanCastTo(destTH))
+ return ArrayAssignType.BoxValueClassOrPrimitive;
+ else
+ return ArrayAssignType.WrongType;
+ }
+
+ // Value class unboxing.
+ if (!pMTsrc->IsValueType && pMTdest->IsValueType)
+ {
+ if (srcTH.CanCastTo(destTH))
+ return ArrayAssignType.UnboxValueClass;
+ else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V.
+ return ArrayAssignType.UnboxValueClass;
+ else
+ return ArrayAssignType.WrongType;
+ }
+
+ // Copying primitives from one type to another
+ if (pMTsrc->IsPrimitive && pMTdest->IsPrimitive)
+ {
+ CorElementType srcElType = pMTsrc->GetPrimitiveCorElementType();
+ CorElementType destElType = pMTdest->GetPrimitiveCorElementType();
+
+ if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
+ return ArrayAssignType.SimpleCopy;
+ else if (RuntimeHelpers.CanPrimitiveWiden(srcElType, destElType))
+ return ArrayAssignType.PrimitiveWiden;
+ else
+ return ArrayAssignType.WrongType;
+ }
+
+ // src Object extends dest
+ if (srcTH.CanCastTo(destTH))
+ return ArrayAssignType.SimpleCopy;
+
+ // dest Object extends src
+ if (destTH.CanCastTo(srcTH))
+ return ArrayAssignType.MustCast;
+
+ // class X extends/implements src and implements dest.
+ if (pMTdest->IsInterface)
+ return ArrayAssignType.MustCast;
+
+ // class X implements src and extends/implements dest
+ if (pMTsrc->IsInterface)
+ return ArrayAssignType.MustCast;
+ }
+ else
+ {
+ // Only pointers are valid for TypeDesc in array element
+
+ // Compatible pointers
+ if (srcTH.CanCastTo(destTH))
+ return ArrayAssignType.SimpleCopy;
+ }
+
+ return ArrayAssignType.WrongType;
+ }
// Unboxes from an Object[] into a value class or primitive array.
private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
index 052f6970a298e1..77e579ce4680bd 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
@@ -818,7 +818,7 @@ public bool CanCompareBitsOrUseFastGetHashCode
///
/// A type handle, which can wrap either a pointer to a TypeDesc or to a .
///
- internal unsafe struct TypeHandle
+ internal readonly unsafe partial struct TypeHandle
{
// Subset of src\vm\typehandle.h
@@ -865,6 +865,29 @@ public static TypeHandle TypeHandleOf()
{
return new TypeHandle((void*)RuntimeTypeHandle.ToIntPtr(typeof(T).TypeHandle));
}
+
+ public static bool AreSameType(TypeHandle left, TypeHandle right) => left.m_asTAddr == right.m_asTAddr;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool CanCastTo(TypeHandle destTH)
+ {
+ if (m_asTAddr == destTH.m_asTAddr)
+ return true;
+
+ if (!IsTypeDesc && destTH.IsTypeDesc)
+ return false;
+
+ CastResult result = CastCache.TryGet(CastHelpers.s_table!, (nuint)m_asTAddr, (nuint)destTH.m_asTAddr);
+
+ if (result != CastResult.MaybeCast)
+ return result == CastResult.CanCast;
+
+ return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr);
+ }
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo_NoCacheLookup")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd);
}
// Helper structs used for tail calls via helper.
diff --git a/src/coreclr/classlibnative/bcltype/arraynative.cpp b/src/coreclr/classlibnative/bcltype/arraynative.cpp
index 7a42245f76888a..c5e95e7e824709 100644
--- a/src/coreclr/classlibnative/bcltype/arraynative.cpp
+++ b/src/coreclr/classlibnative/bcltype/arraynative.cpp
@@ -47,166 +47,6 @@ extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHand
return ctorEntrypoint;
}
- // Returns whether you can directly copy an array of srcType into destType.
-FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst)
-{
- FCALL_CONTRACT;
-
- _ASSERTE(pSrc != NULL);
- _ASSERTE(pDst != NULL);
-
- // This case is expected to be handled by the fast path
- _ASSERTE(pSrc->GetMethodTable() != pDst->GetMethodTable());
-
- TypeHandle srcTH = pSrc->GetMethodTable()->GetArrayElementTypeHandle();
- TypeHandle destTH = pDst->GetMethodTable()->GetArrayElementTypeHandle();
- if (srcTH == destTH) // This check kicks for different array kind or dimensions
- FC_RETURN_BOOL(true);
-
- if (srcTH.IsValueType())
- {
- // Value class boxing
- if (!destTH.IsValueType())
- FC_RETURN_BOOL(false);
-
- const CorElementType srcElType = srcTH.GetVerifierCorElementType();
- const CorElementType destElType = destTH.GetVerifierCorElementType();
- _ASSERTE(srcElType < ELEMENT_TYPE_MAX);
- _ASSERTE(destElType < ELEMENT_TYPE_MAX);
-
- // Copying primitives from one type to another
- if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
- {
- if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
- FC_RETURN_BOOL(true);
- }
- }
- else
- {
- // Value class unboxing
- if (destTH.IsValueType())
- FC_RETURN_BOOL(false);
- }
-
- TypeHandle::CastResult r = srcTH.CanCastToCached(destTH);
- if (r != TypeHandle::MaybeCast)
- {
- FC_RETURN_BOOL(r);
- }
-
- struct
- {
- OBJECTREF src;
- OBJECTREF dst;
- } gc;
-
- gc.src = ObjectToOBJECTREF(pSrc);
- gc.dst = ObjectToOBJECTREF(pDst);
-
- BOOL iRetVal = FALSE;
-
- HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
- iRetVal = srcTH.CanCastTo(destTH);
- HELPER_METHOD_FRAME_END();
-
- FC_RETURN_BOOL(iRetVal);
-}
-FCIMPLEND
-
-
-// Return values for CanAssignArrayType
-enum AssignArrayEnum
-{
- AssignWrongType,
- AssignMustCast,
- AssignBoxValueClassOrPrimitive,
- AssignUnboxValueClass,
- AssignPrimitiveWiden,
-};
-
-// Returns an enum saying whether you can copy an array of srcType into destType.
-static AssignArrayEnum CanAssignArrayType(const TypeHandle srcTH, const TypeHandle destTH)
-{
- CONTRACTL
- {
- THROWS;
- GC_TRIGGERS;
- PRECONDITION(!srcTH.IsNull());
- PRECONDITION(!destTH.IsNull());
- }
- CONTRACTL_END;
-
- _ASSERTE(srcTH != destTH); // Handled by fast path
-
- // Value class boxing
- if (srcTH.IsValueType() && !destTH.IsValueType())
- {
- if (srcTH.CanCastTo(destTH))
- return AssignBoxValueClassOrPrimitive;
- else
- return AssignWrongType;
- }
-
- // Value class unboxing.
- if (!srcTH.IsValueType() && destTH.IsValueType())
- {
- if (srcTH.CanCastTo(destTH))
- return AssignUnboxValueClass;
- else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V.
- return AssignUnboxValueClass;
- else
- return AssignWrongType;
- }
-
- const CorElementType srcElType = srcTH.GetVerifierCorElementType();
- const CorElementType destElType = destTH.GetVerifierCorElementType();
- _ASSERTE(srcElType < ELEMENT_TYPE_MAX);
- _ASSERTE(destElType < ELEMENT_TYPE_MAX);
-
- // Copying primitives from one type to another
- if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
- {
- _ASSERTE(srcElType != destElType); // Handled by fast path
- if (InvokeUtil::CanPrimitiveWiden(destElType, srcElType))
- return AssignPrimitiveWiden;
- else
- return AssignWrongType;
- }
-
- // dest Object extends src
- _ASSERTE(!srcTH.CanCastTo(destTH)); // Handled by fast path
-
- // src Object extends dest
- if (destTH.CanCastTo(srcTH))
- return AssignMustCast;
-
- // class X extends/implements src and implements dest.
- if (destTH.IsInterface() && srcElType != ELEMENT_TYPE_VALUETYPE)
- return AssignMustCast;
-
- // class X implements src and extends/implements dest
- if (srcTH.IsInterface() && destElType != ELEMENT_TYPE_VALUETYPE)
- return AssignMustCast;
-
- return AssignWrongType;
-}
-
-extern "C" int QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH)
-{
- QCALL_CONTRACT;
-
- INT32 ret = 0;
-
- BEGIN_QCALL;
-
- ret = CanAssignArrayType(TypeHandle::FromPtr(srcTH), TypeHandle::FromPtr(destTH));
-
- END_QCALL;
-
- return ret;
-}
-
-
//
// This is a GC safe variant of the memmove intrinsic. It sets the cards, and guarantees that the object references in the GC heap are
// updated atomically.
diff --git a/src/coreclr/classlibnative/bcltype/arraynative.h b/src/coreclr/classlibnative/bcltype/arraynative.h
index 86b125c416bf0b..aa4430c9b21d8f 100644
--- a/src/coreclr/classlibnative/bcltype/arraynative.h
+++ b/src/coreclr/classlibnative/bcltype/arraynative.h
@@ -13,19 +13,15 @@
#ifndef _ARRAYNATIVE_H_
#define _ARRAYNATIVE_H_
-#include "fcall.h"
#include "qcall.h"
class ArrayNative
{
public:
static FCDECL1(INT32, GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE);
-
- static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst);
};
extern "C" void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT32* pLengths, INT32* pBounds, BOOL createFromArrayType, QCall::ObjectHandleOnStack retArray);
extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd);
-extern "C" INT32 QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH);
#endif // _ARRAYNATIVE_H_
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs
index 0c9c571ed0f7e8..58378b92a9c5f0 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs
@@ -239,9 +239,14 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de
{
// CLR compat note: CLR only allows Array.Copy between pointee types that would be assignable
// to using array covariance rules (so int*[] can be copied to uint*[], but not to float*[]).
- // This is rather weird since e.g. we don't allow casting int*[] to uint*[] otherwise.
- // Instead of trying to replicate the behavior, we're choosing to be simply more permissive here.
- CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
+ if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType))
+ {
+ CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
+ }
+ else
+ {
+ throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType);
+ }
}
else if (IsSourceElementABaseClassOrInterfaceOfDestinationValueType(sourceElementEEType, destinationElementEEType))
{
diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp
index 73547a59e4df3c..d30177f9dc7978 100644
--- a/src/coreclr/vm/comutilnative.cpp
+++ b/src/coreclr/vm/comutilnative.cpp
@@ -1846,6 +1846,41 @@ extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, Metho
return bResult;
}
+extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd)
+{
+ QCALL_CONTRACT;
+
+ BOOL ret = false;
+
+ BEGIN_QCALL;
+
+ // Cache lookup and trivial cases are already handled at managed side. Call the uncached versions directly.
+ _ASSERTE(fromTypeHnd != toTypeHnd);
+
+ GCX_COOP();
+
+ TypeHandle fromTH = TypeHandle::FromPtr(fromTypeHnd);
+ TypeHandle toTH = TypeHandle::FromPtr(toTypeHnd);
+
+ if (fromTH.IsTypeDesc())
+ {
+ ret = fromTH.AsTypeDesc()->CanCastTo(toTH, NULL);
+ }
+ else if (Nullable::IsNullableForType(toTH, fromTH.AsMethodTable()))
+ {
+ // do not allow type T to be cast to Nullable
+ ret = FALSE;
+ }
+ else
+ {
+ ret = fromTH.AsMethodTable()->CanCastTo(toTH.AsMethodTable(), NULL);
+ }
+
+ END_QCALL;
+
+ return ret;
+}
+
static MethodTable * g_pStreamMT;
static WORD g_slotBeginRead, g_slotEndRead;
static WORD g_slotBeginWrite, g_slotEndWrite;
diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h
index 0f00aca48466a1..4e70173758f887 100644
--- a/src/coreclr/vm/comutilnative.h
+++ b/src/coreclr/vm/comutilnative.h
@@ -252,6 +252,7 @@ class MethodTableNative {
extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb);
extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable* mt);
+extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd);
extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize, MethodTable** fieldMT);
class StreamNative {
diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h
index db21d429daa9ad..47f630717ab902 100644
--- a/src/coreclr/vm/ecalllist.h
+++ b/src/coreclr/vm/ecalllist.h
@@ -358,7 +358,6 @@ FCFuncEnd()
FCFuncStart(gArrayFuncs)
FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType)
- FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy)
FCFuncEnd()
FCFuncStart(gBufferFuncs)
diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp
index 329989cde6db48..d4d073d9b3abf5 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -103,6 +103,7 @@ static const Entry s_QCall[] =
DllImportEntry(QCall_FreeGCHandleForTypeHandle)
DllImportEntry(MethodTable_AreTypesEquivalent)
DllImportEntry(MethodTable_CanCompareBitsOrUseFastGetHashCode)
+ DllImportEntry(TypeHandle_CanCastTo_NoCacheLookup)
DllImportEntry(ValueType_GetHashCodeStrategy)
DllImportEntry(RuntimeTypeHandle_MakePointer)
DllImportEntry(RuntimeTypeHandle_MakeByRef)
@@ -172,7 +173,6 @@ static const Entry s_QCall[] =
DllImportEntry(MdUtf8String_EqualsCaseInsensitive)
DllImportEntry(Array_CreateInstance)
DllImportEntry(Array_GetElementConstructorEntrypoint)
- DllImportEntry(Array_CanAssignArrayType)
DllImportEntry(AssemblyName_InitializeAssemblySpec)
DllImportEntry(AssemblyNative_GetFullName)
DllImportEntry(AssemblyNative_GetLocation)
diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs
index 832d890ab61c21..998094377be4f4 100644
--- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs
+++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -1597,13 +1598,32 @@ public static void Copy_SourceAndDestinationNeverConvertible_ThrowsArrayTypeMism
Assert.Throws(() => sourceArray.CopyTo(destinationArray, (long)destinationArray.GetLowerBound(0)));
}
+ [Fact]
+ [SkipOnMono("https://github.com/dotnet/runtime/issues/104197")]
+ public static unsafe void Copy_CompatiblePointers()
+ {
+ // Can copy between compatible pointers
+ uint*[] uintPointerArray = new uint*[1];
+ Array.ConstrainedCopy(new int*[1] { (int*)0x12345678 }, 0, uintPointerArray, 0, 1);
+ Assert.Equal((UIntPtr)0x12345678, (UIntPtr)uintPointerArray[0]);
+ }
+
[Fact]
public static void Copy_SourceAndDestinationPointers_ThrowsArrayTypeMismatchException()
{
unsafe
{
+ // Can't copy between pointer and object
Assert.Throws(() => Array.Copy(new void*[1], new object[1], 0));
Assert.Throws(() => Array.Copy(new object[1], new void*[1], 0));
+
+ // Can't copy between pointer and interface
+ Assert.Throws(() => Array.Copy(new int*[1], new IConvertible[1], 1));
+ Assert.Throws(() => Array.Copy(new IConvertible[1], new int*[1], 1));
+
+ // Can't copy between incompatible pointer types
+ Assert.Throws(() => Array.Copy(new int*[1], new bool*[1], 0));
+ Assert.Throws(() => Array.Copy(new int*[1], new void*[1], 0));
}
}
diff --git a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs
index 7ca268d6cc050b..cded34be47b1f8 100644
--- a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs
@@ -259,7 +259,7 @@ private static bool CanAssignArrayElement(Type source, Type target)
}
else if (source.IsPointer && target.IsPointer)
{
- return true;
+ return target.IsAssignableFrom(source);
}
else if (source.IsPrimitive && target.IsPrimitive)
{