Commit b720a018 by Pedro J. Estébanez

Fix leaks and crashes in OAHashMap

This changes the way the lifespan of items is managed to be consistent. Bonus: Simplify cases of destroy-then-emplace.
parent 163687d1
...@@ -45,6 +45,9 @@ ...@@ -45,6 +45,9 @@
* *
* The entries are stored inplace, so huge keys or values might fill cache lines * The entries are stored inplace, so huge keys or values might fill cache lines
* a lot faster. * a lot faster.
*
* Only used keys and values are constructed. For free positions there's space
* in the arrays for each, but that memory is kept uninitialized.
*/ */
template <class TKey, class TValue, template <class TKey, class TValue,
class Hasher = HashMapHasherDefault, class Hasher = HashMapHasherDefault,
...@@ -146,9 +149,9 @@ private: ...@@ -146,9 +149,9 @@ private:
uint32_t *old_hashes = hashes; uint32_t *old_hashes = hashes;
num_elements = 0; num_elements = 0;
keys = memnew_arr(TKey, capacity); keys = static_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
values = memnew_arr(TValue, capacity); values = static_cast<TValue *>(Memory::alloc_static(sizeof(TValue) * capacity));
hashes = memnew_arr(uint32_t, capacity); hashes = static_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
for (uint32_t i = 0; i < capacity; i++) { for (uint32_t i = 0; i < capacity; i++) {
hashes[i] = 0; hashes[i] = 0;
...@@ -160,11 +163,14 @@ private: ...@@ -160,11 +163,14 @@ private:
} }
_insert_with_hash(old_hashes[i], old_keys[i], old_values[i]); _insert_with_hash(old_hashes[i], old_keys[i], old_values[i]);
old_keys[i].~TKey();
old_values[i].~TValue();
} }
memdelete_arr(old_keys); Memory::free_static(old_keys);
memdelete_arr(old_values); Memory::free_static(old_values);
memdelete_arr(old_hashes); Memory::free_static(old_hashes);
} }
void _resize_and_rehash() { void _resize_and_rehash() {
...@@ -208,8 +214,7 @@ public: ...@@ -208,8 +214,7 @@ public:
bool exists = _lookup_pos(p_key, pos); bool exists = _lookup_pos(p_key, pos);
if (exists) { if (exists) {
values[pos].~TValue(); values[pos] = p_data;
memnew_placement(&values[pos], TValue(p_data));
} else { } else {
insert(p_key, p_data); insert(p_key, p_data);
} }
...@@ -226,8 +231,7 @@ public: ...@@ -226,8 +231,7 @@ public:
bool exists = _lookup_pos(p_key, pos); bool exists = _lookup_pos(p_key, pos);
if (exists) { if (exists) {
r_data.~TValue(); r_data = values[pos];
memnew_placement(&r_data, TValue(values[pos]));
return true; return true;
} }
...@@ -343,9 +347,9 @@ public: ...@@ -343,9 +347,9 @@ public:
OAHashMap(uint32_t p_initial_capacity = 64) { OAHashMap(uint32_t p_initial_capacity = 64) {
capacity = p_initial_capacity; capacity = p_initial_capacity;
keys = memnew_arr(TKey, p_initial_capacity); keys = static_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
values = memnew_arr(TValue, p_initial_capacity); values = static_cast<TValue *>(Memory::alloc_static(sizeof(TValue) * capacity));
hashes = memnew_arr(uint32_t, p_initial_capacity); hashes = static_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
for (uint32_t i = 0; i < p_initial_capacity; i++) { for (uint32_t i = 0; i < p_initial_capacity; i++) {
hashes[i] = EMPTY_HASH; hashes[i] = EMPTY_HASH;
...@@ -353,9 +357,18 @@ public: ...@@ -353,9 +357,18 @@ public:
} }
~OAHashMap() { ~OAHashMap() {
memdelete_arr(keys); for (uint32_t i = 0; i < capacity; i++) {
memdelete_arr(values); if (hashes[i] == EMPTY_HASH) {
memdelete_arr(hashes); continue;
}
values[i].~TValue();
keys[i].~TKey();
}
Memory::free_static(keys);
Memory::free_static(values);
Memory::free_static(hashes);
} }
}; };
......
...@@ -35,6 +35,37 @@ ...@@ -35,6 +35,37 @@
namespace TestOAHashMap { namespace TestOAHashMap {
struct CountedItem {
static int count;
int id = -1;
bool destroyed = false;
CountedItem() {
count++;
}
CountedItem(int p_id) :
id(p_id) {
count++;
}
CountedItem(const CountedItem &p_other) :
id(p_other.id) {
count++;
}
CountedItem &operator=(const CountedItem &p_other) = default;
~CountedItem() {
CRASH_COND(destroyed);
count--;
destroyed = true;
}
};
int CountedItem::count;
MainLoop *test() { MainLoop *test() {
OS::get_singleton()->print("\n\n\nHello from test\n"); OS::get_singleton()->print("\n\n\nHello from test\n");
...@@ -152,6 +183,33 @@ MainLoop *test() { ...@@ -152,6 +183,33 @@ MainLoop *test() {
map.set(5, 1); map.set(5, 1);
} }
// test memory management of items, should not crash or leak items
{
// Exercise different patterns of removal
for (int i = 0; i < 4; ++i) {
{
OAHashMap<String, CountedItem> map;
int id = 0;
for (int j = 0; j < 100; ++j) {
map.insert(itos(j), CountedItem(id));
}
if (i <= 1) {
for (int j = 0; j < 100; ++j) {
map.remove(itos(j));
}
}
if (i % 2 == 0) {
map.clear();
}
}
if (CountedItem::count != 0) {
OS::get_singleton()->print("%d != 0 (not performing the other test sub-cases, breaking...)\n", CountedItem::count);
break;
}
}
}
return nullptr; return nullptr;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment