nostrdb

an unfairly fast embedded nostr database backed by lmdb
git clone git://jb55.com/nostrdb
Log | Files | Refs | Submodules | README | LICENSE

commit 79765ba148ec6e8c9e91adfdf8d6b47e8c764bd8
parent e0be35c20d314b204d932e4093b29f0a27bda21c
Author: William Casarin <jb55@jb55.com>
Date:   Tue,  8 Jul 2025 14:12:52 -0700

memory: fix a bunch of memory leaks

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
MMakefile | 4++++
Mndb.c | 2++
Msrc/nostrdb.c | 21+++++++++++++++++----
Mtest.c | 23+++++++++++++++++++++--
4 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/Makefile b/Makefile @@ -21,6 +21,8 @@ C_BINDINGS_COMMON=$(BINDINGS)/c/flatbuffers_common_builder.h $(BINDINGS)/c/flatb C_BINDINGS=$(C_BINDINGS_COMMON) $(C_BINDINGS_PROFILE) $(C_BINDINGS_META) BIN=ndb +SANFLAGS = -fsanitize=leak + # Detect operating system UNAME_S := $(shell uname -s) @@ -189,6 +191,8 @@ testdata/db/.dir: @mkdir -p testdata/db touch testdata/db/.dir +test: CFLAGS += $(SANFLAGS) # compile test objects with ASan/UBSan +test: LDFLAGS += $(SANFLAGS) # link test binary with the sanitizer runtime test: test.c $(DEPS) testdata/db/.dir $(CC) $(CFLAGS) test.c $(LDS) $(LDFLAGS) -o $@ diff --git a/ndb.c b/ndb.c @@ -387,7 +387,9 @@ int main(int argc, char *argv[]) } ndb_end_query(&txn); + ndb_filter_destroy(f); + free(results); } else if (argc == 3 && !strcmp(argv[1], "import")) { if (!strcmp(argv[2], "-")) { ndb_process_events_stream(ndb, stdin); diff --git a/src/nostrdb.c b/src/nostrdb.c @@ -591,6 +591,11 @@ int ndb_filter_end(struct ndb_filter *filter) size_t orig_size; #endif size_t data_len, elem_len; + unsigned char *rel; + + assert(filter); + assert(filter->elem_buf.start); + if (filter->finalized == 1) return 0; @@ -609,7 +614,10 @@ int ndb_filter_end(struct ndb_filter *filter) memmove(filter->elem_buf.p, filter->data_buf.start, data_len); // realloc the whole thing - filter->elem_buf.start = realloc(filter->elem_buf.start, elem_len + data_len); + rel = realloc(filter->elem_buf.start, elem_len + data_len); + if (rel) + filter->elem_buf.start = rel; + assert(filter->elem_buf.start); filter->elem_buf.end = filter->elem_buf.start + elem_len; filter->elem_buf.p = filter->elem_buf.end; @@ -4149,12 +4157,12 @@ static int ndb_query_plan_execute_profile_search( // get the authors element after we finalize the filter, since // the data could have moved if (!(els = ndb_filter_find_elements(f, NDB_FILTER_AUTHORS))) - return 0; + goto fail; // grab pointer to pubkey in the filter so that we can // update the filter as we go if (!(filter_pubkey = ndb_filter_get_id_element(f, els, 0))) - return 0; + goto fail; for (i = 0; !query_is_full(results, limit); i++) { if (i == 0) { @@ -4170,10 +4178,15 @@ static int ndb_query_plan_execute_profile_search( // Look up the corresponding note associated with that pubkey if (!ndb_query_plan_execute_author_kinds(txn, f, results, limit)) - return 0; + goto fail; } + ndb_filter_destroy(f); return 1; + +fail: + ndb_filter_destroy(f); + return 0; } static int ndb_query_plan_execute_relay_kinds( diff --git a/test.c b/test.c @@ -233,6 +233,7 @@ static void test_timeline_query() assert(ndb_query(&txn, &filter, 1, results, sizeof(results)/sizeof(results[0]), &count)); ndb_end_query(&txn); + ndb_filter_destroy(&filter); assert(count == 10); } @@ -291,6 +292,9 @@ static void test_fetched_at() fetched_at = ndb_read_last_profile_fetch(&txn, pubkey); assert(fetched_at == t1); + + ndb_end_query(&txn); + ndb_destroy(ndb); } static void test_reaction_counter() @@ -422,6 +426,7 @@ static void test_profile_updates() assert(!strcmp(name, "c")); + ndb_end_query(&txn); ndb_destroy(ndb); free(json); } @@ -1413,6 +1418,7 @@ static void test_tag_query() assert(!strcmp(ndb_note_content(results[0].note), "hi")); ndb_end_query(&txn); + ndb_filter_destroy(f); ndb_destroy(ndb); } @@ -1491,6 +1497,7 @@ static void test_query() assert(!strcmp(ndb_note_content(results[1].note), "what")); ndb_end_query(&txn); + ndb_filter_destroy(f); ndb_destroy(ndb); } @@ -1614,6 +1621,7 @@ static void test_subscriptions() assert(ndb_num_subscriptions(ndb) == 0); ndb_end_query(&txn); + ndb_filter_destroy(f); ndb_destroy(ndb); } @@ -1675,6 +1683,7 @@ static void test_weird_note_corruption() { assert(i == 1); ndb_end_query(&txn); + ndb_filter_destroy(f); ndb_destroy(ndb); } @@ -1703,6 +1712,9 @@ static void test_filter_eq() { ndb_filter_end(f2); assert(ndb_filter_eq(f, f2)); + + ndb_filter_destroy(f); + ndb_filter_destroy(f2); } static void test_filter_is_subset() { @@ -1738,6 +1750,9 @@ static void test_filter_is_subset() { assert(ndb_filter_is_subset_of(k, g) == 1); assert(ndb_filter_is_subset_of(ki, k) == 1); assert(ndb_filter_is_subset_of(k, ki) == 0); + + ndb_filter_destroy(g); + ndb_filter_destroy(ki); } static void test_filter_search() @@ -1752,6 +1767,7 @@ static void test_filter_search() ndb_filter_end_field(f); assert(ndb_filter_end(f)); + ndb_filter_destroy(f); } static void test_filter_parse_search_json() { @@ -1784,6 +1800,8 @@ static void test_filter_parse_search_json() { assert(ndb_filter_json(f, (char *)buf, sizeof(buf))); printf("search json: '%s'\n", (const char *)buf); assert(!strcmp((const char*)buf, json)); + + ndb_filter_destroy(f); } static void test_note_relay_index() @@ -1903,6 +1921,7 @@ static void test_nip50_profile_search() { assert(!memcmp(ndb_note_id(result.note), expected_id, 32)); // Cleanup + ndb_filter_destroy(f); ndb_destroy(ndb); printf("ok test_nip50_profile_search\n"); @@ -1954,7 +1973,7 @@ static void test_custom_filter() struct ndb_filter filter, *f = &filter; struct ndb_filter filter2, *f2 = &filter2; int count, nres = 2; - uint64_t sub_id, note_key; + uint64_t sub_id, note_keys[2]; struct ndb_query_result results[2]; struct ndb_ingest_meta meta; @@ -1989,7 +2008,7 @@ static void test_custom_filter() assert(ndb_process_event_with(ndb, reply_json, strlen(reply_json), &meta)); for (nres = 2; nres > 0;) - nres -= ndb_wait_for_notes(ndb, sub_id, &note_key, 2); + nres -= ndb_wait_for_notes(ndb, sub_id, note_keys, 2); ndb_begin_query(ndb, &txn); ndb_query(&txn, f, 1, results, 2, &count);