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.
This commit is contained in:
parent
21180675c9
commit
8349635ffc
|
@ -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,
|
||||||
|
@ -149,9 +152,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;
|
||||||
|
@ -163,11 +166,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() {
|
||||||
|
@ -214,8 +220,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);
|
||||||
}
|
}
|
||||||
|
@ -232,8 +237,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,9 +340,9 @@ public:
|
||||||
capacity = p_initial_capacity;
|
capacity = p_initial_capacity;
|
||||||
num_elements = 0;
|
num_elements = 0;
|
||||||
|
|
||||||
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;
|
||||||
|
@ -347,9 +351,19 @@ public:
|
||||||
|
|
||||||
~OAHashMap() {
|
~OAHashMap() {
|
||||||
|
|
||||||
memdelete_arr(keys);
|
for (uint32_t i = 0; i < capacity; i++) {
|
||||||
memdelete_arr(values);
|
|
||||||
memdelete_arr(hashes);
|
if (hashes[i] == EMPTY_HASH) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
values[i].~TValue();
|
||||||
|
keys[i].~TKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory::free_static(keys);
|
||||||
|
Memory::free_static(values);
|
||||||
|
Memory::free_static(hashes);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,41 @@
|
||||||
|
|
||||||
namespace TestOAHashMap {
|
namespace TestOAHashMap {
|
||||||
|
|
||||||
|
struct CountedItem {
|
||||||
|
static int count;
|
||||||
|
|
||||||
|
int id;
|
||||||
|
bool destroyed;
|
||||||
|
|
||||||
|
CountedItem() :
|
||||||
|
id(-1),
|
||||||
|
destroyed(false) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountedItem(int p_id) :
|
||||||
|
id(p_id),
|
||||||
|
destroyed(false) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountedItem(const CountedItem &p_other) :
|
||||||
|
id(p_other.id),
|
||||||
|
destroyed(false) {
|
||||||
|
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 +187,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 NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} // namespace TestOAHashMap
|
} // namespace TestOAHashMap
|
||||||
|
|
Loading…
Reference in New Issue