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
16CordbHashTable::~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
26HRESULT 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}
59bool CordbHashTable::IsInitialized()
60{
61 return m_initialized;
62}
63
64CordbBase *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
77HRESULT 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
107CordbBase *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
134CordbBase *CordbHashTable::UnsafeFindFirst(HASHFIND *find)
135{
136 AssertIsProtected();
137 return UnsafeUnlockedFindFirst(find);
138}
139
140CordbBase *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
150CordbBase *CordbHashTable::UnsafeFindNext(HASHFIND *find)
151{
152 AssertIsProtected();
153 return UnsafeUnlockedFindNext(find);
154}
155
156CordbBase *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.
172CordbHashTableEnum::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//
201void 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!
225CordbHashTableEnum::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
268CordbHashTableEnum::~CordbHashTableEnum()
269{
270 _ASSERTE(this->IsNeutered());
271
272 _ASSERTE(m_table == NULL);
273 _ASSERTE(m_pOwnerObj == NULL);
274 _ASSERTE(m_pOwnerNeuterList == NULL);
275}
276
277void CordbHashTableEnum::Neuter()
278{
279 m_table = NULL;
280 m_pOwnerObj = NULL;
281 m_pOwnerNeuterList = NULL;
282
283 CordbBase::Neuter();
284}
285
286
287HRESULT CordbHashTableEnum::Reset()
288{
289 HRESULT hr = S_OK;
290
291 m_started = false;
292 m_done = false;
293
294 return hr;
295}
296
297HRESULT 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
337LExit:
338 return hr;
339}
340
341HRESULT 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
354HRESULT 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
371HRESULT CordbHashTableEnum::SetupModuleEnum(void)
372{
373 return S_OK;
374}
375
376
377HRESULT CordbHashTableEnum::AdvancePreAssign(CordbBase **pBase)
378{
379 HRESULT hr = S_OK;
380 return hr;
381}
382
383HRESULT 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.
405HRESULT 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
508LError:
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
529HRESULT 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
567HRESULT 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
609void 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 situation illegal, but clients could have very reasonably taken a dependency
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//
625void 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
639