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) {