diff options
Diffstat (limited to 'src/vm/object.cpp')
-rw-r--r-- | src/vm/object.cpp | 3663 |
1 files changed, 3663 insertions, 0 deletions
diff --git a/src/vm/object.cpp b/src/vm/object.cpp new file mode 100644 index 0000000000..7c47e26627 --- /dev/null +++ b/src/vm/object.cpp @@ -0,0 +1,3663 @@ +// 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. +// +// OBJECT.CPP +// +// Definitions of a Com+ Object +// + + + +#include "common.h" + +#include "vars.hpp" +#include "class.h" +#include "object.h" +#include "threads.h" +#include "excep.h" +#include "eeconfig.h" +#include "gc.h" +#ifdef FEATURE_REMOTING +#include "remoting.h" +#endif +#include "field.h" +#include "gcscan.h" +#include "argdestination.h" + +#ifdef FEATURE_COMPRESSEDSTACK +void* CompressedStackObject::GetUnmanagedCompressedStack() +{ + LIMITED_METHOD_CONTRACT; + return ((m_compressedStackHandle != NULL)?m_compressedStackHandle->GetHandle():NULL); +} +#endif // FEATURE_COMPRESSEDSTACK + +#ifndef FEATURE_PAL +LPVOID FrameSecurityDescriptorBaseObject::GetCallerToken() +{ + LIMITED_METHOD_CONTRACT; + return ((m_callerToken!= NULL)?m_callerToken->GetHandle():NULL); + +} + +LPVOID FrameSecurityDescriptorBaseObject::GetImpersonationToken() +{ + LIMITED_METHOD_CONTRACT; + return ((m_impToken != NULL)?m_impToken->GetHandle():NULL); +} +#endif + +SVAL_IMPL(INT32, ArrayBase, s_arrayBoundsZero); + +// follow the necessary rules to get a new valid hashcode for an object +DWORD Object::ComputeHashCode() +{ + DWORD hashCode; + + // note that this algorithm now uses at most HASHCODE_BITS so that it will + // fit into the objheader if the hashcode has to be moved back into the objheader + // such as for an object that is being frozen + do + { + // we use the high order bits in this case because they're more random + hashCode = GetThread()->GetNewHashCode() >> (32-HASHCODE_BITS); + } + while (hashCode == 0); // need to enforce hashCode != 0 + + // verify that it really fits into HASHCODE_BITS + _ASSERTE((hashCode & ((1<<HASHCODE_BITS)-1)) == hashCode); + + return hashCode; +} + +#ifndef DACCESS_COMPILE +INT32 Object::GetHashCodeEx() +{ + CONTRACTL + { + MODE_COOPERATIVE; + THROWS; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END + + // This loop exists because we're inspecting the header dword of the object + // and it may change under us because of races with other threads. + // On top of that, it may have the spin lock bit set, in which case we're + // not supposed to change it. + // In all of these case, we need to retry the operation. + DWORD iter = 0; + DWORD dwSwitchCount = 0; + while (true) + { + DWORD bits = GetHeader()->GetBits(); + + if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) + { + if (bits & BIT_SBLK_IS_HASHCODE) + { + // Common case: the object already has a hash code + return bits & MASK_HASHCODE; + } + else + { + // We have a sync block index. This means if we already have a hash code, + // it is in the sync block, otherwise we generate a new one and store it there + SyncBlock *psb = GetSyncBlock(); + DWORD hashCode = psb->GetHashCode(); + if (hashCode != 0) + return hashCode; + + hashCode = ComputeHashCode(); + + return psb->SetHashCode(hashCode); + } + } + else + { + // If a thread is holding the thin lock or an appdomain index is set, we need a syncblock + if ((bits & (SBLK_MASK_LOCK_THREADID | (SBLK_MASK_APPDOMAININDEX << SBLK_APPDOMAIN_SHIFT))) != 0) + { + GetSyncBlock(); + // No need to replicate the above code dealing with sync blocks + // here - in the next iteration of the loop, we'll realize + // we have a syncblock, and we'll do the right thing. + } + else + { + // We want to change the header in this case, so we have to check the BIT_SBLK_SPIN_LOCK bit first + if (bits & BIT_SBLK_SPIN_LOCK) + { + iter++; + if ((iter % 1024) != 0 && g_SystemInfo.dwNumberOfProcessors > 1) + { + YieldProcessor(); // indicate to the processor that we are spining + } + else + { + __SwitchToThread(0, ++dwSwitchCount); + } + continue; + } + + DWORD hashCode = ComputeHashCode(); + + DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode; + + if (GetHeader()->SetBits(newBits, bits) == bits) + return hashCode; + // Header changed under us - let's restart this whole thing. + } + } + } +} +#endif // #ifndef DACCESS_COMPILE + +BOOL Object::ValidateObjectWithPossibleAV() +{ + CANNOT_HAVE_CONTRACT; + SUPPORTS_DAC; + + return GetGCSafeMethodTable()->ValidateWithPossibleAV(); +} + + +#ifndef DACCESS_COMPILE + +MethodTable *Object::GetTrueMethodTable() +{ + CONTRACT(MethodTable*) + { + MODE_COOPERATIVE; + GC_NOTRIGGER; + NOTHROW; + SO_TOLERANT; + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + MethodTable *mt = GetMethodTable(); + +#ifdef FEATURE_REMOTING + if(mt->IsTransparentProxy()) + { + mt = ((TransparentProxyObject *)this)->GetMethodTableBeingProxied(); + } + _ASSERTE(!mt->IsTransparentProxy()); +#endif + + RETURN mt; +} + +TypeHandle Object::GetTrueTypeHandle() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (m_pMethTab->IsArray()) + return ((ArrayBase*) this)->GetTypeHandle(); + else + return TypeHandle(GetTrueMethodTable()); +} + +// There are cases where it is not possible to get a type handle during a GC. +// If we can get the type handle, this method will return it. +// Otherwise, the method will return NULL. +TypeHandle Object::GetGCSafeTypeHandleIfPossible() const +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + if(!IsGCThread()) { MODE_COOPERATIVE; } + } + CONTRACTL_END; + + // Although getting the type handle is unsafe and could cause recursive type lookups + // in some cases, it's always safe and straightforward to get to the MethodTable. + MethodTable * pMT = GetGCSafeMethodTable(); + _ASSERTE(pMT != NULL); + + // Don't look at types that belong to an unloading AppDomain, or else + // pObj->GetGCSafeTypeHandle() can AV. For example, we encountered this AV when pObj + // was an array like this: + // + // MyValueType1<MyValueType2>[] myArray + // + // where MyValueType1<T> & MyValueType2 are defined in different assemblies. In such + // a case, looking up the type handle for myArray requires looking in + // MyValueType1<T>'s module's m_AssemblyRefByNameTable, which is garbage if its + // AppDomain is unloading. + // + // Another AV was encountered in a similar case, + // + // MyRefType1<MyRefType2>[] myArray + // + // where MyRefType2's module was unloaded by the time the GC occurred. In at least + // one case, the GC was caused by the AD unload itself (AppDomain::Unload -> + // AppDomain::Exit -> GCInterface::AddMemoryPressure -> WKS::GCHeap::GarbageCollect). + // + // To protect against all scenarios, verify that + // + // * The MT of the object is not getting unloaded, OR + // * In the case of arrays (potentially of arrays of arrays of arrays ...), the + // MT of the innermost element is not getting unloaded. This then ensures the + // MT of the original object (i.e., array) itself must not be getting + // unloaded either, since the MTs of arrays and of their elements are + // allocated on the same loader heap, except the case where the array is + // Object[], in which case its MT is in mscorlib and thus doesn't unload. + + MethodTable * pMTToCheck = pMT; + if (pMTToCheck->IsArray()) + { + TypeHandle thElem = static_cast<const ArrayBase * const>(this)->GetArrayElementTypeHandle(); + + // Ideally, we would just call thElem.GetLoaderModule() here. Unfortunately, the + // current TypeDesc::GetLoaderModule() implementation depends on data structures + // that might have been unloaded already. So we just simulate + // TypeDesc::GetLoaderModule() for the limited array case that we care about. In + // case we're dealing with an array of arrays of arrays etc. traverse until we + // find the deepest element, and that's the type we'll check + while (thElem.HasTypeParam()) + { + thElem = thElem.GetTypeParam(); + } + + pMTToCheck = thElem.GetMethodTable(); + } + + Module * pLoaderModule = pMTToCheck->GetLoaderModule(); + + BaseDomain * pBaseDomain = pLoaderModule->GetDomain(); + if ((pBaseDomain != NULL) && + (pBaseDomain->IsAppDomain()) && + (pBaseDomain->AsAppDomain()->IsUnloading())) + { + return NULL; + } + + // Don't look up types that are unloading due to Collectible Assemblies. Haven't been + // able to find a case where we actually encounter objects like this that can cause + // problems; however, it seems prudent to add this protection just in case. + LoaderAllocator * pLoaderAllocator = pLoaderModule->GetLoaderAllocator(); + _ASSERTE(pLoaderAllocator != NULL); + if ((pLoaderAllocator->IsCollectible()) && + (ObjectHandleIsNull(pLoaderAllocator->GetLoaderAllocatorObjectHandle()))) + { + return NULL; + } + + // Ok, it should now be safe to get the type handle + return GetGCSafeTypeHandle(); +} + +/* static */ BOOL Object::SupportsInterface(OBJECTREF pObj, MethodTable* pInterfaceMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM()); + PRECONDITION(CheckPointer(pInterfaceMT)); + PRECONDITION(pObj->GetTrueMethodTable()->IsRestored_NoLogging()); + PRECONDITION(pInterfaceMT->IsInterface()); + } + CONTRACTL_END + + BOOL bSupportsItf = FALSE; + + GCPROTECT_BEGIN(pObj) + { + // Make sure the interface method table has been restored. + pInterfaceMT->CheckRestore(); + + // Check to see if the static class definition indicates we implement the interface. + MethodTable * pMT = pObj->GetTrueMethodTable(); + if (pMT->CanCastToInterface(pInterfaceMT)) + { + bSupportsItf = TRUE; + } +#ifdef FEATURE_COMINTEROP + else + if (pMT->IsComObjectType()) + { + // If this is a COM object, the static class definition might not be complete so we need + // to check if the COM object implements the interface. + bSupportsItf = ComObject::SupportsInterface(pObj, pInterfaceMT); + } +#endif // FEATURE_COMINTEROP + } + GCPROTECT_END(); + + return bSupportsItf; +} + +Assembly *AssemblyBaseObject::GetAssembly() +{ + WRAPPER_NO_CONTRACT; + return m_pAssembly->GetAssembly(); +} + +#ifdef _DEBUG +// Object::DEBUG_SetAppDomain specified DEBUG_ONLY in the contract to disable SO-tolerance +// checking for paths that are DEBUG-only. +// +// NOTE: currently this is only used by WIN64 allocation helpers, but they really should +// be calling the JIT helper SetObjectAppDomain (which currently only exists for +// x86). +void Object::DEBUG_SetAppDomain(AppDomain *pDomain) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + DEBUG_ONLY; + INJECT_FAULT(COMPlusThrowOM();); + PRECONDITION(CheckPointer(pDomain)); + } + CONTRACTL_END; + + /*_ASSERTE(GetThread()->IsSOTolerant());*/ + SetAppDomain(pDomain); +} +#endif + +void Object::SetAppDomain(AppDomain *pDomain) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + SO_INTOLERANT; + INJECT_FAULT(COMPlusThrowOM();); + PRECONDITION(CheckPointer(pDomain)); + } + CONTRACTL_END; + +#ifndef _DEBUG + if (!GetMethodTable()->IsDomainNeutral()) + { + // + // If we have a per-app-domain method table, we can + // infer the app domain from the method table, so + // there is no reason to mark the object. + // + // But we don't do this in a debug build, because + // we want to be able to detect the case when the + // domain was unloaded from underneath an object (and + // the MethodTable will be toast in that case.) + // + + _ASSERTE(pDomain == GetMethodTable()->GetDomain()); + } + else +#endif + { + ADIndex index = pDomain->GetIndex(); + GetHeader()->SetAppDomainIndex(index); + } + + _ASSERTE(GetHeader()->GetAppDomainIndex().m_dwIndex != 0); +} + +BOOL Object::SetAppDomainNoThrow() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_INTOLERANT; + } + CONTRACTL_END; + + BOOL success = FALSE; + + EX_TRY + { + SetAppDomain(); + success = TRUE; + } + EX_CATCH + { + _ASSERTE (!"Exception happened during Object::SetAppDomain"); + } + EX_END_CATCH(RethrowTerminalExceptions) + + return success; +} + +AppDomain *Object::GetAppDomain() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_COOPERATIVE; + } + CONTRACTL_END; +#ifndef _DEBUG + if (!GetMethodTable()->IsDomainNeutral()) + return (AppDomain*) GetMethodTable()->GetDomain(); +#endif + + ADIndex index = GetHeader()->GetAppDomainIndex(); + + if (index.m_dwIndex == 0) + return NULL; + + AppDomain *pDomain = SystemDomain::TestGetAppDomainAtIndex(index); + +#if CHECK_APP_DOMAIN_LEAKS + if (! g_pConfig->AppDomainLeaks()) + return pDomain; + + if (IsAppDomainAgile()) + return NULL; + + // + // If an object has an index of an unloaded domain (its ok to be of a + // domain where an unload is in progress through), go ahead + // and make it agile. If this fails, we have an invalid reference + // to an unloaded domain. If it succeeds, the object is no longer + // contained in that app domain so we can continue. + // + + if (pDomain == NULL) + { + if (SystemDomain::IndexOfAppDomainBeingUnloaded() == index) { + // if appdomain is unloading but still alive and is valid to have instances + // in that domain, then use it. + AppDomain *tmpDomain = SystemDomain::AppDomainBeingUnloaded(); + if (tmpDomain && tmpDomain->ShouldHaveInstances()) + pDomain = tmpDomain; + } + if (!pDomain && ! TrySetAppDomainAgile(FALSE)) + { + _ASSERTE(!"Attempt to reference an object belonging to an unloaded domain"); + } + } +#endif + + return pDomain; +} + +STRINGREF AllocateString(SString sstr) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + } CONTRACTL_END; + + COUNT_T length = sstr.GetCount(); // count of WCHARs excluding terminating NULL + STRINGREF strObj = AllocateString(length); + memcpyNoGCRefs(strObj->GetBuffer(), sstr.GetUnicode(), length*sizeof(WCHAR)); + + return strObj; +} + +CHARARRAYREF AllocateCharArray(DWORD dwArrayLength) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + return (CHARARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_CHAR, dwArrayLength); +} + +#if CHECK_APP_DOMAIN_LEAKS + +BOOL Object::IsAppDomainAgile() +{ + WRAPPER_NO_CONTRACT; + DEBUG_ONLY_FUNCTION; + + SyncBlock *psb = PassiveGetSyncBlock(); + + if (psb) + { + if (psb->IsAppDomainAgile()) + return TRUE; + if (psb->IsCheckedForAppDomainAgile()) + return FALSE; + } + return CheckAppDomain(NULL); +} + +BOOL Object::TrySetAppDomainAgile(BOOL raiseAssert) +{ + LIMITED_METHOD_CONTRACT; + FAULT_NOT_FATAL(); + DEBUG_ONLY_FUNCTION; + + BOOL ret = TRUE; + + EX_TRY + { + ret = SetAppDomainAgile(raiseAssert); + } + EX_CATCH{} + EX_END_CATCH(SwallowAllExceptions); + + return ret; +} + + +BOOL Object::ShouldCheckAppDomainAgile (BOOL raiseAssert, BOOL *pfResult) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; + + if (!g_pConfig->AppDomainLeaks()) + { + *pfResult = TRUE; + return FALSE; + } + + if (this == NULL) + { + *pfResult = TRUE; + return FALSE; + } + + if (IsAppDomainAgile()) + { + *pfResult = TRUE; + return FALSE; + } + + // if it's not agile and we've already checked it, just bail early + if (IsCheckedForAppDomainAgile()) + { + *pfResult = FALSE; + return FALSE; + } + + if (IsTypeNeverAppDomainAgile()) + { + if (raiseAssert) + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + *pfResult = FALSE; + return FALSE; + } + + // + // Do not allow any object to be set to be agile unless we + // are compiling field access checking into the class. This + // will help guard against unintentional "agile" propagation + // as well. + // + + if (!IsTypeAppDomainAgile() && !IsTypeCheckAppDomainAgile()) + { + if (raiseAssert) + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + *pfResult = FALSE; + return FALSE; + } + + return TRUE; +} + + +BOOL Object::SetAppDomainAgile(BOOL raiseAssert, SetAppDomainAgilePendingTable *pTable) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + INJECT_FAULT(COMPlusThrowOM();); + DEBUG_ONLY; + } + CONTRACTL_END; + BEGIN_DEBUG_ONLY_CODE; + BOOL fResult; + if (!this->ShouldCheckAppDomainAgile(raiseAssert, &fResult)) + return fResult; + + // + // If a SetAppDomainAgilePendingTable is provided, then SetAppDomainAgile + // was called via SetAppDomainAgile. Simply store this object in the + // table, and let the calling SetAppDomainAgile process it later in a + // non-recursive manner. + // + + if (pTable == NULL) + { + pTable = (SetAppDomainAgilePendingTable *)ClrFlsGetValue(TlsIdx_AppDomainAgilePendingTable); + } + if (pTable) + { + // + // If the object is already being checked (on this thread or another), + // don't duplicate the effort. Return TRUE to tell the caller to + // continue processing other references. Since we're just testing + // the bit we don't need to take the spin lock. + // + + ObjHeader* pOh = this->GetHeader(); + _ASSERTE(pOh); + + if (pOh->GetBits() & BIT_SBLK_AGILE_IN_PROGRESS) + { + return TRUE; + } + + pTable->PushReference(this); + } + else + { + // + // Initialize the table of pending objects + // + + SetAppDomainAgilePendingTable table; + class ResetPendingTable + { + public: + ResetPendingTable(SetAppDomainAgilePendingTable *pTable) + { + ClrFlsSetValue(TlsIdx_AppDomainAgilePendingTable, pTable); + } + ~ResetPendingTable() + { + ClrFlsSetValue(TlsIdx_AppDomainAgilePendingTable, NULL); + } + }; + + ResetPendingTable resetPendingTable(&table); + + // + // Iterate over the table, processing all referenced objects until the + // entire graph has its sync block marked, or a non-agile object is + // found. The loop will start with the current object, as though we + // just removed it from the table as a pending reference. + // + + Object *pObject = this; + + do + { + // + // Mark the object to identify recursion. + // ~SetAppDomainAgilePendingTable will clean up + // BIT_SBLK_AGILE_IN_PROGRESS, so attempt to push the object first + // in case it needs to throw an exception. + // + + table.PushParent(pObject); + + ObjHeader* pOh = pObject->GetHeader(); + _ASSERTE(pOh); + + bool fInProgress = false; + + { + ENTER_SPIN_LOCK(pOh); + { + if (pOh->GetBits() & BIT_SBLK_AGILE_IN_PROGRESS) + { + fInProgress = true; + } + else + { + pOh->SetBit(BIT_SBLK_AGILE_IN_PROGRESS); + } + } + LEAVE_SPIN_LOCK(pOh); + } + + if (fInProgress) + { + // + // Object is already being processed, so just remove it from + // the table and look for another object. + // + + bool fReturnedToParent = false; + Object *pLastObject = table.GetPendingObject(&fReturnedToParent); + CONSISTENCY_CHECK(pLastObject == pObject && fReturnedToParent); + } + else + { + + // + // Finish processing this object. Any references will be added to + // the table. + // + + if (!pObject->SetAppDomainAgileWorker(raiseAssert, &table)) + return FALSE; + } + + // + // Find the next object to explore. + // + + for (;;) + { + bool fReturnedToParent; + pObject = table.GetPendingObject(&fReturnedToParent); + + // + // No more objects in the table? + // + + if (!pObject) + break; + + // + // If we've processed all objects reachable through an object, + // then clear BIT_SBLK_AGILE_IN_PROGRESS, and look for another + // object in the table. + // + + if (fReturnedToParent) + { + pOh = pObject->GetHeader(); + _ASSERTE(pOh); + + ENTER_SPIN_LOCK(pOh); + pOh->ClrBit(BIT_SBLK_AGILE_IN_PROGRESS); + LEAVE_SPIN_LOCK(pOh); + } + else + { + // + // Re-check whether we should explore through this reference. + // + + if (pObject->ShouldCheckAppDomainAgile(raiseAssert, &fResult)) + break; + + if (!fResult) + return FALSE; + } + } + } + while (pObject); + } + END_DEBUG_ONLY_CODE; + return TRUE; +} + + +BOOL Object::SetAppDomainAgileWorker(BOOL raiseAssert, SetAppDomainAgilePendingTable *pTable) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; + + BOOL ret = TRUE; + + if (! IsTypeAppDomainAgile() && ! SetFieldsAgile(raiseAssert, pTable)) + { + SetIsCheckedForAppDomainAgile(); + + ret = FALSE; + } + + if (ret) + { + SetSyncBlockAppDomainAgile(); + } + + return ret; +} + + +SetAppDomainAgilePendingTable::SetAppDomainAgilePendingTable () + : m_Stack(sizeof(PendingEntry)) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; +} + + +SetAppDomainAgilePendingTable::~SetAppDomainAgilePendingTable () +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; + + while (TRUE) + { + Object *pObj; + bool fObjMarked; + pObj = GetPendingObject(&fObjMarked); + if (pObj == NULL) + { + break; + } + + if (fObjMarked) + { + ObjHeader* pOh = pObj->GetHeader(); + _ASSERTE(pOh); + + ENTER_SPIN_LOCK(pOh); + pOh->ClrBit(BIT_SBLK_AGILE_IN_PROGRESS); + LEAVE_SPIN_LOCK(pOh); + } +} +} + + +void Object::SetSyncBlockAppDomainAgile() +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; + + SyncBlock *psb = PassiveGetSyncBlock(); + if (! psb) + { + psb = GetSyncBlock(); + } + psb->SetIsAppDomainAgile(); +} + +#if CHECK_APP_DOMAIN_LEAKS +BOOL Object::CheckAppDomain(AppDomain *pAppDomain) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + DEBUG_ONLY_FUNCTION; + + if (!g_pConfig->AppDomainLeaks()) + return TRUE; + + if (this == NULL) + return TRUE; + + if (IsAppDomainAgileRaw()) + return TRUE; + +#ifndef _DEBUG + MethodTable *pMT = GetGCSafeMethodTable(); + + if (!pMT->IsDomainNeutral()) + return pAppDomain == pMT->GetDomain(); +#endif + + ADIndex index = GetHeader()->GetAppDomainIndex(); + + _ASSERTE(index.m_dwIndex != 0); + + return (pAppDomain != NULL && index == pAppDomain->GetIndex()); +} +#endif + +BOOL Object::IsTypeAppDomainAgile() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + DEBUG_ONLY_FUNCTION; + + MethodTable *pMT = GetGCSafeMethodTable(); + + if (pMT->IsArray()) + { + TypeHandle th = pMT->GetApproxArrayElementTypeHandle(); + return th.IsArrayOfElementsAppDomainAgile(); + } + else + return pMT->GetClass()->IsAppDomainAgile(); +} + +BOOL Object::IsTypeCheckAppDomainAgile() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + DEBUG_ONLY_FUNCTION; + + MethodTable *pMT = GetGCSafeMethodTable(); + + if (pMT->IsArray()) + { + TypeHandle th = pMT->GetApproxArrayElementTypeHandle(); + return th.IsArrayOfElementsCheckAppDomainAgile(); + } + else + return pMT->GetClass()->IsCheckAppDomainAgile(); +} + +BOOL Object::IsTypeNeverAppDomainAgile() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + DEBUG_ONLY_FUNCTION; + + return !IsTypeAppDomainAgile() && !IsTypeCheckAppDomainAgile(); +} + +BOOL Object::IsTypeTypesafeAppDomainAgile() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + DEBUG_ONLY_FUNCTION; + + return IsTypeAppDomainAgile() && !IsTypeCheckAppDomainAgile(); +} + +BOOL Object::TryAssignAppDomain(AppDomain *pAppDomain, BOOL raiseAssert) +{ + LIMITED_METHOD_CONTRACT; + FAULT_NOT_FATAL(); + DEBUG_ONLY_FUNCTION; + + BOOL ret = TRUE; + + EX_TRY + { + ret = AssignAppDomain(pAppDomain,raiseAssert); + } + EX_CATCH{} + EX_END_CATCH(SwallowAllExceptions); + + return ret; +} + +BOOL Object::AssignAppDomain(AppDomain *pAppDomain, BOOL raiseAssert) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; + + if (!g_pConfig->AppDomainLeaks()) + return TRUE; + + if (CheckAppDomain(pAppDomain)) + return TRUE; + + // + // App domain does not match; try to make this object agile + // + + if (IsTypeNeverAppDomainAgile()) + { + if (raiseAssert) + { + if (pAppDomain == NULL) + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + else + _ASSERTE(!"Attempt to reference a domain bound object from a different domain"); + } + return FALSE; + } + else + { + // + // Make object agile + // + + if (! IsTypeAppDomainAgile() && ! SetFieldsAgile(raiseAssert)) + { + SetIsCheckedForAppDomainAgile(); + return FALSE; + } + + SetSyncBlockAppDomainAgile(); + + return TRUE; + } +} + +BOOL Object::AssignValueTypeAppDomain(MethodTable *pMT, void *base, AppDomain *pAppDomain, BOOL raiseAssert) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + DEBUG_ONLY_FUNCTION; + + if (!g_pConfig->AppDomainLeaks()) + return TRUE; + + if (pMT->GetClass()->IsAppDomainAgile()) + return TRUE; + + if (pAppDomain == NULL) + { + // + // Do not allow any object to be set to be agile unless we + // are compiling field access checking into the class. This + // will help guard against unintentional "agile" propagation + // as well. + // + + if (pMT->GetClass()->IsNeverAppDomainAgile()) + { + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + return FALSE; + } + + return SetClassFieldsAgile(pMT, base, TRUE/*=baseIsVT*/, raiseAssert); + } + else + { + return ValidateClassFields(pMT, base, TRUE/*=baseIsVT*/, pAppDomain, raiseAssert); + } +} + +BOOL Object::SetFieldsAgile(BOOL raiseAssert, SetAppDomainAgilePendingTable *pTable) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + INJECT_FAULT(COMPlusThrowOM();); + DEBUG_ONLY; + } + CONTRACTL_END; + + BOOL result = TRUE; + + MethodTable *pMT= GetGCSafeMethodTable(); + + if (pMT->IsArray()) + { + switch (pMT->GetArrayElementType()) + { + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + { + PtrArray *pArray = (PtrArray *) this; + + DWORD n = pArray->GetNumComponents(); + OBJECTREF *p = (OBJECTREF *) + (((BYTE*)pArray) + ArrayBase::GetDataPtrOffset(GetGCSafeMethodTable())); + + for (DWORD i=0; i<n; i++) + { + if (!p[i]->SetAppDomainAgile(raiseAssert, pTable)) + result = FALSE; + } + + break; + } + case ELEMENT_TYPE_VALUETYPE: + { + ArrayBase *pArray = (ArrayBase *) this; + + MethodTable *pElemMT = pMT->GetApproxArrayElementTypeHandle().GetMethodTable(); + + BYTE *p = ((BYTE*)pArray) + ArrayBase::GetDataPtrOffset(GetGCSafeMethodTable()); + SIZE_T size = pArray->GetComponentSize(); + SIZE_T n = pArray->GetNumComponents(); + + for (SIZE_T i=0; i<n; i++) + if (!SetClassFieldsAgile(pElemMT, p + i*size, TRUE/*=baseIsVT*/, raiseAssert, pTable)) + result = FALSE; + + break; + } + + default: + _ASSERTE(!"Unexpected array type"); + } + } + else + { + if (pMT->GetClass()->IsNeverAppDomainAgile()) + { + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + return FALSE; + } + + while (pMT != NULL && !pMT->GetClass()->IsTypesafeAppDomainAgile()) + { + if (!SetClassFieldsAgile(pMT, this, FALSE/*=baseIsVT*/, raiseAssert, pTable)) + result = FALSE; + + pMT = pMT->GetParentMethodTable(); + + if (pMT->GetClass()->IsNeverAppDomainAgile()) + { + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + return FALSE; + } + } + } + + return result; +} + +BOOL Object::SetClassFieldsAgile(MethodTable *pMT, void *base, BOOL baseIsVT, BOOL raiseAssert, SetAppDomainAgilePendingTable *pTable) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + BOOL result = TRUE; + + if (pMT->GetClass()->IsNeverAppDomainAgile()) + { + _ASSERTE(!"Attempt to reference a domain bound object from an agile location"); + return FALSE; + } + + // This type approximation is OK since we are only checking some layout information + // and all compatible instantiations share the same GC characteristics + ApproxFieldDescIterator fdIterator(pMT, ApproxFieldDescIterator::INSTANCE_FIELDS); + FieldDesc* pField; + + while ((pField = fdIterator.Next()) != NULL) + { + if (pField->IsDangerousAppDomainAgileField()) + { + if (pField->GetFieldType() == ELEMENT_TYPE_CLASS) + { + OBJECTREF ref; + + if (baseIsVT) + ref = *(OBJECTREF*) pField->GetAddressNoThrowNoGC(base); + else + ref = *(OBJECTREF*) pField->GetAddressGuaranteedInHeap(base); + + if (ref != 0 && !ref->IsAppDomainAgile()) + { + if (!ref->SetAppDomainAgile(raiseAssert, pTable)) + result = FALSE; + } + } + else if (pField->GetFieldType() == ELEMENT_TYPE_VALUETYPE) + { + // Be careful here - we may not have loaded a value + // type field of a class under prejit, and we don't + // want to trigger class loading here. + + TypeHandle th = pField->LookupFieldTypeHandle(); + if (!th.IsNull()) + { + void *nestedBase; + + if (baseIsVT) + nestedBase = pField->GetAddressNoThrowNoGC(base); + else + nestedBase = pField->GetAddressGuaranteedInHeap(base); + + if (!SetClassFieldsAgile(th.GetMethodTable(), + nestedBase, + TRUE/*=baseIsVT*/, + raiseAssert, + pTable)) + { + result = FALSE; + } + } + } + else + { + _ASSERTE(!"Bad field type"); + } + } + } + + return result; +} + +BOOL Object::ValidateAppDomain(AppDomain *pAppDomain) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + + if (!g_pConfig->AppDomainLeaks()) + return TRUE; + + if (this == NULL) + return TRUE; + + if (CheckAppDomain()) + return ValidateAppDomainFields(pAppDomain); + + return AssignAppDomain(pAppDomain); +} + +BOOL Object::ValidateAppDomainFields(AppDomain *pAppDomain) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + BOOL result = TRUE; + + MethodTable *pMT = GetGCSafeMethodTable(); + + while (pMT != NULL && !pMT->GetClass()->IsTypesafeAppDomainAgile()) + { + if (!ValidateClassFields(pMT, this, FALSE/*=baseIsVT*/, pAppDomain)) + result = FALSE; + + pMT = pMT->GetParentMethodTable(); + } + + return result; +} + +BOOL Object::ValidateValueTypeAppDomain(MethodTable *pMT, void *base, AppDomain *pAppDomain, BOOL raiseAssert) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!g_pConfig->AppDomainLeaks()) + return TRUE; + + if (pAppDomain == NULL) + { + if (pMT->GetClass()->IsTypesafeAppDomainAgile()) + return TRUE; + else if (pMT->GetClass()->IsNeverAppDomainAgile()) + { + if (raiseAssert) + _ASSERTE(!"Value type cannot be app domain agile"); + return FALSE; + } + } + + return ValidateClassFields(pMT, base, TRUE/*=baseIsVT*/, pAppDomain, raiseAssert); +} + +BOOL Object::ValidateClassFields(MethodTable *pMT, void *base, BOOL baseIsVT, AppDomain *pAppDomain, BOOL raiseAssert) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + BOOL result = TRUE; + + // This type approximation is OK since we are only checking some layout information + // and all compatible instantiations share the same GC characteristics + ApproxFieldDescIterator fdIterator(pMT, ApproxFieldDescIterator::INSTANCE_FIELDS); + FieldDesc* pField; + + while ((pField = fdIterator.Next()) != NULL) + { + if (!pMT->GetClass()->IsCheckAppDomainAgile() + || pField->IsDangerousAppDomainAgileField()) + { + if (pField->GetFieldType() == ELEMENT_TYPE_CLASS) + { + OBJECTREF ref; + + if (baseIsVT) + ref = ObjectToOBJECTREF(*(Object**) pField->GetAddressNoThrowNoGC(base)); + else + ref = ObjectToOBJECTREF(*(Object**) pField->GetAddressGuaranteedInHeap(base)); + + if (ref != 0 && !ref->AssignAppDomain(pAppDomain, raiseAssert)) + result = FALSE; + } + else if (pField->GetFieldType() == ELEMENT_TYPE_VALUETYPE) + { + // Be careful here - we may not have loaded a value + // type field of a class under prejit, and we don't + // want to trigger class loading here. + + TypeHandle th = pField->LookupFieldTypeHandle(); + if (!th.IsNull()) + { + void *nestedBase; + + if (baseIsVT) + nestedBase = pField->GetAddressNoThrowNoGC(base); + else + nestedBase = pField->GetAddressGuaranteedInHeap(base); + + if (!ValidateValueTypeAppDomain(th.GetMethodTable(), + nestedBase, + pAppDomain, + raiseAssert + )) + result = FALSE; + + } + } + } + } + + return result; +} + +#endif // CHECK_APP_DOMAIN_LEAKS + +void Object::ValidatePromote(ScanContext *sc, DWORD flags) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + +#if defined (VERIFY_HEAP) + Validate(); +#endif + +#if CHECK_APP_DOMAIN_LEAKS + // Do app domain integrity checking here + if (g_pConfig->AppDomainLeaks()) + { + AppDomain *pDomain = GetAppDomain(); + +// This assert will incorrectly trip when +// InternalCrossContextCallback is on the stack. InternalCrossContextCallback +// intentionally passes an object across domains on the same thread. +#if 0 + if (flags & GC_CALL_CHECK_APP_DOMAIN) + _ASSERTE(TryAssignAppDomain(sc->pCurrentDomain)); +#endif + + if ((flags & GC_CALL_CHECK_APP_DOMAIN) + && pDomain != NULL + && !pDomain->ShouldHaveRoots() + && !TrySetAppDomainAgile(FALSE)) + { + _ASSERTE(!"Found GC object which should have been purged during app domain unload."); + } + } +#endif +} + +void Object::ValidateHeap(Object *from, BOOL bDeep) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + +#if defined (VERIFY_HEAP) + //no need to verify next object's header in this case + //since this is called in verify_heap, which will verfiy every object anyway + Validate(bDeep, FALSE); +#endif + +#if CHECK_APP_DOMAIN_LEAKS + // Do app domain integrity checking here + if (g_pConfig->AppDomainLeaks() && bDeep) + { + AppDomain *pDomain = from->GetAppDomain(); + + // + // Don't perform check if we're checking for agility, and the containing type is not + // marked checked agile - this will cover "proxy" type agility + // where cross references are allowed + // + + // Changed the GetMethodTable calls in this function to GetGCSafeMethodTable + // because GC could use the mark bit to simulate a mark and can have it set during + // verify heap (and would be cleared when verify heap is done). + // We'd get AV pretty soon anyway if it was truly mistakenly set. + if (pDomain != NULL || from->GetGCSafeMethodTable()->GetClass()->IsCheckAppDomainAgile()) + { + //special case:thread object is allowed to hold a context belonging to current domain + if (from->GetGCSafeMethodTable() == g_pThreadClass && + ( +#ifdef FEATURE_REMOTING + this == OBJECTREFToObject(((ThreadBaseObject *)from)->m_ExposedContext) || +#endif +#ifndef FEATURE_CORECLR + this == OBJECTREFToObject(((ThreadBaseObject *)from)->m_ExecutionContext) || +#endif + false)) + { + if (((ThreadBaseObject *)from)->m_InternalThread) + _ASSERTE (CheckAppDomain (((ThreadBaseObject *)from)->m_InternalThread->GetDomain ())); + } + // special case: Overlapped has a field OverlappedData which may be moved to default domain + // during AD unload + else if (GetGCSafeMethodTable() == g_pOverlappedDataClass && + GetAppDomainIndex() == SystemDomain::System()->DefaultDomain()->GetIndex()) + { + } + else + { + TryAssignAppDomain(pDomain); + } + } + + if (pDomain != NULL + && !pDomain->ShouldHaveInstances() + && !TrySetAppDomainAgile(FALSE)) + _ASSERTE(!"Found GC object which should have been purged during app domain unload."); + } +#endif +} + +void Object::SetOffsetObjectRef(DWORD dwOffset, size_t dwValue) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_SO_TOLERANT; + + OBJECTREF* location; + OBJECTREF o; + + location = (OBJECTREF *) &GetData()[dwOffset]; + o = ObjectToOBJECTREF(*(Object **) &dwValue); + + SetObjectReference( location, o, GetAppDomain() ); +} + +/******************************************************************/ +/* + * Write Barrier Helper + * + * Use this function to assign an object reference into + * another object. + * + * It will set the appropriate GC Write Barrier data + */ + +#if CHECK_APP_DOMAIN_LEAKS +void SetObjectReferenceChecked(OBJECTREF *dst,OBJECTREF ref,AppDomain *pAppDomain) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + DEBUG_ONLY_FUNCTION; + + ref->TryAssignAppDomain(pAppDomain); + return SetObjectReferenceUnchecked(dst,ref); +} +#endif + +void SetObjectReferenceUnchecked(OBJECTREF *dst,OBJECTREF ref) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + // Assign value. We use casting to avoid going thru the overloaded + // OBJECTREF= operator which in this case would trigger a false + // write-barrier violation assert. + VolatileStore((Object**)dst, OBJECTREFToObject(ref)); +#ifdef _DEBUG + Thread::ObjectRefAssign(dst); +#endif + ErectWriteBarrier(dst, ref); +} + +/******************************************************************/ + // copies src to dest worrying about write barriers. + // Note that it can work on normal objects (but not arrays) + // if dest, points just after the VTABLE. +#if CHECK_APP_DOMAIN_LEAKS +void CopyValueClassChecked(void* dest, void* src, MethodTable *pMT, AppDomain *pDomain) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + + DEBUG_ONLY_FUNCTION; + + FAULT_NOT_FATAL(); + EX_TRY + { + Object::AssignValueTypeAppDomain(pMT, src, pDomain); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + CopyValueClassUnchecked(dest,src,pMT); +} + +// Copy value class into the argument specified by the argDest, performing an appdomain check first. +// The destOffset is nonzero when copying values into Nullable<T>, it is the offset +// of the T value inside of the Nullable<T> +void CopyValueClassArgChecked(ArgDestination *argDest, void* src, MethodTable *pMT, AppDomain *pDomain, int destOffset) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + + DEBUG_ONLY_FUNCTION; + + FAULT_NOT_FATAL(); + EX_TRY + { + Object::AssignValueTypeAppDomain(pMT, src, pDomain); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + CopyValueClassArgUnchecked(argDest, src, pMT, destOffset); +} +#endif + +void STDCALL CopyValueClassUnchecked(void* dest, void* src, MethodTable *pMT) +{ + + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + + _ASSERTE(!pMT->IsArray()); // bunch of assumptions about arrays wrong. + + // Copy the bulk of the data, and any non-GC refs. + switch (pMT->GetNumInstanceFieldBytes()) + { + case 1: + VolatileStore((UINT8*)dest, *(UINT8*)src); + break; +#ifndef ALIGN_ACCESS + // we can hit an alignment fault if the value type has multiple + // smaller fields. Example: if there are two I4 fields, the + // value class can be aligned to 4-byte boundaries, yet the + // NumInstanceFieldBytes is 8 + case 2: + VolatileStore((UINT16*)dest, *(UINT16*)src); + break; + case 4: + VolatileStore((UINT32*)dest, *(UINT32*)src); + break; + case 8: + VolatileStore((UINT64*)dest, *(UINT64*)src); + break; +#endif // !ALIGN_ACCESS + default: + memcpyNoGCRefs(dest, src, pMT->GetNumInstanceFieldBytes()); + break; + } + + // Tell the GC about any copies. + if (pMT->ContainsPointers()) + { + CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); + CGCDescSeries* cur = map->GetHighestSeries(); + CGCDescSeries* last = map->GetLowestSeries(); + DWORD size = pMT->GetBaseSize(); + _ASSERTE(cur >= last); + do + { + // offset to embedded references in this series must be + // adjusted by the VTable pointer, when in the unboxed state. + size_t offset = cur->GetSeriesOffset() - sizeof(void*); + OBJECTREF* srcPtr = (OBJECTREF*)(((BYTE*) src) + offset); + OBJECTREF* destPtr = (OBJECTREF*)(((BYTE*) dest) + offset); + OBJECTREF* srcPtrStop = (OBJECTREF*)((BYTE*) srcPtr + cur->GetSeriesSize() + size); + while (srcPtr < srcPtrStop) + { + SetObjectReferenceUnchecked(destPtr, ObjectToOBJECTREF(*(Object**)srcPtr)); + srcPtr++; + destPtr++; + } + cur--; + } while (cur >= last); + } +} + +// Copy value class into the argument specified by the argDest. +// The destOffset is nonzero when copying values into Nullable<T>, it is the offset +// of the T value inside of the Nullable<T> +void STDCALL CopyValueClassArgUnchecked(ArgDestination *argDest, void* src, MethodTable *pMT, int destOffset) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + +#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + + if (argDest->IsStructPassedInRegs()) + { + argDest->CopyStructToRegisters(src, pMT->GetNumInstanceFieldBytes(), destOffset); + return; + } + +#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING + // destOffset is only valid for Nullable<T> passed in registers + _ASSERTE(destOffset == 0); + + CopyValueClassUnchecked(argDest->GetDestinationAddress(), src, pMT); +} + +// Initialize the value class argument to zeros +void InitValueClassArg(ArgDestination *argDest, MethodTable *pMT) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + +#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + + if (argDest->IsStructPassedInRegs()) + { + argDest->ZeroStructInRegisters(pMT->GetNumInstanceFieldBytes()); + return; + } + +#endif + InitValueClass(argDest->GetDestinationAddress(), pMT); +} + +#if defined (VERIFY_HEAP) + +#include "dbginterface.h" + + // make the checking code goes as fast as possible! +#if defined(_MSC_VER) +#pragma optimize("tgy", on) +#endif + +#define CREATE_CHECK_STRING(x) #x +#define CHECK_AND_TEAR_DOWN(x) \ + do{ \ + if (!(x)) \ + { \ + _ASSERTE(!CREATE_CHECK_STRING(x)); \ + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); \ + } \ + } while (0) + +VOID Object::Validate(BOOL bDeep, BOOL bVerifyNextHeader, BOOL bVerifySyncBlock) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + if (this == NULL) + { + return; // NULL is ok + } + + if (g_IBCLogger.InstrEnabled() && !GCStress<cfg_any>::IsEnabled()) + { + // If we are instrumenting for IBC (and GCStress is not enabled) + // then skip these Object::Validate() as they slow down the + // instrument phase by an order of magnitude + return; + } + + if (g_fEEShutDown & ShutDown_Phase2) + { + // During second phase of shutdown the code below is not guaranteed to work. + return; + } + +#ifdef _DEBUG + { + BEGIN_GETTHREAD_ALLOWED_IN_NO_THROW_REGION; + Thread *pThread = GetThread(); + + if (pThread != NULL && !(pThread->PreemptiveGCDisabled())) + { + // Debugger helper threads are special in that they take over for + // what would normally be a nonEE thread (the RCThread). If an + // EE thread is doing RCThread duty, then it should be treated + // as such. + // + // There are some GC threads in the same kind of category. Note that + // GetThread() sometimes returns them, if DLL_THREAD_ATTACH notifications + // have run some managed code. + if (!dbgOnly_IsSpecialEEThread() && !IsGCSpecialThread()) + _ASSERTE(!"OBJECTREF being accessed while thread is in preemptive GC mode."); + } + END_GETTHREAD_ALLOWED_IN_NO_THROW_REGION; + } +#endif + + + { // ValidateInner can throw or fault on failure which violates contract. + CONTRACT_VIOLATION(ThrowsViolation | FaultViolation); + + // using inner helper because of TRY and stack objects with destructors. + ValidateInner(bDeep, bVerifyNextHeader, bVerifySyncBlock); + } +} + +VOID Object::ValidateInner(BOOL bDeep, BOOL bVerifyNextHeader, BOOL bVerifySyncBlock) +{ + STATIC_CONTRACT_THROWS; // See CONTRACT_VIOLATION above + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; // See CONTRACT_VIOLATION above + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + int lastTest = 0; + + EX_TRY + { + // in order to avoid contract violations in the EH code we'll allow AVs here, + // they'll be handled in the catch block + AVInRuntimeImplOkayHolder avOk; + + MethodTable *pMT = GetGCSafeMethodTable(); + lastTest = 1; + + CHECK_AND_TEAR_DOWN(pMT->Validate()); + lastTest = 2; + + bool noRangeChecks = + (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_NO_RANGE_CHECKS) == EEConfig::HEAPVERIFY_NO_RANGE_CHECKS; + + // noRangeChecks depends on initial values being FALSE + BOOL bSmallObjectHeapPtr = FALSE, bLargeObjectHeapPtr = FALSE; + if (!noRangeChecks) + { + bSmallObjectHeapPtr = GCHeap::GetGCHeap()->IsHeapPointer(this, TRUE); + if (!bSmallObjectHeapPtr) + bLargeObjectHeapPtr = GCHeap::GetGCHeap()->IsHeapPointer(this); + + CHECK_AND_TEAR_DOWN(bSmallObjectHeapPtr || bLargeObjectHeapPtr); + } + + lastTest = 3; + + if (bDeep) + { + CHECK_AND_TEAR_DOWN(GetHeader()->Validate(bVerifySyncBlock)); + } + + lastTest = 4; + + if (bDeep && (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_GC)) { + GCHeap::GetGCHeap()->ValidateObjectMember(this); + } + + lastTest = 5; + + // since bSmallObjectHeapPtr is initialized to FALSE + // we skip checking noRangeChecks since if skipping + // is enabled bSmallObjectHeapPtr will always be false. + if (bSmallObjectHeapPtr) { + CHECK_AND_TEAR_DOWN(!GCHeap::GetGCHeap()->IsObjectInFixedHeap(this)); + } + + lastTest = 6; + +#if CHECK_APP_DOMAIN_LEAKS + // when it's not safe to verify the fields, it's not safe to verify AppDomain either + // because the process might try to access fields. + if (bDeep && g_pConfig->AppDomainLeaks()) + { + // + // Check to see that our domain is valid. This will assert if it has been unloaded. + // + SCAN_IGNORE_FAULT; + GetAppDomain(); + } +#endif + + lastTest = 7; + + // try to validate next object's header + if (bDeep + && bVerifyNextHeader + && GCScan::GetGcRuntimeStructuresValid () + //NextObj could be very slow if concurrent GC is going on + && !(GCHeap::IsGCHeapInitialized() && GCHeap::GetGCHeap ()->IsConcurrentGCInProgress ())) + { + Object * nextObj = GCHeap::GetGCHeap ()->NextObj (this); + if ((nextObj != NULL) && + (nextObj->GetGCSafeMethodTable() != g_pFreeObjectMethodTable)) + { + CHECK_AND_TEAR_DOWN(nextObj->GetHeader()->Validate(FALSE)); + } + } + + lastTest = 8; + +#ifdef FEATURE_64BIT_ALIGNMENT + if (pMT->RequiresAlign8()) + { + CHECK_AND_TEAR_DOWN((((size_t)this) & 0x7) == (pMT->IsValueType()? 4:0)); + } + lastTest = 9; +#endif // FEATURE_64BIT_ALIGNMENT + + } + EX_CATCH + { + STRESS_LOG3(LF_ASSERT, LL_ALWAYS, "Detected use of corrupted OBJECTREF: %p [MT=%p] (lastTest=%d)", this, lastTest > 0 ? (*(size_t*)this) : 0, lastTest); + CHECK_AND_TEAR_DOWN(!"Detected use of a corrupted OBJECTREF. Possible GC hole."); + } + EX_END_CATCH(SwallowAllExceptions); +} + + +#endif // VERIFY_HEAP + +/*==================================NewString=================================== +**Action: Creates a System.String object. +**Returns: +**Arguments: +**Exceptions: +==============================================================================*/ +STRINGREF StringObject::NewString(INT32 length) { + CONTRACTL { + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(length>=0); + } CONTRACTL_END; + + STRINGREF pString; + + if (length<0) { + return NULL; + } else if (length == 0) { + return GetEmptyString(); + } else { + pString = AllocateString(length); + _ASSERTE(pString->GetBuffer()[length] == 0); + + return pString; + } +} + + +/*==================================NewString=================================== +**Action: Many years ago, VB didn't have the concept of a byte array, so enterprising +** users created one by allocating a BSTR with an odd length and using it to +** store bytes. A generation later, we're still stuck supporting this behavior. +** The way that we do this is to take advantage of the difference between the +** array length and the string length. The string length will always be the +** number of characters between the start of the string and the terminating 0. +** If we need an odd number of bytes, we'll take one wchar after the terminating 0. +** (e.g. at position StringLength+1). The high-order byte of this wchar is +** reserved for flags and the low-order byte is our odd byte. This function is +** used to allocate a string of that shape, but we don't actually mark the +** trailing byte as being in use yet. +**Returns: A newly allocated string. Null if length is less than 0. +**Arguments: length -- the length of the string to allocate +** bHasTrailByte -- whether the string also has a trailing byte. +**Exceptions: OutOfMemoryException if AllocateString fails. +==============================================================================*/ +STRINGREF StringObject::NewString(INT32 length, BOOL bHasTrailByte) { + CONTRACTL { + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(length>=0 && length != INT32_MAX); + } CONTRACTL_END; + + STRINGREF pString; + if (length<0 || length == INT32_MAX) { + return NULL; + } else if (length == 0) { + return GetEmptyString(); + } else { + pString = AllocateString(length); + _ASSERTE(pString->GetBuffer()[length]==0); + if (bHasTrailByte) { + _ASSERTE(pString->GetBuffer()[length+1]==0); + } + } + + return pString; +} + +//======================================================================== +// Creates a System.String object and initializes from +// the supplied null-terminated C string. +// +// Maps NULL to null. This function does *not* return null to indicate +// error situations: it throws an exception instead. +//======================================================================== +STRINGREF StringObject::NewString(const WCHAR *pwsz) +{ + CONTRACTL { + GC_TRIGGERS; + MODE_COOPERATIVE; + } CONTRACTL_END; + + if (!pwsz) + { + return NULL; + } + else + { + + DWORD nch = (DWORD)wcslen(pwsz); + if (nch==0) { + return GetEmptyString(); + } + +#if 0 + // + // This assert is disabled because it is valid for us to get a + // pointer from the gc heap here as long as it is pinned. This + // can happen when a string is marshalled to unmanaged by + // pinning and then later put into a struct and that struct is + // then marshalled to managed. + // + _ASSERTE(!GCHeap::GetGCHeap()->IsHeapPointer((BYTE *) pwsz) || + !"pwsz can not point to GC Heap"); +#endif // 0 + + STRINGREF pString = AllocateString( nch ); + + memcpyNoGCRefs(pString->GetBuffer(), pwsz, nch*sizeof(WCHAR)); + _ASSERTE(pString->GetBuffer()[nch] == 0); + return pString; + } +} + +#if defined(_MSC_VER) && defined(_TARGET_X86_) +#pragma optimize("y", on) // Small critical routines, don't put in EBP frame +#endif + +STRINGREF StringObject::NewString(const WCHAR *pwsz, int length) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(length>=0); + } CONTRACTL_END; + + if (!pwsz) + { + return NULL; + } + else if (length <= 0) { + return GetEmptyString(); + } else { +#if 0 + // + // This assert is disabled because it is valid for us to get a + // pointer from the gc heap here as long as it is pinned. This + // can happen when a string is marshalled to unmanaged by + // pinning and then later put into a struct and that struct is + // then marshalled to managed. + // + _ASSERTE(!GCHeap::GetGCHeap()->IsHeapPointer((BYTE *) pwsz) || + !"pwsz can not point to GC Heap"); +#endif // 0 + STRINGREF pString = AllocateString(length); + + memcpyNoGCRefs(pString->GetBuffer(), pwsz, length*sizeof(WCHAR)); + _ASSERTE(pString->GetBuffer()[length] == 0); + return pString; + } +} + +#if defined(_MSC_VER) && defined(_TARGET_X86_) +#pragma optimize("", on) // Go back to command line default optimizations +#endif + +STRINGREF StringObject::NewString(LPCUTF8 psz) +{ + CONTRACTL { + GC_TRIGGERS; + MODE_COOPERATIVE; + THROWS; + PRECONDITION(CheckPointer(psz)); + } CONTRACTL_END; + + int length = (int)strlen(psz); + if (length == 0) { + return GetEmptyString(); + } + CQuickBytes qb; + WCHAR* pwsz = (WCHAR*) qb.AllocThrows((length) * sizeof(WCHAR)); + length = WszMultiByteToWideChar(CP_UTF8, 0, psz, length, pwsz, length); + if (length == 0) { + COMPlusThrow(kArgumentException, W("Arg_InvalidUTF8String")); + } + return NewString(pwsz, length); +} + +STRINGREF StringObject::NewString(LPCUTF8 psz, int cBytes) +{ + CONTRACTL { + GC_TRIGGERS; + MODE_COOPERATIVE; + THROWS; + PRECONDITION(CheckPointer(psz, NULL_OK)); + } CONTRACTL_END; + + if (!psz) + return NULL; + + _ASSERTE(psz); + _ASSERTE(cBytes >= 0); + if (cBytes == 0) { + return GetEmptyString(); + } + int cWszBytes = 0; + if (!ClrSafeInt<int>::multiply(cBytes, sizeof(WCHAR), cWszBytes)) + COMPlusThrowOM(); + CQuickBytes qb; + WCHAR* pwsz = (WCHAR*) qb.AllocThrows(cWszBytes); + int length = WszMultiByteToWideChar(CP_UTF8, 0, psz, cBytes, pwsz, cBytes); + if (length == 0) { + COMPlusThrow(kArgumentException, W("Arg_InvalidUTF8String")); + } + return NewString(pwsz, length); +} + +// +// +// STATIC MEMBER VARIABLES +// +// +STRINGREF* StringObject::EmptyStringRefPtr=NULL; + +//The special string helpers are used as flag bits for weird strings that have bytes +//after the terminating 0. The only case where we use this right now is the VB BSTR as +//byte array which is described in MakeStringAsByteArrayFromBytes. +#define SPECIAL_STRING_VB_BYTE_ARRAY 0x100 + +FORCEINLINE BOOL MARKS_VB_BYTE_ARRAY(WCHAR x) +{ + return static_cast<BOOL>(x & SPECIAL_STRING_VB_BYTE_ARRAY); +} + +FORCEINLINE WCHAR MAKE_VB_TRAIL_BYTE(BYTE x) +{ + return static_cast<WCHAR>(x) | SPECIAL_STRING_VB_BYTE_ARRAY; +} + +FORCEINLINE BYTE GET_VB_TRAIL_BYTE(WCHAR x) +{ + return static_cast<BYTE>(x & 0xFF); +} + + +/*==============================InitEmptyStringRefPtr============================ +**Action: Gets an empty string refptr, cache the result. +**Returns: The retrieved STRINGREF. +==============================================================================*/ +STRINGREF* StringObject::InitEmptyStringRefPtr() { + CONTRACTL { + THROWS; + MODE_ANY; + GC_TRIGGERS; + } CONTRACTL_END; + + GCX_COOP(); + + EEStringData data(0, W(""), TRUE); + EmptyStringRefPtr = SystemDomain::System()->DefaultDomain()->GetLoaderAllocator()->GetStringObjRefPtrFromUnicodeString(&data); + return EmptyStringRefPtr; +} + +/*=============================StringInitCharHelper============================= +**Action: +**Returns: +**Arguments: +**Exceptions: +**Note this +==============================================================================*/ +STRINGREF __stdcall StringObject::StringInitCharHelper(LPCSTR pszSource, int length) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } CONTRACTL_END; + + STRINGREF pString=NULL; + int dwSizeRequired=0; + _ASSERTE(length>=-1); + + if (!pszSource || length == 0) { + return StringObject::GetEmptyString(); + } + else if ((size_t)pszSource < 64000) { + COMPlusThrow(kArgumentException, W("Arg_MustBeStringPtrNotAtom")); + } + + // Make sure we can read from the pointer. + // This is better than try to read from the pointer and catch the access violation exceptions. + if( length == -1) { + length = (INT32)strlen(pszSource); + } + + if(length > 0) { + dwSizeRequired=WszMultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszSource, length, NULL, 0); + } + + if (dwSizeRequired == 0) { + if (length == 0) { + return StringObject::GetEmptyString(); + } + COMPlusThrow(kArgumentException, W("Arg_InvalidANSIString")); + } + + pString = AllocateString(dwSizeRequired); + dwSizeRequired = WszMultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)pszSource, length, pString->GetBuffer(), dwSizeRequired); + if (dwSizeRequired == 0) { + COMPlusThrow(kArgumentException, W("Arg_InvalidANSIString")); + } + + _ASSERTE(dwSizeRequired != INT32_MAX && pString->GetBuffer()[dwSizeRequired]==0); + + return pString; +} + + +// strAChars must be null-terminated, with an appropriate aLength +// strBChars must be null-terminated, with an appropriate bLength OR bLength == -1 +// If bLength == -1, we stop on the first null character in strBChars +BOOL StringObject::CaseInsensitiveCompHelper(__in_ecount(aLength) WCHAR *strAChars, __in_z INT8 *strBChars, INT32 aLength, INT32 bLength, INT32 *result) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(strAChars)); + PRECONDITION(CheckPointer(strBChars)); + PRECONDITION(CheckPointer(result)); + SO_TOLERANT; + } CONTRACTL_END; + + WCHAR *strAStart = strAChars; + INT8 *strBStart = strBChars; + unsigned charA; + unsigned charB; + + for(;;) { + charA = *strAChars; + charB = (unsigned) *strBChars; + + //Case-insensitive comparison on chars greater than 0x7F + //requires a locale-aware casing operation and we're not going there. + if ((charA|charB)>0x7F) { + *result = 0; + return FALSE; + } + + // uppercase both chars. + if (charA>='a' && charA<='z') { + charA ^= 0x20; + } + if (charB>='a' && charB<='z') { + charB ^= 0x20; + } + + //Return the (case-insensitive) difference between them. + if (charA!=charB) { + *result = (int)(charA-charB); + return TRUE; + } + + + if (charA==0) // both strings have null character + { + if (bLength == -1) + { + *result = aLength - static_cast<INT32>(strAChars - strAStart); + return TRUE; + } + if (strAChars==strAStart + aLength || strBChars==strBStart + bLength) + { + *result = aLength - bLength; + return TRUE; + } + // else both embedded zeros + } + + // Next char + strAChars++; strBChars++; + } + +} + +INT32 StringObject::FastCompareStringHelper(DWORD* strAChars, INT32 countA, DWORD* strBChars, INT32 countB) +{ + STATIC_CONTRACT_SO_TOLERANT; + + INT32 count = (countA < countB) ? countA : countB; + + PREFIX_ASSUME(count >= 0); + + ptrdiff_t diff = (char *)strAChars - (char *)strBChars; + +#if defined(_WIN64) || defined(ALIGN_ACCESS) + int alignmentA = ((SIZE_T)strAChars) & (sizeof(SIZE_T) - 1); + int alignmentB = ((SIZE_T)strBChars) & (sizeof(SIZE_T) - 1); +#endif // _WIN64 || ALIGN_ACCESS + +#if defined(_WIN64) + if (alignmentA == alignmentB) + { + if ((alignmentA == 2 || alignmentA == 6) && (count >= 1)) + { + LPWSTR ptr2 = (WCHAR *)strBChars; + + if (( *((WCHAR*)((char *)ptr2 + diff)) - *ptr2) != 0) + { + return ((int)*((WCHAR*)((char *)ptr2 + diff)) - (int)*ptr2); + } + strBChars = (DWORD*)(++ptr2); + count -= 1; + alignmentA = (alignmentA == 2 ? 4 : 0); + } + + if ((alignmentA == 4) && (count >= 2)) + { + DWORD* ptr2 = (DWORD*)strBChars; + + if (( *((DWORD*)((char *)ptr2 + diff)) - *ptr2) != 0) + { + LPWSTR chkptr1 = (WCHAR*)((char *)strBChars + diff); + LPWSTR chkptr2 = (WCHAR*)strBChars; + + if (*chkptr1 != *chkptr2) + { + return ((int)*chkptr1 - (int)*chkptr2); + } + return ((int)*(chkptr1+1) - (int)*(chkptr2+1)); + } + strBChars = ++ptr2; + count -= 2; + alignmentA = 0; + } + + if (alignmentA == 0) + { + while (count >= 4) + { + SIZE_T* ptr2 = (SIZE_T*)strBChars; + + if (( *((SIZE_T*)((char *)ptr2 + diff)) - *ptr2) != 0) + { + if (( *((DWORD*)((char *)ptr2 + diff)) - *(DWORD*)ptr2) != 0) + { + LPWSTR chkptr1 = (WCHAR*)((char *)strBChars + diff); + LPWSTR chkptr2 = (WCHAR*)strBChars; + + if (*chkptr1 != *chkptr2) + { + return ((int)*chkptr1 - (int)*chkptr2); + } + return ((int)*(chkptr1+1) - (int)*(chkptr2+1)); + } + else + { + LPWSTR chkptr1 = (WCHAR*)((DWORD*)((char *)strBChars + diff) + 1); + LPWSTR chkptr2 = (WCHAR*)((DWORD*)strBChars + 1); + + if (*chkptr1 != *chkptr2) + { + return ((int)*chkptr1 - (int)*chkptr2); + } + return ((int)*(chkptr1+1) - (int)*(chkptr2+1)); + } + } + strBChars = (DWORD*)(++ptr2); + count -= 4; + } + } + + LPWSTR ptr2 = (WCHAR*)strBChars; + while ((count -= 1) >= 0) + { + if (( *((WCHAR*)((char *)ptr2 + diff)) - *ptr2) != 0) + { + return ((int)*((WCHAR*)((char *)ptr2 + diff)) - (int)*ptr2); + } + ++ptr2; + } + } + else +#endif // _WIN64 +#if defined(ALIGN_ACCESS) + if ( ( !IS_ALIGNED((size_t)strAChars, sizeof(DWORD)) || + !IS_ALIGNED((size_t)strBChars, sizeof(DWORD)) ) && + (abs(alignmentA - alignmentB) != 4) ) + { + _ASSERTE(IS_ALIGNED((size_t)strAChars, sizeof(WCHAR))); + _ASSERTE(IS_ALIGNED((size_t)strBChars, sizeof(WCHAR))); + LPWSTR ptr2 = (WCHAR *)strBChars; + + while ((count -= 1) >= 0) + { + if (( *((WCHAR*)((char *)ptr2 + diff)) - *ptr2) != 0) + { + return ((int)*((WCHAR*)((char *)ptr2 + diff)) - (int)*ptr2); + } + ++ptr2; + } + } + else +#endif // ALIGN_ACCESS + { +#if defined(_WIN64) || defined(ALIGN_ACCESS) + if (abs(alignmentA - alignmentB) == 4) + { + if ((alignmentA == 2) || (alignmentB == 2)) + { + LPWSTR ptr2 = (WCHAR *)strBChars; + + if (( *((WCHAR*)((char *)ptr2 + diff)) - *ptr2) != 0) + { + return ((int)*((WCHAR*)((char *)ptr2 + diff)) - (int)*ptr2); + } + strBChars = (DWORD*)(++ptr2); + count -= 1; + } + } +#endif // WIN64 || ALIGN_ACCESS + + // Loop comparing a DWORD at a time. + while ((count -= 2) >= 0) + { + if ((*((DWORD* )((char *)strBChars + diff)) - *strBChars) != 0) + { + LPWSTR ptr1 = (WCHAR*)((char *)strBChars + diff); + LPWSTR ptr2 = (WCHAR*)strBChars; + if (*ptr1 != *ptr2) { + return ((int)*ptr1 - (int)*ptr2); + } + return ((int)*(ptr1+1) - (int)*(ptr2+1)); + } + ++strBChars; + } + + int c; + if (count == -1) + if ((c = *((WCHAR *) ((char *)strBChars + diff)) - *((WCHAR *) strBChars)) != 0) + return c; + } + + return countA - countB; +} + + +/*=============================InternalHasHighChars============================= +**Action: Checks if the string can be sorted quickly. The requirements are that +** the string contain no character greater than 0x80 and that the string not +** contain an apostrophe or a hypen. Apostrophe and hyphen are excluded so that +** words like co-op and coop sort together. +**Returns: Void. The side effect is to set a bit on the string indicating whether or not +** the string contains high chars. +**Arguments: The String to be checked. +**Exceptions: None +==============================================================================*/ +DWORD StringObject::InternalCheckHighChars() { + WRAPPER_NO_CONTRACT; + + WCHAR *chars; + WCHAR c; + INT32 length; + + RefInterpretGetStringValuesDangerousForGC((WCHAR **) &chars, &length); + + DWORD stringState = STRING_STATE_FAST_OPS; + + for (int i=0; i<length; i++) { + c = chars[i]; + if (c>=0x80) { + SetHighCharState(STRING_STATE_HIGH_CHARS); + return STRING_STATE_HIGH_CHARS; + } else if (HighCharHelper::IsHighChar((int)c)) { + //This means that we have a character which forces special sorting, + //but doesn't necessarily force slower casing and indexing. We'll + //set a value to remember this, but we need to check the rest of + //the string because we may still find a charcter greater than 0x7f. + stringState = STRING_STATE_SPECIAL_SORT; + } + } + + SetHighCharState(stringState); + return stringState; +} + +#ifdef VERIFY_HEAP +/*=============================ValidateHighChars============================= +**Action: Validate if the HighChars bits is set correctly, no side effect +**Returns: BOOL for result of validation +**Arguments: The String to be checked. +**Exceptions: None +==============================================================================*/ +BOOL StringObject::ValidateHighChars() +{ + WRAPPER_NO_CONTRACT; + DWORD curStringState = GetHighCharState (); + // state could always be undetermined + if (curStringState == STRING_STATE_UNDETERMINED) + { + return TRUE; + } + + WCHAR *chars; + INT32 length; + RefInterpretGetStringValuesDangerousForGC((WCHAR **) &chars, &length); + + DWORD stringState = STRING_STATE_FAST_OPS; + for (int i=0; i<length; i++) { + WCHAR c = chars[i]; + if (c>=0x80) + { + // if there is a high char in the string, the state has to be STRING_STATE_HIGH_CHARS + return curStringState == STRING_STATE_HIGH_CHARS; + } + else if (HighCharHelper::IsHighChar((int)c)) { + //This means that we have a character which forces special sorting, + //but doesn't necessarily force slower casing and indexing. We'll + //set a value to remember this, but we need to check the rest of + //the string because we may still find a charcter greater than 0x7f. + stringState = STRING_STATE_SPECIAL_SORT; + } + } + + return stringState == curStringState; +} + +#endif //VERIFY_HEAP + +/*============================InternalTrailByteCheck============================ +**Action: Many years ago, VB didn't have the concept of a byte array, so enterprising +** users created one by allocating a BSTR with an odd length and using it to +** store bytes. A generation later, we're still stuck supporting this behavior. +** The way that we do this is stick the trail byte in the sync block +** whenever we encounter such a situation. Since we expect this to be a very corner case +** accessing the sync block seems like a good enough solution +** +**Returns: True if <CODE>str</CODE> contains a VB trail byte, false otherwise. +**Arguments: str -- The string to be examined. +**Exceptions: None +==============================================================================*/ +BOOL StringObject::HasTrailByte() { + WRAPPER_NO_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + + SyncBlock * pSyncBlock = PassiveGetSyncBlock(); + if(pSyncBlock != NULL) + { + return pSyncBlock->HasCOMBstrTrailByte(); + } + + return FALSE; +} + +/*=================================GetTrailByte================================= +**Action: If <CODE>str</CODE> contains a vb trail byte, returns a copy of it. +**Returns: True if <CODE>str</CODE> contains a trail byte. *bTrailByte is set to +** the byte in question if <CODE>str</CODE> does have a trail byte, otherwise +** it's set to 0. +**Arguments: str -- The string being examined. +** bTrailByte -- An out param to hold the value of the trail byte. +**Exceptions: None. +==============================================================================*/ +BOOL StringObject::GetTrailByte(BYTE *bTrailByte) { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + _ASSERTE(bTrailByte); + *bTrailByte=0; + + BOOL retValue = HasTrailByte(); + + if(retValue) + { + *bTrailByte = GET_VB_TRAIL_BYTE(GetHeader()->PassiveGetSyncBlock()->GetCOMBstrTrailByte()); + } + + return retValue; +} + +/*=================================SetTrailByte================================= +**Action: Sets the trail byte in the sync block +**Returns: True. +**Arguments: str -- The string into which to set the trail byte. +** bTrailByte -- The trail byte to be added to the string. +**Exceptions: None. +==============================================================================*/ +BOOL StringObject::SetTrailByte(BYTE bTrailByte) { + WRAPPER_NO_CONTRACT; + + GetHeader()->GetSyncBlock()->SetCOMBstrTrailByte(MAKE_VB_TRAIL_BYTE(bTrailByte)); + return TRUE; +} + + +#define DEFAULT_CAPACITY 16 +#define DEFAULT_MAX_CAPACITY 0x7FFFFFFF + +/*================================ReplaceBuffer================================= +**This is a helper function designed to be used by N/Direct it replaces the entire +**contents of the String with a new string created by some native method. This +**will not be exposed through the StringBuilder class. +==============================================================================*/ +void StringBufferObject::ReplaceBuffer(STRINGBUFFERREF *thisRef, __in_ecount(newLength) WCHAR *newBuffer, INT32 newLength) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(newBuffer)); + PRECONDITION(newLength>=0); + PRECONDITION(CheckPointer(thisRef)); + PRECONDITION(IsProtectedByGCFrame(thisRef)); + } CONTRACTL_END; + + if(newLength > (*thisRef)->GetMaxCapacity()) + { + COMPlusThrowArgumentOutOfRange(W("capacity"), W("ArgumentOutOfRange_Capacity")); + } + + CHARARRAYREF newCharArray = AllocateCharArray((*thisRef)->GetAllocationLength(newLength+1)); + (*thisRef)->ReplaceBuffer(&newCharArray, newBuffer, newLength); +} + + +/*================================ReplaceBufferAnsi================================= +**This is a helper function designed to be used by N/Direct it replaces the entire +**contents of the String with a new string created by some native method. This +**will not be exposed through the StringBuilder class. +** +**This version does Ansi->Unicode conversion along the way. Although +**making it a member of COMStringBuffer exposes more stringbuffer internals +**than necessary, it does avoid requiring a temporary buffer to hold +**the Ansi->Unicode conversion. +==============================================================================*/ +void StringBufferObject::ReplaceBufferAnsi(STRINGBUFFERREF *thisRef, __in_ecount(newCapacity) CHAR *newBuffer, INT32 newCapacity) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(newBuffer)); + PRECONDITION(CheckPointer(thisRef)); + PRECONDITION(IsProtectedByGCFrame(thisRef)); + PRECONDITION(newCapacity>=0); + } CONTRACTL_END; + + if(newCapacity > (*thisRef)->GetMaxCapacity()) + { + COMPlusThrowArgumentOutOfRange(W("capacity"), W("ArgumentOutOfRange_Capacity")); + } + + CHARARRAYREF newCharArray = AllocateCharArray((*thisRef)->GetAllocationLength(newCapacity+1)); + (*thisRef)->ReplaceBufferWithAnsi(&newCharArray, newBuffer, newCapacity); +} + + +/*==============================LocalIndexOfString============================== +**Finds search within base and returns the index where it was found. The search +**starts from startPos and we return -1 if search isn't found. This is a direct +**copy from COMString::IndexOfString, but doesn't require that we build up +**an instance of indexOfStringArgs before calling it. +** +**Args: +**base -- the string in which to search +**search -- the string for which to search +**strLength -- the length of base +**patternLength -- the length of search +**startPos -- the place from which to start searching. +** +==============================================================================*/ +/* static */ INT32 StringBufferObject::LocalIndexOfString(__in_ecount(strLength) WCHAR *base, __in_ecount(patternLength) WCHAR *search, int strLength, int patternLength, int startPos) { + LIMITED_METHOD_CONTRACT + _ASSERTE(base != NULL); + _ASSERTE(search != NULL); + + int iThis, iPattern; + for (iThis=startPos; iThis < (strLength-patternLength+1); iThis++) { + for (iPattern=0; iPattern<patternLength && base[iThis+iPattern]==search[iPattern]; iPattern++); + if (iPattern == patternLength) return iThis; + } + return -1; +} + + +#ifdef USE_CHECKED_OBJECTREFS + +//------------------------------------------------------------- +// Default constructor, for non-initializing declarations: +// +// OBJECTREF or; +//------------------------------------------------------------- +OBJECTREF::OBJECTREF() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + STATIC_CONTRACT_VIOLATION(SOToleranceViolation); + + m_asObj = (Object*)POISONC; + Thread::ObjectRefNew(this); +} + +//------------------------------------------------------------- +// Copy constructor, for passing OBJECTREF's as function arguments. +//------------------------------------------------------------- +OBJECTREF::OBJECTREF(const OBJECTREF & objref) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_FORBID_FAULT; + + STATIC_CONTRACT_VIOLATION(SOToleranceViolation); + + VALIDATEOBJECT(objref.m_asObj); + + // !!! If this assert is fired, there are two possibilities: + // !!! 1. You are doing a type cast, e.g. *(OBJECTREF*)pObj + // !!! Instead, you should use ObjectToOBJECTREF(*(Object**)pObj), + // !!! or ObjectToSTRINGREF(*(StringObject**)pObj) + // !!! 2. There is a real GC hole here. + // !!! Either way you need to fix the code. + _ASSERTE(Thread::IsObjRefValid(&objref)); + if ((objref.m_asObj != 0) && + ((GCHeap*)GCHeap::GetGCHeap())->IsHeapPointer( (BYTE*)this )) + { + _ASSERTE(!"Write Barrier violation. Must use SetObjectReference() to assign OBJECTREF's into the GC heap!"); + } + m_asObj = objref.m_asObj; + + if (m_asObj != 0) { + ENABLESTRESSHEAP(); + } + + Thread::ObjectRefNew(this); +} + + +//------------------------------------------------------------- +// To allow NULL to be used as an OBJECTREF. +//------------------------------------------------------------- +OBJECTREF::OBJECTREF(TADDR nul) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + STATIC_CONTRACT_VIOLATION(SOToleranceViolation); + + //_ASSERTE(nul == 0); + m_asObj = (Object*)nul; + if( m_asObj != NULL) + { + // REVISIT_TODO: fix this, why is this constructor being used for non-null object refs? + STATIC_CONTRACT_VIOLATION(ModeViolation); + + VALIDATEOBJECT(m_asObj); + ENABLESTRESSHEAP(); + } + Thread::ObjectRefNew(this); +} + +//------------------------------------------------------------- +// This is for the GC's use only. Non-GC code should never +// use the "Object" class directly. The unused "int" argument +// prevents C++ from using this to implicitly convert Object*'s +// to OBJECTREF. +//------------------------------------------------------------- +OBJECTREF::OBJECTREF(Object *pObject) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_FORBID_FAULT; + + DEBUG_ONLY_FUNCTION; + + if ((pObject != 0) && + ((GCHeap*)GCHeap::GetGCHeap())->IsHeapPointer( (BYTE*)this )) + { + _ASSERTE(!"Write Barrier violation. Must use SetObjectReference() to assign OBJECTREF's into the GC heap!"); + } + m_asObj = pObject; + VALIDATEOBJECT(m_asObj); + if (m_asObj != 0) { + ENABLESTRESSHEAP(); + } + Thread::ObjectRefNew(this); +} + +void OBJECTREF::Validate(BOOL bDeep, BOOL bVerifyNextHeader, BOOL bVerifySyncBlock) +{ + LIMITED_METHOD_CONTRACT; + m_asObj->Validate(bDeep, bVerifyNextHeader, bVerifySyncBlock); +} + +//------------------------------------------------------------- +// Test against NULL. +//------------------------------------------------------------- +int OBJECTREF::operator!() const +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + // We don't do any validation here, as we want to allow zero comparison in preemptive mode + return !m_asObj; +} + +//------------------------------------------------------------- +// Compare two OBJECTREF's. +//------------------------------------------------------------- +int OBJECTREF::operator==(const OBJECTREF &objref) const +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (objref.m_asObj != NULL) // Allow comparison to zero in preemptive mode + { + // REVISIT_TODO: Weakening the contract system a little bit here. We should really + // add a special NULLOBJECTREF which can be used for these situations and have + // a seperate code path for that with the correct contract protections. + STATIC_CONTRACT_VIOLATION(ModeViolation); + + VALIDATEOBJECT(objref.m_asObj); + + // !!! If this assert is fired, there are two possibilities: + // !!! 1. You are doing a type cast, e.g. *(OBJECTREF*)pObj + // !!! Instead, you should use ObjectToOBJECTREF(*(Object**)pObj), + // !!! or ObjectToSTRINGREF(*(StringObject**)pObj) + // !!! 2. There is a real GC hole here. + // !!! Either way you need to fix the code. + _ASSERTE(Thread::IsObjRefValid(&objref)); + VALIDATEOBJECT(m_asObj); + // If this assert fires, you probably did not protect + // your OBJECTREF and a GC might have occurred. To + // where the possible GC was, set a breakpoint in Thread::TriggersGC + _ASSERTE(Thread::IsObjRefValid(this)); + + if (m_asObj != 0 || objref.m_asObj != 0) { + ENABLESTRESSHEAP(); + } + } + return m_asObj == objref.m_asObj; +} + +//------------------------------------------------------------- +// Compare two OBJECTREF's. +//------------------------------------------------------------- +int OBJECTREF::operator!=(const OBJECTREF &objref) const +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (objref.m_asObj != NULL) // Allow comparison to zero in preemptive mode + { + // REVISIT_TODO: Weakening the contract system a little bit here. We should really + // add a special NULLOBJECTREF which can be used for these situations and have + // a seperate code path for that with the correct contract protections. + STATIC_CONTRACT_VIOLATION(ModeViolation); + + VALIDATEOBJECT(objref.m_asObj); + + // !!! If this assert is fired, there are two possibilities: + // !!! 1. You are doing a type cast, e.g. *(OBJECTREF*)pObj + // !!! Instead, you should use ObjectToOBJECTREF(*(Object**)pObj), + // !!! or ObjectToSTRINGREF(*(StringObject**)pObj) + // !!! 2. There is a real GC hole here. + // !!! Either way you need to fix the code. + _ASSERTE(Thread::IsObjRefValid(&objref)); + VALIDATEOBJECT(m_asObj); + // If this assert fires, you probably did not protect + // your OBJECTREF and a GC might have occurred. To + // where the possible GC was, set a breakpoint in Thread::TriggersGC + _ASSERTE(Thread::IsObjRefValid(this)); + + if (m_asObj != 0 || objref.m_asObj != 0) { + ENABLESTRESSHEAP(); + } + } + + return m_asObj != objref.m_asObj; +} + + +//------------------------------------------------------------- +// Forward method calls. +//------------------------------------------------------------- +Object* OBJECTREF::operator->() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + VALIDATEOBJECT(m_asObj); + // If this assert fires, you probably did not protect + // your OBJECTREF and a GC might have occurred. To + // where the possible GC was, set a breakpoint in Thread::TriggersGC + _ASSERTE(Thread::IsObjRefValid(this)); + + if (m_asObj != 0) { + ENABLESTRESSHEAP(); + } + + // if you are using OBJECTREF directly, + // you probably want an Object * + return (Object *)m_asObj; +} + + +//------------------------------------------------------------- +// Forward method calls. +//------------------------------------------------------------- +const Object* OBJECTREF::operator->() const +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + VALIDATEOBJECT(m_asObj); + // If this assert fires, you probably did not protect + // your OBJECTREF and a GC might have occurred. To + // where the possible GC was, set a breakpoint in Thread::TriggersGC + _ASSERTE(Thread::IsObjRefValid(this)); + + if (m_asObj != 0) { + ENABLESTRESSHEAP(); + } + + // if you are using OBJECTREF directly, + // you probably want an Object * + return (Object *)m_asObj; +} + + +//------------------------------------------------------------- +// Assignment. We don't validate the destination so as not +// to break the sequence: +// +// OBJECTREF or; +// or = ...; +//------------------------------------------------------------- +OBJECTREF& OBJECTREF::operator=(const OBJECTREF &objref) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + VALIDATEOBJECT(objref.m_asObj); + + // !!! If this assert is fired, there are two possibilities: + // !!! 1. You are doing a type cast, e.g. *(OBJECTREF*)pObj + // !!! Instead, you should use ObjectToOBJECTREF(*(Object**)pObj), + // !!! or ObjectToSTRINGREF(*(StringObject**)pObj) + // !!! 2. There is a real GC hole here. + // !!! Either way you need to fix the code. + _ASSERTE(Thread::IsObjRefValid(&objref)); + + if ((objref.m_asObj != 0) && + ((GCHeap*)GCHeap::GetGCHeap())->IsHeapPointer( (BYTE*)this )) + { + _ASSERTE(!"Write Barrier violation. Must use SetObjectReference() to assign OBJECTREF's into the GC heap!"); + } + Thread::ObjectRefAssign(this); + + m_asObj = objref.m_asObj; + if (m_asObj != 0) { + ENABLESTRESSHEAP(); + } + return *this; +} + +//------------------------------------------------------------- +// Allows for the assignment of NULL to a OBJECTREF +//------------------------------------------------------------- + +OBJECTREF& OBJECTREF::operator=(TADDR nul) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + _ASSERTE(nul == 0); + Thread::ObjectRefAssign(this); + m_asObj = (Object*)nul; + if (m_asObj != 0) { + ENABLESTRESSHEAP(); + } + return *this; +} +#endif // DEBUG + +#ifdef _DEBUG + +void* __cdecl GCSafeMemCpy(void * dest, const void * src, size_t len) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SO_TOLERANT; + + if (!(((*(BYTE**)&dest) < g_lowest_address ) || + ((*(BYTE**)&dest) >= g_highest_address))) + { + Thread* pThread = GetThread(); + + // GCHeap::IsHeapPointer has race when called in preemptive mode. It walks the list of segments + // that can be modified by GC. Do the check below only if it is safe to do so. + if (pThread != NULL && pThread->PreemptiveGCDisabled()) + { + // Note there is memcpyNoGCRefs which will allow you to do a memcpy into the GC + // heap if you really know you don't need to call the write barrier + + _ASSERTE(!GCHeap::GetGCHeap()->IsHeapPointer((BYTE *) dest) || + !"using memcpy to copy into the GC heap, use CopyValueClass"); + } + } + return memcpyNoGCRefs(dest, src, len); +} + +#endif // _DEBUG + +// This function clears a piece of memory in a GC safe way. It makes the guarantee +// that it will clear memory in at least pointer sized chunks whenever possible. +// Unaligned memory at the beginning and remaining bytes at the end are written bytewise. +// We must make this guarantee whenever we clear memory in the GC heap that could contain +// object references. The GC or other user threads can read object references at any time, +// clearing them bytewise can result in a read on another thread getting incorrect data. +void __fastcall ZeroMemoryInGCHeap(void* mem, size_t size) +{ + WRAPPER_NO_CONTRACT; + BYTE* memBytes = (BYTE*) mem; + BYTE* endBytes = &memBytes[size]; + + // handle unaligned bytes at the beginning + while (!IS_ALIGNED(memBytes, sizeof(PTR_PTR_VOID)) && memBytes < endBytes) + *memBytes++ = 0; + + // now write pointer sized pieces + size_t nPtrs = (endBytes - memBytes) / sizeof(PTR_PTR_VOID); + PTR_PTR_VOID memPtr = (PTR_PTR_VOID) memBytes; + for (size_t i = 0; i < nPtrs; i++) + *memPtr++ = 0; + + // handle remaining bytes at the end + memBytes = (BYTE*) memPtr; + while (memBytes < endBytes) + *memBytes++ = 0; +} + +void StackTraceArray::Append(StackTraceElement const * begin, StackTraceElement const * end) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // ensure that only one thread can write to the array + EnsureThreadAffinity(); + + size_t newsize = Size() + (end - begin); + Grow(newsize); + memcpyNoGCRefs(GetData() + Size(), begin, (end - begin) * sizeof(StackTraceElement)); + MemoryBarrier(); // prevent the newsize from being reordered with the array copy + SetSize(newsize); + +#if defined(_DEBUG) + CheckState(); +#endif +} + +void StackTraceArray::AppendSkipLast(StackTraceElement const * begin, StackTraceElement const * end) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // to skip the last element, we need to replace it with the first element + // from m_pStackTrace and do it atomically if possible, + // otherwise we'll create a copy of the entire array, which is bad for performance, + // and so should not be on the main path + // + + // ensure that only one thread can write to the array + EnsureThreadAffinity(); + + assert(Size() > 0); + + StackTraceElement & last = GetData()[Size() - 1]; + if (last.PartiallyEqual(*begin)) + { + // fast path: atomic update + last.PartialAtomicUpdate(*begin); + + // append the rest + if (end - begin > 1) + Append(begin + 1, end); + } + else + { + // slow path: create a copy and append + StackTraceArray copy(*this); + GCPROTECT_BEGIN(copy); + copy.SetSize(copy.Size() - 1); + copy.Append(begin, end); + this->Swap(copy); + GCPROTECT_END(); + } + +#if defined(_DEBUG) + CheckState(); +#endif +} + +void StackTraceArray::CheckState() const +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (!m_array) + return; + + assert(GetObjectThread() == GetThread()); + + size_t size = Size(); + StackTraceElement const * p; + p = GetData(); + for (size_t i = 0; i < size; ++i) + assert(p[i].pFunc != NULL); +} + +void StackTraceArray::Grow(size_t grow_size) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory();); + } + CONTRACTL_END; + + size_t raw_size = grow_size * sizeof(StackTraceElement) + sizeof(ArrayHeader); + + if (!m_array) + { + SetArray(I1ARRAYREF(AllocatePrimitiveArray(ELEMENT_TYPE_I1, static_cast<DWORD>(raw_size)))); + SetSize(0); + SetObjectThread(); + } + else + { + if (Capacity() >= raw_size) + return; + + // allocate a new array, copy the data + size_t new_capacity = Max(Capacity() * 2, raw_size); + + _ASSERTE(new_capacity >= grow_size * sizeof(StackTraceElement) + sizeof(ArrayHeader)); + + I1ARRAYREF newarr = (I1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I1, static_cast<DWORD>(new_capacity)); + memcpyNoGCRefs(newarr->GetDirectPointerToNonObjectElements(), + GetRaw(), + Size() * sizeof(StackTraceElement) + sizeof(ArrayHeader)); + + SetArray(newarr); + } +} + +void StackTraceArray::EnsureThreadAffinity() +{ + WRAPPER_NO_CONTRACT; + + if (!m_array) + return; + + if (GetObjectThread() != GetThread()) + { + // object is being changed by a thread different from the one which created it + // make a copy of the array to prevent a race condition when two different threads try to change it + StackTraceArray copy(*this); + this->Swap(copy); + } +} + +#ifdef _MSC_VER +#pragma warning(disable: 4267) +#endif + +StackTraceArray::StackTraceArray(StackTraceArray const & rhs) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory();); + } + CONTRACTL_END; + + m_array = (I1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I1, static_cast<DWORD>(rhs.Capacity())); + + GCPROTECT_BEGIN(m_array); + Volatile<size_t> size = rhs.Size(); + memcpyNoGCRefs(GetRaw(), rhs.GetRaw(), size * sizeof(StackTraceElement) + sizeof(ArrayHeader)); + + SetSize(size); // set size to the exact value which was used when we copied the data + // another thread might have changed it at the time of copying + SetObjectThread(); // affinitize the newly created array with the current thread + GCPROTECT_END(); +} + +// Deep copies the stack trace array +void StackTraceArray::CopyFrom(StackTraceArray const & src) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory();); + } + CONTRACTL_END; + + m_array = (I1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I1, static_cast<DWORD>(src.Capacity())); + + GCPROTECT_BEGIN(m_array); + Volatile<size_t> size = src.Size(); + memcpyNoGCRefs(GetRaw(), src.GetRaw(), size * sizeof(StackTraceElement) + sizeof(ArrayHeader)); + + SetSize(size); // set size to the exact value which was used when we copied the data + // another thread might have changed it at the time of copying + SetObjectThread(); // affinitize the newly created array with the current thread + GCPROTECT_END(); +} + +#ifdef _MSC_VER +#pragma warning(default: 4267) +#endif + + +#ifdef _DEBUG +//=============================================================================== +// Code that insures that our unmanaged version of Nullable is consistant with +// the managed version Nullable<T> for all T. + +void Nullable::CheckFieldOffsets(TypeHandle nullableType) +{ + LIMITED_METHOD_CONTRACT; + +/*** + // The non-instantiated method tables like List<T> that are used + // by reflection and verification do not have correct field offsets + // but we never make instances of these anyway. + if (nullableMT->ContainsGenericVariables()) + return; +***/ + + MethodTable* nullableMT = nullableType.GetMethodTable(); + + // insure that the managed version of the table is the same as the + // unmanaged. Note that we can't do this in mscorlib.h because this + // class is generic and field layout depends on the instantiation. + + _ASSERTE(nullableMT->GetNumInstanceFields() == 2); + FieldDesc* field = nullableMT->GetApproxFieldDescListRaw(); + + _ASSERTE(strcmp(field->GetDebugName(), "hasValue") == 0); +// _ASSERTE(field->GetOffset() == offsetof(Nullable, hasValue)); + field++; + + _ASSERTE(strcmp(field->GetDebugName(), "value") == 0); +// _ASSERTE(field->GetOffset() == offsetof(Nullable, value)); +} +#endif + +//=============================================================================== +// Returns true if nullableMT is Nullable<T> for T is equivalent to paramMT + +BOOL Nullable::IsNullableForTypeHelper(MethodTable* nullableMT, MethodTable* paramMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + if (!nullableMT->IsNullable()) + return FALSE; + + // we require the parameter types to be equivalent + return TypeHandle(paramMT).IsEquivalentTo(nullableMT->GetInstantiation()[0]); +} + +//=============================================================================== +// Returns true if nullableMT is Nullable<T> for T == paramMT + +BOOL Nullable::IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable* paramMT) +{ + LIMITED_METHOD_CONTRACT; + if (!nullableMT->IsNullable()) + return FALSE; + + // we require an exact match of the parameter types + return TypeHandle(paramMT) == nullableMT->GetInstantiation()[0]; +} + +//=============================================================================== +CLR_BOOL* Nullable::HasValueAddr(MethodTable* nullableMT) { + + LIMITED_METHOD_CONTRACT; + + _ASSERTE(strcmp(nullableMT->GetApproxFieldDescListRaw()[0].GetDebugName(), "hasValue") == 0); + _ASSERTE(nullableMT->GetApproxFieldDescListRaw()[0].GetOffset() == 0); + return (CLR_BOOL*) this; +} + +//=============================================================================== +void* Nullable::ValueAddr(MethodTable* nullableMT) { + + LIMITED_METHOD_CONTRACT; + + _ASSERTE(strcmp(nullableMT->GetApproxFieldDescListRaw()[1].GetDebugName(), "value") == 0); + return (((BYTE*) this) + nullableMT->GetApproxFieldDescListRaw()[1].GetOffset()); +} + +//=============================================================================== +// Special Logic to box a nullable<T> as a boxed<T> + +OBJECTREF Nullable::Box(void* srcPtr, MethodTable* nullableMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + FAULT_NOT_FATAL(); // FIX_NOW: why do we need this? + + Nullable* src = (Nullable*) srcPtr; + + _ASSERTE(IsNullableType(nullableMT)); + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!nullableMT->ContainsGenericVariables()); + + if (!*src->HasValueAddr(nullableMT)) + return NULL; + + OBJECTREF obj = 0; + GCPROTECT_BEGININTERIOR (src); + MethodTable* argMT = nullableMT->GetInstantiation()[0].GetMethodTable(); + obj = argMT->Allocate(); + CopyValueClass(obj->UnBox(), src->ValueAddr(nullableMT), argMT, obj->GetAppDomain()); + GCPROTECT_END (); + + return obj; +} + +//=============================================================================== +// Special Logic to unbox a boxed T as a nullable<T> + +BOOL Nullable::UnBox(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + Nullable* dest = (Nullable*) destPtr; + BOOL fRet = TRUE; + + // We should only get here if we are unboxing a T as a Nullable<T> + _ASSERTE(IsNullableType(destMT)); + + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!destMT->ContainsGenericVariables()); + + if (boxedVal == NULL) + { + // Logically we are doing *dest->HasValueAddr(destMT) = false; + // We zero out the whole structure becasue it may contain GC references + // and these need to be initialized to zero. (could optimize in the non-GC case) + InitValueClass(destPtr, destMT); + fRet = TRUE; + } + else + { + GCPROTECT_BEGIN(boxedVal); + if (!IsNullableForType(destMT, boxedVal->GetMethodTable())) + { + // For safety's sake, also allow true nullables to be unboxed normally. + // This should not happen normally, but we want to be robust + if (destMT->IsEquivalentTo(boxedVal->GetMethodTable())) + { + CopyValueClass(dest, boxedVal->GetData(), destMT, boxedVal->GetAppDomain()); + fRet = TRUE; + } + else + { + fRet = FALSE; + } + } + else + { + *dest->HasValueAddr(destMT) = true; + CopyValueClass(dest->ValueAddr(destMT), boxedVal->UnBox(), boxedVal->GetMethodTable(), boxedVal->GetAppDomain()); + fRet = TRUE; + } + GCPROTECT_END(); + } + return fRet; +} + +//=============================================================================== +// Special Logic to unbox a boxed T as a nullable<T> +// Does not handle type equivalence (may conservatively return FALSE) +BOOL Nullable::UnBoxNoGC(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + Nullable* dest = (Nullable*) destPtr; + + // We should only get here if we are unboxing a T as a Nullable<T> + _ASSERTE(IsNullableType(destMT)); + + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!destMT->ContainsGenericVariables()); + + if (boxedVal == NULL) + { + // Logically we are doing *dest->HasValueAddr(destMT) = false; + // We zero out the whole structure becasue it may contain GC references + // and these need to be initialized to zero. (could optimize in the non-GC case) + InitValueClass(destPtr, destMT); + } + else + { + if (!IsNullableForTypeNoGC(destMT, boxedVal->GetMethodTable())) + { + // For safety's sake, also allow true nullables to be unboxed normally. + // This should not happen normally, but we want to be robust + if (destMT == boxedVal->GetMethodTable()) + { + CopyValueClass(dest, boxedVal->GetData(), destMT, boxedVal->GetAppDomain()); + return TRUE; + } + return FALSE; + } + + *dest->HasValueAddr(destMT) = true; + CopyValueClass(dest->ValueAddr(destMT), boxedVal->UnBox(), boxedVal->GetMethodTable(), boxedVal->GetAppDomain()); + } + return TRUE; +} + +//=============================================================================== +// Special Logic to unbox a boxed T as a nullable<T> into an argument +// specified by the argDest. +// Does not handle type equivalence (may conservatively return FALSE) +BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, MethodTable* destMT) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + +#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + if (argDest->IsStructPassedInRegs()) + { + // We should only get here if we are unboxing a T as a Nullable<T> + _ASSERTE(IsNullableType(destMT)); + + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!destMT->ContainsGenericVariables()); + + if (boxedVal == NULL) + { + // Logically we are doing *dest->HasValueAddr(destMT) = false; + // We zero out the whole structure becasue it may contain GC references + // and these need to be initialized to zero. (could optimize in the non-GC case) + InitValueClassArg(argDest, destMT); + } + else + { + if (!IsNullableForTypeNoGC(destMT, boxedVal->GetMethodTable())) + { + // For safety's sake, also allow true nullables to be unboxed normally. + // This should not happen normally, but we want to be robust + if (destMT == boxedVal->GetMethodTable()) + { + CopyValueClassArg(argDest, boxedVal->GetData(), destMT, boxedVal->GetAppDomain(), 0); + return TRUE; + } + return FALSE; + } + + Nullable* dest = (Nullable*)argDest->GetStructGenRegDestinationAddress(); + *dest->HasValueAddr(destMT) = true; + int destOffset = (BYTE*)dest->ValueAddr(destMT) - (BYTE*)dest; + CopyValueClassArg(argDest, boxedVal->UnBox(), boxedVal->GetMethodTable(), boxedVal->GetAppDomain(), destOffset); + } + return TRUE; + } + +#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING + + return UnBoxNoGC(argDest->GetDestinationAddress(), boxedVal, destMT); +} + +//=============================================================================== +// Special Logic to unbox a boxed T as a nullable<T> +// Does not do any type checks. +void Nullable::UnBoxNoCheck(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + Nullable* dest = (Nullable*) destPtr; + + // We should only get here if we are unboxing a T as a Nullable<T> + _ASSERTE(IsNullableType(destMT)); + + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!destMT->ContainsGenericVariables()); + + if (boxedVal == NULL) + { + // Logically we are doing *dest->HasValueAddr(destMT) = false; + // We zero out the whole structure becasue it may contain GC references + // and these need to be initialized to zero. (could optimize in the non-GC case) + InitValueClass(destPtr, destMT); + } + else + { + if (IsNullableType(boxedVal->GetMethodTable())) + { + // For safety's sake, also allow true nullables to be unboxed normally. + // This should not happen normally, but we want to be robust + CopyValueClass(dest, boxedVal->GetData(), destMT, boxedVal->GetAppDomain()); + } + + *dest->HasValueAddr(destMT) = true; + CopyValueClass(dest->ValueAddr(destMT), boxedVal->UnBox(), boxedVal->GetMethodTable(), boxedVal->GetAppDomain()); + } +} + +//=============================================================================== +// a boxed Nullable<T> should either be null or a boxed T, but sometimes it is +// useful to have a 'true' boxed Nullable<T> (that is it has two fields). This +// function returns a 'normalized' version of this pointer. + +OBJECTREF Nullable::NormalizeBox(OBJECTREF obj) { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (obj != NULL) { + MethodTable* retMT = obj->GetMethodTable(); + if (Nullable::IsNullableType(retMT)) + obj = Nullable::Box(obj->GetData(), retMT); + } + return obj; +} + + +void ThreadBaseObject::SetInternal(Thread *it) +{ + WRAPPER_NO_CONTRACT; + + // only allow a transition from NULL to non-NULL + _ASSERTE((m_InternalThread == NULL) && (it != NULL)); + m_InternalThread = it; + + // Now the native Thread will only be destroyed after the managed Thread is collected. + // Tell the GC that the managed Thread actually represents much more memory. + GCInterface::NewAddMemoryPressure(sizeof(Thread)); +} + +void ThreadBaseObject::ClearInternal() +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_InternalThread != NULL); + m_InternalThread = NULL; + GCInterface::NewRemoveMemoryPressure(sizeof(Thread)); +} + +#endif // #ifndef DACCESS_COMPILE + + +StackTraceElement const & StackTraceArray::operator[](size_t index) const +{ + WRAPPER_NO_CONTRACT; + return GetData()[index]; +} + +StackTraceElement & StackTraceArray::operator[](size_t index) +{ + WRAPPER_NO_CONTRACT; + return GetData()[index]; +} + +#if !defined(DACCESS_COMPILE) +// Define the lock used to access stacktrace from an exception object +SpinLock g_StackTraceArrayLock; + +void ExceptionObject::SetStackTrace(StackTraceArray const & stackTrace, PTRARRAYREF dynamicMethodArray) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + + Thread *m_pThread = GetThread(); + SpinLock::AcquireLock(&g_StackTraceArrayLock, SPINLOCK_THREAD_PARAM_ONLY_IN_SOME_BUILDS); + + SetObjectReference((OBJECTREF*)&_stackTrace, (OBJECTREF)stackTrace.Get(), GetAppDomain()); + SetObjectReference((OBJECTREF*)&_dynamicMethods, (OBJECTREF)dynamicMethodArray, GetAppDomain()); + + SpinLock::ReleaseLock(&g_StackTraceArrayLock, SPINLOCK_THREAD_PARAM_ONLY_IN_SOME_BUILDS); + +} + +void ExceptionObject::SetNullStackTrace() +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + + Thread *m_pThread = GetThread(); + SpinLock::AcquireLock(&g_StackTraceArrayLock, SPINLOCK_THREAD_PARAM_ONLY_IN_SOME_BUILDS); + + I1ARRAYREF stackTraceArray = NULL; + PTRARRAYREF dynamicMethodArray = NULL; + + SetObjectReference((OBJECTREF*)&_stackTrace, (OBJECTREF)stackTraceArray, GetAppDomain()); + SetObjectReference((OBJECTREF*)&_dynamicMethods, (OBJECTREF)dynamicMethodArray, GetAppDomain()); + + SpinLock::ReleaseLock(&g_StackTraceArrayLock, SPINLOCK_THREAD_PARAM_ONLY_IN_SOME_BUILDS); +} + +#endif // !defined(DACCESS_COMPILE) + +void ExceptionObject::GetStackTrace(StackTraceArray & stackTrace, PTRARRAYREF * outDynamicMethodArray /*= NULL*/) const +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + +#if !defined(DACCESS_COMPILE) + Thread *m_pThread = GetThread(); + SpinLock::AcquireLock(&g_StackTraceArrayLock, SPINLOCK_THREAD_PARAM_ONLY_IN_SOME_BUILDS); +#endif // !defined(DACCESS_COMPILE) + + StackTraceArray temp(_stackTrace); + stackTrace.Swap(temp); + + if (outDynamicMethodArray != NULL) + { + *outDynamicMethodArray = _dynamicMethods; + } + +#if !defined(DACCESS_COMPILE) + SpinLock::ReleaseLock(&g_StackTraceArrayLock, SPINLOCK_THREAD_PARAM_ONLY_IN_SOME_BUILDS); +#endif // !defined(DACCESS_COMPILE) + +} |