Remove Read/WriteProcessMemory from PAL. (#8655)
[platform/upstream/coreclr.git] / src / debug / di / rspriv.inl
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 //*****************************************************************************
5 // File: rspriv.inl
6 // 
7
8 //
9 // Inline functions for rspriv.h
10 //
11 //*****************************************************************************
12
13 #ifndef RSPRIV_INL_
14 #define RSPRIV_INL_
15
16 #include "rspriv.h"
17
18 // Get the native pipeline object, which resides on the Win32EventThread.
19 inline
20 INativeEventPipeline * CordbWin32EventThread::GetNativePipeline()
21 {
22     return m_pNativePipeline;
23 }
24
25
26 // True if we're interop-debugging, else false.
27 // Note, we include this even in Non-interop builds because there are runtime checks throughout the APIs
28 // that certain operations only succeed/fail in interop-debugging. 
29 inline
30 bool CordbProcess::IsInteropDebugging()
31 {
32 #ifdef FEATURE_INTEROP_DEBUGGING
33     return (m_state & PS_WIN32_ATTACHED) != 0;
34 #else
35     return false;
36 #endif // FEATURE_INTEROP_DEBUGGING
37 }
38
39
40 //-----------------------------------------------------------------------------
41 // Get the ShimProcess object.
42 //
43 // Returns:
44 //    ShimProcess object if available; else NULL.
45 //
46 // Notes: 
47 //    This shim has V2 emulation logic. 
48 //    If we have no ShimProcess object, then we're in a V3 codepath.
49 //    @dbgtodo - eventually, remove all emulation and this function.
50 //-----------------------------------------------------------------------------
51 inline
52 ShimProcess * CordbProcess::GetShim() 
53 {
54     return m_pShim;
55 };
56
57
58
59 //---------------------------------------------------------------------------------------
60 // Helper to read a structure from the target
61 //
62 // Arguments:
63 //    T - type of structure to read.
64 //    pRemotePtr - remote pointer into target (src).
65 //    pLocalBuffer - local buffer to copy into (Dest). 
66 //
67 // Return Value:
68 //    Returns S_OK on success, in the event of a short read returns ERROR_PARTIAL_COPY
69 //
70 // Notes:
71 //    This just does a raw Byte copy, but does not do any Marshalling. 
72 //    This fails if any part of the buffer can't be read. 
73 //
74 //---------------------------------------------------------------------------------------
75 template<typename T>
76 HRESULT CordbProcess::SafeReadStruct(CORDB_ADDRESS pRemotePtr, T * pLocalBuffer)
77 {
78     HRESULT hr = S_OK;
79     EX_TRY
80     {
81         TargetBuffer tb(pRemotePtr, sizeof(T));
82         SafeReadBuffer(tb, (PBYTE) pLocalBuffer);
83     } 
84     EX_CATCH_HRESULT(hr) ;
85     return hr;
86 }
87
88 //---------------------------------------------------------------------------------------
89 // Destructor for RSInitHolder. Will safely neuter and release the object.
90 template<class T> inline
91 RSInitHolder<T>::~RSInitHolder()
92 {
93     if (m_pObject != NULL)
94     {
95         CordbProcess * pProcess = m_pObject->GetProcess();
96         RSLockHolder lockHolder(pProcess->GetProcessLock());
97         
98         m_pObject->Neuter();
99
100         // Can't explicitly call 'delete' because somebody may have taken a reference.
101         m_pObject.Clear();
102     }
103 }
104
105 //---------------------------------------------------------------------------------------
106 // Helper to write a structure to the target
107 //
108 // Arguments:
109 //    T - type of structure to read.
110 //    pRemotePtr - remote pointer into target (dest).
111 //    pLocalBuffer - local buffer to write (Src). 
112 //
113 // Return Value:
114 //    Returns S_OK on success, in the event of a short write returns ERROR_PARTIAL_COPY
115 //
116 // Notes:
117 //    This just does a raw Byte copy into the Target, but does not do any Marshalling. 
118 //    This fails if any part of the buffer can't be written. 
119 //
120 //---------------------------------------------------------------------------------------
121 template<typename T> inline
122 HRESULT CordbProcess::SafeWriteStruct(CORDB_ADDRESS pRemotePtr, const T* pLocalBuffer)
123 {
124     HRESULT hr= S_OK;
125     EX_TRY 
126     {
127         TargetBuffer tb(pRemotePtr, sizeof(T));
128         SafeWriteBuffer(tb, (BYTE *) (pLocalBuffer));
129     } 
130     EX_CATCH_HRESULT(hr);
131     return hr;
132 }
133
134 inline
135 CordbModule *CordbJITILFrame::GetModule()
136 {
137     return (m_ilCode->GetModule());
138 }
139
140 inline
141 CordbAppDomain *CordbJITILFrame::GetCurrentAppDomain()
142 {
143     return (m_nativeFrame->GetCurrentAppDomain());
144 }
145
146 //-----------------------------------------------------------------------------
147 // Called to notify that we must flush DAC
148 //-----------------------------------------------------------------------------
149 inline
150 void CordbProcess::ForceDacFlush()
151 {
152     // We need to take the process lock here because otherwise we could race with the Arrowhead stackwalking 
153     // APIs.  The Arrowhead stackwalking APIs check the flush counter and refresh all the state if necessary.
154     // However, while one thread is refreshing the state of the stackwalker, another thread may come in
155     // and force a flush.  That's why we need to take a process lock before we flush.  We need to synchronize
156     // with other threads which are using DAC memory.
157     RSLockHolder lockHolder(GetProcessLock());
158
159     // For Mac debugging, it is not safe to call into the DAC once code:INativeEventPipeline::TerminateProcess 
160     // is called.  Also, we must check m_exiting under the process lock.
161     if (!m_exiting)
162     {
163         if (m_pDacPrimitives != NULL)
164         {
165             STRESS_LOG1(LF_CORDB, LL_INFO1000, "Flush() - old counter: %d\n", m_flushCounter);
166             m_flushCounter++;
167             HRESULT hr = S_OK;
168             EX_TRY
169             {
170                 m_pDacPrimitives->FlushCache();
171             }
172             EX_CATCH_HRESULT(hr);
173             SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
174         }
175     }
176 }
177
178
179 inline
180 CordbFunction *CordbJITILFrame::GetFunction()
181 {
182     return m_nativeFrame->m_nativeCode->GetFunction();
183 }
184
185 //-----------------------------------------------------------------------------
186 // Helpers to assert threading semantics.
187 //-----------------------------------------------------------------------------
188 inline bool IsWin32EventThread(CordbProcess * p)
189 {
190     _ASSERTE(p!= NULL);
191     return p->IsWin32EventThread();
192 }
193
194 inline bool IsRCEventThread(Cordb* p)
195 {
196     _ASSERTE(p!= NULL);
197     return (p->m_rcEventThread != NULL) && p->m_rcEventThread->IsRCEventThread();
198 }
199
200
201
202 //-----------------------------------------------------------------------------
203 // StopContinueHolder. Ensure that we're synced during a certain region.
204 //-----------------------------------------------------------------------------
205 inline HRESULT StopContinueHolder::Init(CordbProcess * p)
206 {
207     _ASSERTE(p != NULL);
208     LOG((LF_CORDB, LL_INFO100000, "Doing RS internal Stop\n"));
209     HRESULT hr = p->StopInternal(INFINITE, VMPTR_AppDomain::NullPtr());
210     if ((hr == CORDBG_E_PROCESS_TERMINATED) || SUCCEEDED(hr))
211     {
212         // Better be synced after calling Stop!
213         _ASSERTE(p->GetSynchronized());
214         m_p = p;
215     }
216
217     return hr;
218 };
219
220 inline StopContinueHolder::~StopContinueHolder()
221 {
222     // If Init() failed to call Stop, then don't call continue
223     if (m_p == NULL)
224         return;
225
226     HRESULT hr;
227     LOG((LF_CORDB, LL_INFO100000, "Doing RS internal Continue\n"));
228     hr = m_p->ContinueInternal(false);
229     SIMPLIFYING_ASSUMPTION(
230         (hr == CORDBG_E_PROCESS_TERMINATED) ||
231         (hr == CORDBG_E_PROCESS_DETACHED) ||
232         (hr == CORDBG_E_OBJECT_NEUTERED) ||
233         (hr == E_ACCESSDENIED) || //Sadly in rare cases we leak this error code instead of PROCESS_TERMINATED
234                                   //See Dev10 bug 872621
235         SUCCEEDED(hr));
236 }
237
238 //-----------------------------------------------------------------------------
239 // Neutering on the base object
240 //-----------------------------------------------------------------------------
241 inline
242 void CordbCommonBase::Neuter()
243 {
244     LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object neutered: this=%p, id=%p\n", this, m_id));
245     m_fIsNeutered = 1;
246 }
247
248 // Unsafe neuter for an object that's already dead. Only use this if you know exactly what you're doing.
249 // The point here is that we can mark the object neutered even though we may not hold the stop-go lock.
250 inline
251 void CordbCommonBase::UnsafeNeuterDeadObject()
252 {
253     LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object neutered: this=%p, id=%p\n", this, m_id));
254     m_fIsNeutered = 1;
255 }
256
257
258 //-----------------------------------------------------------------------------
259 // Reference Counting
260 //-----------------------------------------------------------------------------
261 inline
262 void CordbCommonBase::InternalAddRef()
263 {
264     CONSISTENCY_CHECK_MSGF((m_RefCount & CordbBase_InternalRefCountMask) != (CordbBase_InternalRefCountMax),
265         ("Internal AddRef overlow, External Count = %d,\n'%s' @ 0x%p",
266         (m_RefCount >> CordbBase_ExternalRefCountShift), this->DbgGetName(), this));
267
268     // Since the internal ref-count is the lower bits, and we know we'll never overflow ;)
269     // we can just do an interlocked increment on the whole 32 bits.
270 #ifdef TRACK_OUTSTANDING_OBJECTS
271     MixedRefCountUnsigned Count =
272 #endif
273
274     InterlockedIncrement64((MixedRefCountSigned*) &m_RefCount);
275
276
277 #ifdef _DEBUG_IMPL
278
279     // For leak detection in debug builds, track all internal references.
280     InterlockedIncrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
281 #endif
282
283 #ifdef TRACK_OUTSTANDING_OBJECTS
284     if ((Count & CordbBase_InternalRefCountMask) != 1)
285     {
286         return;
287     }
288
289     LONG i;
290
291     for (i = 0; i < Cordb::s_DbgMemOutstandingObjectMax; i++)
292     {
293         if (Cordb::s_DbgMemOutstandingObjects[i] == NULL)
294         {
295             if (InterlockedCompareExchangeT(&(Cordb::s_DbgMemOutstandingObjects[i]), (LPVOID) this, NULL) == NULL)
296             {
297                 return;
298             }
299         }
300     }
301
302     do
303     {
304         i = Cordb::s_DbgMemOutstandingObjectMax + 1;
305     }
306     while ((i < MAX_TRACKED_OUTSTANDING_OBJECTS) &&
307            (InterlockedCompareExchange(&Cordb::s_DbgMemOutstandingObjectMax, i, i - 1) != (i - 1)));
308
309     if (i < MAX_TRACKED_OUTSTANDING_OBJECTS)
310     {
311         Cordb::s_DbgMemOutstandingObjects[i] = this;
312     }
313 #endif
314
315 }
316
317 // Derived versions of AddRef / Release will call these.
318 // External AddRef.
319 inline
320 ULONG CordbCommonBase::BaseAddRef()
321 {
322     Volatile<MixedRefCountUnsigned> ref;
323     MixedRefCountUnsigned refNew;
324     ExternalRefCount cExternalCount;
325
326     // Compute what refNew ought to look like; and then If m_RefCount hasn't changed on us
327     // (via another thread), then stash the new one in.
328     do
329     {
330         ref = m_RefCount;
331
332         cExternalCount = (ExternalRefCount) (ref >> CordbBase_ExternalRefCountShift);
333
334         if (cExternalCount == CordbBase_InternalRefCountMax)
335         {
336             CONSISTENCY_CHECK_MSGF(false, ("Overflow in External AddRef. Internal Count =%d,\n'%s' @ 0x%p",
337                 (ref & CordbBase_InternalRefCountMask), this->DbgGetName(), this));
338
339             // Ignore any AddRefs beyond this... This will screw up Release(), but we're
340             // probably already so screwed it wouldn't matter.
341             return cExternalCount;
342         }
343
344         cExternalCount++;
345
346         refNew = (((MixedRefCountUnsigned)cExternalCount) << CordbBase_ExternalRefCountShift) | (ref & CordbBase_InternalRefCountMask);
347     }
348     while ((MixedRefCountUnsigned)InterlockedCompareExchange64((MixedRefCountSigned*)&m_RefCount, refNew, ref) != ref);
349
350     return cExternalCount;
351 }
352
353 // Do an AddRef against the External count. This is a semantics issue.
354 // We use this when an internal component Addrefs out-parameters (which Cordbg will call Release on).
355 inline
356 void CordbCommonBase::ExternalAddRef()
357 {
358     // Call on BaseAddRef() to avoid any asserts that prevent stuff from inside the RS from bumping
359     // up the external ref count.
360     BaseAddRef();
361 }
362
363 inline
364 void CordbCommonBase::InternalRelease()
365 {
366     CONSISTENCY_CHECK_MSGF((m_RefCount & CordbBase_InternalRefCountMask) != 0,
367         ("Internal Release underflow, External Count = %d,\n'%s' @ 0x%p",
368         (m_RefCount >> CordbBase_ExternalRefCountShift), this->DbgGetName(), this));
369
370 #ifdef _DEBUG_IMPL
371     // For leak detection in debug builds, track all internal references.
372     InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
373 #endif
374
375
376
377     // The internal count is in the low 16 bits, and we know that we'll never underflow the internal
378     // release. ;)
379     // Furthermore we know that ExternalRelease  will prevent us from underflowing the external release count.
380     // Thus we can just do an simple decrement here, and compare against 0x00000000 (which is the value
381     // when both the Internal + External counts are at 0)
382     MixedRefCountSigned cRefCount = InterlockedDecrement64((MixedRefCountSigned*) &m_RefCount);
383
384 #ifdef TRACK_OUTSTANDING_OBJECTS
385     if ((cRefCount & CordbBase_InternalRefCountMask) == 0)
386     {
387         for (LONG i = 0; i < Cordb::s_DbgMemOutstandingObjectMax; i++)
388         {
389             if (Cordb::s_DbgMemOutstandingObjects[i] == this)
390             {
391                 Cordb::s_DbgMemOutstandingObjects[i] = NULL;
392                 break;
393             }
394         }
395     }
396 #endif
397
398
399     if (cRefCount == 0x00000000)
400     {
401         delete this;
402     }
403 }
404
405 // Do an external release.
406 inline
407 ULONG CordbCommonBase::BaseRelease()
408 {
409     Volatile<MixedRefCountUnsigned> ref;
410     MixedRefCountUnsigned refNew;
411     ExternalRefCount cExternalCount;
412
413     // Compute what refNew ought to look like; and then If m_RefCount hasn't changed on us
414     // (via another thread), then stash the new one in.
415     do
416     {
417         ref = m_RefCount;
418
419         cExternalCount = (ExternalRefCount) (ref >> CordbBase_ExternalRefCountShift);
420
421         if (cExternalCount == 0)
422         {
423             CONSISTENCY_CHECK_MSGF(false, ("Underflow in External Release. Internal Count = %d\n'%s' @ 0x%p",
424                 (ref & CordbBase_InternalRefCountMask), this->DbgGetName(), this));
425
426             // Ignore any Releases beyond this... This will screw up Release(), but we're
427             // probably already so screwed it wouldn't matter.
428             // It's very important that we don't let the release count go negative (both
429             // Releases assumes this when deciding whether to delete)
430             return 0;
431         }
432
433         cExternalCount--;
434
435         refNew = (((MixedRefCountUnsigned) cExternalCount) << CordbBase_ExternalRefCountShift) | (ref & CordbBase_InternalRefCountMask);
436     }
437     while ((MixedRefCountUnsigned)InterlockedCompareExchange64((MixedRefCountSigned*)&m_RefCount, refNew, ref) != ref);
438
439     // If the external count just dropped to 0, then this object can be neutered.
440     if (cExternalCount == 0)
441     {
442         m_fNeuterAtWill = 1;
443     }
444
445     if (refNew == 0)
446     {
447         delete this;
448         return 0;
449     }
450     return cExternalCount;
451
452 }
453
454
455 inline ULONG CordbCommonBase::BaseAddRefEnforceExternal()
456 {
457     // External refs shouldn't be called while in the RS
458 #ifdef RSCONTRACTS   
459     DbgRSThread * pThread = DbgRSThread::GetThread();
460     CONSISTENCY_CHECK_MSGF(!pThread->IsInRS(), 
461         ("External addref for pThis=0x%p, name='%s' called from within RS", 
462             this, this->DbgGetName()
463         ));
464 #endif
465     return (BaseAddRef());
466
467 }
468
469 inline ULONG CordbCommonBase::BaseReleaseEnforceExternal()
470 {
471 #ifdef RSCONTRACTS   
472     DbgRSThread * pThread = DbgRSThread::GetThread();
473     
474     CONSISTENCY_CHECK_MSGF(!pThread->IsInRS(), 
475         ("External release for pThis=0x%p, name='%s' called from within RS", 
476             this, this->DbgGetName()
477         ));
478 #endif
479
480     return (BaseRelease());
481 }
482
483
484
485 //-----------------------------------------------------------------------------
486 // Locks
487 //-----------------------------------------------------------------------------
488
489 // Base class
490 #ifdef _DEBUG
491 inline bool RSLock::HasLock()
492 {
493     CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
494     return m_tidOwner == ::GetCurrentThreadId();
495 }
496 #endif
497
498 #ifdef _DEBUG
499 // Ctor+  Dtor are only used for asserts.
500 inline RSLock::RSLock()
501 {
502     m_eAttr = cLockUninit;
503     m_tidOwner = (DWORD)-1;
504 };
505
506 inline RSLock::~RSLock()
507 {
508     // If this lock is still ininitialized, then no body ever deleted the critical section
509     // for it and we're leaking.
510     CONSISTENCY_CHECK_MSGF(!IsInit(), ("Leaking Critical section for RS Lock '%s'", m_szTag));
511 }
512 #endif
513
514
515 // Initialize a lock.
516 inline void RSLock::Init(const char * szTag, int eAttr, ERSLockLevel level)
517 {
518     CONSISTENCY_CHECK_MSGF(!IsInit(), ("RSLock '%s' already inited", szTag));
519 #ifdef _DEBUG
520     m_szTag = szTag;
521     m_eAttr = eAttr;
522     m_count = 0;
523     m_level = level;
524
525     // Must be either re-entrant xor flat. (not neither; not both)
526     _ASSERTE(IsReentrant() ^ ((m_eAttr & cLockFlat) == cLockFlat));
527 #endif
528     _ASSERTE((level >= 0) && (level <= RSLock::LL_MAX));
529
530     _ASSERTE(IsInit());
531
532     InitializeCriticalSection(&m_lock);
533 }
534
535 // Cleanup a lock.
536 inline void RSLock::Destroy()
537 {
538     CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
539     DeleteCriticalSection(&m_lock);
540
541 #ifdef _DEBUG
542     m_eAttr = cLockUninit; // No longer initialized.
543     _ASSERTE(!IsInit());
544 #endif
545 }
546
547 inline void RSLock::Lock()
548 {
549     CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
550
551 #ifdef RSCONTRACTS
552     DbgRSThread * pThread = DbgRSThread::GetThread();
553     pThread->NotifyTakeLock(this);
554 #endif
555
556     EnterCriticalSection(&m_lock);
557 #ifdef _DEBUG
558     m_tidOwner = ::GetCurrentThreadId();
559     m_count++;
560
561     // Either count == 1 or we're re-entrant.
562     _ASSERTE((m_count == 1) || (m_eAttr == cLockReentrant));
563 #endif
564 }
565
566 inline void RSLock::Unlock()
567 {
568     CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
569
570 #ifdef _DEBUG
571     _ASSERTE(HasLock());
572     m_count--;
573     _ASSERTE(m_count >= 0);
574     if (m_count == 0)
575     {
576         m_tidOwner = (DWORD)-1;
577     }
578 #endif
579
580 #ifdef RSCONTRACTS
581     // NotifyReleaseLock needs to be called before we release the lock.
582     // Note that HasLock()==false at this point. NotifyReleaseLock relies on that.
583     DbgRSThread * pThread = DbgRSThread::GetThread();
584     pThread->NotifyReleaseLock(this);
585 #endif
586
587     LeaveCriticalSection(&m_lock);
588 }
589
590 template <class T>
591 inline T* CordbSafeHashTable<T>::GetBase(ULONG_PTR id, BOOL fFab)
592 {
593     return static_cast<T*>(UnsafeGetBase(id, fFab));
594 }
595
596 template <class T>
597 inline T* CordbSafeHashTable<T>::GetBaseOrThrow(ULONG_PTR id, BOOL fFab)
598 {
599     T* pResult = GetBase(id, fFab);
600     if (pResult == NULL)
601     {
602         ThrowHR(E_INVALIDARG);
603     }
604     else
605     {
606         return pResult;
607     }
608 }
609
610 // Copy the contents of the hash to an strong-ref array
611 //
612 // Arguments:
613 //    pArray - array to allocate storage and copy to
614 //
615 // Assumptions:
616 //    Caller locks.
617 //
618 // Notes:
619 //    Array takes strong internal references. 
620 //    This can be useful for dancing around locks; eg: If we want to iterate on a hash
621 //    and do an operation that requires a lock that can't be held when iterating.
622 //    (Example: Neuter needs Big stop-go lock; Hash is protected by little Process-lock).
623 //
624 template <class T>
625 inline void CordbSafeHashTable<T>::CopyToArray(RSPtrArray<T> * pArray)
626 {
627     // Assumes caller has necessary locks to iterate
628     UINT32 count = GetCount();
629     pArray->AllocOrThrow(count);
630     
631
632     HASHFIND find;
633     UINT32 idx = 0;
634
635     T * pCordbBase = FindFirst(&find);
636     while(idx < count)
637     {
638         pArray->Assign(idx, pCordbBase);
639         idx++;
640         pCordbBase = FindNext(&find);
641     }
642
643     // Assert is at end.
644     _ASSERTE(pCordbBase == NULL);  
645 }
646
647 // Empty the contents of the hash to an array. Array gets ownersship.
648 //
649 // Arguments: 
650 //    pArray - array to allocate and get ownership
651 //
652 // Assumptions:
653 //    Caller locks.
654 //
655 // Notes:
656 //    Hashtable will be empty after this.
657 template <class T>
658 inline void CordbSafeHashTable<T>::TransferToArray(RSPtrArray<T> * pArray)
659 {
660     // Assumes caller has necessary locks
661     
662     HASHFIND find;
663     UINT32 count = GetCount();
664     UINT32 idx = 0;
665
666     pArray->AllocOrThrow(count);
667     
668     while(idx < count)
669     {        
670         T * pCordbBase = FindFirst(&find);
671         _ASSERTE(pCordbBase != NULL);
672         pArray->Assign(idx, pCordbBase);
673
674         idx++;
675         // We're removing while iterating the collection.
676         // But we reset the iteration each time by calling FindFirst.
677         RemoveBase((ULONG_PTR)pCordbBase->m_id); // this will call release, adjust GetCount()
678     }
679
680     // Assert is at end.
681     _ASSERTE(GetCount() == 0);  
682 }
683
684 //
685 // Neuter all elements in the hash table and empty the hash.
686 // 
687 // Arguments:
688 //    pLock - lock required to iterate through hash.
689 //
690 // Assumptions:
691 //    Caller ensured it's safe to Neuter. 
692 //    Caller has locked the hash.
693 //
694 template <class T>
695 inline void CordbSafeHashTable<T>::NeuterAndClear(RSLock * pLock)
696 {
697     _ASSERTE(pLock->HasLock());
698     
699     HASHFIND find;
700     UINT32 count = GetCount();
701     UINT32 idx = 0;
702
703     while(idx < count)
704     {        
705         T * pCordbBase = FindFirst(&find);
706         _ASSERTE(pCordbBase != NULL);
707
708         // Using this Validate to help track down bug DevDiv bugs 739406
709         pCordbBase->ValidateObject();
710         pCordbBase->Neuter();
711         idx++;
712
713         // We're removing while iterating the collection.
714         // But we reset the iteration each time by calling FindFirst.
715         RemoveBase((ULONG_PTR)pCordbBase->m_id); // this will call release, adjust GetCount()
716     }
717
718     // Assert is at end.
719     _ASSERTE(GetCount() == 0);  
720 }
721
722
723 #endif  // RSPRIV_INL_