Merge pull request #2878 from jashook/x86_exclude_update
[platform/upstream/coreclr.git] / src / debug / di / hash.cpp
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: hash.cpp
6 // 
7
8 //
9 //*****************************************************************************
10 #include "stdafx.h"
11
12 /* ------------------------------------------------------------------------- *
13  * Hash Table class
14  * ------------------------------------------------------------------------- */
15
16 CordbHashTable::~CordbHashTable()
17 {
18     HASHFIND    find;
19
20     for (CordbHashEntry *entry = (CordbHashEntry *) FindFirstEntry(&find);
21          entry != NULL;
22          entry = (CordbHashEntry *) FindNextEntry(&find))
23         entry->pBase->InternalRelease();
24 }
25
26 HRESULT CordbHashTable::UnsafeAddBase(CordbBase *pBase)
27 {
28     AssertIsProtected();
29     DbgIncChangeCount();
30
31     HRESULT hr = S_OK;
32
33     if (!m_initialized)
34     {
35         HRESULT res = NewInit(m_iBuckets, sizeof(CordbHashEntry), 0xffff);
36
37         if (res != S_OK)
38         {
39             return res;
40         }
41
42         m_initialized = true;
43     }
44
45     CordbHashEntry *entry = (CordbHashEntry *) Add(HASH((ULONG_PTR)pBase->m_id));
46
47     if (entry == NULL)
48     {
49         hr = E_FAIL;
50     }
51     else
52     {
53         entry->pBase = pBase;
54         m_count++;
55         pBase->InternalAddRef();
56     }
57     return hr;
58 }
59 bool CordbHashTable::IsInitialized()
60 {
61     return m_initialized;
62 }
63
64 CordbBase *CordbHashTable::UnsafeGetBase(ULONG_PTR id, BOOL fFab)
65 {
66     AssertIsProtected();
67
68     CordbHashEntry *entry = NULL;
69
70     if (!m_initialized)
71         return (NULL);
72
73     entry = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
74     return (entry ? entry->pBase : NULL);
75 }
76
77 HRESULT CordbHashTable::UnsafeSwapBase(CordbBase *pOldBase, CordbBase *pNewBase)
78 {
79     if (!m_initialized)
80         return E_FAIL;
81
82     AssertIsProtected();
83     DbgIncChangeCount();
84
85     ULONG_PTR id = (ULONG_PTR)pOldBase->m_id;
86
87     CordbHashEntry *entry
88       = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
89
90     if (entry == NULL)
91     {
92         return E_FAIL;
93     }
94
95     _ASSERTE(entry->pBase == pOldBase);
96     entry->pBase = pNewBase;
97
98     // release the hash table's reference to the old base and transfer it
99     // to the new one.
100     pOldBase->InternalRelease();
101     pNewBase->InternalAddRef();
102
103     return S_OK;
104 }
105
106
107 CordbBase *CordbHashTable::UnsafeRemoveBase(ULONG_PTR id)
108 {
109     AssertIsProtected();
110
111     if (!m_initialized)
112         return NULL;
113
114     DbgIncChangeCount();
115
116
117     CordbHashEntry *entry
118       = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
119
120     if (entry == NULL)
121     {
122         return NULL;
123     }
124
125     CordbBase *base = entry->pBase;
126
127     Delete(HASH((ULONG_PTR)id), (HASHENTRY *) entry);
128     m_count--;
129     base->InternalRelease();
130
131     return base;
132 }
133
134 CordbBase *CordbHashTable::UnsafeFindFirst(HASHFIND *find)
135 {
136     AssertIsProtected();
137     return UnsafeUnlockedFindFirst(find);
138 }
139
140 CordbBase *CordbHashTable::UnsafeUnlockedFindFirst(HASHFIND *find)
141 {
142     CordbHashEntry *entry = (CordbHashEntry *) FindFirstEntry(find);
143
144     if (entry == NULL)
145         return NULL;
146     else
147         return entry->pBase;
148 }
149
150 CordbBase *CordbHashTable::UnsafeFindNext(HASHFIND *find)
151 {
152     AssertIsProtected();
153     return UnsafeUnlockedFindNext(find);
154 }
155
156 CordbBase *CordbHashTable::UnsafeUnlockedFindNext(HASHFIND *find)
157 {
158     CordbHashEntry *entry = (CordbHashEntry *) FindNextEntry(find);
159
160     if (entry == NULL)
161         return NULL;
162     else
163         return entry->pBase;
164 }
165
166 /* ------------------------------------------------------------------------- *
167  * Hash Table Enumerator class
168  * ------------------------------------------------------------------------- */
169
170 // This constructor is part of 2 phase construction.
171 // Use the BuildOrThrow method to instantiate.
172 CordbHashTableEnum::CordbHashTableEnum(
173     CordbBase * pOwnerObj, NeuterList * pOwnerList,
174     CordbHashTable *table,
175     REFIID guid
176 )
177   : CordbBase(pOwnerObj->GetProcess(), 0, enumCordbHashTableEnum),
178     m_pOwnerObj(pOwnerObj),
179     m_pOwnerNeuterList(pOwnerList),
180     m_table(table),
181     m_started(false),
182     m_done(false),
183     m_guid(guid),
184     m_iCurElt(0),
185     m_count(0),
186     m_fCountInit(FALSE)
187 {
188 }
189
190 //---------------------------------------------------------------------------------------
191 //
192 // Build a new Hash enumerator or throw
193 //
194 // Arguments:
195 //     pOwnerObj - owner
196 //     pOwnerList - neuter list to add to
197 //     table - hash table to enumerate.
198 //     id - guid of objects to enumerate
199 //     pHolder - holder to get ownership.
200 //
201 void CordbHashTableEnum::BuildOrThrow(
202     CordbBase * pOwnerObj, 
203     NeuterList * pOwnerList, 
204     CordbHashTable *pTable,
205     const _GUID &id,
206     RSInitHolder<CordbHashTableEnum> * pHolder)
207 {
208     CordbHashTableEnum * pEnum = new CordbHashTableEnum(pOwnerObj, pOwnerList, pTable, id);
209     pHolder->Assign(pEnum);
210
211     // If no neuter-list supplied, then our owner is manually managing us.
212     // It also means we can't be cloned.
213     if (pOwnerList != NULL)
214     {        
215         pOwnerList->Add(pOwnerObj->GetProcess(), pEnum);
216     }
217
218 #ifdef _DEBUG
219     pEnum->m_DbgChangeCount = pEnum->m_table->GetChangeCount();
220 #endif
221 }
222
223 // Only for cloning.
224 // Copy constructor makes life easy & fun!
225 CordbHashTableEnum::CordbHashTableEnum(CordbHashTableEnum *cloneSrc)
226   : CordbBase(cloneSrc->m_pOwnerObj->GetProcess(), 0, enumCordbHashTableEnum),
227     m_pOwnerObj(cloneSrc->m_pOwnerObj),
228     m_pOwnerNeuterList(cloneSrc->m_pOwnerNeuterList),
229     m_started(cloneSrc->m_started),
230     m_done(cloneSrc->m_done),
231     m_hashfind(cloneSrc->m_hashfind),
232     m_guid(cloneSrc->m_guid),
233     m_iCurElt(cloneSrc->m_iCurElt),
234     m_count(cloneSrc->m_count),
235     m_fCountInit(cloneSrc->m_fCountInit)
236 {
237     // We can get cloned at any time, so our owner can't manually control us,
238     // so we need explicit access to a neuter list.
239     _ASSERTE(m_pOwnerNeuterList != NULL);
240
241     m_table = cloneSrc->m_table;
242
243     HRESULT hr = S_OK;
244     EX_TRY
245     {       
246         // Add to neuter list
247         if (m_pOwnerObj->GetProcess() != NULL)
248         {
249             // Normal case. For things enumerating stuff within a CordbProcess tree.
250             m_pOwnerNeuterList->Add(m_pOwnerObj->GetProcess(), this);
251         }
252         else
253         {
254             // For Process-list enums that have broken neuter semantics.
255             // @dbgtodo: this goes away once we remove the top-level ICorDebug interface, 
256             // and thus no longer have a Process enumerator.
257             m_pOwnerNeuterList->UnsafeAdd(NULL, this);
258         }
259     } 
260     EX_CATCH_HRESULT(hr);
261     SetUnrecoverableIfFailed(GetProcess(), hr);
262
263 #ifdef _DEBUG
264     m_DbgChangeCount = cloneSrc->m_DbgChangeCount;
265 #endif
266 }
267
268 CordbHashTableEnum::~CordbHashTableEnum()
269 {
270     _ASSERTE(this->IsNeutered());
271
272     _ASSERTE(m_table == NULL);
273     _ASSERTE(m_pOwnerObj == NULL);
274     _ASSERTE(m_pOwnerNeuterList == NULL);
275 }
276
277 void CordbHashTableEnum::Neuter()
278 {
279     m_table = NULL;
280     m_pOwnerObj = NULL;
281     m_pOwnerNeuterList = NULL;
282
283     CordbBase::Neuter();
284 }
285
286
287 HRESULT CordbHashTableEnum::Reset()
288 {
289     HRESULT hr = S_OK;
290
291     m_started = false;
292     m_done = false;
293
294     return hr;
295 }
296
297 HRESULT CordbHashTableEnum::Clone(ICorDebugEnum **ppEnum)
298 {
299     PUBLIC_REENTRANT_API_ENTRY(this);
300     FAIL_IF_NEUTERED(this);
301     AssertValid();
302
303     VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
304
305     HRESULT hr;
306     hr = S_OK;
307
308     CordbHashTableEnum *e = NULL;
309
310     CordbProcess * pProc = GetProcess();
311
312     if (pProc != NULL)
313     {
314         // @todo - this is really ugly. This macro sets up stack-based dtor cleanup
315         // objects, and so it has to be in the same block of code as the code
316         // it protectes. Eg, we couldn't say: 'if (...) { ATT_ }  { common code }'
317         // because the ATT_ stack based object would get destructed before the 'common code'
318         // was executed.
319         ATT_REQUIRE_STOPPED_MAY_FAIL(pProc);
320         e = new (nothrow) CordbHashTableEnum(this);
321     }
322     else
323     {
324         e = new (nothrow) CordbHashTableEnum(this);
325
326     }
327
328     if (e == NULL)
329     {
330         (*ppEnum) = NULL;
331         hr = E_OUTOFMEMORY;
332         goto LExit;
333     }
334
335     e->QueryInterface(m_guid, (void **) ppEnum);
336
337 LExit:
338     return hr;
339 }
340
341 HRESULT CordbHashTableEnum::GetCount(ULONG *pcelt)
342 {
343     PUBLIC_REENTRANT_API_ENTRY(this);
344     FAIL_IF_NEUTERED(this);
345     AssertValid();
346
347     VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
348
349     *pcelt = m_table->GetCount();
350
351     return S_OK;
352 }
353
354 HRESULT CordbHashTableEnum::PrepForEnum(CordbBase **pBase)
355 {
356     HRESULT hr = S_OK;
357
358     if (!m_started)
359     {
360         (*pBase) = m_table->UnsafeUnlockedFindFirst(&m_hashfind);
361         m_started = true;
362     }
363     else
364     {
365         (*pBase) = m_table->UnsafeUnlockedFindNext(&m_hashfind);
366     }
367
368     return hr;
369 }
370
371 HRESULT CordbHashTableEnum::SetupModuleEnum(void)
372 {
373     return S_OK;
374 }
375
376
377 HRESULT CordbHashTableEnum::AdvancePreAssign(CordbBase **pBase)
378 {
379     HRESULT hr = S_OK;
380     return hr;
381 }
382
383 HRESULT CordbHashTableEnum::AdvancePostAssign(CordbBase **pBase,
384                                               CordbBase     **b,
385                                               CordbBase   **bEnd)
386 {
387     CordbBase *base;
388
389     if (pBase == NULL)
390         pBase = &base;
391
392     // If we're looping like normal, or we're in skip
393     if ( ((b < bEnd) || ((b ==bEnd)&&(b==NULL)))
394        )
395     {
396         (*pBase) = m_table->UnsafeUnlockedFindNext(&m_hashfind);
397         if (*pBase == NULL)
398            m_done = true;
399     }
400
401     return S_OK;
402 }
403
404 // This is an public function implementing all of the ICorDebugXXXEnum interfaces.
405 HRESULT CordbHashTableEnum::Next(ULONG celt,
406                                  CordbBase *bases[],
407                                  ULONG *pceltFetched)
408 {
409     PUBLIC_REENTRANT_API_ENTRY(this);
410     FAIL_IF_NEUTERED(this);
411     AssertValid();
412
413     VALIDATE_POINTER_TO_OBJECT_ARRAY(bases,
414                                      CordbBase *,
415                                      celt,
416                                      true,
417                                      true);
418     VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched,
419                                        ULONG *);
420
421     if ((pceltFetched == NULL) && (celt != 1))
422     {
423         return E_INVALIDARG;
424     }
425
426     if (celt == 0)
427     {
428         if (pceltFetched != NULL)
429         {
430             *pceltFetched = 0;
431         }
432         return S_OK;
433     }
434
435     HRESULT         hr      = S_OK;
436     CordbBase      *base    = NULL;
437     CordbBase     **b       = bases;
438     CordbBase     **bEnd    = bases + celt;
439
440     hr = PrepForEnum(&base);
441     if (FAILED(hr))
442     {
443         goto LError;
444     }
445
446     while (b < bEnd && !m_done)
447     {
448         hr = AdvancePreAssign(&base);
449         if (FAILED(hr))
450         {
451             goto LError;
452         }
453
454         if (base == NULL)
455         {
456             m_done = true;
457         }
458         else
459         {
460             if (m_guid == IID_ICorDebugProcessEnum)
461             {
462                 *b = (CordbBase*)(ICorDebugProcess*)(CordbProcess*)base;
463             }
464             else if (m_guid == IID_ICorDebugBreakpointEnum)
465             {
466                 *b = (CordbBase*)(ICorDebugBreakpoint*)(CordbBreakpoint*)base;
467             }
468             else if (m_guid == IID_ICorDebugStepperEnum)
469             {
470                 *b = (CordbBase*)(ICorDebugStepper*)(CordbStepper*)base;
471             }
472             else if (m_guid == IID_ICorDebugModuleEnum)
473             {
474                 *b = (CordbBase*)(ICorDebugModule*)(CordbModule*)base;
475             }
476             else if (m_guid == IID_ICorDebugThreadEnum)
477             {
478                 *b = (CordbBase*)(ICorDebugThread*)(CordbThread*)base;
479             }
480             else if (m_guid == IID_ICorDebugAppDomainEnum)
481             {
482                 *b = (CordbBase*)(ICorDebugAppDomain*)(CordbAppDomain*)base;
483             }
484             else if (m_guid == IID_ICorDebugAssemblyEnum)
485             {
486                 *b = (CordbBase*)(ICorDebugAssembly*)(CordbAssembly*)base;
487             }
488             else
489             {
490                 *b = (CordbBase*)(IUnknown*)base;
491             }
492
493             if (*b)
494             {
495                 // 'b' is not a valid CordbBase ptr.
496                 base->ExternalAddRef();
497                 b++;
498             }
499
500             hr = AdvancePostAssign(&base, b, bEnd);
501             if (FAILED(hr))
502             {
503                 goto LError;
504             }
505         }
506     }
507
508 LError:
509     //
510     // If celt == 1, then the pceltFetched parameter is optional.
511     //
512     if (pceltFetched != NULL)
513     {
514         *pceltFetched = (ULONG)(b - bases);
515     }
516
517     //
518     // If we reached the end of the enumeration, but not the end
519     // of the number of requested items, we return S_FALSE.
520     //
521     if (!FAILED(hr) && m_done && (b != bEnd))
522     {
523         hr = S_FALSE;
524     }
525
526     return hr;
527 }
528
529 HRESULT CordbHashTableEnum::Skip(ULONG celt)
530 {
531     PUBLIC_REENTRANT_API_ENTRY(this);
532     FAIL_IF_NEUTERED(this);
533     AssertValid();
534
535     HRESULT hr = S_OK;
536
537     CordbBase   *base;
538
539     if (celt > 0)
540     {
541         if (!m_started)
542         {
543             base = m_table->UnsafeUnlockedFindFirst(&m_hashfind);
544
545             if (base == NULL)
546                 m_done = true;
547             else
548                 celt--;
549
550             m_started = true;
551         }
552
553         while (celt > 0 && !m_done)
554         {
555             base = m_table->UnsafeUnlockedFindNext(&m_hashfind);
556
557             if (base == NULL)
558                 m_done = true;
559             else
560                 celt--;
561         }
562     }
563
564     return hr;
565 }
566
567 HRESULT CordbHashTableEnum::QueryInterface(REFIID id, void **pInterface)
568 {
569     if (id == IID_ICorDebugEnum)
570     {
571         ExternalAddRef();
572         *pInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugProcessEnum *>(this));
573
574         return S_OK;
575     }
576     if (id == IID_IUnknown)
577     {
578         ExternalAddRef();
579         *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugProcessEnum *>(this));
580
581         return S_OK;
582     }
583     if (id == m_guid)
584     {
585         ExternalAddRef();
586
587         if (id == IID_ICorDebugProcessEnum)
588             *pInterface = static_cast<ICorDebugProcessEnum *>(this);
589         else if (id == IID_ICorDebugBreakpointEnum)
590             *pInterface = static_cast<ICorDebugBreakpointEnum *>(this);
591         else if (id == IID_ICorDebugStepperEnum)
592             *pInterface = static_cast<ICorDebugStepperEnum *>(this);
593         else if (id == IID_ICorDebugModuleEnum)
594             *pInterface = static_cast<ICorDebugModuleEnum *>(this);
595         else if (id == IID_ICorDebugThreadEnum)
596             *pInterface = static_cast<ICorDebugThreadEnum *>(this);
597         else if (id == IID_ICorDebugAppDomainEnum)
598             *pInterface = static_cast<ICorDebugAppDomainEnum *>(this);
599         else if (id == IID_ICorDebugAssemblyEnum)
600             *pInterface = static_cast<ICorDebugAssemblyEnum *>(this);
601
602         return S_OK;
603     }
604
605     return E_NOINTERFACE;
606 }
607
608 #ifdef _DEBUG
609 void CordbHashTableEnum::AssertValid()
610 {
611     // @todo - Our behavior is undefined when enumerating a collection that changes underneath us.
612     // We'd love to just call this situatation illegal, but clients could very reasonably taken a depedency
613     // on it. Various APIs (eg, ICDStepper::Deactivate) may remove items from the hash.
614     // So we need to figure out what the behavior is here, spec it, and then enforce that and enable the
615     // strongest asserts possible there.
616     //
617     // Specifically, we cannot check that the hash hasn't change from underneath us:
618     // CONSISTENCY_CHECK_MSGF(m_DbgChangeCount == m_table->GetChangeCount(),
619     // ("Underlying hashtable has changed while enumerating.\nOriginal stamp=%d\nNew stamp=%d\nThis enum=0x%p",
620     //  m_DbgChangeCount, m_table->GetChangeCount(), this));
621 }
622
623
624 // 
625 void CordbHashTable::AssertIsProtected()
626 {
627 #ifdef RSCONTRACTS
628     if (m_pDbgLock != NULL)
629     {
630         DbgRSThread * pThread = DbgRSThread::GetThread();
631         if (pThread->IsInRS())
632         {
633             CONSISTENCY_CHECK_MSGF(m_pDbgLock->HasLock(), ("Hash table being accessed w/o holding '%s'", m_pDbgLock->Name()));
634         }
635     }
636 #endif
637 }
638 #endif