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 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 | // |
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 |
639 | |