// 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: ObjectClone.cpp // // #include "common.h" #ifdef FEATURE_REMOTING #include "objectclone.h" #include "frames.h" #include "assembly.hpp" #include "field.h" #include "security.h" #include "virtualcallstub.h" #include "crossdomaincalls.h" #include "callhelpers.h" #include "jitinterface.h" #include "typestring.h" #include "typeparse.h" #include "runtimehandles.h" #include "appdomain.inl" // Define the following to re-enable object cloner strict mode (where we require source fields for non-optional destination fields // and don't attempt to load assemblies we can't find via display via partial names instead). //#define OBJECT_CLONER_STRICT_MODE void MakeIDeserializationCallback(OBJECTREF refTarget); MethodDesc *GetInterfaceMethodImpl(MethodTable *pMT, MethodTable *pItfMT, WORD wSlot) { CONTRACTL { THROWS; GC_TRIGGERS; } CONTRACTL_END; MethodDesc *pMeth = NULL; DispatchSlot slot(pMT->FindDispatchSlot(pItfMT->GetTypeID(), (UINT32)wSlot)); CONSISTENCY_CHECK(!slot.IsNull()); pMeth = slot.GetMethodDesc(); return pMeth; } // Given a FieldDesc which may be representative and an object which contains said field, return the actual type of the field. This // works even when called from a different appdomain from which the type was loaded (though naturally it is the caller's // responsbility to ensure such an appdomain cannot be unloaded during the processing of this method). TypeHandle LoadExactFieldType(FieldDesc *pFD, OBJECTREF orefParent, AppDomain *pDomain) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; } CONTRACTL_END; MethodTable *pEnclosingMT = orefParent->GetMethodTable(); // Set up a field signature with the owning type providing a type context for any type variables. MetaSig sig(pFD, TypeHandle(pEnclosingMT)); sig.NextArg(); // If the enclosing type is resident to this domain or domain neutral and loaded in this domain then we can simply go get it. // The logic is trickier (and more expensive to calculate) for generic types, so skip the optimization there. if (pEnclosingMT->GetDomain() == GetAppDomain() || (pEnclosingMT->IsDomainNeutral() && !pEnclosingMT->HasInstantiation() && pEnclosingMT->GetAssembly()->FindDomainAssembly(GetAppDomain()))) return sig.GetLastTypeHandleThrowing(); TypeHandle retTH; // Otherwise we have to do this the expensive way -- switch to the home domain for the type lookup. ENTER_DOMAIN_PTR(pDomain, ADV_RUNNINGIN); retTH = sig.GetLastTypeHandleThrowing(); END_DOMAIN_TRANSITION; return retTH; } extern TypeHandle GetTypeByName( _In_opt_z_ LPUTF8 szFullClassName, BOOL bThrowOnError, BOOL bIgnoreCase, StackCrawlMark *stackMark, BOOL *pbAssemblyIsLoading); #ifndef DACCESS_COMPILE #define CUSTOM_GCPROTECT_BEGIN(context) do { \ FrameWithCookie __gcframe(context); \ /* work around unreachable code warning */ \ if (true) { DEBUG_ASSURE_NO_RETURN_BEGIN(GCPROTECT) #define CUSTOM_GCPROTECT_END() \ DEBUG_ASSURE_NO_RETURN_END(GCPROTECT) } \ __gcframe.Pop(); } while(0) #else // #ifndef DACCESS_COMPILE #define CUSTOM_GCPROTECT_BEGIN(context) #define CUSTOM_GCPROTECT_END() #endif // #ifndef DACCESS_COMPILE int GCSafeObjectHashTable::HasID(OBJECTREF refObj, OBJECTREF *newObj) { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_NOTRIGGER; } CONTRACTL_END BOOL seenBefore = FALSE; *newObj = NULL; int index = FindElement(refObj, seenBefore); if (seenBefore) { _ASSERTE(index < (int)m_currArraySize); *newObj = m_newObjects[index]; return m_ids[index]; } return -1; } // returns the object id int GCSafeObjectHashTable::AddObject(OBJECTREF refObj, OBJECTREF newObj) { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_NOTRIGGER; } CONTRACTL_END int index = -1; GCPROTECT_BEGIN(refObj); GCPROTECT_BEGIN(newObj); if (m_count > m_currArraySize / 2) { Resize(); } BOOL seenBefore = FALSE; index = FindElement(refObj, seenBefore); _ASSERTE(index >= 0 && index < (int)m_currArraySize); if (seenBefore) { _ASSERTE(!"Adding an object thats already present"); } else { m_objects[index] = refObj; m_newObjects[index] = newObj; m_ids[index] = ++m_count; } GCPROTECT_END(); GCPROTECT_END(); return m_ids[index]; } // returns the object id int GCSafeObjectHashTable::UpdateObject(OBJECTREF refObj, OBJECTREF newObj) { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_NOTRIGGER; } CONTRACTL_END int index = -1; GCPROTECT_BEGIN(refObj); GCPROTECT_BEGIN(newObj); BOOL seenBefore = FALSE; index = FindElement(refObj, seenBefore); _ASSERTE(index >= 0 && index < (int)m_currArraySize); if (!seenBefore) { _ASSERTE(!"An object has to exist in the table, to update it"); } else { _ASSERTE(m_objects[index] == refObj); m_newObjects[index] = newObj; } GCPROTECT_END(); GCPROTECT_END(); return m_ids[index]; } // returns index into array where obj was found or will fit in int GCSafeObjectHashTable::FindElement(OBJECTREF refObj, BOOL &seenBefore) { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_NOTRIGGER; } CONTRACTL_END int currentNumBuckets = m_currArraySize / NUM_SLOTS_PER_BUCKET; int hashcode = 0; GCPROTECT_BEGIN(refObj); hashcode = refObj->GetHashCodeEx(); GCPROTECT_END(); hashcode &= 0x7FFFFFFF; // ignore sign bit int hashIncrement = (1+((hashcode)%(currentNumBuckets-2))); #ifdef _DEBUG int numLoops = 0; #endif do { int index = ((unsigned)hashcode % currentNumBuckets) * NUM_SLOTS_PER_BUCKET; _ASSERTE(index >= 0 && index < (int)m_currArraySize); for (int i = index; i < index + NUM_SLOTS_PER_BUCKET; i++) { if (m_objects[i] == refObj) { seenBefore = TRUE; return i; } if (m_objects[i] == NULL) { seenBefore = FALSE; return i; } } hashcode += hashIncrement; #ifdef _DEBUG if (++numLoops > currentNumBuckets) _ASSERTE(!"Looped too many times, trying to find object in hashtable. If hitting ignore doesnt seem to help, then contact Ashok"); #endif }while (true); _ASSERTE(!"Not expected to reach here in GCSafeObjectHashTable::FindElement"); return -1; } void GCSafeObjectHashTable::Resize() { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_NOTRIGGER; } CONTRACTL_END // Allocate new space DWORD newSize = m_currArraySize * 2; for (int i = 0; (DWORD) i < sizeof(g_rgPrimes)/sizeof(DWORD); i++) { if (g_rgPrimes[i] > newSize) { newSize = g_rgPrimes[i]; break; } } newSize *= NUM_SLOTS_PER_BUCKET; NewArrayHolder refTemp (new OBJECTREF[newSize]); ZeroMemory((void *)refTemp, sizeof(OBJECTREF) * newSize); NewArrayHolder refTempNewObj (new OBJECTREF[newSize]); #ifdef USE_CHECKED_OBJECTREFS ZeroMemory((void *)refTempNewObj, sizeof(OBJECTREF) * newSize); #endif NewArrayHolder bTemp (new int[newSize]); ZeroMemory((void *)bTemp, sizeof(int) * newSize); // Copy over objects and data NewArrayHolder refOldObj (m_objects); NewArrayHolder refOldNewObj (m_newObjects); NewArrayHolder oldIds (m_ids); DWORD oldArrSize = m_currArraySize; if (oldIds == (int *)&m_dataOnStack[0]) { refOldObj.SuppressRelease(); refOldNewObj.SuppressRelease(); oldIds.SuppressRelease(); } refTemp.SuppressRelease(); refTempNewObj.SuppressRelease(); bTemp.SuppressRelease(); m_ids = bTemp; m_objects = refTemp; m_newObjects = refTempNewObj; m_currArraySize = newSize; for (DWORD i = 0; i < oldArrSize; i++) { if (refOldObj[i] == NULL) continue; BOOL seenBefore = FALSE; int newIndex = FindElement(refOldObj[i], seenBefore); if (!seenBefore) { _ASSERTE(newIndex < (int)m_currArraySize); m_objects[newIndex] = refOldObj[i]; m_newObjects[newIndex] = refOldNewObj[i]; m_ids[newIndex] = oldIds[i]; } else _ASSERTE(!"Object seen twice while rehashing"); } #ifdef USE_CHECKED_OBJECTREFS for(DWORD i = 0; i < m_currArraySize; i++) Thread::ObjectRefProtected(&m_objects[i]); for(DWORD i = 0; i < m_currArraySize; i++) Thread::ObjectRefProtected(&m_newObjects[i]); #endif } void GCSafeObjectTable::Push(OBJECTREF refObj, OBJECTREF refParent, OBJECTREF refAux, QueuedObjectInfo * pQOI) { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END _ASSERTE(refObj != NULL); _ASSERTE(m_QueueType == LIFO_QUEUE); _ASSERTE(m_head == 0 && m_dataHead == 0); // First find the size of the object info DWORD size = pQOI->GetSize(); // Check if resize is needed EnsureSize(size); // Push on the stack, first the objects DWORD index = m_count; if (m_Objects1) m_Objects1[index] = refObj; #ifdef _DEBUG else _ASSERTE(refObj == NULL); #endif if (m_Objects2) m_Objects2[index] = refParent; #ifdef _DEBUG else _ASSERTE(refParent == NULL); #endif if (m_Objects3) m_Objects3[index] = refAux; #ifdef _DEBUG else _ASSERTE(refAux == NULL); #endif // then the info if (m_dataIndices) m_dataIndices[index] = m_numDataBytes; BYTE *pData = &m_data[m_numDataBytes]; memcpy(pData, (VOID*)pQOI, size); m_numDataBytes += size; m_count++; } OBJECTREF GCSafeObjectTable::Pop(OBJECTREF *refParent, OBJECTREF *refAux, QueuedObjectInfo ** pQOI) { LIMITED_METHOD_CONTRACT; _ASSERTE(m_QueueType == LIFO_QUEUE); _ASSERTE(m_head == 0 && m_dataHead == 0); _ASSERTE(m_dataIndices != NULL); *pQOI = NULL; OBJECTREF refRet = NULL; *refParent = NULL; *refAux = NULL; if (m_count == 0) return NULL; m_count--; refRet = m_Objects1[m_count]; if (m_Objects2) *refParent = m_Objects2[m_count]; if (m_Objects3) *refAux = m_Objects3[m_count]; *pQOI = (QueuedObjectInfo *) &m_data[m_dataIndices[m_count]]; m_numDataBytes -= (*pQOI)->GetSize(); return refRet; } void GCSafeObjectTable::SetAt(DWORD index, OBJECTREF refObj, OBJECTREF refParent, OBJECTREF refAux, QueuedObjectInfo * pQOI) { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END _ASSERTE(refObj != NULL); #ifdef _DEBUG if (m_QueueType == LIFO_QUEUE) _ASSERTE(index >= 0 && index < m_count); else _ASSERTE(index < m_currArraySize); #endif // First find the size of the object info DWORD size = pQOI->GetSize(); // Push on the stack, first the objects m_Objects1[index] = refObj; if (m_Objects2) m_Objects2[index] = refParent; if (m_Objects3) m_Objects3[index] = refAux; // then the info _ASSERTE(m_dataIndices != NULL); QueuedObjectInfo *pData = (QueuedObjectInfo *)&m_data[m_dataIndices[index]]; _ASSERTE(pData->GetSize() == size); memcpy(pData, (VOID*)pQOI, size); } OBJECTREF GCSafeObjectTable::GetAt(DWORD index, OBJECTREF *refParent, OBJECTREF *refAux, QueuedObjectInfo ** pQOI) { LIMITED_METHOD_CONTRACT; #ifdef _DEBUG if (m_QueueType == LIFO_QUEUE) _ASSERTE(index >= 0 && index < m_count); else _ASSERTE(index < m_currArraySize); #endif OBJECTREF refRet = m_Objects1[index]; if (m_Objects2) *refParent = m_Objects2[index]; else *refParent = NULL; if (m_Objects3) *refAux = m_Objects3[index]; else *refAux = NULL; _ASSERTE(m_dataIndices != NULL); *pQOI = (QueuedObjectInfo *) &m_data[m_dataIndices[index]]; return refRet; } void GCSafeObjectTable::Enqueue(OBJECTREF refObj, OBJECTREF refParent, OBJECTREF refAux, QueuedObjectInfo *pQOI) { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END _ASSERTE(refObj != NULL); _ASSERTE(m_QueueType == FIFO_QUEUE); // First find the size of the object info DWORD size = pQOI ? pQOI->GetSize() : 0; // Check if resize is needed EnsureSize(size); // Append to queue, first the objects DWORD index = (m_head + m_count) % m_currArraySize; m_Objects1[index] = refObj; if (m_Objects2) m_Objects2[index] = refParent; if (m_Objects3) m_Objects3[index] = refAux; // then the info if (pQOI) { DWORD dataIndex = (m_dataHead + m_numDataBytes) % (m_currArraySize * MAGIC_FACTOR); BYTE *pData = &m_data[dataIndex]; memcpy(pData, (VOID*)pQOI, size); if (m_dataIndices) m_dataIndices[index] = dataIndex; m_numDataBytes += size; } m_count++; } OBJECTREF GCSafeObjectTable::Dequeue(OBJECTREF *refParent, OBJECTREF *refAux, QueuedObjectInfo ** pQOI) { LIMITED_METHOD_CONTRACT; _ASSERTE(m_QueueType == FIFO_QUEUE); if (pQOI) *pQOI = NULL; OBJECTREF refRet = NULL; *refParent = NULL; *refAux = NULL; if (m_count == 0) return NULL; refRet = m_Objects1[m_head]; if (m_Objects2) *refParent = m_Objects2[m_head]; if (m_Objects3) *refAux = m_Objects3[m_head]; if (pQOI) { *pQOI = (QueuedObjectInfo *) &m_data[m_dataHead]; m_dataHead = (m_dataHead + (*pQOI)->GetSize()) % (m_currArraySize * MAGIC_FACTOR); m_numDataBytes -= (*pQOI)->GetSize(); } m_head = (m_head + 1) % m_currArraySize; m_count--; return refRet; } OBJECTREF GCSafeObjectTable::Peek(OBJECTREF *refParent, OBJECTREF *refAux, QueuedObjectInfo **pQOI) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; MODE_COOPERATIVE; } CONTRACTL_END; *pQOI = NULL; *refParent = NULL; *refAux = NULL; if (m_count == 0) return NULL; DWORD indexToPeek; if (m_QueueType == LIFO_QUEUE) { indexToPeek = m_count; return GetAt(indexToPeek, refParent, refAux, pQOI); } else { indexToPeek = m_head; if (m_Objects2) *refParent = m_Objects2[m_head]; if (m_Objects3) *refParent = m_Objects3[m_head]; *pQOI = (QueuedObjectInfo *) &m_data[m_dataHead]; return m_Objects1[m_head]; } } void GCSafeObjectTable::EnsureSize(DWORD requiredDataSize) { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END // Check if the object queue is sized enough if (m_count == m_currArraySize) { Resize(); return; } // Check if the data array size is enough if (m_numDataBytes + requiredDataSize > m_currArraySize * MAGIC_FACTOR) { Resize(); return; } if (m_QueueType == FIFO_QUEUE) { // Will current QueuedObjectInfo go beyond the edge of the array ? if (m_dataHead + m_numDataBytes + requiredDataSize > m_currArraySize * MAGIC_FACTOR) { Resize(); return; } } } void GCSafeObjectTable::Resize() { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END // Allocate new space DWORD newSize = m_currArraySize * 2; NewArrayHolder refTemp (NULL); NewArrayHolder refParentTemp (NULL); NewArrayHolder refAuxTemp (NULL); refTemp = new OBJECTREF[newSize]; if (m_Objects2) refParentTemp = new OBJECTREF[newSize]; if (m_Objects3) refAuxTemp = new OBJECTREF[newSize]; #ifdef USE_CHECKED_OBJECTREFS ZeroMemory((void *)refTemp, sizeof(OBJECTREF) * newSize); if (m_Objects2) ZeroMemory((void *)refParentTemp, sizeof(OBJECTREF) * newSize); if (m_Objects3) ZeroMemory((void *)refAuxTemp, sizeof(OBJECTREF) * newSize); #endif NewArrayHolder bTemp (NULL); NewArrayHolder dwIndicesTemp (NULL); bTemp = new BYTE[newSize * MAGIC_FACTOR]; if (m_dataIndices) dwIndicesTemp = new DWORD[newSize]; // Copy over objects and data if (m_QueueType == LIFO_QUEUE || (m_QueueType == FIFO_QUEUE && m_head == 0)) { void *pSrc = (void *)&m_Objects1[0]; void *pDest = (void *)&refTemp[0]; memcpyUnsafe(pDest, pSrc, m_count * sizeof(OBJECTREF)); if (m_Objects2) { pSrc = (void *)&m_Objects2[0]; pDest = (void *)&refParentTemp[0]; memcpyUnsafe(pDest, pSrc, m_count * sizeof(OBJECTREF)); } if (m_Objects3) { pSrc = (void *)&m_Objects3[0]; pDest = (void *)&refAuxTemp[0]; memcpyUnsafe(pDest, pSrc, m_count * sizeof(OBJECTREF)); } pSrc = (void *)&m_data[0]; pDest = (void *)&bTemp[0]; memcpyNoGCRefs(pDest, pSrc, m_numDataBytes); if (m_dataIndices) { pSrc = (void *)&m_dataIndices[0]; pDest = (void *)&dwIndicesTemp[0]; memcpyNoGCRefs(pDest, pSrc, m_count * sizeof(DWORD)); } } else { _ASSERTE(m_QueueType == FIFO_QUEUE && m_head != 0); _ASSERTE(m_currArraySize > m_head); DWORD numObjRefsToCopy = (m_count > m_currArraySize - m_head ? m_currArraySize - m_head : m_count); void *pSrc = (void *)&m_Objects1[m_head]; void *pDest = (void *)&refTemp[0]; memcpyUnsafe(pDest, pSrc, numObjRefsToCopy * sizeof(OBJECTREF)); pSrc = (void *)&m_Objects1[0]; pDest = (void *)&refTemp[numObjRefsToCopy]; memcpyUnsafe(pDest, pSrc, (m_count - numObjRefsToCopy) * sizeof(OBJECTREF)); if (m_Objects2) { pSrc = (void *)&m_Objects2[m_head]; pDest = (void *)&refParentTemp[0]; memcpyUnsafe(pDest, pSrc, numObjRefsToCopy * sizeof(OBJECTREF)); pSrc = (void *)&m_Objects2[0]; pDest = (void *)&refParentTemp[numObjRefsToCopy]; memcpyUnsafe(pDest, pSrc, (m_count - numObjRefsToCopy) * sizeof(OBJECTREF)); } if (m_Objects3) { pSrc = (void *)&m_Objects3[m_head]; pDest = (void *)&refAuxTemp[0]; memcpyUnsafe(pDest, pSrc, numObjRefsToCopy * sizeof(OBJECTREF)); pSrc = (void *)&m_Objects3[0]; pDest = (void *)&refAuxTemp[numObjRefsToCopy]; memcpyUnsafe(pDest, pSrc, (m_count - numObjRefsToCopy) * sizeof(OBJECTREF)); } if (m_dataIndices) { pSrc = (void *)&m_dataIndices[m_head]; pDest = (void *)&dwIndicesTemp[0]; memcpyUnsafe(pDest, pSrc, numObjRefsToCopy * sizeof(DWORD)); pSrc = (void *)&m_dataIndices[0]; pDest = (void *)&dwIndicesTemp[numObjRefsToCopy]; memcpyUnsafe(pDest, pSrc, (m_count - numObjRefsToCopy) * sizeof(DWORD)); } DWORD numBytesToCopy = (m_numDataBytes > ((m_currArraySize * MAGIC_FACTOR) - m_dataHead) ? ((m_currArraySize * MAGIC_FACTOR) - m_dataHead) : m_numDataBytes);//(m_currArraySize * MAGIC_FACTOR) - m_dataHead; memcpyNoGCRefs((void *)bTemp, (void *) &m_data[m_dataHead], numBytesToCopy); memcpyNoGCRefs((void *) &bTemp[numBytesToCopy], (void *)m_data, (m_numDataBytes - numBytesToCopy)); } // Delete old allocation if (m_usingHeap) { delete[] m_data; delete[] m_Objects1; delete[] m_Objects2; delete[] m_Objects3; delete[] m_dataIndices; } refTemp.SuppressRelease(); refParentTemp.SuppressRelease(); refAuxTemp.SuppressRelease(); dwIndicesTemp.SuppressRelease(); bTemp.SuppressRelease(); m_currArraySize = newSize; m_Objects1 = refTemp; m_Objects2 = refParentTemp; m_Objects3 = refAuxTemp; m_dataIndices = dwIndicesTemp; m_data = bTemp; m_head = 0; m_dataHead = 0; m_usingHeap = TRUE; #ifdef USE_CHECKED_OBJECTREFS for(DWORD i = 0; i < m_currArraySize; i++) { Thread::ObjectRefProtected(&m_Objects1[i]); if (m_Objects2) Thread::ObjectRefProtected(&m_Objects2[i]); if (m_Objects3) Thread::ObjectRefProtected(&m_Objects3[i]); } #endif } VOID GCScanRootsInCollection(promote_func *fn, ScanContext* sc, void *context) { STATIC_CONTRACT_SO_TOLERANT; GCSafeCollection *pObjCollection = (GCSafeCollection *)context; pObjCollection->ReportGCRefs(fn, sc); } VOID BeginCloning(ObjectClone *pOC) { pOC->Init(FALSE); } VOID EndCloning(ObjectClone *pOC) { pOC->Cleanup(FALSE); } typedef Holder ObjectCloneHolder; OBJECTREF ObjectClone::Clone(OBJECTREF refObj, TypeHandle expectedType, AppDomain* fromDomain, AppDomain* toDomain, OBJECTREF refExecutionContext) { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_TRIGGERS; } CONTRACTL_END if (refObj == NULL) return NULL; if (m_context != ObjectFreezer && refObj->GetMethodTable() == g_pStringClass) return refObj; ObjectCloneHolder ocHolder(this); m_fromDomain = fromDomain; m_toDomain = toDomain; m_currObject = refObj; GCPROTECT_BEGIN(m_currObject); m_topObject = NULL; GCPROTECT_BEGIN(m_topObject); m_fromExecutionContext = refExecutionContext; GCPROTECT_BEGIN(m_fromExecutionContext); // Enter the domain we're cloning into, if we're not already there ENTER_DOMAIN_PTR(toDomain,ADV_RUNNINGIN); if (!m_securityChecked) { Security::SpecialDemand(SSWT_DEMAND_FROM_NATIVE, SECURITY_SERIALIZATION); m_securityChecked = TRUE; } #ifdef _DEBUG DefineFullyQualifiedNameForClass(); LOG((LF_REMOTING, LL_INFO100, "Clone. Cloning instance of type %s.\n", GetFullyQualifiedNameForClassNestedAware(m_currObject->GetMethodTable()))); #endif m_newObject = NULL; GCPROTECT_BEGIN(m_newObject); PTRARRAYREF refValues = NULL; GCPROTECT_BEGIN(refValues); OBJECTREF refParent = NULL; GCPROTECT_BEGIN(refParent); QueuedObjectInfo *currObjFixupInfo = NULL; // For some dynamically sized stack objects void *pTempStackSpace = NULL; DWORD dwCurrStackSpaceSize = 0; // Initialize QOM QueuedObjectInfo topObj; OBJECTREF dummy1, dummy2; QOM.Enqueue(m_currObject, NULL, NULL, (QueuedObjectInfo *)&topObj); while ((m_currObject = QOM.Dequeue(&dummy1, &dummy2, &currObjFixupInfo)) != NULL) { m_newObject = NULL; MethodTable *newMT = NULL; BOOL repeatObject = FALSE; BOOL isISerializable = FALSE, isIObjRef = FALSE, isBoxed = FALSE; DWORD ISerializableTSOIndex = (DWORD) -1; DWORD IObjRefTSOIndex = (DWORD) -1; DWORD BoxedValTSOIndex = (DWORD) -1; m_skipFieldScan = FALSE; // ALLOCATE PHASE // Was currObject seen before ? int currID = TOS.HasID(m_currObject, &m_newObject); if (currID != -1) { // Yes repeatObject = TRUE; m_skipFieldScan = TRUE; newMT = m_newObject->GetMethodTable(); if (m_cbInterface->IsISerializableType(newMT)) { currObjFixupInfo->SetIsISerializableInstance(); isISerializable = TRUE; ISerializableTSOIndex = FindObjectInTSO(currID, ISerializable); } #ifdef _DEBUG LOG((LF_REMOTING, LL_INFO1000, "Clone. Object of type %s with id %d seen before.\n", GetFullyQualifiedNameForClassNestedAware(m_currObject->GetMethodTable()), currID)); #endif } else { #ifdef _DEBUG LOG((LF_REMOTING, LL_INFO1000, "Clone. Object of type %s not seen before.\n", GetFullyQualifiedNameForClassNestedAware(m_currObject->GetMethodTable()))); #endif // No MethodTable *currMT = m_currObject->GetMethodTable(); // Check whether object is serializable m_cbInterface->ValidateFromType(currMT); // Add current object to table of seen objects and get an id currID = TOS.AddObject(m_currObject, m_newObject); LOG((LF_REMOTING, LL_INFO1000, "Clone. Current object added to Table of Objects Seen. Given id %d.\n", currID)); if ( m_cbInterface->IsRemotedType(currMT, m_fromDomain, m_toDomain)) { refValues = AllocateISerializable(currID, TRUE); isISerializable = TRUE; ISerializableTSOIndex = TSO.GetCount() - 1; currObjFixupInfo->SetIsISerializableInstance(); if (refValues == NULL) { // We found a smugglable objref. No field scanning needed m_skipFieldScan = TRUE; } } else if( m_cbInterface->IsISerializableType(currMT)) { InvokeVtsCallbacks(m_currObject, RemotingVtsInfo::VTS_CALLBACK_ON_SERIALIZING, fromDomain); if (HasVtsCallbacks(m_currObject->GetMethodTable(), RemotingVtsInfo::VTS_CALLBACK_ON_SERIALIZED)) VSC.Enqueue(m_currObject, NULL, NULL, NULL); refValues = AllocateISerializable(currID, FALSE); isISerializable = TRUE; ISerializableTSOIndex = TSO.GetCount() - 1; currObjFixupInfo->SetIsISerializableInstance(); } else if (currMT->IsArray()) { AllocateArray(); } else { // This is a regular object InvokeVtsCallbacks(m_currObject, RemotingVtsInfo::VTS_CALLBACK_ON_SERIALIZING, fromDomain); if (HasVtsCallbacks(m_currObject->GetMethodTable(), RemotingVtsInfo::VTS_CALLBACK_ON_SERIALIZED)) VSC.Enqueue(m_currObject, NULL, NULL, NULL); AllocateObject(); if (m_cbInterface->IsISerializableType(m_newObject->GetMethodTable())) { // We have a situation where the serialized instnce was not ISerializable, // but the target instance is. So we make the from object look like a ISerializable refValues = MakeObjectLookLikeISerializable(currID); isISerializable = TRUE; ISerializableTSOIndex = TSO.GetCount() - 1; currObjFixupInfo->SetIsISerializableInstance(); } } _ASSERTE(m_newObject != NULL); newMT = m_newObject->GetMethodTable(); // Check whether new object is serializable m_cbInterface->ValidateToType(newMT); // Update the TOS, to include the new object int retId; retId = TOS.UpdateObject(m_currObject, m_newObject); _ASSERTE(retId == currID); } _ASSERTE(m_newObject != NULL); // FIXUP PHASE // Get parent to be fixed up ParentInfo *parentInfo; refParent = QOF.Peek(&dummy1, &dummy2, (QueuedObjectInfo **)&parentInfo); MethodTable *pParentMT = NULL; if (refParent == NULL) { LOG((LF_REMOTING, LL_INFO1000, "Clone. No parent found. This is the top object.\n")); // This is the top object _ASSERTE(m_topObject == NULL); m_topObject = m_newObject; } else { #ifdef _DEBUG LOG((LF_REMOTING, LL_INFO1000, "Clone. Parent is of type %s.\n", GetFullyQualifiedNameForClassNestedAware(m_currObject->GetMethodTable()))); #endif pParentMT = refParent->GetMethodTable(); } if (IsDelayedFixup(newMT, currObjFixupInfo)) { // New object is IObjRef or a boxed object if (m_cbInterface->IsIObjectReferenceType(newMT)) { LOG((LF_REMOTING, LL_INFO1000, "Clone. This is an IObjectReference. Delaying fixup.\n")); DWORD size = sizeof(IObjRefInstanceInfo) + (currObjFixupInfo ? currObjFixupInfo->GetSize() : 0); if (size > dwCurrStackSpaceSize) { pTempStackSpace = _alloca(size); dwCurrStackSpaceSize = size; } IObjRefInstanceInfo *pIORInfo = new (pTempStackSpace) IObjRefInstanceInfo(currID, 0, 0); if (currObjFixupInfo) pIORInfo->SetFixupInfo(currObjFixupInfo); // Check if this instance is ISerializable also if (isISerializable) { LOG((LF_REMOTING, LL_INFO1000, "Clone. This is also an ISerializable type at index %d in TSO.\n", ISerializableTSOIndex)); _ASSERTE(ISerializableTSOIndex != (DWORD) -1); pIORInfo->SetISerTSOIndex(ISerializableTSOIndex); } if (repeatObject) pIORInfo->SetIsRepeatObject(); // Add to TSO TSO.Push(m_newObject, m_currObject, refParent, pIORInfo); isIObjRef = TRUE; IObjRefTSOIndex = TSO.GetCount() - 1; LOG((LF_REMOTING, LL_INFO1000, "Clone. Added to TSO at index %d.\n", IObjRefTSOIndex)); // Any special object parent, would wait till the current object is resolved if (parentInfo) { parentInfo->IncrementSpecialMembers(); TMappings.Add(IObjRefTSOIndex); } } if (currObjFixupInfo->NeedsUnboxing()) { LOG((LF_REMOTING, LL_INFO1000, "Clone. This is a boxed value type. Delaying fixup.\n")); DWORD size = sizeof(ValueTypeInfo) + currObjFixupInfo->GetSize(); if (size > dwCurrStackSpaceSize) { pTempStackSpace = _alloca(size); dwCurrStackSpaceSize = size; } ValueTypeInfo *valInfo = new (pTempStackSpace) ValueTypeInfo(currID, currObjFixupInfo); // If the value type is also ISer or IObj, then it has to wait till those interfaces are addressed if (isISerializable) { LOG((LF_REMOTING, LL_INFO1000, "Clone. This is also an ISerializable type at index %d in TSO.\n", ISerializableTSOIndex)); valInfo->SetISerTSOIndex(ISerializableTSOIndex); } if (isIObjRef) { LOG((LF_REMOTING, LL_INFO1000, "Clone. This is also an IObjectReference type at index %d in TSO.\n", IObjRefTSOIndex)); valInfo->SetIObjRefTSOIndex(IObjRefTSOIndex); } // Add to TSO TSO.Push(m_newObject, refParent, NULL, valInfo); isBoxed = TRUE; BoxedValTSOIndex = TSO.GetCount() - 1; LOG((LF_REMOTING, LL_INFO1000, "Clone. Added to TSO at index %d.\n", BoxedValTSOIndex)); // An IObjRef parent, or a parent itself boxed, would wait till the current object is resolved if (parentInfo && (parentInfo->NeedsUnboxing() || parentInfo->IsIObjRefInstance())) { parentInfo->IncrementSpecialMembers(); TMappings.Add(BoxedValTSOIndex); } } } if (refParent != NULL) { if (!IsDelayedFixup(newMT, currObjFixupInfo)) Fixup(m_newObject, refParent, currObjFixupInfo); // If currObj is ISer, then an IObjRef parent would wait till the current object is resolved if (currObjFixupInfo->IsISerializableInstance() && parentInfo->IsIObjRefInstance()) { parentInfo->IncrementSpecialMembers(); TMappings.Add(ISerializableTSOIndex); } } // If we are done with this parent, remove it from QOF if (parentInfo && parentInfo->DecrementFixupCount() == 0) { LOG((LF_REMOTING, LL_INFO1000, "Clone. All children fixed up. Removing parent from QOF.\n", BoxedValTSOIndex)); LOG((LF_REMOTING, LL_INFO1000, "Clone. Parent has %d special member objects.\n", parentInfo->GetNumSpecialMembers())); OBJECTREF refTemp; ParentInfo *pFITemp; refTemp = QOF.Dequeue(&dummy1, &dummy2, (QueuedObjectInfo **)&pFITemp); _ASSERTE(refTemp == refParent); _ASSERTE(pFITemp == parentInfo); // If parent is a special object, then we need to know how many special members it has if ((parentInfo->IsIObjRefInstance() || parentInfo->IsISerializableInstance() || parentInfo->NeedsUnboxing()) && parentInfo->GetNumSpecialMembers() > 0) { // Make a note in TSO that this parent has non-zero special members DWORD index[3]; index[0] = parentInfo->GetIObjRefIndexIntoTSO(); index[1] = parentInfo->GetISerIndexIntoTSO(); index[2] = parentInfo->GetBoxedValIndexIntoTSO(); for (DWORD count = 0; count < 3; count++) { OBJECTREF refIser, refNames, refValuesTemp; SpecialObjectInfo *pISerInfo; if (index[count] == (DWORD) -1) continue; refIser = TSO.GetAt(index[count], &refNames, &refValuesTemp, (QueuedObjectInfo **)&pISerInfo); _ASSERTE(refIser == refParent); DWORD numSpecialObjects = parentInfo->GetNumSpecialMembers(); pISerInfo->SetNumSpecialMembers(numSpecialObjects); _ASSERTE(TMappings.GetCount() >= numSpecialObjects); pISerInfo->SetMappingTableIndex(TMappings.GetCount() - numSpecialObjects); } } } // FIELD SCAN PHASE if (!m_skipFieldScan) { if (m_currObject->GetMethodTable()->IsArray()) ScanArrayMembers(); else if (isISerializable) ScanISerializableMembers(IObjRefTSOIndex, ISerializableTSOIndex, BoxedValTSOIndex, refValues); else ScanMemberFields(IObjRefTSOIndex, BoxedValTSOIndex); } } // While there are objects in QOM // OBJECT COMPLETION PHASE CompleteSpecialObjects(); // Deliver VTS OnDeserialized callbacks. CompleteVtsOnDeserializedCallbacks(); CompleteIDeserializationCallbacks(); _ASSERTE(m_topObject != NULL); // If a type check was requested, see if the returned object is of the expected type if (!expectedType.IsNull() && !ObjIsInstanceOf(OBJECTREFToObject(m_topObject), expectedType)) COMPlusThrow(kArgumentException,W("Arg_ObjObj")); GCPROTECT_END(); // refParent GCPROTECT_END(); // refValues GCPROTECT_END(); // m_newObject END_DOMAIN_TRANSITION; // Deliver VTS OnSerialized callbacks. CompleteVtsOnSerializedCallbacks(); GCPROTECT_END(); // m_fromExecutionContext GCPROTECT_END(); // m_topObject GCPROTECT_END(); // m_currObject return m_topObject; } // IObjRef and value types boxed by us, need to be fixed up towards the end BOOL ObjectClone::IsDelayedFixup(MethodTable *newMT, QueuedObjectInfo *pCurrInfo) { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END if (m_cbInterface->IsIObjectReferenceType(newMT) || pCurrInfo->NeedsUnboxing()) return TRUE; else return FALSE; } void ObjectClone::Fixup(OBJECTREF newObj, OBJECTREF refParent, QueuedObjectInfo *pFixupInfo) { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END MethodTable *pParentMT = refParent->GetMethodTable(); if (pFixupInfo->IsISerializableMember()) { HandleISerializableFixup(refParent, pFixupInfo); } else if (pParentMT->IsArray()) { HandleArrayFixup(refParent, pFixupInfo); } else { HandleObjectFixup(refParent, pFixupInfo); } } PTRARRAYREF ObjectClone::MakeObjectLookLikeISerializable(int objectId) { CONTRACTL { GC_TRIGGERS; THROWS; MODE_COOPERATIVE; } CONTRACTL_END _ASSERTE(m_context != ObjectFreezer); LOG((LF_REMOTING, LL_INFO1000, "MakeObjectLookLikeISerializable. Target object is ISerializable, so making from object look ISerializable\n")); MethodTable *pCurrMT = m_currObject->GetMethodTable(); DWORD numFields = pCurrMT->GetNumInstanceFields(); PTRARRAYREF fieldNames = NULL; PTRARRAYREF fieldValues = NULL; GCPROTECT_BEGIN(fieldNames); GCPROTECT_BEGIN(fieldValues); // Go back to from domain ENTER_DOMAIN_PTR(m_fromDomain,ADV_RUNNINGIN); // Reset the execution context to the original state it was in when we first // left the from domain (this will automatically be popped once we return // from this domain again). Thread *pThread = GetThread(); if (pThread->IsExposedObjectSet()) { THREADBASEREF refThread = (THREADBASEREF)pThread->GetExposedObjectRaw(); refThread->SetExecutionContext(m_fromExecutionContext); } fieldNames = (PTRARRAYREF)AllocateObjectArray(numFields, g_pStringClass, FALSE); fieldValues = (PTRARRAYREF)AllocateObjectArray(numFields, g_pObjectClass, FALSE); DWORD fieldIndex = 0; while (pCurrMT) { DWORD numInstanceFields = pCurrMT->GetNumIntroducedInstanceFields(); FieldDesc *pFields = pCurrMT->GetApproxFieldDescListRaw(); for (DWORD i = 0; i < numInstanceFields; i++) { if (pFields[i].IsNotSerialized()) { LOG((LF_REMOTING, LL_INFO1000, "MakeObjectLookLikeISerializable. Field %s is marked NonSerialized. Skipping.\n", pFields[i].GetName())); continue; } CorElementType typ = pFields[i].GetFieldType(); DWORD offset = pFields[i].GetOffset(); LPCUTF8 szFieldName = pFields[i].GetName(); STRINGREF refName = StringObject::NewString(szFieldName); _ASSERTE(refName != NULL); fieldNames->SetAt(fieldIndex, refName); switch (typ) { case ELEMENT_TYPE_BOOLEAN: case ELEMENT_TYPE_I1: case ELEMENT_TYPE_U1: case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_I: case ELEMENT_TYPE_U: case ELEMENT_TYPE_R4: case ELEMENT_TYPE_R8: { MethodTable *pFldMT = MscorlibBinder::GetElementType(typ); void *pData = m_currObject->GetData() + offset; OBJECTREF refBoxed = pFldMT->Box(pData); fieldValues->SetAt(fieldIndex, refBoxed); break; } case ELEMENT_TYPE_VALUETYPE: case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_FNPTR: { TypeHandle th = LoadExactFieldType(&pFields[i], m_currObject, m_fromDomain); _ASSERTE(!th.AsMethodTable()->IsByRefLike() && "Field types cannot contain stack pointers."); OBJECTREF refBoxed = BoxValueTypeInWrongDomain(m_currObject, offset, th.AsMethodTable()); fieldValues->SetAt(fieldIndex, refBoxed); break; } case ELEMENT_TYPE_SZARRAY: // Single Dim case ELEMENT_TYPE_ARRAY: // General Array case ELEMENT_TYPE_CLASS: // Class case ELEMENT_TYPE_OBJECT: case ELEMENT_TYPE_STRING: // System.String case ELEMENT_TYPE_VAR: { OBJECTREF refField = *((OBJECTREF *) m_currObject->GetData() + offset); fieldValues->SetAt(fieldIndex, refField); break; } default: _ASSERTE(!"Unknown element type in MakeObjectLookLikeISerializalbe"); } fieldIndex++; } pCurrMT = pCurrMT->GetParentMethodTable(); } // Back to original domain END_DOMAIN_TRANSITION; // Add object to TSO ISerializableInstanceInfo iserInfo(objectId, 0); TSO.Push(m_newObject, fieldNames, NULL, (QueuedObjectInfo *)&iserInfo); LOG((LF_REMOTING, LL_INFO1000, "MakeObjectLookLikeISerializable. Added to TSO at index %d.\n", TSO.GetCount() - 1)); GCPROTECT_END(); GCPROTECT_END(); return fieldValues; } PTRARRAYREF ObjectClone::AllocateISerializable(int objectId, BOOL bIsRemotingObject) { CONTRACTL { GC_TRIGGERS; THROWS; MODE_COOPERATIVE; } CONTRACTL_END _ASSERTE(m_context != ObjectFreezer); // Go back to from domain StackSString ssAssemName; StackSString ssTypeName; struct _gc { STRINGREF typeName; STRINGREF assemblyName; PTRARRAYREF fieldNames; PTRARRAYREF fieldValues; OBJECTREF refObjRef; } gc; ZeroMemory(&gc, sizeof(gc)); GCPROTECT_BEGIN(gc); ENTER_DOMAIN_PTR(m_fromDomain,ADV_RUNNINGIN); // Reset the execution context to the original state it was in when we first // left the from domain (this will automatically be popped once we return // from this domain again). Thread *pThread = GetThread(); if (pThread->IsExposedObjectSet()) { THREADBASEREF refThread = (THREADBASEREF)pThread->GetExposedObjectRaw(); refThread->SetExecutionContext(m_fromExecutionContext); } // Call GetObjectData on the interface LOG((LF_REMOTING, LL_INFO1000, "AllocateISerializable. Instance is ISerializable type. Calling GetObjectData.\n")); PREPARE_NONVIRTUAL_CALLSITE(METHOD__OBJECTCLONEHELPER__GET_OBJECT_DATA); DECLARE_ARGHOLDER_ARRAY(args, 5); args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(m_currObject); args[ARGNUM_1] = PTR_TO_ARGHOLDER(&gc.typeName); args[ARGNUM_2] = PTR_TO_ARGHOLDER(&gc.assemblyName); args[ARGNUM_3] = PTR_TO_ARGHOLDER(&gc.fieldNames); args[ARGNUM_4] = PTR_TO_ARGHOLDER(&gc.fieldValues); CATCH_HANDLER_FOUND_NOTIFICATION_CALLSITE; CALL_MANAGED_METHOD_RETREF(gc.refObjRef, OBJECTREF, args); if (!bIsRemotingObject || gc.refObjRef == NULL) { ssAssemName.Set(gc.assemblyName->GetBuffer()); ssTypeName.Set(gc.typeName->GetBuffer()); } // Back to original domain END_DOMAIN_TRANSITION; // if its a remoting object we are dealing with, we may already have the smugglable objref if (bIsRemotingObject && gc.refObjRef != NULL) { m_newObject = gc.refObjRef; // Add object to TSO. We dont need a ISerializable record, because we are smuggling the ObjRef // and so, technically the ISerializable ctor can be considered already called. But we still make an entry in // TSO and mark it "processed", so repeat references to the same remoting object work correctly ISerializableInstanceInfo iserInfo(objectId, 0); iserInfo.SetHasBeenProcessed(); TSO.Push(m_newObject, NULL, NULL, (QueuedObjectInfo *)&iserInfo); LOG((LF_REMOTING, LL_INFO1000, "AllocateISerializable. GetObjectData returned smugglable ObjRef. Added dummy record to TSO at index %d.\n", TSO.GetCount() - 1)); } else { // Find the type (and choke on any exotics such as arrays, function pointers or generic type definitions). TypeHandle th = GetType(ssTypeName, ssAssemName); if (th.IsTypeDesc() || th.ContainsGenericVariables()) { StackSString ssBeforeTypeName, ssAfterTypeName; TypeString::AppendType(ssBeforeTypeName, m_currObject->GetTypeHandle(), TypeString::FormatNamespace | TypeString::FormatFullInst); TypeString::AppendType(ssAfterTypeName, th, TypeString::FormatNamespace | TypeString::FormatFullInst); COMPlusThrow(kSerializationException, IDS_SERIALIZATION_BAD_ISER_TYPE, ssBeforeTypeName.GetUnicode(), ssAfterTypeName.GetUnicode()); } MethodTable *pSrvMT = th.AsMethodTable(); _ASSERTE(pSrvMT); #ifdef _DEBUG { DefineFullyQualifiedNameForClass(); LPCUTF8 __szTypeName = GetFullyQualifiedNameForClassNestedAware(pSrvMT); LOG((LF_REMOTING, LL_INFO1000, "AllocateISerializable. Allocating instance of type %s.\n", &__szTypeName[0])); } #endif // Allocate the object m_newObject = m_cbInterface->AllocateObject(m_currObject, pSrvMT); // Add object to TSO ISerializableInstanceInfo iserInfo(objectId, 0); // Check if the target object is ISerializable. If not, we need to treat construction of this object differently if (!m_cbInterface->IsISerializableType(pSrvMT)) { iserInfo.SetTargetNotISerializable(); } TSO.Push(m_newObject, gc.fieldNames, NULL, (QueuedObjectInfo *)&iserInfo); LOG((LF_REMOTING, LL_INFO1000, "AllocateISerializable. Added to TSO at index %d.\n", TSO.GetCount() - 1)); } GCPROTECT_END(); return gc.fieldValues; } void ObjectClone::AllocateArray() { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END LOG((LF_REMOTING, LL_INFO1000, "AllocateArray. Instance is an array type.\n")); MethodTable *pCurrMT = m_currObject->GetMethodTable(); _ASSERTE(pCurrMT->IsArray()); BASEARRAYREF refArray = (BASEARRAYREF)m_currObject; GCPROTECT_BEGIN(refArray); TypeHandle elemTh = refArray->GetArrayElementTypeHandle(); CorElementType elemType = refArray->GetArrayElementType(); DWORD numComponents = refArray->GetNumComponents(); TypeHandle __elemTh = GetCorrespondingTypeForTargetDomain(elemTh); _ASSERTE(!__elemTh.IsNull()); unsigned __rank = pCurrMT->GetRank(); TypeHandle __arrayTh = ClassLoader::LoadArrayTypeThrowing(__elemTh, __rank == 1 ? ELEMENT_TYPE_SZARRAY : ELEMENT_TYPE_ARRAY, __rank); DWORD __numArgs = __rank*2; INT32* __args = (INT32*) _alloca(sizeof(INT32)*__numArgs); if (__arrayTh.AsArray()->GetInternalCorElementType() == ELEMENT_TYPE_ARRAY) { const INT32* bounds = refArray->GetBoundsPtr(); const INT32* lowerBounds = refArray->GetLowerBoundsPtr(); for(unsigned int i=0; i < __rank; i++) { __args[2*i] = lowerBounds[i]; __args[2*i+1] = bounds[i]; } } else { __numArgs = 1; __args[0] = numComponents; } m_newObject = m_cbInterface->AllocateArray(m_currObject, __arrayTh, __args, __numArgs, FALSE); // Treat pointer as a primitive type (we shallow copy the bits). if (CorTypeInfo::IsPrimitiveType(elemType) || elemType == ELEMENT_TYPE_PTR) { LOG((LF_REMOTING, LL_INFO1000, "AllocateArray. Instance is an array of primitive type. Copying contents.\n")); // Copy contents. SIZE_T numBytesToCopy = refArray->GetComponentSize() * numComponents; I1ARRAYREF refI1Arr = (I1ARRAYREF)m_newObject; BYTE *pDest = (BYTE *)refI1Arr->GetDirectPointerToNonObjectElements(); I1ARRAYREF refFromArr = (I1ARRAYREF)refArray; BYTE *pSrc = (BYTE *)refFromArr->GetDirectPointerToNonObjectElements(); memcpyNoGCRefs(pDest, pSrc, numBytesToCopy); m_skipFieldScan = TRUE; } else if (elemType == ELEMENT_TYPE_VALUETYPE) { if (!__elemTh.GetMethodTable()->HasFieldsWhichMustBeInited() && RemotableMethodInfo::TypeIsConduciveToBlitting(elemTh.AsMethodTable(), __elemTh.GetMethodTable())) { LOG((LF_REMOTING, LL_INFO1000, "AllocateArray. Instance is an array of value type with no embedded GC type. Copying contents.\n")); // Copy contents. SIZE_T numBytesToCopy = refArray->GetComponentSize() * numComponents; I1ARRAYREF refI1Arr = (I1ARRAYREF)m_newObject; BYTE *pDest = (BYTE *)refI1Arr->GetDirectPointerToNonObjectElements(); I1ARRAYREF refFromArr = (I1ARRAYREF)refArray; BYTE *pSrc = (BYTE *)refFromArr->GetDirectPointerToNonObjectElements(); memcpyNoGCRefs(pDest, pSrc, numBytesToCopy); m_skipFieldScan = TRUE; } } GCPROTECT_END(); } void ObjectClone::AllocateObject() { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END LOG((LF_REMOTING, LL_INFO1000, "AllocateObject. Instance is a regular object.\n")); MethodTable *pCurrMT = m_currObject->GetMethodTable(); _ASSERTE(!pCurrMT->IsArray()); _ASSERTE(!pCurrMT->IsMarshaledByRef() && !pCurrMT->IsTransparentProxy()); _ASSERTE(!m_cbInterface->IsISerializableType(pCurrMT)); MethodTable *pCorrespondingMT = GetCorrespondingTypeForTargetDomain(pCurrMT); _ASSERTE(pCorrespondingMT); pCorrespondingMT->EnsureInstanceActive(); m_newObject = m_cbInterface->AllocateObject(m_currObject, pCorrespondingMT); InvokeVtsCallbacks(m_newObject, RemotingVtsInfo::VTS_CALLBACK_ON_DESERIALIZING, m_toDomain); } // Use this wrapper when the type handle can't be represented as a raw MethodTable (i.e. it's a pointer or array type). TypeHandle ObjectClone::GetCorrespondingTypeForTargetDomain(TypeHandle thCli) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END TypeHandle thBaseType = thCli; TypeHandle thSrvType; // Strip off any pointer information (and record the depth). We'll put this back later (when we've translated the base type). DWORD dwPointerDepth = 0; while (thBaseType.IsPointer()) { dwPointerDepth++; thBaseType = thBaseType.AsTypeDesc()->GetTypeParam(); } // If we hit an array then we'll recursively translate the element type then build an array type out of it. if (thBaseType.IsArray()) { ArrayTypeDesc *atd = (ArrayTypeDesc *)thBaseType.AsTypeDesc(); thSrvType = GetCorrespondingTypeForTargetDomain(atd->GetArrayElementTypeHandle()); thSrvType = ClassLoader::LoadArrayTypeThrowing(thSrvType, atd->GetInternalCorElementType(), atd->GetRank()); } else { // We should have only unshared types if we get here. _ASSERTE(!thBaseType.IsTypeDesc()); thSrvType = GetCorrespondingTypeForTargetDomain(thBaseType.AsMethodTable()); } // Match the level of pointer indirection from the original client type. while (dwPointerDepth--) { thSrvType = thSrvType.MakePointer(); } return thSrvType; } MethodTable * ObjectClone::GetCorrespondingTypeForTargetDomain(MethodTable *pCliMT) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END MethodTable *pSrvMT = NULL; if (m_fromDomain == m_toDomain) return pCliMT; _ASSERTE(m_context != ObjectFreezer); #ifdef _DEBUG SString __ssTypeName; StackScratchBuffer __scratchBuf; if (pCliMT->IsArray()) pCliMT->_GetFullyQualifiedNameForClass(__ssTypeName); else pCliMT->_GetFullyQualifiedNameForClassNestedAware(__ssTypeName); #endif // Take benefit of shared types. If a type is shared, and its assembly has been loaded // in the target domain, go ahead and use the same MT ptr. // The logic is trickier (and more expensive to calculate) for generic types, so skip the optimization there. if (pCliMT->IsDomainNeutral() && !pCliMT->HasInstantiation()) { if (pCliMT->GetAssembly()->FindDomainAssembly(m_toDomain)) { LOG((LF_REMOTING, LL_INFO1000, "GetCorrespondingTypeForTargetDomain. Type %s is shared. Using same MethodTable.\n", __ssTypeName.GetUTF8(__scratchBuf))); return pCliMT; } } pSrvMT = CrossDomainTypeMap::GetMethodTableForDomain(pCliMT, m_fromDomain, m_toDomain); if (pSrvMT) { LOG((LF_REMOTING, LL_INFO1000, "GetCorrespondingTypeForTargetDomain. Found matching type for %s in domain %d from cache.\n", __ssTypeName.GetUTF8(__scratchBuf), m_toDomain)); return pSrvMT; } // Need to find the name and lookup in target domain SString ssCliTypeName; if (pCliMT->IsArray()) { pCliMT->_GetFullyQualifiedNameForClass(ssCliTypeName); } else if (pCliMT->HasInstantiation()) { TypeString::AppendType(ssCliTypeName, TypeHandle(pCliMT), TypeString::FormatNamespace | TypeString::FormatFullInst); } else { pCliMT->_GetFullyQualifiedNameForClassNestedAware(ssCliTypeName); } SString ssAssemblyName; pCliMT->GetAssembly()->GetDisplayName(ssAssemblyName); // Get the assembly TypeHandle th = GetType(ssCliTypeName, ssAssemblyName); if (!pCliMT->IsArray()) { pSrvMT = th.AsMethodTable(); } else { _ASSERTE(th.IsArray()); TypeDesc *td = th.AsTypeDesc(); pSrvMT = td->GetMethodTable(); } CrossDomainTypeMap::SetMethodTableForDomain(pCliMT, m_fromDomain, pSrvMT, m_toDomain); LOG((LF_REMOTING, LL_INFO1000, "GetCorrespondingTypeForTargetDomain. Loaded matching type for %s in domain %d. Added to cache.\n", __ssTypeName.GetUTF8(__scratchBuf), m_toDomain)); return pSrvMT; } TypeHandle ObjectClone::GetType(const SString &ssTypeName, const SString &ssAssemName) { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END; Assembly *pAssembly = NULL; #ifndef OBJECT_CLONER_STRICT_MODE EX_TRY #endif { AssemblySpec spec; StackScratchBuffer scratchBuf; HRESULT hr = spec.Init(ssAssemName.GetUTF8(scratchBuf)); if (SUCCEEDED(hr)) { pAssembly = spec.LoadAssembly(FILE_ACTIVE); } else { COMPlusThrowHR(hr); } } #ifndef OBJECT_CLONER_STRICT_MODE EX_CATCH { if (GET_EXCEPTION()->IsTransient()) { EX_RETHROW; } DomainAssembly *pDomainAssembly = NULL; #ifdef FEATURE_FUSION // If the normal load fails then try loading from a partial assembly name (relaxed serializer rules). pDomainAssembly = LoadAssemblyFromPartialNameHack((SString*)&ssAssemName, TRUE); #endif // FEATURE_FUSION if (pDomainAssembly == NULL) COMPlusThrow(kSerializationException, IDS_SERIALIZATION_UNRESOLVED_TYPE, ssTypeName.GetUnicode(), ssAssemName.GetUnicode()); else pAssembly = pDomainAssembly->GetAssembly(); } EX_END_CATCH(SwallowAllExceptions); #endif _ASSERTE(pAssembly); TypeHandle th = TypeName::GetTypeFromAssembly(ssTypeName.GetUnicode(), pAssembly); if (th.IsNull()) { COMPlusThrow(kSerializationException, IDS_SERIALIZATION_UNRESOLVED_TYPE, ssTypeName.GetUnicode(), ssAssemName.GetUnicode()); } LOG((LF_REMOTING, LL_INFO1000, "GetType. Loaded type %S from assembly %S in domain %d. \n", ssTypeName.GetUnicode(), ssAssemName.GetUnicode(), m_toDomain->GetId().m_dwId)); return th; } void ObjectClone::HandleISerializableFixup(OBJECTREF refParent, QueuedObjectInfo *currObjFixupInfo) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; MODE_COOPERATIVE; } CONTRACTL_END; _ASSERTE(m_context != ObjectFreezer); ISerializableMemberInfo *pIsInfo = (ISerializableMemberInfo *)currObjFixupInfo; OBJECTREF refNames, refValues; ISerializableInstanceInfo *dummy; OBJECTREF parent; parent = TSO.GetAt(pIsInfo->GetTableIndex(), &refNames, &refValues, (QueuedObjectInfo **)&dummy); _ASSERTE(parent == refParent); _ASSERTE(dummy->IsISerializableInstance()); PTRARRAYREF refFields = (PTRARRAYREF)refValues; _ASSERTE(pIsInfo->GetFieldIndex() < refFields->GetNumComponents()); refFields->SetAt(pIsInfo->GetFieldIndex(), m_newObject); LOG((LF_REMOTING, LL_INFO1000, "HandleISerializableFixup. Parent is ISerializable. Added field #%d to TSO record at index %d\n", pIsInfo->GetFieldIndex(), pIsInfo->GetTableIndex())); } void ObjectClone::HandleArrayFixup(OBJECTREF refParent, QueuedObjectInfo *currObjFixupInfo) { CONTRACTL { MODE_COOPERATIVE; THROWS; GC_TRIGGERS; } CONTRACTL_END _ASSERTE(refParent->GetMethodTable()->IsArray()); BASEARRAYREF refParentArray = (BASEARRAYREF) refParent; GCPROTECT_BEGIN(refParentArray); NDimArrayMemberInfo *pArrInfo = (NDimArrayMemberInfo *)currObjFixupInfo; DWORD *pIndices = pArrInfo->GetIndices(); TypeHandle arrayElementType = refParentArray->GetArrayElementTypeHandle(); MethodTable *pArrayMT = refParentArray->GetMethodTable(); DWORD Rank = pArrayMT->GetRank(); SIZE_T Offset = 0; SIZE_T Multiplier = 1; _ASSERTE(Rank == pArrInfo->GetNumDimensions()); for (int i = Rank-1; i >= 0; i--) { INT32 curIndex = pIndices[i]; const INT32 *pBoundsPtr = refParentArray->GetBoundsPtr(); // Bounds check each index // Casting to unsigned allows us to use one compare for [0..limit-1] _ASSERTE((UINT32) curIndex < (UINT32) pBoundsPtr[i]); Offset += curIndex * Multiplier; Multiplier *= pBoundsPtr[i]; } // The follwing code is loosely based on COMArrayInfo::SetValue if (!arrayElementType.IsValueType()) { if (!ObjIsInstanceOf(OBJECTREFToObject(m_newObject), arrayElementType)) COMPlusThrow(kInvalidCastException,W("InvalidCast_StoreArrayElement")); OBJECTREF* pElem = (OBJECTREF*)(refParentArray->GetDataPtr() + (Offset * pArrayMT->GetComponentSize())); SetObjectReference(pElem,m_newObject,GetAppDomain()); } else { // value class or primitive type OBJECTREF* pElem = (OBJECTREF*)(refParentArray->GetDataPtr() + (Offset * pArrayMT->GetComponentSize())); if (!arrayElementType.GetMethodTable()->UnBoxInto(pElem, m_newObject)) COMPlusThrow(kInvalidCastException, W("InvalidCast_StoreArrayElement")); } LOG((LF_REMOTING, LL_INFO1000, "HandleArrayFixup. Parent is an array. Added element at offset %d\n", Offset)); GCPROTECT_END(); } void ObjectClone::HandleObjectFixup(OBJECTREF refParent, QueuedObjectInfo *currObjFixupInfo) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END ObjectMemberInfo *pObjInfo = (ObjectMemberInfo *)currObjFixupInfo; FieldDesc *pTargetField = pObjInfo->GetFieldDesc(); DWORD offset = pTargetField->GetOffset(); #ifdef _DEBUG MethodTable *pTemp = refParent->GetMethodTable(); _ASSERTE(offset < pTemp->GetBaseSize()); #endif GCPROTECT_BEGIN(refParent); TypeHandle fldType = LoadExactFieldType(pTargetField, refParent, m_toDomain); if (!ObjIsInstanceOf(OBJECTREFToObject(m_newObject), fldType)) COMPlusThrow(kArgumentException,W("Arg_ObjObj")); OBJECTREF *pDest = (OBJECTREF *) (refParent->GetData() + offset); _ASSERTE(GetAppDomain()==m_toDomain); SetObjectReference(pDest, m_newObject, GetAppDomain()); GCPROTECT_END(); LOG((LF_REMOTING, LL_INFO1000, "HandleObjectFixup. Parent is a regular object. Added field at offset %d\n", offset)); } #ifdef OBJECT_CLONER_STRICT_MODE static void DECLSPEC_NORETURN ThrowMissingFieldException(FieldDesc *pFD) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END; StackSString szField(SString::Utf8, pFD->GetName()); StackSString szType; TypeString::AppendType(szType, TypeHandle(pFD->GetApproxEnclosingMethodTable())); COMPlusThrow(kSerializationException, IDS_SERIALIZATION_MISSING_FIELD, szField.GetUnicode(), szType.GetUnicode()); } #endif void ObjectClone::ScanMemberFields(DWORD IObjRefTSOIndex, DWORD BoxedValTSOIndex) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END _ASSERTE(m_currObject != NULL); _ASSERTE(m_newObject != NULL); MethodTable *pMT = m_currObject->GetMethodTable(); _ASSERTE(!pMT->IsMarshaledByRef() && !pMT->IsTransparentProxy()); _ASSERTE(!pMT->IsArray()); MethodTable *pTargetMT = m_newObject->GetMethodTable(); DWORD numFixupsNeeded = 0; if (RemotableMethodInfo::TypeIsConduciveToBlitting(pMT, pTargetMT)) { _ASSERTE(pMT->GetAlignedNumInstanceFieldBytes() == pTargetMT->GetAlignedNumInstanceFieldBytes()); DWORD numBytes = pMT->GetNumInstanceFieldBytes(); BYTE *pFrom = m_currObject->GetData(); BYTE *pTo = m_newObject->GetData(); memcpyNoGCRefs(pTo, pFrom, numBytes); LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Object has no reference type fields. Blitting contents.\n")); } else if (AreTypesEmittedIdentically(pMT, pTargetMT)) { LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Object not blittable but types are layed out for easy cloning .\n")); MethodTable *pCurrMT = pMT; MethodTable *pCurrTargetMT = pTargetMT; while (pCurrMT) { DWORD numInstanceFields = pCurrMT->GetNumIntroducedInstanceFields(); _ASSERTE(pCurrTargetMT->GetNumIntroducedInstanceFields() == numInstanceFields); FieldDesc *pFields = pCurrMT->GetApproxFieldDescListRaw(); FieldDesc *pTargetFields = pCurrTargetMT->GetApproxFieldDescListRaw(); for (DWORD i = 0; i < numInstanceFields; i++) { if (pFields[i].IsNotSerialized()) { LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Field %s is marked NonSerialized. Skipping.\n", pFields[i].GetName())); continue; } numFixupsNeeded += CloneField(&pFields[i], &pTargetFields[i]); } pCurrMT = pCurrMT->GetParentMethodTable(); pCurrTargetMT = pCurrTargetMT->GetParentMethodTable(); } } else { LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Object type layout is different.\n")); // The object types between source and destination have significant differences (some fields may be added, removed or // re-ordered, the type hierarchy may have had layers added or removed). We can still clone the object if every non-optional // field in the destination object can be found and serialized in a type with the same name in the source object. We ignore // fields and entire type layers that have been added in the source object and also any fields or layers that have been // removed as long as they don't include any fields that are mandatory in the destination object. We allow the fields within // a type layer to move around (we key the field by name only, the latter stage of cloning will check type equivalency and // as above we will widen primitive types if necessary). Since it requires significant effort to calculate whether the // objects can be cloned (and then locate corresponding fields in order to do so) we cache a mapping of source object fields // to destination object fields. // The following call will return such a mapping (it's an array where each entry is a pointer to a source object field desc // and the entries are in destination field index order, most derived type first, followed by second most derived type // etc.). If a mapping is impossible the method will throw. FieldDesc **pFieldMap = CrossDomainFieldMap::LookupOrCreateFieldMapping(pTargetMT, pMT); DWORD dwMapIndex = 0; MethodTable *pDstMT = pTargetMT; while (pDstMT) { FieldDesc *pDstFields = pDstMT->GetApproxFieldDescListRaw(); DWORD numInstanceFields = pDstMT->GetNumIntroducedInstanceFields(); for (DWORD i = 0; i < numInstanceFields; i++) { FieldDesc *pSrcField = pFieldMap[dwMapIndex++]; // Non-serialized fields in the destination type (or optional fields where the source type doesn't have an // equivalent) don't have a source field desc. if (pSrcField == NULL) continue; numFixupsNeeded += CloneField(pSrcField, &pDstFields[i]); } pDstMT = pDstMT->GetParentMethodTable(); } _ASSERTE(dwMapIndex == pTargetMT->GetNumInstanceFields()); } if (numFixupsNeeded > 0) { ParentInfo fxInfo(numFixupsNeeded); if (IObjRefTSOIndex != (DWORD) -1) { _ASSERTE(m_cbInterface->IsIObjectReferenceType(pMT)); fxInfo.SetIsIObjRefInstance(); fxInfo.SetIObjRefIndexIntoTSO(IObjRefTSOIndex); } if (BoxedValTSOIndex != (DWORD) -1) { _ASSERTE(pMT->IsValueType()); fxInfo.SetNeedsUnboxing(); fxInfo.SetBoxedValIndexIntoTSO(BoxedValTSOIndex); } QOF.Enqueue(m_newObject, NULL, NULL, (QueuedObjectInfo *)&fxInfo); LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Current object had total of %d reference type fields. Adding to QOF.\n", numFixupsNeeded)); // Delay calling any OnDeserialized callbacks until the end of the cloning operation (it's difficult to tell when all the // children have been deserialized). if (HasVtsCallbacks(m_newObject->GetMethodTable(), RemotingVtsInfo::VTS_CALLBACK_ON_DESERIALIZED)) VDC.Enqueue(m_newObject, NULL, NULL, NULL); if (m_cbInterface->RequiresDeserializationCallback(m_newObject->GetMethodTable())) { LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Adding object to Table of IDeserialization Callbacks\n")); QueuedObjectInfo noInfo; TDC.Enqueue(m_newObject, NULL, NULL, &noInfo); } } else { // This is effectively a leaf node (no complex children) so if the type has a callback for OnDeserialized we'll deliver it // now. This fixes callback ordering for a few more edge cases (e.g. VSW 415611) and is reasonably cheap. We can never do a // perfect job (in the presence of object graph cycles) and a near perfect job (intuitively ordered callbacks for acyclic // object graphs) is prohibitively expensive; so we're stuck with workarounds like this. InvokeVtsCallbacks(m_newObject, RemotingVtsInfo::VTS_CALLBACK_ON_DESERIALIZED, m_toDomain); if (m_cbInterface->RequiresDeserializationCallback(m_newObject->GetMethodTable())) MakeIDeserializationCallback(m_newObject); } } DWORD ObjectClone::CloneField(FieldDesc *pSrcField, FieldDesc *pDstField) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END; BOOL bFixupNeeded = FALSE; CorElementType srcType = pSrcField->GetFieldType(); CorElementType dstType = pDstField->GetFieldType(); DWORD srcOffset = pSrcField->GetOffset(); DWORD dstOffset = pDstField->GetOffset(); BOOL bUseWidenedValue = FALSE; ARG_SLOT fieldData = 0; if (srcType != dstType) { void *pData = m_currObject->GetData() + srcOffset; MethodTable *pSrcFieldMT = NULL; if (CorTypeInfo::IsPrimitiveType(srcType)) pSrcFieldMT = MscorlibBinder::GetElementType(srcType); else pSrcFieldMT = LoadExactFieldType(pSrcField, m_currObject, m_fromDomain).AsMethodTable(); LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Field %s has differing types at source and destination. Will try to convert.\n", pSrcField->GetName())); fieldData = HandleFieldTypeMismatch(dstType, srcType, pData, pSrcFieldMT); bUseWidenedValue = TRUE; } switch (dstType) { case ELEMENT_TYPE_I1: case ELEMENT_TYPE_U1: case ELEMENT_TYPE_BOOLEAN: { BYTE *pDest = m_newObject->GetData() + dstOffset; if (bUseWidenedValue) *pDest = (unsigned char) fieldData; else { BYTE *pByte = m_currObject->GetData() + srcOffset; *pDest = *pByte; } } break; case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: { WORD *pDest = (WORD*)(m_newObject->GetData() + dstOffset); if (bUseWidenedValue) *pDest = (short) fieldData; else { WORD *pWord = (WORD*)(m_currObject->GetData() + srcOffset); *(pDest) = *pWord; } } break; case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_R4: IN_WIN32(case ELEMENT_TYPE_FNPTR:) IN_WIN32(case ELEMENT_TYPE_I:) IN_WIN32(case ELEMENT_TYPE_U:) { DWORD *pDest = (DWORD*)(m_newObject->GetData() + dstOffset); if (bUseWidenedValue) *pDest = (int) fieldData; else { DWORD *pDword = (DWORD*)(m_currObject->GetData() + srcOffset); *(pDest) = *pDword; } } break; case ELEMENT_TYPE_R8: case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: IN_WIN64(case ELEMENT_TYPE_FNPTR:) IN_WIN64(case ELEMENT_TYPE_I:) IN_WIN64(case ELEMENT_TYPE_U:) { INT64 *pDest = (INT64*)(m_newObject->GetData() + dstOffset); if (bUseWidenedValue) *pDest = fieldData; else { INT64 *pLong = (INT64*)(m_currObject->GetData() + srcOffset); *(pDest) = *pLong; } } break; case ELEMENT_TYPE_PTR: { void **pDest = (void**)(m_newObject->GetData() + dstOffset); void **pPtr = (void**)(m_currObject->GetData() + srcOffset); *(pDest) = *pPtr; } break; case ELEMENT_TYPE_STRING: case ELEMENT_TYPE_CLASS: // objectrefs case ELEMENT_TYPE_OBJECT: case ELEMENT_TYPE_SZARRAY: // single dim, zero case ELEMENT_TYPE_ARRAY: // all other arrays { OBJECTREF *pSrc = (OBJECTREF *)(m_currObject->GetData() + srcOffset); OBJECTREF *pDest = (OBJECTREF *)(m_newObject->GetData() + dstOffset); if ((*pSrc) == NULL) break; // If no deep copy is required, just copy the reference if (!m_cbInterface->RequiresDeepCopy(*pSrc)) { _ASSERTE(GetAppDomain()==m_toDomain); SetObjectReference(pDest, *pSrc, GetAppDomain()); break; } // Special case String if ((*pSrc)->GetMethodTable() == g_pStringClass) { // Better check the destination really expects a string (or maybe an object). TypeHandle thDstField = LoadExactFieldType(pDstField, m_newObject, m_toDomain); if (thDstField != TypeHandle(g_pStringClass) && thDstField != TypeHandle(g_pObjectClass)) COMPlusThrow(kArgumentException, W("Arg_ObjObj")); STRINGREF refStr = (STRINGREF) *pSrc; refStr = m_cbInterface->AllocateString(refStr); // Get dest addr again, as a GC might have occurred pDest = (OBJECTREF *)(m_newObject->GetData() + dstOffset); _ASSERTE(GetAppDomain()==m_toDomain); SetObjectReference(pDest, refStr, GetAppDomain()); break; } // Add the object to QOM LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Adding object in field %s to Queue of Objects to be Marshalled.\n", pSrcField->GetName())); ObjectMemberInfo objInfo(pDstField); bFixupNeeded = TRUE; QOM.Enqueue(*pSrc, NULL, NULL, (QueuedObjectInfo *)&objInfo); } break; case ELEMENT_TYPE_VALUETYPE: { TypeHandle th = LoadExactFieldType(pSrcField, m_currObject, m_fromDomain); _ASSERTE(!th.AsMethodTable()->IsByRefLike() && "Field types cannot contain stack pointers."); TypeHandle thTarget = LoadExactFieldType(pDstField, m_newObject, m_toDomain); MethodTable *pValueClassMT = th.AsMethodTable(); MethodTable *pValueClassTargetMT = thTarget.AsMethodTable(); if (!RemotableMethodInfo::TypeIsConduciveToBlitting(pValueClassMT, pValueClassTargetMT)) { // Needs marshalling // We're allocating an object in the "to" domain // using a type from the "from" domain. OBJECTREF refTmpBox = BoxValueTypeInWrongDomain(m_currObject, srcOffset, pValueClassMT); // Nullable might return null here. In that case we don't need to do anything // and the null value otherwise confuxes the fixup queue. if (refTmpBox != NULL) { // Add the object to QOM ObjectMemberInfo objInfo(pDstField); objInfo.SetNeedsUnboxing(); bFixupNeeded = TRUE; QOM.Enqueue(refTmpBox, NULL, NULL, (QueuedObjectInfo *)&objInfo); LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Value type field %s has reference type contents. Boxing and adding to QOM.\n", pSrcField->GetName())); } } else { DWORD numBytesToCopy = th.AsMethodTable()->GetNumInstanceFieldBytes(); BYTE *pByte = m_currObject->GetData() + srcOffset; BYTE *pDest = m_newObject->GetData() + dstOffset; memcpyNoGCRefs(pDest, pByte, numBytesToCopy); LOG((LF_REMOTING, LL_INFO1000, "ScanMemberFields. Value type field %s has no reference type contents. Blitting.\n", pSrcField->GetName())); } } break; default: _ASSERTE(!"Unknown element type seen in ObjectClone::ScanMemberFields"); break; } return bFixupNeeded ? 1 : 0; } BOOL ObjectClone::AreTypesEmittedIdentically(MethodTable *pMT1, MethodTable *pMT2) { LIMITED_METHOD_CONTRACT; // Identical here means that both types have the same hierarchy (depth and names match) and that each level of the hierarchy has // the same fields (by name) at the same index. // We're going to be called quite frequently (once per call to ScanMemberFields) so until we're convinced that caching this // information is worth it we'll just compute the fast cases here and let the rest fall through to the slower technique. The // fast check is that the types are shared and identical or that they're loaded from the same file (in which case we have to be // a little more paranoid and check up the hierarchy). if (pMT1 == pMT2) return TRUE; // While the current level of the type is loaded from the same file... // Note that we used to check that the assemblies were the same; now we're more paranoid and check the actual modules scoping // the type are identical. This closes a security hole where identically named types in different modules of the same assembly // could cause the wrong type to be loaded in the server context allowing violation of the type system. while (pMT1->GetModule()->GetFile()->Equals(pMT2->GetModule()->GetFile())) { // Inspect the parents. pMT1 = pMT1->GetParentMethodTable(); pMT2 = pMT2->GetParentMethodTable(); // If the parents are the same shared type (e.g. Object), then we've found a match. if (pMT1 == pMT2) return TRUE; // Else check if one of the hierarchies has run out before the other (and therefore can't be equivalent). if (pMT1 == NULL || pMT2 == NULL) return FALSE; } return FALSE; } BOOL AreTypesEquivalent(MethodTable *pMT1, MethodTable *pMT2) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END; // Equivalent here is quite a weak predicate. All it means is that the types have the same (fully assembly qualified) name. The // derivation hierarchy is not inspected at all. StackSString szType1; StackSString szType2; TypeString::AppendType(szType1, TypeHandle(pMT1), TypeString::FormatNamespace | TypeString::FormatFullInst | TypeString::FormatAssembly | TypeString::FormatNoVersion); TypeString::AppendType(szType2, TypeHandle(pMT2), TypeString::FormatNamespace | TypeString::FormatFullInst | TypeString::FormatAssembly | TypeString::FormatNoVersion); return szType1.Equals(szType2); } PtrHashMap *CrossDomainFieldMap::s_pFieldMap = NULL; SimpleRWLock *CrossDomainFieldMap::s_pFieldMapLock = NULL; BOOL CrossDomainFieldMap::CompareFieldMapEntry(UPTR val1, UPTR val2) { CONTRACTL { MODE_ANY; NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; CrossDomainFieldMap::FieldMapEntry *pEntry1 = (CrossDomainFieldMap::FieldMapEntry *)(val1 << 1); CrossDomainFieldMap::FieldMapEntry *pEntry2 = (CrossDomainFieldMap::FieldMapEntry *)val2; if (pEntry1->m_pSrcMT == pEntry2->m_pSrcMT && pEntry1->m_pDstMT == pEntry2->m_pDstMT) return TRUE; return FALSE; } CrossDomainFieldMap::FieldMapEntry::FieldMapEntry(MethodTable *pSrcMT, MethodTable *pDstMT, FieldDesc **pFieldMap) { WRAPPER_NO_CONTRACT; m_pSrcMT = pSrcMT; m_pDstMT = pDstMT; m_pFieldMap = pFieldMap; BaseDomain *pSrcDomain = pSrcMT->GetDomain(); m_dwSrcDomain = pSrcDomain->IsAppDomain() ? ((AppDomain*)pSrcDomain)->GetId() : ADID(0); BaseDomain *pDstDomain = pDstMT->GetDomain(); m_dwDstDomain = pDstDomain->IsAppDomain() ? ((AppDomain*)pDstDomain)->GetId() : ADID(0); } static BOOL IsOwnerOfRWLock(LPVOID lock) { // @TODO - SimpleRWLock does not have knowledge of which thread gets the writer // lock, so no way to verify return TRUE; } // Remove any entries in the table that refer to an appdomain that is no longer live. void CrossDomainFieldMap::FlushStaleEntries() { if (s_pFieldMapLock == NULL || s_pFieldMap == NULL) return; SimpleWriteLockHolder swlh(s_pFieldMapLock); bool fDeletedEntry = false; PtrHashMap::PtrIterator iter = s_pFieldMap->begin(); while (!iter.end()) { FieldMapEntry *pEntry = (FieldMapEntry *)iter.GetValue(); AppDomainFromIDHolder adFrom(pEntry->m_dwSrcDomain, TRUE); AppDomainFromIDHolder adTo(pEntry->m_dwDstDomain, TRUE); if (adFrom.IsUnloaded() || adTo.IsUnloaded()) //we do not use ptr for anything { #ifdef _DEBUG LPVOID pDeletedEntry = #endif s_pFieldMap->DeleteValue(pEntry->GetHash(), pEntry); _ASSERTE(pDeletedEntry == pEntry); delete pEntry; fDeletedEntry = true; } ++iter; } if (fDeletedEntry) s_pFieldMap->Compact(); } FieldDesc **CrossDomainFieldMap::LookupOrCreateFieldMapping(MethodTable *pDstMT, MethodTable *pSrcMT) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END; // We lazily allocate the reader/writer lock we synchronize access to the hash with. if (s_pFieldMapLock == NULL) { void *pLockSpace = SystemDomain::GetGlobalLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(SimpleRWLock))); SimpleRWLock *pLock = new (pLockSpace) SimpleRWLock(COOPERATIVE_OR_PREEMPTIVE, LOCK_TYPE_DEFAULT); if (FastInterlockCompareExchangePointer(&s_pFieldMapLock, pLock, NULL) != NULL) // We lost the race, give up our copy. SystemDomain::GetGlobalLoaderAllocator()->GetLowFrequencyHeap()->BackoutMem(pLockSpace, sizeof(SimpleRWLock)); } // Now we have a lock we can use to synchronize the remainder of the init. if (s_pFieldMap == NULL) { SimpleWriteLockHolder swlh(s_pFieldMapLock); if (s_pFieldMap == NULL) { PtrHashMap *pMap = new (SystemDomain::GetGlobalLoaderAllocator()->GetLowFrequencyHeap()) PtrHashMap(); LockOwner lock = {s_pFieldMapLock, IsOwnerOfRWLock}; pMap->Init(32, CompareFieldMapEntry, TRUE, &lock); s_pFieldMap = pMap; } } else { // Try getting an existing value first. FieldMapEntry sEntry(pSrcMT, pDstMT, NULL); SimpleReadLockHolder srlh(s_pFieldMapLock); FieldMapEntry *pFound = (FieldMapEntry *)s_pFieldMap->LookupValue(sEntry.GetHash(), (LPVOID)&sEntry); if (pFound != (FieldMapEntry *)INVALIDENTRY) return pFound->m_pFieldMap; } // We couldn't find an existing entry in the hash. Now we must go through the painstaking process of matching fields in the // destination object to their counterparts in the source object. We build an array of pointers to source field descs ordered by // destination type field index (all the fields for the most derived type first, then all the fields for the second most derived // type etc.). NewArrayHolder pFieldMap(new FieldDesc*[pDstMT->GetNumInstanceFields()]); DWORD dwMapIndex = 0; // We start with the source and destination types for the object (which we know are equivalent at least in type name). For each // layer of the type hierarchy for the destination object (from the instance type through to Object) we attempt to locate the // corresponding source type in the hierarchy. This is non-trivial since either source or destination type hierarchies may have // added or removed layers. We ignore extra type layers in the source hierarchy and just concentrate on destination type layers // that introduce instance fields that are not marked NotSerializable. For each such layer we first locate the corresponding // source layer (via fully qualified type name) and then map each serialized (and possibly optional) destination field to the // corresponding source field (again by name). We don't allow a field to move around the type hierarchy (i.e. a field defined in // the base class in one version can't move to a derived type in later versions and be recognized as the original field). // Allowing this would introduce all sorts of ambiguity problems (consider the case of private fields all with the same name // implemented at every layer of the type hierarchy). bool fFirstPass = true; MethodTable *pCurrDstMT = pDstMT; MethodTable *pCurrSrcMT = pSrcMT; while (pCurrDstMT) { DWORD numInstanceFields = pCurrDstMT->GetNumIntroducedInstanceFields(); // Skip destination types with no instance fields to clone. if (numInstanceFields == 0) { pCurrDstMT = pCurrDstMT->GetParentMethodTable(); // Only safe to skip the source type as well on the first pass (the source version may have eliminated this level of // the type hierarchy). if (fFirstPass) pCurrSrcMT = pCurrSrcMT->GetParentMethodTable(); fFirstPass = false; continue; } // We need to synchronize the source type with the destination type. This means skipping any source types in the // hierarchy that the destination doesn't know about. MethodTable *pCandidateMT = pCurrSrcMT; while (pCandidateMT) { if (fFirstPass || pCandidateMT == pCurrDstMT || AreTypesEquivalent(pCandidateMT, pCurrDstMT)) { // Skip intermediate source types (the destination type didn't know anything about them, so they're surplus // to requirements). pCurrSrcMT = pCandidateMT; break; } pCandidateMT = pCandidateMT->GetParentMethodTable(); } #ifdef OBJECT_CLONER_STRICT_MODE // If there's no candidate source type equivalent to the current destination type we need to prove that the destination // type has no mandatory instance fields or throw an exception (since there's no place to fetch the field values from). if (pCandidateMT == NULL) { FieldDesc *pFields = pCurrDstMT->GetApproxFieldDescListRaw(); for (DWORD i = 0; i < numInstanceFields; i++) { if (pFields[i].IsNotSerialized() || pFields[i].IsOptionallySerialized()) { pFieldMap[dwMapIndex++] = NULL; continue; } // We've found a field that must be cloned but have no corresponding source-side type to clone it from. Raise an // exception. ThrowMissingFieldException(&pFields[i]); } // If we get here we know the current destination type level was effectively a no-op. Move onto the next level. pCurrDstMT = pCurrDstMT->GetParentMethodTable(); fFirstPass = false; continue; } #else // In lax matching mode we can ignore all fields, even those not marked optional. So the lack of an equivalent type in the // source hierarchy doesn't bother us. Mark all fields as having a default value and then move onto the next level in the // type hierarchy. if (pCandidateMT == NULL) { for (DWORD i = 0; i < numInstanceFields; i++) pFieldMap[dwMapIndex++] = NULL; pCurrDstMT = pCurrDstMT->GetParentMethodTable(); fFirstPass = false; continue; } #endif // If we get here we have equivalent types in pCurrDstMT and pCurrSrcMT. Now we need to locate the source field desc // corresponding to every mandatory (and possibly optional) field in the destination type and record it in the field map. DWORD numSrcFields = pCurrSrcMT->GetNumIntroducedInstanceFields(); DWORD numDstFields = pCurrDstMT->GetNumIntroducedInstanceFields(); FieldDesc *pDstFields = pCurrDstMT->GetApproxFieldDescListRaw(); FieldDesc *pSrcFields = pCurrSrcMT->GetApproxFieldDescListRaw(); for (DWORD i = 0; i < numDstFields; i++) { // Non-serialized destination fields aren't filled in from source types. if (pDstFields[i].IsNotSerialized()) { pFieldMap[dwMapIndex++] = NULL; continue; } // Go look for a field in the source type with the same name. LPCUTF8 szDstFieldName = pDstFields[i].GetName(); DWORD j; for (j = 0; j < numSrcFields; j++) { LPCUTF8 szSrcFieldName = pSrcFields[j].GetName(); if (strcmp(szDstFieldName, szSrcFieldName) == 0) { // Check that the field isn't marked NotSerialized (if it is then it's invisible to the cloner). if (pSrcFields[j].IsNotSerialized()) j = numSrcFields; break; } } #ifdef OBJECT_CLONER_STRICT_MODE // If we didn't find a corresponding field it might not be fatal; the field could be optionally serializable from the // destination type's point of view. if (j == numSrcFields) { if (pDstFields[i].IsOptionallySerialized()) { pFieldMap[dwMapIndex++] = NULL; continue; } // The field was required. Throw an exception. ThrowMissingFieldException(&pDstFields[i]); } #else // In lax matching mode we can ignore all fields, even those not marked optional. Simply mark this field as having the // default value. if (j == numSrcFields) { pFieldMap[dwMapIndex++] = NULL; continue; } #endif // Otherwise we found matching fields (in name at least, type processing is done later). pFieldMap[dwMapIndex++] = &pSrcFields[j]; } pCurrDstMT = pCurrDstMT->GetParentMethodTable(); pCurrSrcMT = pCurrSrcMT->GetParentMethodTable(); fFirstPass = false; } _ASSERTE(dwMapIndex == pDstMT->GetNumInstanceFields()); // Now we have a field map we should insert it into the hash. NewHolder pEntry(new FieldMapEntry(pSrcMT, pDstMT, pFieldMap)); PREFIX_ASSUME(pEntry != NULL); pFieldMap.SuppressRelease(); SimpleWriteLockHolder swlh(s_pFieldMapLock); UPTR key = pEntry->GetHash(); FieldMapEntry *pFound = (FieldMapEntry *)s_pFieldMap->LookupValue(key, (LPVOID)pEntry); if (pFound == (FieldMapEntry *)INVALIDENTRY) { s_pFieldMap->InsertValue(key, (LPVOID)pEntry); pEntry.SuppressRelease(); return pFieldMap; } else return pFound->m_pFieldMap; } ARG_SLOT ObjectClone::HandleFieldTypeMismatch(CorElementType dstType, CorElementType srcType, void *pData, MethodTable *pSrcMT) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END _ASSERTE(m_context != ObjectFreezer); ARG_SLOT data = 0; InvokeUtil::CreatePrimitiveValue(dstType, srcType, pData, pSrcMT, &data); return data; } void ObjectClone::ScanISerializableMembers(DWORD IObjRefTSOIndex, DWORD ISerTSOIndex, DWORD BoxedValTSOIndex, PTRARRAYREF refValues) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END _ASSERTE(m_context != ObjectFreezer); // Queue the non-primitive types DWORD numFieldsToBeMarshalled = 0; PTRARRAYREF refNewValues = NULL; LOG((LF_REMOTING, LL_INFO1000, "ScanISerializableMembers. Scanning members of ISerializable type object.\n")); GCPROTECT_BEGIN(refValues); refNewValues = (PTRARRAYREF) AllocateObjectArray(refValues->GetNumComponents(), g_pObjectClass, FALSE); _ASSERTE(refNewValues != NULL); for (DWORD index = 0; index < refValues->GetNumComponents(); index++) { OBJECTREF refField = refValues->GetAt(index); if (refField == NULL) continue; if (CorTypeInfo::IsPrimitiveType(refField->GetTypeHandle().GetSignatureCorElementType()) || refField->GetMethodTable() == g_pStringClass) { refNewValues->SetAt(index, refField); continue; } ISerializableMemberInfo isInfo(ISerTSOIndex, index); QOM.Enqueue(refField, NULL, NULL, (QueuedObjectInfo *) &isInfo); numFieldsToBeMarshalled++; refNewValues->SetAt(index, NULL); LOG((LF_REMOTING, LL_INFO1000, "ScanISerializableMembers. Member at index %d is reference type. Adding to QOM.\n", index)); } GCPROTECT_END(); // Update TSO OBJECTREF refNames = NULL, refFields = NULL; QueuedObjectInfo *pDummy; OBJECTREF newObj; newObj = TSO.GetAt(ISerTSOIndex, &refNames, &refFields, &pDummy); _ASSERTE(newObj == m_newObject); TSO.SetAt(ISerTSOIndex, m_newObject, refNames, refNewValues, pDummy); if (numFieldsToBeMarshalled > 0) { ParentInfo fxInfo(numFieldsToBeMarshalled); fxInfo.SetIsISerializableInstance(); fxInfo.SetIObjRefIndexIntoTSO(IObjRefTSOIndex); fxInfo.SetISerIndexIntoTSO(ISerTSOIndex); fxInfo.SetBoxedValIndexIntoTSO(BoxedValTSOIndex); QOF.Enqueue(m_newObject, NULL, NULL, (QueuedObjectInfo *) &fxInfo); LOG((LF_REMOTING, LL_INFO1000, "ScanISerializableMembers. Current object had total of %d reference type fields. Adding to QOF.\n", numFieldsToBeMarshalled)); // Delay calling any OnDeserialized callbacks until the end of the cloning operation (it's difficult to tell when all the // children have been deserialized). if (HasVtsCallbacks(m_newObject->GetMethodTable(), RemotingVtsInfo::VTS_CALLBACK_ON_DESERIALIZED)) VDC.Enqueue(m_newObject, NULL, NULL, NULL); if (m_cbInterface->RequiresDeserializationCallback(m_newObject->GetMethodTable())) { LOG((LF_REMOTING, LL_INFO1000, "ScanISerializableMembers. Adding object to Table of IDeserialization Callbacks\n")); QueuedObjectInfo noInfo; TDC.Enqueue(m_newObject, NULL, NULL, &noInfo); } } else { // This is effectively a leaf node (no complex children) so if the type has a callback for OnDeserialized we'll deliver it // now. This fixes callback ordering for a few more edge cases (e.g. VSW 415611) and is reasonably cheap. We can never do a // perfect job (in the presence of object graph cycles) and a near perfect job (intuitively ordered callbacks for acyclic // object graphs) is prohibitively expensive; so we're stuck with workarounds like this. InvokeVtsCallbacks(m_newObject, RemotingVtsInfo::VTS_CALLBACK_ON_DESERIALIZED, m_toDomain); if (m_cbInterface->RequiresDeserializationCallback(m_newObject->GetMethodTable())) MakeIDeserializationCallback(m_newObject); } } void ObjectClone::ScanArrayMembers() { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END #ifdef _DEBUG MethodTable *pCurrMT = m_currObject->GetMethodTable(); _ASSERTE(pCurrMT && pCurrMT->IsArray()); MethodTable *pNewMT = m_newObject->GetMethodTable(); _ASSERTE(pNewMT && pNewMT->IsArray()); #endif LOG((LF_REMOTING, LL_INFO1000, "ScanArrayMembers. Scanning members of array object.\n")); BASEARRAYREF refFromArray = (BASEARRAYREF) m_currObject; BASEARRAYREF refToArray = (BASEARRAYREF) m_newObject; GCPROTECT_BEGIN(refFromArray); GCPROTECT_BEGIN(refToArray); TypeHandle toArrayElementType = refToArray->GetArrayElementTypeHandle(); DWORD numComponents = refFromArray->GetNumComponents(); MethodTable *pArrayMT = refFromArray->GetMethodTable(); DWORD rank = pArrayMT->GetRank(); DWORD dwOffset = 0; DWORD *pIndices = (DWORD*) _alloca(sizeof(DWORD) * rank); VOID *pTemp = _alloca(sizeof(NDimArrayMemberInfo) + rank * sizeof(DWORD)); NDimArrayMemberInfo *pArrInfo = new (pTemp) NDimArrayMemberInfo(rank); bool boxingObjects = (pArrayMT->GetArrayElementType() == ELEMENT_TYPE_VALUETYPE); // Must enter the from domain if we are going to be allocating any non-agile boxes ENTER_DOMAIN_PTR_PREDICATED(m_fromDomain,ADV_RUNNINGIN,boxingObjects); if (boxingObjects) { pArrInfo->SetNeedsUnboxing(); // We may be required to activate value types of array elements, since we // are going to box them. Hoist out the required domain transition and // activation. MethodTable *pMT = ((BASEARRAYREF)m_currObject)->GetArrayElementTypeHandle().GetMethodTable(); pMT->EnsureInstanceActive(); } DWORD numFixupsNeeded = 0; for (DWORD i = 0; i < numComponents; i++) { // The array could be huge. To avoid keeping a pending GC waiting (and maybe timing out) we're going to pulse the GC mode // every so often. Do this more freqeuntly in debug builds, where each iteration through this loop takes considerably // longer. #ifdef _DEBUG #define COPY_CYCLES 1024 #else #define COPY_CYCLES 8192 #endif if ((i % COPY_CYCLES) == (COPY_CYCLES - 1)) GetThread()->PulseGCMode(); const INT32 *pBoundsPtr = refFromArray->GetBoundsPtr(); DWORD findIndices = i; for (DWORD rankIndex = rank; rankIndex > 0; rankIndex--) { DWORD numElementsInDimension = pBoundsPtr[rankIndex - 1]; DWORD quotient = findIndices / numElementsInDimension; DWORD remainder = findIndices % numElementsInDimension; pIndices[rankIndex - 1] = remainder; findIndices = quotient; } pArrInfo->SetIndices(pIndices); Object *rv = GetObjectFromArray((BASEARRAYREF *)&m_currObject, dwOffset); if (rv != NULL) { OBJECTREF oRef = ObjectToOBJECTREF(rv); if (oRef->GetMethodTable() == g_pStringClass && m_context != ObjectFreezer) { OBJECTREF* pElem = (OBJECTREF*)(refToArray->GetDataPtr() + (dwOffset * pArrayMT->GetComponentSize())); SetObjectReference(pElem,oRef,GetAppDomain()); } else { // Add the object to QOM numFixupsNeeded++; QOM.Enqueue(oRef, NULL, NULL, pArrInfo); LOG((LF_REMOTING, LL_INFO1000, "ScanArrayMembers. Element at offset %d is reference type. Adding to QOM.\n", dwOffset)); } } dwOffset ++; } if (numFixupsNeeded > 0) { ParentInfo fxInfo(numFixupsNeeded); QOF.Enqueue(m_newObject, NULL, NULL, (QueuedObjectInfo *)&fxInfo); LOG((LF_REMOTING, LL_INFO1000, "ScanArrayMembers. Current object had total of %d reference type fields. Adding to QOF.\n", numFixupsNeeded)); } END_DOMAIN_TRANSITION; GCPROTECT_END(); GCPROTECT_END(); } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4244) #endif // _MSC_VER Object *ObjectClone::GetObjectFromArray(BASEARRAYREF* arrObj, DWORD dwOffset) { CONTRACTL { THROWS; if ((*arrObj)->GetArrayElementTypeHandle().GetMethodTable()->IsValueType()) GC_TRIGGERS; else GC_NOTRIGGER; } CONTRACTL_END; // Get the type of the element... switch ((*arrObj)->GetArrayElementType()) { case ELEMENT_TYPE_VOID: return NULL; case ELEMENT_TYPE_CLASS: // Class case ELEMENT_TYPE_SZARRAY: // Single Dim, Zero case ELEMENT_TYPE_ARRAY: // General Array case ELEMENT_TYPE_STRING: case ELEMENT_TYPE_OBJECT: { _ASSERTE((*arrObj)->GetComponentSize() == sizeof(OBJECTREF)); BYTE* pData = ((BYTE*)(*arrObj)->GetDataPtr()) + (dwOffset * sizeof(OBJECTREF)); return *(Object **)pData; } case ELEMENT_TYPE_VALUETYPE: { MethodTable *pMT = (*arrObj)->GetArrayElementTypeHandle().GetMethodTable(); WORD wComponentSize = (*arrObj)->GetComponentSize(); BYTE* pData = ((BYTE*)(*arrObj)->GetDataPtr()) + (dwOffset * wComponentSize); return OBJECTREFToObject(pMT->Box(pData)); } case ELEMENT_TYPE_BOOLEAN: // boolean case ELEMENT_TYPE_I1: // sbyte case ELEMENT_TYPE_U1: case ELEMENT_TYPE_I2: // short case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: // char case ELEMENT_TYPE_I4: // int case ELEMENT_TYPE_I: case ELEMENT_TYPE_U: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_I8: // long case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R4: // float case ELEMENT_TYPE_R8: // double case ELEMENT_TYPE_PTR: { // Note that this is a cloned version of the value class case above for performance // Watch for GC here. We allocate the object and then // grab the void* to the data we are going to copy. MethodTable *pMT = (*arrObj)->GetArrayElementTypeHandle().GetMethodTable(); OBJECTREF obj = ::AllocateObject(pMT); WORD wComponentSize = (*arrObj)->GetComponentSize(); BYTE* pData = ((BYTE*)(*arrObj)->GetDataPtr()) + (dwOffset * wComponentSize); CopyValueClassUnchecked(obj->UnBox(), pData, (*arrObj)->GetArrayElementTypeHandle().GetMethodTable()); return OBJECTREFToObject(obj); } case ELEMENT_TYPE_END: default: _ASSERTE(!"Unknown array element type"); } _ASSERTE(!"Should never get here"); return NULL; } #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER: warning C4244 void ObjectClone::CompleteValueTypeFields(OBJECTREF newObj, OBJECTREF refParent, QueuedObjectInfo *objInfo) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; } CONTRACTL_END #ifdef _DEBUG { SString ssTypeName; SString ssParentTypeName; newObj->GetMethodTable()->_GetFullyQualifiedNameForClassNestedAware(ssTypeName); refParent->GetMethodTable()->_GetFullyQualifiedNameForClassNestedAware(ssParentTypeName); LOG((LF_REMOTING, LL_INFO1000, "CompleteValueTypeFields. Fixing up value type field of type %S into parent of type %S.\n", ssTypeName.GetUnicode(), ssParentTypeName.GetUnicode())); } #endif ValueTypeInfo *pValTypeInfo = (ValueTypeInfo *)objInfo; QueuedObjectInfo *pFixupInfo = pValTypeInfo->GetFixupInfo(); PREFIX_ASSUME(pFixupInfo != NULL); _ASSERTE(pFixupInfo->NeedsUnboxing()); if (pFixupInfo->IsArray()) { m_newObject = newObj; HandleArrayFixup(refParent, pFixupInfo); } else { GCPROTECT_BEGIN(refParent); GCPROTECT_BEGIN(newObj); ObjectMemberInfo *pObjInfo = (ObjectMemberInfo *)pFixupInfo; FieldDesc *pTargetField = pObjInfo->GetFieldDesc(); TypeHandle fldType = LoadExactFieldType(pTargetField, refParent, m_toDomain); void *pDest = refParent->GetData() + pTargetField->GetOffset(); _ASSERTE(GetAppDomain()==m_toDomain); if (!fldType.GetMethodTable()->UnBoxInto(pDest, newObj)) COMPlusThrow(kArgumentException,W("Arg_ObjObj")); GCPROTECT_END(); GCPROTECT_END(); } pValTypeInfo->SetHasBeenProcessed(); } void ObjectClone::CompleteSpecialObjects() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; } CONTRACTL_END; OBJECTREF nextObj = NULL; OBJECTREF refNames = NULL; OBJECTREF refValues = NULL; SpecialObjectInfo *pObjInfo = NULL; GCPROTECT_BEGIN(refNames); GCPROTECT_BEGIN(refValues); DWORD skippedObjects = 0; DWORD numLoops = 0; if (TSO.GetCount() == 0) goto EarlyExit; LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. Beginning.\n")); do { skippedObjects = 0; numLoops++; DWORD index = 0; TSO.BeginEnumeration(&index); while((nextObj = TSO.GetNext(&index, &refNames, &refValues, (QueuedObjectInfo **)&pObjInfo)) != NULL) { if (pObjInfo->HasBeenProcessed()) continue; if (pObjInfo->IsISerializableInstance()) { _ASSERTE(m_context != ObjectFreezer); LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. ISerializable instance at index %d.\n", index)); ISerializableInstanceInfo *iserInfo = (ISerializableInstanceInfo *)pObjInfo; if (iserInfo->GetNumSpecialMembers() > 0) { if (CheckForUnresolvedMembers(iserInfo)) { LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. Skipping ISerializable instance due to unresolved members.\n")); skippedObjects++; continue; } } CompleteISerializableObject(nextObj, refNames, refValues, iserInfo); } else if (pObjInfo->IsIObjRefInstance()) { _ASSERTE(m_context != ObjectFreezer); LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. IObjectReference instance at index %d.\n", index)); IObjRefInstanceInfo *iorInfo = (IObjRefInstanceInfo *)pObjInfo; if (iorInfo->GetNumSpecialMembers() > 0 || iorInfo->GetISerTSOIndex() != (DWORD) -1) { if (CheckForUnresolvedMembers(iorInfo)) { LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. Skipping IObjectReference instance due to unresolved members.\n")); skippedObjects++; continue; } } if (!CompleteIObjRefObject(nextObj, index, iorInfo)) skippedObjects++; } else { _ASSERTE(pObjInfo->IsBoxedObject()); LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. Boxed valuetype instance at index %d.\n", index)); ValueTypeInfo *valTypeInfo = (ValueTypeInfo *)pObjInfo; if (valTypeInfo->GetNumSpecialMembers() > 0 || valTypeInfo->GetISerTSOIndex() != (DWORD) -1 || valTypeInfo->GetIObjRefTSOIndex() != (DWORD) -1) { if (CheckForUnresolvedMembers(valTypeInfo)) { LOG((LF_REMOTING, LL_INFO1000, "CompleteSpecialObjects. Skipping boxed value instance due to unresolved members.\n")); skippedObjects++; continue; } } // If we were waiting on an IObjRef fixup then the target object will have changed. if (valTypeInfo->GetIObjRefTSOIndex() != (DWORD) -1) { OBJECTREF dummy1, dummy2; QueuedObjectInfo *dummy3; nextObj = TSO.GetAt(valTypeInfo->GetIObjRefTSOIndex(), &dummy1, &dummy2, &dummy3); } CompleteValueTypeFields(nextObj, refNames, valTypeInfo); } }; } while (skippedObjects > 0 && numLoops < 100); if (skippedObjects > 0 && numLoops >= 100) { COMPlusThrow(kSerializationException, IDS_SERIALIZATION_UNRESOLVED_SPECIAL_OBJECT); } EarlyExit: ; GCPROTECT_END(); GCPROTECT_END(); } BOOL ObjectClone::CheckForUnresolvedMembers(SpecialObjectInfo *splInfo) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; MODE_COOPERATIVE; } CONTRACTL_END; BOOL foundUnresolvedMember = FALSE; DWORD mappingIndex = splInfo->GetMappingTableIndex(); for (DWORD count = 0; count < splInfo->GetNumSpecialMembers(); count++) { DWORD memberIndex = TMappings.GetAt(mappingIndex++); SpecialObjectInfo *pMemberInfo; OBJECTREF dummy1, dummy2, dummy3; dummy1 = TSO.GetAt(memberIndex, &dummy2, &dummy3, (QueuedObjectInfo **)&pMemberInfo); // An unresolved IObjRef member is a blocker for any special object parent if (pMemberInfo->IsIObjRefInstance() && !pMemberInfo->HasBeenProcessed()) { LOG((LF_REMOTING, LL_INFO1000, "CheckForUnresolvedMembers. Found unresolved IObjectReference member at index %d.\n", memberIndex)); foundUnresolvedMember = TRUE; break; } // An unresolved ISer member is a blocker for IObjRef parent if (pMemberInfo->IsISerializableInstance() && !pMemberInfo->HasBeenProcessed() && splInfo->IsIObjRefInstance()) { LOG((LF_REMOTING, LL_INFO1000, "CheckForUnresolvedMembers. Found unresolved ISerializable member at index %d.\n", memberIndex)); foundUnresolvedMember = TRUE; break; } // An unresolved boxed object is a blocker for a boxed parent or an IObjRef parent if (pMemberInfo->IsBoxedObject() && !pMemberInfo->HasBeenProcessed() && (splInfo->IsIObjRefInstance() || splInfo->IsBoxedObject())) { LOG((LF_REMOTING, LL_INFO1000, "CheckForUnresolvedMembers. Found unresolved boxed valuetype member at index %d.\n", memberIndex)); foundUnresolvedMember = TRUE; break; } } // Done checking members. Now check if this instance itself needs some processing // If an instance is both ISer and IObj, then ISer should be processed before IObjRef if (!foundUnresolvedMember && splInfo->IsIObjRefInstance()) { IObjRefInstanceInfo *pObjRefInfo = (IObjRefInstanceInfo *)splInfo; if (pObjRefInfo->GetISerTSOIndex() != (DWORD) -1) { // Check if the ISer requirements have been met SpecialObjectInfo *pMemberInfo; OBJECTREF dummy1, dummy2, dummy3; dummy1 = TSO.GetAt(pObjRefInfo->GetISerTSOIndex(), &dummy2, &dummy3, (QueuedObjectInfo **)&pMemberInfo); if (!pMemberInfo->HasBeenProcessed()) { LOG((LF_REMOTING, LL_INFO1000, "CheckForUnresolvedMembers. This instance is also ISerializable at index %d. Not resolved yet.\n", pObjRefInfo->GetISerTSOIndex())); foundUnresolvedMember = TRUE; } } } // If an instance is ISer, IObj and a boxed value type, then ISer,IObj should be processed before unboxing if (!foundUnresolvedMember && splInfo->IsBoxedObject()) { ValueTypeInfo *pValTypeInfo = (ValueTypeInfo *)splInfo; if (pValTypeInfo->GetISerTSOIndex() != (DWORD) -1) { // Check if the ISer requirements have been met SpecialObjectInfo *pMemberInfo; OBJECTREF dummy1, dummy2, dummy3; dummy1 = TSO.GetAt(pValTypeInfo->GetISerTSOIndex(), &dummy2, &dummy3, (QueuedObjectInfo **)&pMemberInfo); if (!pMemberInfo->HasBeenProcessed()) { LOG((LF_REMOTING, LL_INFO1000, "CheckForUnresolvedMembers. This instance is also ISerializable at index %d. Not resolved yet.\n", pValTypeInfo->GetISerTSOIndex())); foundUnresolvedMember = TRUE; } } if (!foundUnresolvedMember && pValTypeInfo->GetIObjRefTSOIndex() != (DWORD) -1) { // Check if the ISer requirements have been met SpecialObjectInfo *pMemberInfo; OBJECTREF dummy1, dummy2, dummy3; dummy1 = TSO.GetAt(pValTypeInfo->GetIObjRefTSOIndex(), &dummy2, &dummy3, (QueuedObjectInfo **)&pMemberInfo); if (!pMemberInfo->HasBeenProcessed()) { LOG((LF_REMOTING, LL_INFO1000, "CheckForUnresolvedMembers. This instance is also IObjectReference at index %d. Not resolved yet.\n", pValTypeInfo->GetIObjRefTSOIndex())); foundUnresolvedMember = TRUE; } } } return foundUnresolvedMember; } void ObjectClone::CompleteISerializableObject(OBJECTREF IserObj, OBJECTREF refNames, OBJECTREF refValues, ISerializableInstanceInfo *iserInfo) { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END _ASSERTE(m_context != ObjectFreezer); struct _gc { OBJECTREF IserObj; OBJECTREF refNames; OBJECTREF refValues; OBJECTREF refSerInfo; } gc; gc.IserObj = IserObj; gc.refNames = refNames; gc.refValues = refValues; gc.refSerInfo = NULL; GCPROTECT_BEGIN(gc); #ifdef _DEBUG { DefineFullyQualifiedNameForClass(); LOG((LF_REMOTING, LL_INFO1000, "CompleteISerializableObject. Completing ISerializable object of type %s.\n", GetFullyQualifiedNameForClassNestedAware(gc.IserObj->GetMethodTable()))); } #endif BOOL bIsBoxed = gc.IserObj->GetMethodTable()->IsValueType(); // StreamingContextData is an out parameter of the managed callback, so it's passed by reference on all platforms. RuntimeMethodHandle::StreamingContextData context = {0}; PREPARE_NONVIRTUAL_CALLSITE(METHOD__OBJECTCLONEHELPER__PREPARE_DATA); DECLARE_ARGHOLDER_ARRAY(args, 4); args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(gc.IserObj); args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.refNames); args[ARGNUM_2] = OBJECTREF_TO_ARGHOLDER(gc.refValues); args[ARGNUM_3] = PTR_TO_ARGHOLDER(&context); CATCH_HANDLER_FOUND_NOTIFICATION_CALLSITE; CALL_MANAGED_METHOD_RETREF(gc.refSerInfo, OBJECTREF, args); if (iserInfo->IsTargetNotISerializable()) { // Prepare data would have constructed the object already _ASSERTE(gc.refSerInfo == NULL); } else { _ASSERTE(gc.refSerInfo != NULL); MethodTable *pMT = gc.IserObj->GetMethodTable(); _ASSERTE(pMT); MethodDesc * pCtor; #ifdef FEATURE_IMPERSONATION // Deal with the WindowsIdentity class specially by calling an internal // serialization constructor; the public one has a security demand that // breaks partial trust scenarios and is too expensive to assert for. if (MscorlibBinder::IsClass(pMT, CLASS__WINDOWS_IDENTITY)) pCtor = MscorlibBinder::GetMethod(METHOD__WINDOWS_IDENTITY__SERIALIZATION_CTOR); else #endif pCtor = MemberLoader::FindConstructor(pMT, &gsig_IM_SerInfo_StrContext_RetVoid); if (pCtor == NULL) { DefineFullyQualifiedNameForClassW(); COMPlusThrow(kSerializationException, IDS_SERIALIZATION_CTOR_NOT_FOUND, GetFullyQualifiedNameForClassNestedAwareW(pMT)); } MethodDescCallSite ctor(pCtor); ARG_SLOT argSlots[3]; // Nullable does not implement ISerializable. _ASSERTE(!Nullable::IsNullableType(gc.IserObj->GetMethodTable())); argSlots[0] = (bIsBoxed ? (ARG_SLOT)(SIZE_T)(gc.IserObj->UnBox()) : ObjToArgSlot(gc.IserObj)); argSlots[1] = ObjToArgSlot(gc.refSerInfo); #if defined(_TARGET_X86_) || defined(_TARGET_ARM_) static_assert_no_msg(sizeof(context) == sizeof(ARG_SLOT)); argSlots[2] = *(ARG_SLOT*)(&context); // StreamingContext is passed by value on x86 and ARM #elif defined(_WIN64) static_assert_no_msg(sizeof(context) > sizeof(ARG_SLOT)); argSlots[2] = PtrToArgSlot(&context); // StreamingContext is passed by reference on WIN64 #else // !_TARGET_X86_ && !_WIN64 && !_TARGET_ARM_ PORTABILITY_ASSERT("ObjectClone::CompleteISerializableObject() - NYI on this platform"); #endif // !_TARGET_X86_ && !_WIN64 && !_TARGET_ARM_ ctor.CallWithValueTypes(&argSlots[0]); } iserInfo->SetHasBeenProcessed(); GCPROTECT_END(); } // FALSE means the object could not be resolved and need to perform more iterations BOOL ObjectClone::CompleteIObjRefObject(OBJECTREF IObjRef, DWORD tsoIndex, IObjRefInstanceInfo *iorInfo) { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END BOOL bResult = FALSE; struct _gc { OBJECTREF IObjRef; OBJECTREF newObj; OBJECTREF refParent; OBJECTREF refFromObj; OBJECTREF resolvedObject; } gc; gc.IObjRef = IObjRef; gc.newObj = NULL; gc.refParent = NULL; gc.refFromObj = NULL; gc.resolvedObject = NULL; GCPROTECT_BEGIN(gc); _ASSERTE(m_context != ObjectFreezer); // First check if this is a repeat object if (iorInfo->IsRepeatObject()) { OBJECTREF dummy; dummy = TSO.GetAt(tsoIndex, &gc.refFromObj, &gc.refParent, (QueuedObjectInfo **)&iorInfo); PREFIX_ASSUME(gc.refFromObj != NULL); // Look in the Table of Seen objects whether this IObjRef has been resolved int currId; currId = TOS.HasID(gc.refFromObj, &gc.resolvedObject); _ASSERTE(currId != -1); MethodTable *pResolvedMT = gc.resolvedObject->GetMethodTable(); if (!pResolvedMT->IsTransparentProxy() && m_cbInterface->IsIObjectReferenceType(pResolvedMT)) { bResult = FALSE; } else { #ifdef _DEBUG { DefineFullyQualifiedNameForClass(); LOG((LF_REMOTING, LL_INFO1000, "CompleteIObjRefObject. Found IObjectReference object of type %s already resolved.\n", GetFullyQualifiedNameForClassNestedAware(gc.IObjRef->GetMethodTable()))); } #endif // Yes, its been resolved. // Fix the object into its parent (unless it requires unboxing, in which case there's another entry in the TSO ready to // do that). QueuedObjectInfo *pFixupInfo = (QueuedObjectInfo *)iorInfo->GetFixupInfo(); PREFIX_ASSUME(pFixupInfo != NULL); if (pFixupInfo->NeedsUnboxing()) { TSO.SetAt(tsoIndex, gc.resolvedObject, gc.refFromObj, gc.refParent, iorInfo); iorInfo->SetHasBeenProcessed(); bResult = TRUE; } else { if (gc.refParent == NULL) m_topObject = gc.resolvedObject; else { m_newObject = gc.resolvedObject; if (pFixupInfo->NeedsUnboxing()) CompleteValueTypeFields(gc.resolvedObject, gc.refParent, pFixupInfo); else Fixup(gc.resolvedObject, gc.refParent, pFixupInfo); } iorInfo->SetHasBeenProcessed(); bResult = TRUE; } } } else { MethodTable *pMT = gc.IObjRef->GetMethodTable(); _ASSERTE(pMT); MethodTable *pItf = MscorlibBinder::GetClass(CLASS__IOBJECTREFERENCE); MethodDesc *pMeth = GetInterfaceMethodImpl(pMT, pItf, 0); MethodDescCallSite method(pMeth, &gc.IObjRef); // Ensure Streamingcontext type is loaded. Do not delete this line MethodTable *pMTStreamingContext; pMTStreamingContext = MscorlibBinder::GetClass(CLASS__STREAMING_CONTEXT); _ASSERTE(pMTStreamingContext); ARG_SLOT arg[2]; arg[0] = ObjToArgSlot(gc.IObjRef); RuntimeMethodHandle::StreamingContextData context = { NULL, GetStreamingContextState() }; #ifdef _WIN64 static_assert_no_msg(sizeof(context) > sizeof(ARG_SLOT)); arg[1] = PtrToArgSlot(&context); #else static_assert_no_msg(sizeof(context) <= sizeof(ARG_SLOT)); arg[1] = *(ARG_SLOT*)(&context); #endif gc.newObj = method.CallWithValueTypes_RetOBJECTREF(&arg[0]); INDEBUG(DefineFullyQualifiedNameForClass();) _ASSERTE(gc.newObj != NULL); MethodTable *pNewMT = gc.newObj->GetMethodTable(); if (!pNewMT->IsTransparentProxy() && gc.newObj != gc.IObjRef && m_cbInterface->IsIObjectReferenceType(pNewMT)) { #ifdef _DEBUG LOG((LF_REMOTING, LL_INFO1000, "CompleteIObjRefObject. GetRealObject on object of type %s returned another IObjectReference. Adding back to TSO.\n", GetFullyQualifiedNameForClassNestedAware(gc.IObjRef->GetMethodTable()))); #endif // Put this back into the table OBJECTREF dummy; dummy = TSO.GetAt(tsoIndex, &gc.refFromObj, &gc.refParent, (QueuedObjectInfo **)&iorInfo); TSO.SetAt(tsoIndex, gc.newObj, gc.refFromObj, gc.refParent, iorInfo); bResult = FALSE; } else { #ifdef _DEBUG LOG((LF_REMOTING, LL_INFO1000, "CompleteIObjRefObject. Called GetRealObject on object of type %s. Fixing it up into its parent.\n", GetFullyQualifiedNameForClassNestedAware(gc.IObjRef->GetMethodTable()))); #endif // Fix the object into its parent (unless it requires unboxing, in which case there's another entry in the TSO ready to // do that). QueuedObjectInfo *pFixupInfo = (QueuedObjectInfo *)iorInfo->GetFixupInfo(); OBJECTREF dummy; dummy = TSO.GetAt(tsoIndex, &gc.refFromObj, &gc.refParent, (QueuedObjectInfo **)&iorInfo); if (pFixupInfo->NeedsUnboxing()) { TSO.SetAt(tsoIndex, gc.newObj, gc.refFromObj, gc.refParent, iorInfo); iorInfo->SetHasBeenProcessed(); bResult = TRUE; } else { if (gc.refParent == NULL) m_topObject = gc.newObj; else { m_newObject = gc.newObj; Fixup(gc.newObj, gc.refParent, pFixupInfo); } // Update Table of Seen objects, so that any repeat objects can be updated too TOS.UpdateObject(gc.refFromObj, gc.newObj); iorInfo->SetHasBeenProcessed(); bResult = TRUE; } } } GCPROTECT_END(); return bResult; } void MakeIDeserializationCallback(OBJECTREF refTarget) { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END; struct _gc { OBJECTREF refTarget; } gc; gc.refTarget = refTarget; GCPROTECT_BEGIN(gc); MethodTable *pMT = gc.refTarget->GetMethodTable(); _ASSERTE(pMT); MethodTable *pItf = MscorlibBinder::GetClass(CLASS__IDESERIALIZATIONCB); MethodDesc *pMeth = GetInterfaceMethodImpl(pMT, pItf, 0); PCODE pCode = pMeth->GetSingleCallableAddrOfCode(); PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(pCode); DECLARE_ARGHOLDER_ARRAY(args, 2); args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(gc.refTarget); args[ARGNUM_1] = NULL; CATCH_HANDLER_FOUND_NOTIFICATION_CALLSITE; CALL_MANAGED_METHOD_NORET(args); GCPROTECT_END(); } void ObjectClone::CompleteIDeserializationCallbacks() { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END OBJECTREF Dummy1 = NULL, Dummy2 = NULL; QueuedObjectInfo *pObjInfo = NULL; if (TDC.GetCount() == 0) return; LOG((LF_REMOTING, LL_INFO1000, "CompleteIDeserializationCallbacks. Beginning.\n")); OBJECTREF nextObj; while ((nextObj = TDC.Dequeue(&Dummy1, &Dummy2, &pObjInfo)) != NULL) { MakeIDeserializationCallback(nextObj); } } void ObjectClone::CompleteVtsOnDeserializedCallbacks() { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END; OBJECTREF nextObj = NULL, Dummy1 = NULL, Dummy2 = NULL; if (VDC.GetCount() == 0) return; LOG((LF_REMOTING, LL_INFO1000, "CompleteVtsOnDeserializedCallbacks. Beginning.\n")); GCPROTECT_BEGIN(nextObj); while ((nextObj = VDC.Dequeue(&Dummy1, &Dummy2, NULL)) != NULL) InvokeVtsCallbacks(nextObj, RemotingVtsInfo::VTS_CALLBACK_ON_DESERIALIZED, m_toDomain); GCPROTECT_END(); } void ObjectClone::CompleteVtsOnSerializedCallbacks() { CONTRACTL { GC_TRIGGERS; MODE_COOPERATIVE; THROWS; } CONTRACTL_END; OBJECTREF nextObj = NULL, Dummy1 = NULL, Dummy2 = NULL; if (VSC.GetCount() == 0) return; LOG((LF_REMOTING, LL_INFO1000, "CompleteVtsOnSerializedCallbacks. Beginning.\n")); GCPROTECT_BEGIN(nextObj); while ((nextObj = VSC.Dequeue(&Dummy1, &Dummy2, NULL)) != NULL) InvokeVtsCallbacks(nextObj, RemotingVtsInfo::VTS_CALLBACK_ON_SERIALIZED, m_fromDomain); GCPROTECT_END(); } // Does a binary search to find the object with given id, and record of given kind DWORD ObjectClone::FindObjectInTSO(int objId, SpecialObjects kind) { CONTRACTL { MODE_COOPERATIVE; GC_NOTRIGGER; NOTHROW; } CONTRACTL_END DWORD lowIndex = 0; DWORD highIndex = TSO.GetCount(); DWORD midIndex = highIndex / 2; DWORD firstMatch; if (highIndex == 0) { _ASSERTE(!"Special Object unexpectedly not found for given object id\n"); return 0; // throw ? } SpecialObjectInfo *splInfo = NULL; while (true) { OBJECTREF refParent, refFromObj; OBJECTREF dummy; dummy = TSO.GetAt(midIndex, &refFromObj, &refParent, (QueuedObjectInfo **)&splInfo); if (objId < splInfo->GetObjectId()) { highIndex = midIndex; } else { if (objId == splInfo->GetObjectId()) break; lowIndex = midIndex; } DWORD oldIndex = midIndex; midIndex = lowIndex + (highIndex - lowIndex)/2; if (oldIndex == midIndex) { // Binary search failed. See comments below goto LinearSearch; } } // Found match at midIndex // Find the first record for this obj id firstMatch = midIndex; while(midIndex != 0) { midIndex -= 1; SpecialObjectInfo *pTemp; OBJECTREF refParent, refFromObj; OBJECTREF dummy; dummy = TSO.GetAt(midIndex, &refFromObj, &refParent, (QueuedObjectInfo **)&pTemp); if (pTemp->GetObjectId() != objId) break; else firstMatch = midIndex; }; // Now look for the right kind of record do { OBJECTREF refParent, refFromObj; OBJECTREF dummy; dummy = TSO.GetAt(firstMatch, &refFromObj, &refParent, (QueuedObjectInfo **)&splInfo); if (splInfo->GetObjectId() == objId) { switch(kind) { case ISerializable: if (splInfo->IsISerializableInstance()) return firstMatch; break; case IObjectReference: if (splInfo->IsIObjRefInstance()) return firstMatch; break; case BoxedValueType: if (splInfo->IsBoxedObject()) return firstMatch; break; default: _ASSERTE(!"Unknown enum value in FindObjectInTSO"); }; } firstMatch++; }while(firstMatch < TSO.GetCount()); LinearSearch: // If there are multiple objects that are ISer/IObj, and some of them repeat in a certain fashion, // then the entries in TSO are not in sorted order. In such a case binary search will fail. Lets do a linear search // in such a case for now. This is probably reasonable since the TSO should usually be short and in-order (and presumably // cheaper than trying to keep the list in sorted order at all times). DWORD currIndex = 0; for (; currIndex < TSO.GetCount(); currIndex++) { OBJECTREF refParent, refFromObj; OBJECTREF dummy; dummy = TSO.GetAt(currIndex, &refFromObj, &refParent, (QueuedObjectInfo **)&splInfo); SpecialObjects foundKind = ISerializable; if (splInfo->IsIObjRefInstance()) foundKind = IObjectReference; else if (splInfo->IsBoxedObject()) foundKind = BoxedValueType; else _ASSERTE(splInfo->IsISerializableInstance()); if (objId == splInfo->GetObjectId() && kind == foundKind) return currIndex; } _ASSERTE(!"Special Object unexpectedly not found for given object id\n"); return 0; // throw ? } // This function is effectively a replica of MethodTable::Box. Its replicated to avoid "GCPROTECT_INTERIOR" that Box uses // and causes some leak detection asserts to go off. This is a controlled leak situation, where we know we're leaking stuff // and dont want the asserts. OBJECTREF ObjectClone::BoxValueTypeInWrongDomain(OBJECTREF refParent, DWORD offset, MethodTable *pValueTypeMT) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(pValueTypeMT->IsValueType()); PRECONDITION(!pValueTypeMT->IsByRefLike()); } CONTRACTL_END; OBJECTREF ref = NULL; void* pSrc = refParent->GetData() + offset; GCPROTECT_BEGININTERIOR(pSrc); // We must enter the target domain if we are boxing a non-agile type. This of course has some overhead // so we want to avoid it if possible. GetLoaderModule() == mscorlib && CanBeBlittedByObjectCloner is a // conservative first approximation of agile types. ENTER_DOMAIN_PTR_PREDICATED(m_fromDomain, ADV_RUNNINGIN, !pValueTypeMT->GetLoaderModule()->IsSystem() || pValueTypeMT->GetClass()->CannotBeBlittedByObjectCloner()); ref = pValueTypeMT->FastBox(&pSrc); END_DOMAIN_TRANSITION; GCPROTECT_END(); return ref; } // Returns whether or not a given type requires VTS callbacks of the specified kind. BOOL ObjectClone::HasVtsCallbacks(MethodTable *pMT, RemotingVtsInfo::VtsCallbackType eCallbackType) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; while (pMT) { if (pMT->HasRemotingVtsInfo()) { PTR_RemotingVtsInfo pVtsInfo = pMT->GetRemotingVtsInfo(); _ASSERTE(pVtsInfo != NULL); if (!pVtsInfo->m_pCallbacks[eCallbackType].IsNull()) return TRUE; } pMT = pMT->GetParentMethodTable(); } return FALSE; } // Calls all of the VTS event methods for a given callback type on the object instance provided (starting at the base class). void ObjectClone::InvokeVtsCallbacks(OBJECTREF refTarget, RemotingVtsInfo::VtsCallbackType eCallbackType, AppDomain* pDomain) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; } CONTRACTL_END; GCPROTECT_BEGIN(refTarget); // Quickly walk the target's type hierarchy and determine the number of methods we'll need to call. DWORD cMethods = 0; MethodDesc *pLastCallback; MethodTable *pMT = refTarget->GetMethodTable(); while (pMT) { if (pMT->HasRemotingVtsInfo()) { PTR_RemotingVtsInfo pVtsInfo = pMT->GetRemotingVtsInfo(); _ASSERTE(pVtsInfo != NULL); if (!pVtsInfo->m_pCallbacks[eCallbackType].IsNull()) { cMethods++; #ifdef FEATURE_PREJIT // Might have to restore cross module method pointers. Module::RestoreMethodDescPointer(&pVtsInfo->m_pCallbacks[eCallbackType]); #endif pLastCallback = pVtsInfo->m_pCallbacks[eCallbackType].GetValue(); } } pMT = pMT->GetParentMethodTable(); } // Maybe there's no work to do. if (cMethods == 0) goto Done; // Allocate an array to hold the methods to invoke (we do this because the invocation order is the opposite way round from the // way we can easily scan for the methods). We can easily optimize this for the single callback case though. MethodDesc **pCallbacks = cMethods == 1 ? &pLastCallback : (MethodDesc**)_alloca(cMethods * sizeof(MethodDesc*)); if (cMethods > 1) { // Walk the type hierarchy again, and this time fill in the methods to call in the correct slot of our callback table. DWORD dwSlotIndex = cMethods; pMT = refTarget->GetMethodTable(); while (pMT) { if (pMT->HasRemotingVtsInfo()) { PTR_RemotingVtsInfo pVtsInfo = pMT->GetRemotingVtsInfo(); _ASSERTE(pVtsInfo != NULL); if (!pVtsInfo->m_pCallbacks[eCallbackType].IsNull()) pCallbacks[--dwSlotIndex] = pVtsInfo->m_pCallbacks[eCallbackType].GetValue(); } pMT = pMT->GetParentMethodTable(); } _ASSERTE(dwSlotIndex == 0); } bool fSwitchDomains = pDomain != GetAppDomain(); ENTER_DOMAIN_PTR(pDomain,ADV_RUNNINGIN); // If we're calling back into the from domain then reset the execution context to its original state (this will automatically be // popped once we return from this domain again). if (pDomain == m_fromDomain && fSwitchDomains) { Thread *pThread = GetThread(); if (pThread->IsExposedObjectSet()) { THREADBASEREF refThread = (THREADBASEREF)pThread->GetExposedObjectRaw(); refThread->SetExecutionContext(m_fromExecutionContext); } } // Remember to adjust this pointer for boxed value types. BOOL bIsBoxed = refTarget->GetMethodTable()->IsValueType(); RuntimeMethodHandle::StreamingContextData sContext = { NULL, GetStreamingContextState() }; // Ensure Streamingcontext type is loaded. Do not delete this line MethodTable *pMTStreamingContext; pMTStreamingContext = MscorlibBinder::GetClass(CLASS__STREAMING_CONTEXT); _ASSERTE(pMTStreamingContext); // Now go and call each method in order. for (DWORD i = 0; i < cMethods; i++) { MethodDescCallSite callback(pCallbacks[i], &refTarget); ARG_SLOT argSlots[2]; // Nullable does not have any VTS functions _ASSERTE(!Nullable::IsNullableType(refTarget->GetMethodTable())); argSlots[0] = (bIsBoxed ? (ARG_SLOT)(SIZE_T)(refTarget->UnBox()) : ObjToArgSlot(refTarget)); #if defined(_TARGET_X86_) || defined(_TARGET_ARM_) static_assert_no_msg(sizeof(sContext) == sizeof(ARG_SLOT)); argSlots[1] = *(ARG_SLOT*)(&sContext); // StreamingContext is passed by value on x86 and ARM #elif defined(_WIN64) static_assert_no_msg(sizeof(sContext) > sizeof(ARG_SLOT)); argSlots[1] = PtrToArgSlot(&sContext); // StreamingContext is passed by reference on WIN64 #else // !_TARGET_X86_ && !_WIN64 && !_TARGET_ARM_ PORTABILITY_ASSERT("ObjectClone::InvokeVtsCallbacks() - NYI on this platform"); #endif // !_TARGET_X86_ && !_WIN64 && !_TARGET_ARM_ callback.CallWithValueTypes(&argSlots[0]); } END_DOMAIN_TRANSITION; Done: ; GCPROTECT_END(); } #endif // FEATURE_REMOTING