Skip to content

Commit f1d42d5

Browse files
HashMapStringKey: detect dangling/temporary string keys in debug builds
1 parent 82cd67d commit f1d42d5

1 file changed

Lines changed: 47 additions & 10 deletions

File tree

Common/interface/HashUtils.hpp

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2025 Diligent Graphics LLC
2+
* Copyright 2019-2026 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -233,6 +233,9 @@ struct HashMapStringKey
233233
HashMapStringKey(const Char* _Str, bool bMakeCopy = false) :
234234
Str{_Str}
235235
{
236+
#ifdef DILIGENT_DEBUG
237+
m_DbgStr = _Str != nullptr ? _Str : "";
238+
#endif
236239
VERIFY(Str, "String pointer must not be null");
237240

238241
Ownership_Hash = CStringHash<Char>{}.operator()(Str) & HashMask;
@@ -250,6 +253,9 @@ struct HashMapStringKey
250253
explicit HashMapStringKey(const String& Str, bool bMakeCopy = true) :
251254
HashMapStringKey{Str.c_str(), bMakeCopy}
252255
{
256+
#ifdef DILIGENT_DEBUG
257+
m_DbgStr = Str;
258+
#endif
253259
}
254260

255261
HashMapStringKey(HashMapStringKey&& Key) noexcept :
@@ -260,6 +266,10 @@ struct HashMapStringKey
260266
{
261267
Key.Str = nullptr;
262268
Key.Ownership_Hash = 0;
269+
#ifdef DILIGENT_DEBUG
270+
m_DbgStr = std::move(Key.m_DbgStr);
271+
Key.m_DbgStr.clear();
272+
#endif
263273
}
264274

265275
HashMapStringKey& operator=(HashMapStringKey&& rhs) noexcept
@@ -275,6 +285,11 @@ struct HashMapStringKey
275285
rhs.Str = nullptr;
276286
rhs.Ownership_Hash = 0;
277287

288+
#ifdef DILIGENT_DEBUG
289+
m_DbgStr = std::move(rhs.m_DbgStr);
290+
rhs.m_DbgStr.clear();
291+
#endif
292+
278293
return *this;
279294
}
280295

@@ -292,22 +307,20 @@ struct HashMapStringKey
292307

293308
HashMapStringKey Clone() const
294309
{
310+
DbgVerifyString();
295311
return HashMapStringKey{GetStr(), (Ownership_Hash & StrOwnershipMask) != 0};
296312
}
297313

298314
bool operator==(const HashMapStringKey& RHS) const noexcept
299315
{
316+
DbgVerifyString();
317+
RHS.DbgVerifyString();
318+
300319
if (Str == RHS.Str)
301320
return true;
302321

303-
if (Str == nullptr)
304-
{
305-
VERIFY_EXPR(RHS.Str != nullptr);
306-
return false;
307-
}
308-
else if (RHS.Str == nullptr)
322+
if (Str == nullptr || RHS.Str == nullptr)
309323
{
310-
VERIFY_EXPR(Str != nullptr);
311324
return false;
312325
}
313326

@@ -324,8 +337,9 @@ struct HashMapStringKey
324337
#if LOG_HASH_CONFLICTS
325338
if (!IsEqual && Hash == RHSHash)
326339
{
327-
LOG_WARNING_MESSAGE("Unequal strings \"", Str, "\" and \"", RHS.Str,
328-
"\" have the same hash. You may want to use a better hash function. "
340+
LOG_WARNING_MESSAGE("Different strings \"", Str, "\" and \"", RHS.Str,
341+
"\" have the same hash. This can happen occasionally; if frequent, "
342+
"consider a stronger hash or a different key strategy. "
329343
"You may disable this warning by defining LOG_HASH_CONFLICTS to 0");
330344
}
331345
#endif
@@ -344,11 +358,13 @@ struct HashMapStringKey
344358

345359
size_t GetHash() const noexcept
346360
{
361+
DbgVerifyString();
347362
return Ownership_Hash & HashMask;
348363
}
349364

350365
const Char* GetStr() const noexcept
351366
{
367+
DbgVerifyString();
352368
return Str;
353369
}
354370

@@ -367,6 +383,22 @@ struct HashMapStringKey
367383

368384
Str = nullptr;
369385
Ownership_Hash = 0;
386+
387+
#ifdef DILIGENT_DEBUG
388+
m_DbgStr.clear();
389+
#endif
390+
}
391+
392+
private:
393+
void DbgVerifyString() const
394+
{
395+
VERIFY(m_DbgStr == (Str != nullptr ? Str : ""),
396+
"Debug copy does not match the current key string. "
397+
"Expected=\"",
398+
m_DbgStr,
399+
"\" Actual=\"", (Str != nullptr ? Str : "<null>"),
400+
"\". This usually means the key was constructed without copying/owning the string and now points to "
401+
"temporary, freed, or mutated storage.");
370402
}
371403

372404
protected:
@@ -377,6 +409,11 @@ struct HashMapStringKey
377409
const Char* Str = nullptr;
378410
// We will use top bit of the hash to indicate if we own the pointer
379411
size_t Ownership_Hash = 0;
412+
413+
private:
414+
#ifdef DILIGENT_DEBUG
415+
std::string m_DbgStr;
416+
#endif
380417
};
381418

382419

0 commit comments

Comments
 (0)