Check for (head)->hh.tbl in HASH_DELETE
jcraffael opened this issue · comments
Hello,
I think it would be necessary to check the validity of the pointer (head)->hh.tbl
being as an argument for
HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del)
inside HASH_DELETE_HH
, since in my case of same item with 2 keys I got a coredump here since the (head)->hh.tbl
has a trash value when trying to delete the item from the 2nd hash table.
Thank you!
Chao
Hi @jcraffael! Can you post a short, self-contained example that demonstrates the crash you're seeing? For example,
#include "utlist.h"
struct el {
int id;
struct el *next, *prev;
};
int main() {
struct el *head = NULL;
struct el x = {0};
DL_APPEND(head, &x);
DL_DELETE(head, &x); // segfault on this line
}
except with whatever your steps-to-reproduce are?
Hi, thank you for the reply!
It's hard to explain but let me try:
struct el {struct key key1; struct key key2; UT_hash_handle hh1; UT_hash_handle hh2;};
int main(){
el n1, n2, s tmp;
n1 = ..., n2 = ...;
HASH_ADD(hh1, n1, ...);
HASH_ADD(hh2. n2, ...);
HASH_ITER(hh1, n1, s, tmp)
{
HASH_DELETE(hh1, n1, s);
if(...)
HASH_DELETE(hh2, n2, s); //segfault here since (el *)(s->hh2.next)->hh2.tbl results in a trash value
}
}
Hope that makes sense, thank you!
Chao
Hi @jcraffael , could you please fill in the ...
parts with something that makes it crash? (Also, on the first line of main
, I don't think e1 n1, n2, s tmp;
is valid syntax.)
Hello, so sorry for the typo.
Again:
struct el {struct key key1; struct key key2; UT_hash_handle hh1; UT_hash_handle hh2;};
int main(){
struct el *n1, *n2, *s, *tmp, *p;
/* Many repeated find and add */
HASH_FIND(hh1, n1, key, size, p);
if(!p)
HASH_ADD(hh1, n1, key, size, p);
HASH_FIND(hh2, n2, key, size, p);
if(!p)
HASH_ADD(hh2. n2, key, size, p);
/* Many repeated iter and delete */
HASH_ITER(hh1, n1, s, tmp)
{
HASH_DELETE(hh1, n1, s);
if(...) {
HASH_DELETE(hh2, n2, s); /* here since the data members under (struct el *)(s->hh2.next)->hh2.tbl got trash value, the macro HASH_DEL_IN_BKT(hh2,(head)->hh2.tbl->buckets[_hd_bkt], _hd_hh_del) causes segfault */
}
free(s);
s = NULL;
}
HASH_FIND(hh1, n1, key, size, p);
if(!p)
HASH_ADD(hh1, n1, key, size, p);
I believe this is your problem, here. You're using HASH_FIND
correctly — the fifth parameter is an "out-parameter," so uthash will fill in p
with the found element's address (or null if not found). But you're using HASH_ADD
incorrectly: HASH_ADD
expects its fifth parameter to be the address of a new node to add into the hash. You're passing p
, which is still null from the previous line. That's a null pointer dereference, which explains the segfault you're seeing.
You should be doing more like this: https://godbolt.org/z/Wc8fjsszT
HASH_FIND(hh1, n1, &k, size, p);
if (!p) {
struct el *newel = (struct el*)malloc(sizeof *newel);
newel->key1 = {1};
newel->key2 = {2};
HASH_ADD(hh1, n1, key1, size, newel);
}
Hope this helps!
@Quuxplusone I think this was also a "typo" on the OP's part. Attempting to HASH_ADD
a NULL
will blow in HASH_ADD
right away, not in some later evaluation of HASH_DELETE
Hi @veselov correct! Thank you for the help!
Hi @Quuxplusone please allow me to explain: I did HASH_ADD exactly as you showed. Just for short I didn't specify that in my code. It's not my first time using uthash so I know pretty well how to use these APIs.
This time I'm dealing with a complicated case where the same struct el got added and deleted in time. What I found in my case is firstly DECLTYPE_ASSIGN()
is called, but (delptr)->hh.next
at that moment should be an address already purged from hh but still present there, with its data members having trash value. Then in HASH_TO_BKT
the value of (head)->hh.tbl->num_buckets
which is a trash one is assigned to _hd_bkt
and afterward in HASH_DEL_IN_BKT (head)->hh.tbl->buckets[_hd_bkt]
results in outside of range so a coredump occurs.
Hope that makes sense, thank you!
Chao