// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // File: ArrayNative.cpp // // // This file contains the native methods that support the Array class // #include "common.h" #include "arraynative.h" #include "excep.h" #include "field.h" #include "security.h" #include "invokeutil.h" FCIMPL1(INT32, ArrayNative::GetRank, ArrayBase* array) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array == NULL) FCThrow(kNullReferenceException); return array->GetRank(); } FCIMPLEND FCIMPL2(INT32, ArrayNative::GetLowerBound, ArrayBase* array, unsigned int dimension) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array == NULL) FCThrow(kNullReferenceException); if (dimension != 0) { // Check the dimension is within our rank unsigned int rank = array->GetRank(); if (dimension >= rank) FCThrowRes(kIndexOutOfRangeException, W("IndexOutOfRange_ArrayRankIndex")); } return array->GetLowerBoundsPtr()[dimension]; } FCIMPLEND // Get inclusive upper bound FCIMPL2(INT32, ArrayNative::GetUpperBound, ArrayBase* array, unsigned int dimension) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array == NULL) FCThrow(kNullReferenceException); if (dimension != 0) { // Check the dimension is within our rank unsigned int rank = array->GetRank(); if (dimension >= rank) FCThrowRes(kIndexOutOfRangeException, W("IndexOutOfRange_ArrayRankIndex")); } return array->GetBoundsPtr()[dimension] + array->GetLowerBoundsPtr()[dimension] - 1; } FCIMPLEND FCIMPL2(INT32, ArrayNative::GetLength, ArrayBase* array, unsigned int dimension) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array==NULL) FCThrow(kNullReferenceException); if (dimension != 0) { // Check the dimension is within our rank unsigned int rank = array->GetRank(); if (dimension >= rank) FCThrow(kIndexOutOfRangeException); } return array->GetBoundsPtr()[dimension]; } FCIMPLEND FCIMPL1(INT32, ArrayNative::GetLengthNoRank, ArrayBase* array) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array==NULL) FCThrow(kNullReferenceException); SIZE_T numComponents = array->GetNumComponents(); if (numComponents > INT32_MAX) FCThrow(kOverflowException); return (INT32)numComponents; } FCIMPLEND FCIMPL1(INT64, ArrayNative::GetLongLengthNoRank, ArrayBase* array) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array==NULL) FCThrow(kNullReferenceException); return array->GetNumComponents(); } FCIMPLEND FCIMPL1(INT32, ArrayNative::GetDataPtrOffsetInternal, ArrayBase* array) { FCALL_CONTRACT; VALIDATEOBJECT(array); if (array == NULL) FCThrow(kNullReferenceException); return ArrayBase::GetDataPtrOffset(array->GetMethodTable()); } FCIMPLEND // array is GC protected by caller void ArrayInitializeWorker(ARRAYBASEREF * arrayRef, MethodTable* pArrayMT, MethodTable* pElemMT) { STATIC_CONTRACT_MODE_COOPERATIVE; STATIC_CONTRACT_SO_INTOLERANT; // Ensure that the array element type is fully loaded before executing its code pElemMT->EnsureInstanceActive(); //can not use contract here because of SEH _ASSERTE(IsProtectedByGCFrame (arrayRef)); SIZE_T offset = ArrayBase::GetDataPtrOffset(pArrayMT); SIZE_T size = pArrayMT->GetComponentSize(); SIZE_T cElements = (*arrayRef)->GetNumComponents(); MethodTable * pCanonMT = pElemMT->GetCanonicalMethodTable(); WORD slot = pCanonMT->GetDefaultConstructorSlot(); PCODE ctorFtn = pCanonMT->GetSlot(slot); #ifdef _X86_ BEGIN_CALL_TO_MANAGED(); typedef void (__fastcall * CtorFtnType)(BYTE*, BYTE*); for (SIZE_T i = 0; i < cElements; i++) { // Since GetSlot() is not idempotent and may have returned // a non-optimal entry-point the first time round. if (i == 1) { ctorFtn = pCanonMT->GetSlot(slot); } BYTE* thisPtr = (((BYTE*) OBJECTREFToObject (*arrayRef)) + offset); #ifdef _DEBUG __asm { mov ECX, thisPtr mov EDX, pElemMT // Instantiation argument if the type is generic call [ctorFtn] nop // Mark the fact that we can call managed code } #else // _DEBUG (*(CtorFtnType)ctorFtn)(thisPtr, (BYTE*)pElemMT); #endif // _DEBUG offset += size; } END_CALL_TO_MANAGED(); #else // _X86_ // // This is quite a bit slower, but it is portable. // for (SIZE_T i =0; i < cElements; i++) { // Since GetSlot() is not idempotent and may have returned // a non-optimal entry-point the first time round. if (i == 1) { ctorFtn = pCanonMT->GetSlot(slot); } BYTE* thisPtr = (((BYTE*) OBJECTREFToObject (*arrayRef)) + offset); PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(ctorFtn); DECLARE_ARGHOLDER_ARRAY(args, 2); args[ARGNUM_0] = PTR_TO_ARGHOLDER(thisPtr); args[ARGNUM_1] = PTR_TO_ARGHOLDER(pElemMT); // Instantiation argument if the type is generic CALL_MANAGED_METHOD_NORET(args); offset += size; } #endif // _X86_ } FCIMPL1(void, ArrayNative::Initialize, ArrayBase* array) { FCALL_CONTRACT; if (array == NULL) { FCThrowVoid(kNullReferenceException); } MethodTable* pArrayMT = array->GetMethodTable(); TypeHandle thElem = pArrayMT->GetApproxArrayElementTypeHandle(); if (thElem.IsTypeDesc()) return; MethodTable * pElemMT = thElem.AsMethodTable(); if (!pElemMT->HasDefaultConstructor() || !pElemMT->IsValueType()) return; ARRAYBASEREF arrayRef (array); HELPER_METHOD_FRAME_BEGIN_1(arrayRef); ArrayInitializeWorker(&arrayRef, pArrayMT, pElemMT); HELPER_METHOD_FRAME_END(); } FCIMPLEND // Returns an enum saying whether you can copy an array of srcType into destType. ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayTypeNoGC(const BASEARRAYREF pSrc, const BASEARRAYREF pDest) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; SO_TOLERANT; PRECONDITION(pSrc != NULL); PRECONDITION(pDest != NULL); } CONTRACTL_END; // The next 50 lines are a little tricky. Change them with great care. // // This first bit is a minor optimization: e.g. when copying byte[] to byte[] // we do not need to call GetArrayElementTypeHandle(). MethodTable *pSrcMT = pSrc->GetMethodTable(); MethodTable *pDestMT = pDest->GetMethodTable(); if (pSrcMT == pDestMT) return AssignWillWork; TypeHandle srcTH = pSrcMT->GetApproxArrayElementTypeHandle(); TypeHandle destTH = pDestMT->GetApproxArrayElementTypeHandle(); if (srcTH == destTH) // This check kicks for different array kind or dimensions return AssignWillWork; // Value class boxing if (srcTH.IsValueType() && !destTH.IsValueType()) { switch (srcTH.CanCastToNoGC(destTH)) { case TypeHandle::CanCast : return AssignBoxValueClassOrPrimitive; case TypeHandle::CannotCast : return AssignWrongType; default : return AssignDontKnow; } } // Value class unboxing. if (!srcTH.IsValueType() && destTH.IsValueType()) { if (srcTH.CanCastToNoGC(destTH) == TypeHandle::CanCast) return AssignUnboxValueClass; else if (destTH.CanCastToNoGC(srcTH) == TypeHandle::CanCast) // V extends IV. Copying from IV to V, or Object to V. return AssignUnboxValueClass; else return AssignDontKnow; } const CorElementType srcElType = srcTH.GetSignatureCorElementType(); const CorElementType destElType = destTH.GetSignatureCorElementType(); _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 (InvokeUtil::CanPrimitiveWiden(destElType, srcElType)) return AssignPrimitiveWiden; else return AssignWrongType; } // dest Object extends src if (srcTH.CanCastToNoGC(destTH) == TypeHandle::CanCast) return AssignWillWork; // src Object extends dest if (destTH.CanCastToNoGC(srcTH) == TypeHandle::CanCast) 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; // Enum is stored as a primitive of type dest. if (srcTH.IsEnum() && srcTH.GetInternalCorElementType() == destElType) return AssignWillWork; return AssignDontKnow; } // Returns an enum saying whether you can copy an array of srcType into destType. ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayType(const BASEARRAYREF pSrc, const BASEARRAYREF pDest) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(pSrc != NULL); PRECONDITION(pDest != NULL); } CONTRACTL_END; // The next 50 lines are a little tricky. Change them with great care. // // This first bit is a minor optimization: e.g. when copying byte[] to byte[] // we do not need to call GetArrayElementTypeHandle(). MethodTable *pSrcMT = pSrc->GetMethodTable(); MethodTable *pDestMT = pDest->GetMethodTable(); if (pSrcMT == pDestMT) return AssignWillWork; TypeHandle srcTH = pSrcMT->GetApproxArrayElementTypeHandle(); TypeHandle destTH = pDestMT->GetApproxArrayElementTypeHandle(); if (srcTH == destTH) // This check kicks for different array kind or dimensions return AssignWillWork; // 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.GetSignatureCorElementType(); const CorElementType destElType = destTH.GetSignatureCorElementType(); _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 (InvokeUtil::CanPrimitiveWiden(destElType, srcElType)) return AssignPrimitiveWiden; else return AssignWrongType; } // dest Object extends src if (srcTH.CanCastTo(destTH)) return AssignWillWork; // 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; // Enum is stored as a primitive of type dest. if (srcTH.IsEnum() && srcTH.GetInternalCorElementType() == destElType) return AssignWillWork; return AssignWrongType; } // Casts and assigns each element of src array to the dest array type. void ArrayNative::CastCheckEachElement(const BASEARRAYREF pSrcUnsafe, const unsigned int srcIndex, BASEARRAYREF pDestUnsafe, unsigned int destIndex, const unsigned int len) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(pSrcUnsafe != NULL); PRECONDITION(srcIndex >= 0); PRECONDITION(pDestUnsafe != NULL); PRECONDITION(len > 0); } CONTRACTL_END; // pSrc is either a PTRARRAYREF or a multidimensional array. TypeHandle destTH = pDestUnsafe->GetArrayElementTypeHandle(); struct _gc { OBJECTREF obj; BASEARRAYREF pDest; BASEARRAYREF pSrc; } gc; gc.obj = NULL; gc.pDest = pDestUnsafe; gc.pSrc = pSrcUnsafe; GCPROTECT_BEGIN(gc); for(unsigned int i=srcIndex; iGetDataPtr() + i)); // Now that we have grabbed obj, we are no longer subject to races from another // mutator thread. if (gc.obj != NULL && !ObjIsInstanceOf(OBJECTREFToObject(gc.obj), destTH)) COMPlusThrow(kInvalidCastException, W("InvalidCast_DownCastArrayElement")); OBJECTREF * destData = (OBJECTREF*)(gc.pDest->GetDataPtr()) + i - srcIndex + destIndex; SetObjectReference(destData, gc.obj, gc.pDest->GetAppDomain()); } GCPROTECT_END(); return; } // Will box each element in an array of value classes or primitives into an array of Objects. void ArrayNative::BoxEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(pSrc != NULL); PRECONDITION(srcIndex >= 0); PRECONDITION(pDest != NULL); PRECONDITION(length > 0); } CONTRACTL_END; // pDest is either a PTRARRAYREF or a multidimensional array. _ASSERTE(pSrc!=NULL && srcIndex>=0 && pDest!=NULL && destIndex>=0 && length>=0); TypeHandle srcTH = pSrc->GetArrayElementTypeHandle(); #ifdef _DEBUG TypeHandle destTH = pDest->GetArrayElementTypeHandle(); #endif _ASSERTE(srcTH.GetSignatureCorElementType() == ELEMENT_TYPE_CLASS || srcTH.GetSignatureCorElementType() == ELEMENT_TYPE_VALUETYPE || CorTypeInfo::IsPrimitiveType(pSrc->GetArrayElementType())); _ASSERTE(!destTH.GetMethodTable()->IsValueType()); // Get method table of type we're copying from - we need to allocate objects of that type. MethodTable * pSrcMT = srcTH.GetMethodTable(); PREFIX_ASSUME(pSrcMT != NULL); if (!pSrcMT->IsClassInited()) { BASEARRAYREF pSrcTmp = pSrc; BASEARRAYREF pDestTmp = pDest; GCPROTECT_BEGIN (pSrcTmp); GCPROTECT_BEGIN (pDestTmp); pSrcMT->CheckRunClassInitThrowing(); pSrc = pSrcTmp; pDest = pDestTmp; GCPROTECT_END (); GCPROTECT_END (); } const unsigned int srcSize = pSrcMT->GetNumInstanceFieldBytes(); unsigned int srcArrayOffset = srcIndex * srcSize; struct _gc { BASEARRAYREF src; BASEARRAYREF dest; OBJECTREF obj; } gc; gc.src = pSrc; gc.dest = pDest; gc.obj = NULL; void* srcPtr = 0; GCPROTECT_BEGIN(gc); GCPROTECT_BEGININTERIOR(srcPtr); for (unsigned int i=destIndex; i < destIndex+length; i++, srcArrayOffset += srcSize) { srcPtr = (BYTE*)gc.src->GetDataPtr() + srcArrayOffset; gc.obj = pSrcMT->FastBox(&srcPtr); OBJECTREF * destData = (OBJECTREF*)((gc.dest)->GetDataPtr()) + i; SetObjectReference(destData, gc.obj, gc.dest->GetAppDomain()); } GCPROTECT_END(); GCPROTECT_END(); } // Unboxes from an Object[] into a value class or primitive array. void ArrayNative::UnBoxEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(pSrc != NULL); PRECONDITION(srcIndex >= 0); PRECONDITION(pDest != NULL); PRECONDITION(destIndex >= 0); PRECONDITION(length > 0); } CONTRACTL_END; #ifdef _DEBUG TypeHandle srcTH = pSrc->GetArrayElementTypeHandle(); #endif TypeHandle destTH = pDest->GetArrayElementTypeHandle(); _ASSERTE(destTH.GetSignatureCorElementType() == ELEMENT_TYPE_CLASS || destTH.GetSignatureCorElementType() == ELEMENT_TYPE_VALUETYPE || CorTypeInfo::IsPrimitiveType(pDest->GetArrayElementType())); _ASSERTE(!srcTH.GetMethodTable()->IsValueType()); MethodTable * pDestMT = destTH.GetMethodTable(); PREFIX_ASSUME(pDestMT != NULL); const unsigned int destSize = pDestMT->GetNumInstanceFieldBytes(); BYTE* srcData = (BYTE*) pSrc->GetDataPtr() + srcIndex * sizeof(OBJECTREF); BYTE* data = (BYTE*) pDest->GetDataPtr() + destIndex * destSize; for(; length>0; length--, srcData += sizeof(OBJECTREF), data += destSize) { OBJECTREF obj = ObjectToOBJECTREF(*(Object**)srcData); // Now that we have retrieved the element, we are no longer subject to race // conditions from another array mutator. if (!pDestMT->UnBoxInto(data, obj)) goto fail; } return; fail: COMPlusThrow(kInvalidCastException, W("InvalidCast_DownCastArrayElement")); } // Widen primitive types to another primitive type. void ArrayNative::PrimitiveWiden(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(pSrc != NULL); PRECONDITION(srcIndex >= 0); PRECONDITION(pDest != NULL); PRECONDITION(destIndex >= 0); PRECONDITION(length > 0); } CONTRACTL_END; // Get appropriate sizes, which requires method tables. TypeHandle srcTH = pSrc->GetArrayElementTypeHandle(); TypeHandle destTH = pDest->GetArrayElementTypeHandle(); const CorElementType srcElType = srcTH.GetSignatureCorElementType(); const CorElementType destElType = destTH.GetSignatureCorElementType(); const unsigned int srcSize = GetSizeForCorElementType(srcElType); const unsigned int destSize = GetSizeForCorElementType(destElType); BYTE* srcData = (BYTE*) pSrc->GetDataPtr() + srcIndex * srcSize; BYTE* data = (BYTE*) pDest->GetDataPtr() + destIndex * destSize; _ASSERTE(srcElType != destElType); // We shouldn't be here if these are the same type. _ASSERTE(CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType)); for(; length>0; length--, srcData += srcSize, data += destSize) { // We pretty much have to do some fancy datatype mangling every time here, for // converting w/ sign extension and floating point conversions. switch (srcElType) { case ELEMENT_TYPE_U1: switch (destElType) { case ELEMENT_TYPE_R4: *(float*)data = *(UINT8*)srcData; break; case ELEMENT_TYPE_R8: *(double*)data = *(UINT8*)srcData; break; #ifndef BIGENDIAN default: *(UINT8*)data = *(UINT8*)srcData; memset(data+1, 0, destSize - 1); break; #else // BIGENDIAN case ELEMENT_TYPE_CHAR: case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: *(INT16*)data = *(UINT8*)srcData; break; case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: *(INT32*)data = *(UINT8*)srcData; break; case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: *(INT64*)data = *(UINT8*)srcData; break; default: _ASSERTE(!"Array.Copy from U1 to another type hit unsupported widening conversion"); #endif // BIGENDIAN } break; case ELEMENT_TYPE_I1: switch (destElType) { case ELEMENT_TYPE_I2: *(INT16*)data = *(INT8*)srcData; break; case ELEMENT_TYPE_I4: *(INT32*)data = *(INT8*)srcData; break; case ELEMENT_TYPE_I8: *(INT64*)data = *(INT8*)srcData; break; case ELEMENT_TYPE_R4: *(float*)data = *(INT8*)srcData; break; case ELEMENT_TYPE_R8: *(double*)data = *(INT8*)srcData; break; default: _ASSERTE(!"Array.Copy from I1 to another type hit unsupported widening conversion"); } break; case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: switch (destElType) { case ELEMENT_TYPE_R4: *(float*)data = *(UINT16*)srcData; break; case ELEMENT_TYPE_R8: *(double*)data = *(UINT16*)srcData; break; #ifndef BIGENDIAN default: *(UINT16*)data = *(UINT16*)srcData; memset(data+2, 0, destSize - 2); break; #else // BIGENDIAN case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: *(UINT16*)data = *(UINT16*)srcData; break; case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: *(UINT32*)data = *(UINT16*)srcData; break; case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: *(UINT64*)data = *(UINT16*)srcData; break; default: _ASSERTE(!"Array.Copy from U1 to another type hit unsupported widening conversion"); #endif // BIGENDIAN } break; case ELEMENT_TYPE_I2: switch (destElType) { case ELEMENT_TYPE_I4: *(INT32*)data = *(INT16*)srcData; break; case ELEMENT_TYPE_I8: *(INT64*)data = *(INT16*)srcData; break; case ELEMENT_TYPE_R4: *(float*)data = *(INT16*)srcData; break; case ELEMENT_TYPE_R8: *(double*)data = *(INT16*)srcData; break; default: _ASSERTE(!"Array.Copy from I2 to another type hit unsupported widening conversion"); } break; case ELEMENT_TYPE_I4: switch (destElType) { case ELEMENT_TYPE_I8: *(INT64*)data = *(INT32*)srcData; break; case ELEMENT_TYPE_R4: *(float*)data = (float)*(INT32*)srcData; break; case ELEMENT_TYPE_R8: *(double*)data = *(INT32*)srcData; break; default: _ASSERTE(!"Array.Copy from I4 to another type hit unsupported widening conversion"); } break; case ELEMENT_TYPE_U4: switch (destElType) { case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: *(INT64*)data = *(UINT32*)srcData; break; case ELEMENT_TYPE_R4: *(float*)data = (float)*(UINT32*)srcData; break; case ELEMENT_TYPE_R8: *(double*)data = *(UINT32*)srcData; break; default: _ASSERTE(!"Array.Copy from U4 to another type hit unsupported widening conversion"); } break; case ELEMENT_TYPE_I8: if (destElType == ELEMENT_TYPE_R4) { *(float*) data = (float) *(INT64*)srcData; } else { _ASSERTE(destElType==ELEMENT_TYPE_R8); *(double*) data = (double) *(INT64*)srcData; } break; case ELEMENT_TYPE_U8: if (destElType == ELEMENT_TYPE_R4) { //*(float*) data = (float) *(UINT64*)srcData; INT64 srcVal = *(INT64*)srcData; float f = (float) srcVal; if (srcVal < 0) f += 4294967296.0f * 4294967296.0f; // This is 2^64 *(float*) data = f; } else { _ASSERTE(destElType==ELEMENT_TYPE_R8); //*(double*) data = (double) *(UINT64*)srcData; INT64 srcVal = *(INT64*)srcData; double d = (double) srcVal; if (srcVal < 0) d += 4294967296.0 * 4294967296.0; // This is 2^64 *(double*) data = d; } break; case ELEMENT_TYPE_R4: *(double*) data = *(float*)srcData; break; default: _ASSERTE(!"Fell through outer switch in PrimitiveWiden! Unknown primitive type for source array!"); } } } // // 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. // // The CRT version of memmove does not always guarantee that updates of aligned fields stay atomic (e.g. it is using "rep movsb" in some cases). // Type safety guarantees and background GC scanning requires object references in GC heap to be updated atomically. // void memmoveGCRefs(void *dest, const void *src, size_t len) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(CheckPointer(dest)); PRECONDITION(CheckPointer(src)); PRECONDITION(len >= 0); SO_TOLERANT; } CONTRACTL_END; // Make sure everything is pointer aligned _ASSERTE(IS_ALIGNED(dest, sizeof(SIZE_T))); _ASSERTE(IS_ALIGNED(src, sizeof(SIZE_T))); _ASSERTE(IS_ALIGNED(len, sizeof(SIZE_T))); size_t size = len; BYTE * dmem = (BYTE *)dest; BYTE * smem = (BYTE *)src; GCHeapMemoryBarrier(); if (dmem <= smem || smem + size <= dmem) { // copy 16 bytes at a time while (size >= 4 * sizeof(SIZE_T)) { size -= 4 * sizeof(SIZE_T); ((SIZE_T *)dmem)[0] = ((SIZE_T *)smem)[0]; ((SIZE_T *)dmem)[1] = ((SIZE_T *)smem)[1]; ((SIZE_T *)dmem)[2] = ((SIZE_T *)smem)[2]; ((SIZE_T *)dmem)[3] = ((SIZE_T *)smem)[3]; smem += 4 * sizeof(SIZE_T); dmem += 4 * sizeof(SIZE_T); } if ((size & (2 * sizeof(SIZE_T))) != 0) { ((SIZE_T *)dmem)[0] = ((SIZE_T *)smem)[0]; ((SIZE_T *)dmem)[1] = ((SIZE_T *)smem)[1]; smem += 2 * sizeof(SIZE_T); dmem += 2 * sizeof(SIZE_T); } if ((size & sizeof(SIZE_T)) != 0) { ((SIZE_T *)dmem)[0] = ((SIZE_T *)smem)[0]; } } else { smem += size; dmem += size; // copy 16 bytes at a time while (size >= 4 * sizeof(SIZE_T)) { size -= 4 * sizeof(SIZE_T); smem -= 4 * sizeof(SIZE_T); dmem -= 4 * sizeof(SIZE_T); ((SIZE_T *)dmem)[3] = ((SIZE_T *)smem)[3]; ((SIZE_T *)dmem)[2] = ((SIZE_T *)smem)[2]; ((SIZE_T *)dmem)[1] = ((SIZE_T *)smem)[1]; ((SIZE_T *)dmem)[0] = ((SIZE_T *)smem)[0]; } if ((size & (2 * sizeof(SIZE_T))) != 0) { smem -= 2 * sizeof(SIZE_T); dmem -= 2 * sizeof(SIZE_T); ((SIZE_T *)dmem)[1] = ((SIZE_T *)smem)[1]; ((SIZE_T *)dmem)[0] = ((SIZE_T *)smem)[0]; } if ((size & sizeof(SIZE_T)) != 0) { smem -= sizeof(SIZE_T); dmem -= sizeof(SIZE_T); ((SIZE_T *)dmem)[0] = ((SIZE_T *)smem)[0]; } } GCHeap::GetGCHeap()->SetCardsAfterBulkCopy((Object**)dest, len); } void ArrayNative::ArrayCopyNoTypeCheck(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; SO_TOLERANT; PRECONDITION(pSrc != NULL); PRECONDITION(srcIndex >= 0); PRECONDITION(pDest != NULL); PRECONDITION(length > 0); } CONTRACTL_END; BYTE *src = (BYTE*)pSrc->GetDataPtr(); BYTE *dst = (BYTE*)pDest->GetDataPtr(); SIZE_T size = pSrc->GetComponentSize(); src += srcIndex * size; dst += destIndex * size; if (pDest->GetMethodTable()->ContainsPointers()) { memmoveGCRefs(dst, src, length * size); } else { memmove(dst, src, length * size); } } FCIMPL6(void, ArrayNative::ArrayCopy, ArrayBase* m_pSrc, INT32 m_iSrcIndex, ArrayBase* m_pDst, INT32 m_iDstIndex, INT32 m_iLength, CLR_BOOL reliable) { FCALL_CONTRACT; BYTE *src; BYTE *dst; SIZE_T size; struct _gc { BASEARRAYREF pSrc; BASEARRAYREF pDst; } gc; gc.pSrc = (BASEARRAYREF)m_pSrc; gc.pDst = (BASEARRAYREF)m_pDst; // // creating a HelperMethodFrame is quite expensive, // so we want to delay this for the most common case which doesn't trigger a GC. // FCThrow is needed to throw an exception without a HelperMethodFrame // // cannot pass null for source or destination if (gc.pSrc == NULL || gc.pDst == NULL) { FCThrowArgumentNullVoid(gc.pSrc==NULL ? W("source") : W("dest")); } // source and destination must be arrays _ASSERTE(gc.pSrc->GetMethodTable()->IsArray()); _ASSERTE(gc.pDst->GetMethodTable()->IsArray()); // Equal method tables should imply equal rank _ASSERTE(!(gc.pSrc->GetMethodTable() == gc.pDst->GetMethodTable() && gc.pSrc->GetRank() != gc.pDst->GetRank())); // Which enables us to avoid touching the EEClass in simple cases if (gc.pSrc->GetMethodTable() != gc.pDst->GetMethodTable() && gc.pSrc->GetRank() != gc.pDst->GetRank()) { FCThrowResVoid(kRankException, W("Rank_MustMatch")); } g_IBCLogger.LogMethodTableAccess(gc.pSrc->GetMethodTable()); g_IBCLogger.LogMethodTableAccess(gc.pDst->GetMethodTable()); int srcLB = gc.pSrc->GetLowerBoundsPtr()[0]; int destLB = gc.pDst->GetLowerBoundsPtr()[0]; // array bounds checking const unsigned int srcLen = gc.pSrc->GetNumComponents(); const unsigned int destLen = gc.pDst->GetNumComponents(); if (m_iLength < 0) FCThrowArgumentOutOfRangeVoid(W("length"), W("ArgumentOutOfRange_NeedNonNegNum")); if (m_iSrcIndex < srcLB || (m_iSrcIndex - srcLB < 0)) FCThrowArgumentOutOfRangeVoid(W("srcIndex"), W("ArgumentOutOfRange_ArrayLB")); if (m_iDstIndex < destLB || (m_iDstIndex - destLB < 0)) FCThrowArgumentOutOfRangeVoid(W("dstIndex"), W("ArgumentOutOfRange_ArrayLB")); if ((DWORD)(m_iSrcIndex - srcLB + m_iLength) > srcLen) FCThrowResVoid(kArgumentException, W("Arg_LongerThanSrcArray")); if ((DWORD)(m_iDstIndex - destLB + m_iLength) > destLen) FCThrowResVoid(kArgumentException, W("Arg_LongerThanDestArray")); int r = 0; // Small perf optimization - we copy from one portion of an array back to // itself a lot when resizing collections, etc. The cost of doing the type // checking is significant for copying small numbers of bytes (~half of the time // for copying 1 byte within one array from element 0 to element 1). if (gc.pSrc == gc.pDst) r = AssignWillWork; else r = CanAssignArrayTypeNoGC(gc.pSrc, gc.pDst); if (r == AssignWrongType) { FCThrowResVoid(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType")); } if (r == AssignWillWork) { if (m_iLength > 0) ArrayCopyNoTypeCheck(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength); FC_GC_POLL(); return; } else if (reliable) { FCThrowResVoid(kArrayTypeMismatchException, W("ArrayTypeMismatch_ConstrainedCopy")); } HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); if (r == AssignDontKnow) { r = CanAssignArrayType(gc.pSrc, gc.pDst); } CONSISTENCY_CHECK(r != AssignDontKnow); if (r == AssignWrongType) COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType")); // If we were called from Array.ConstrainedCopy, ensure that the array copy // is guaranteed to succeed. _ASSERTE(!reliable || r == AssignWillWork); if (m_iLength > 0) { switch (r) { case AssignWillWork: ArrayCopyNoTypeCheck(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength); break; case AssignUnboxValueClass: UnBoxEachElement(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength); break; case AssignBoxValueClassOrPrimitive: BoxEachElement(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength); break; case AssignMustCast: CastCheckEachElement(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength); break; case AssignPrimitiveWiden: PrimitiveWiden(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength); break; default: _ASSERTE(!"Fell through switch in Array.Copy!"); } } HELPER_METHOD_FRAME_END(); } FCIMPLEND FCIMPL3(void, ArrayNative::ArrayClear, ArrayBase* pArrayUNSAFE, INT32 iIndex, INT32 iLength) { FCALL_CONTRACT; BASEARRAYREF pArray = (BASEARRAYREF)pArrayUNSAFE; HELPER_METHOD_FRAME_BEGIN_1(pArray); // cannot pass null for array if (pArray == NULL) COMPlusThrowArgumentNull(W("array"), W("ArgumentNull_Array")); // array must be an array _ASSERTE(pArray->GetMethodTable()->IsArray()); // array bounds checking int lb = pArray->GetLowerBoundsPtr()[0]; if (iIndex < lb || (iIndex - lb) < 0 || iLength < 0) COMPlusThrow(kIndexOutOfRangeException); if ((iIndex - lb) > (int)pArray->GetNumComponents() - iLength) COMPlusThrow(kIndexOutOfRangeException); if (iLength > 0) { char* array = (char*)pArray->GetDataPtr(); SIZE_T size = pArray->GetComponentSize(); _ASSERTE(size >= 1); ZeroMemoryInGCHeap(array + (iIndex - lb) * size, iLength * size); } HELPER_METHOD_FRAME_END(); } FCIMPLEND // Check we're allowed to create an array with the given element type. void ArrayNative::CheckElementType(TypeHandle elementType) { // Check for simple types first. if (!elementType.IsTypeDesc()) { MethodTable *pMT = elementType.AsMethodTable(); // TODO: We also should check for type/member visibility here. To do that we can replace // the following chunk of code with a simple InvokeUtil::CanAccessClass call. // But it's too late to make this change in Dev10 and we want SL4 to be compatible with Dev10. #ifndef FEATURE_CORECLR // Make sure security allows us access to the array type - if it is critical, convert that to a // demand for full trust if (!SecurityStackWalk::HasFlagsOrFullyTrusted(0)) { if (Security::TypeRequiresTransparencyCheck(pMT, true)) { // If we're creating a critical type, convert the critical check into a demand for full trust Security::SpecialDemand(SSWT_LATEBOUND_LINKDEMAND, SECURITY_FULL_TRUST); } } #else if (Security::TypeRequiresTransparencyCheck(pMT)) { // The AccessCheckOptions flag doesn't matter because we just need to get the caller. RefSecContext sCtx(AccessCheckOptions::kMemberAccess); AccessCheckOptions accessCheckOptions(InvokeUtil::GetInvocationAccessCheckType(), NULL /*pAccessContext*/, TRUE /*throwIfTargetIsInaccessible*/, pMT /*pTargetMT*/); accessCheckOptions.DemandMemberAccessOrFail(&sCtx, pMT, FALSE /*visibilityCheck*/); } #endif // !FEATURE_CORECLR // Check for byref-like types. if (pMT->IsByRefLike()) COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefLike[]")); // Check for open generic types. if (pMT->IsGenericTypeDefinition() || pMT->ContainsGenericVariables()) COMPlusThrow(kNotSupportedException, W("NotSupported_OpenType")); // Check for Void. if (elementType.GetSignatureCorElementType() == ELEMENT_TYPE_VOID) COMPlusThrow(kNotSupportedException, W("NotSupported_Void[]")); // That's all the dangerous simple types we know, it must be OK. return; } // Checks apply recursively for arrays of arrays etc. if (elementType.IsArray()) { CheckElementType(elementType.GetElementType()); return; } // ByRefs and generic type variables are never allowed. if (elementType.IsByRef() || elementType.IsGenericVariable()) COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); // We can create pointers and function pointers, but it requires skip verification permission. CorElementType etType = elementType.GetSignatureCorElementType(); if (etType == ELEMENT_TYPE_PTR || etType == ELEMENT_TYPE_FNPTR) { Security::SpecialDemand(SSWT_LATEBOUND_LINKDEMAND, SECURITY_SKIP_VER); return; } // We shouldn't get here (it means we've encountered a new type of typehandle if we do). _ASSERTE(!"Shouldn't get here, unknown type handle type"); COMPlusThrow(kNotSupportedException); } FCIMPL4(Object*, ArrayNative::CreateInstance, void* elementTypeHandle, INT32 rank, INT32* pLengths, INT32* pLowerBounds) { CONTRACTL { FCALL_CHECK; PRECONDITION(rank > 0); PRECONDITION(CheckPointer(pLengths)); PRECONDITION(CheckPointer(pLowerBounds, NULL_OK)); } CONTRACTL_END; OBJECTREF pRet = NULL; TypeHandle elementType = TypeHandle::FromPtr(elementTypeHandle); _ASSERTE(!elementType.IsNull()); // pLengths and pLowerBounds are pinned buffers. No need to protect them. HELPER_METHOD_FRAME_BEGIN_RET_0(); CheckElementType(elementType); CorElementType CorType = elementType.GetSignatureCorElementType(); CorElementType kind = ELEMENT_TYPE_ARRAY; // Is it ELEMENT_TYPE_SZARRAY array? if (rank == 1 && (pLowerBounds == NULL || pLowerBounds[0] == 0) #ifdef FEATURE_64BIT_ALIGNMENT // On platforms where 64-bit types require 64-bit alignment and don't obtain it naturally force us // through the slow path where this will be handled. && (CorType != ELEMENT_TYPE_I8) && (CorType != ELEMENT_TYPE_U8) && (CorType != ELEMENT_TYPE_R8) #endif ) { // Shortcut for common cases if (CorTypeInfo::IsPrimitiveType(CorType)) { pRet = AllocatePrimitiveArray(CorType,pLengths[0]); goto Done; } else if (CorTypeInfo::IsObjRef(CorType)) { pRet = AllocateObjectArray(pLengths[0],elementType); goto Done; } kind = ELEMENT_TYPE_SZARRAY; pLowerBounds = NULL; } { // Find the Array class... TypeHandle typeHnd = ClassLoader::LoadArrayTypeThrowing(elementType, kind, rank); DWORD boundsSize = 0; INT32* bounds; if (pLowerBounds != NULL) { if (!ClrSafeInt::multiply(rank, 2, boundsSize)) COMPlusThrowOM(); DWORD dwAllocaSize = 0; if (!ClrSafeInt::multiply(boundsSize, sizeof(INT32), dwAllocaSize)) COMPlusThrowOM(); bounds = (INT32*) _alloca(dwAllocaSize); for (int i=0;i::multiply(boundsSize, sizeof(INT32), dwAllocaSize)) COMPlusThrowOM(); bounds = (INT32*) _alloca(dwAllocaSize); // We need to create a private copy of pLengths to avoid holes caused // by caller mutating the array for (int i=0;i= 0); } CONTRACTL_END; // FC_GC_POLL not necessary. We poll for GC in Array.Rank that's always called // right before this function FC_GC_POLL_NOT_NEEDED(); BASEARRAYREF refThis = (BASEARRAYREF) refThisUNSAFE; _ASSERTE(rank == (INT32)refThis->GetRank()); SIZE_T Offset = 0; const INT32 *pBoundsPtr = refThis->GetBoundsPtr(); if (rank == 1) { Offset = pIndices[0] - refThis->GetLowerBoundsPtr()[0]; // Bounds check each index // Casting to unsigned allows us to use one compare for [0..limit-1] if (((UINT32) Offset) >= ((UINT32) pBoundsPtr[0])) FCThrowVoid(kIndexOutOfRangeException); } else { // Avoid redundant computation in GetLowerBoundsPtr const INT32 *pLowerBoundsPtr = pBoundsPtr + rank; _ASSERTE(refThis->GetLowerBoundsPtr() == pLowerBoundsPtr); SIZE_T Multiplier = 1; for (int i = rank; i >= 1; i--) { INT32 curIndex = pIndices[i-1] - pLowerBoundsPtr[i-1]; // Bounds check each index // Casting to unsigned allows us to use one compare for [0..limit-1] if (((UINT32) curIndex) >= ((UINT32) pBoundsPtr[i-1])) FCThrowVoid(kIndexOutOfRangeException); Offset += curIndex * Multiplier; Multiplier *= pBoundsPtr[i-1]; } } TypeHandle arrayElementType = refThis->GetArrayElementTypeHandle(); // Legacy behavior if (arrayElementType.IsTypeDesc()) { CorElementType elemtype = arrayElementType.AsTypeDesc()->GetInternalCorElementType(); if (elemtype == ELEMENT_TYPE_PTR || elemtype == ELEMENT_TYPE_FNPTR) FCThrowResVoid(kNotSupportedException, W("NotSupported_Type")); } #ifdef _DEBUG CorElementType elemtype = arrayElementType.GetInternalCorElementType(); _ASSERTE(elemtype != ELEMENT_TYPE_PTR && elemtype != ELEMENT_TYPE_FNPTR); #endif elemRef->data = refThis->GetDataPtr() + (Offset * refThis->GetComponentSize()); elemRef->type = arrayElementType; } FCIMPLEND FCIMPL2(void, ArrayNative::SetValue, TypedByRef * target, Object* objUNSAFE) { FCALL_CONTRACT; OBJECTREF obj = ObjectToOBJECTREF(objUNSAFE); TypeHandle thTarget(target->type); MethodTable* pTargetMT = thTarget.GetMethodTable(); PREFIX_ASSUME(NULL != pTargetMT); if (obj == NULL) { // Null is the universal zero... if (pTargetMT->IsValueType()) InitValueClass(target->data,pTargetMT); else ClearObjectReference((OBJECTREF*)target->data); } else if (thTarget == TypeHandle(g_pObjectClass)) { // Everything is compatible with Object SetObjectReference((OBJECTREF*)target->data,(OBJECTREF)obj,GetAppDomain()); } else if (!pTargetMT->IsValueType()) { if (ObjIsInstanceOfNoGC(OBJECTREFToObject(obj), thTarget) != TypeHandle::CanCast) { // target->data is protected by the caller HELPER_METHOD_FRAME_BEGIN_1(obj); if (!ObjIsInstanceOf(OBJECTREFToObject(obj), thTarget)) COMPlusThrow(kInvalidCastException,W("InvalidCast_StoreArrayElement")); HELPER_METHOD_FRAME_END(); } SetObjectReference((OBJECTREF*)target->data,obj,GetAppDomain()); } else { // value class or primitive type if (!pTargetMT->UnBoxInto(target->data, obj)) { // target->data is protected by the caller HELPER_METHOD_FRAME_BEGIN_1(obj); ARG_SLOT value = 0; // Allow enum -> primitive conversion, disallow primitive -> enum conversion TypeHandle thSrc = obj->GetTypeHandle(); CorElementType srcType = thSrc.GetVerifierCorElementType(); CorElementType targetType = thTarget.GetSignatureCorElementType(); if (!InvokeUtil::IsPrimitiveType(srcType) || !InvokeUtil::IsPrimitiveType(targetType)) COMPlusThrow(kInvalidCastException, W("InvalidCast_StoreArrayElement")); // Get a properly widened type InvokeUtil::CreatePrimitiveValue(targetType,srcType,obj,&value); UINT cbSize = CorTypeInfo::Size(targetType); memcpyNoGCRefs(target->data, ArgSlotEndianessFixup(&value, cbSize), cbSize); HELPER_METHOD_FRAME_END(); } } } FCIMPLEND // This method will initialize an array from a TypeHandle to a field. FCIMPL2_IV(void, ArrayNative::InitializeArray, ArrayBase* pArrayRef, FCALLRuntimeFieldHandle structField) { FCALL_CONTRACT; BASEARRAYREF arr = BASEARRAYREF(pArrayRef); REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(FCALL_RFH_TO_REFLECTFIELD(structField)); HELPER_METHOD_FRAME_BEGIN_2(arr, refField); if ((arr == 0) || (refField == NULL)) COMPlusThrow(kArgumentNullException); FieldDesc* pField = (FieldDesc*) refField->GetField(); if (!pField->IsRVA()) COMPlusThrow(kArgumentException); // Report the RVA field to the logger. g_IBCLogger.LogRVADataAccess(pField); // Note that we do not check that the field is actually in the PE file that is initializing // the array. Basically the data being published is can be accessed by anyone with the proper // permissions (C# marks these as assembly visibility, and thus are protected from outside // snooping) if (!CorTypeInfo::IsPrimitiveType(arr->GetArrayElementType()) && !arr->GetArrayElementTypeHandle().IsEnum()) COMPlusThrow(kArgumentException); SIZE_T dwCompSize = arr->GetComponentSize(); SIZE_T dwElemCnt = arr->GetNumComponents(); SIZE_T dwTotalSize = dwCompSize * dwElemCnt; DWORD size = pField->LoadSize(); // make certain you don't go off the end of the rva static if (dwTotalSize > size) COMPlusThrow(kArgumentException); void *src = pField->GetStaticAddressHandle(NULL); void *dest = arr->GetDataPtr(); #if BIGENDIAN DWORD i; switch (dwCompSize) { case 1: memcpyNoGCRefs(dest, src, dwElemCnt); break; case 2: for (i = 0; i < dwElemCnt; i++) *((UINT16*)dest + i) = GET_UNALIGNED_VAL16((UINT16*)src + i); break; case 4: for (i = 0; i < dwElemCnt; i++) *((UINT32*)dest + i) = GET_UNALIGNED_VAL32((UINT32*)src + i); break; case 8: for (i = 0; i < dwElemCnt; i++) *((UINT64*)dest + i) = GET_UNALIGNED_VAL64((UINT64*)src + i); break; default: // should not reach here. UNREACHABLE_MSG("Incorrect primitive type size!"); break; } #else memcpyNoGCRefs(dest, src, dwTotalSize); #endif HELPER_METHOD_FRAME_END(); } FCIMPLEND