mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(store,ui): export paired free functions for FFI callers
cbm_store_fetch_call_edges, cbm_leiden/cbm_louvain, and
cbm_layout_to_json document caller-owned memory ("caller frees
both" / "the caller frees it" / "Caller must free()") but exported
no matching free function, unlike the rest of the API
(cbm_store_find_nodes -> cbm_store_free_nodes,
cbm_layout_compute -> cbm_layout_free, etc.). From C a bare free()
works via static linking, but a caller across an FFI boundary (e.g.
C# P/Invoke) has no way to reach the DLL's internal free() unless
it is exported by name.
Adds three paired free functions following the existing pattern:
- cbm_store_free_call_edges(src, tgt)
- cbm_leiden_free(result)
- cbm_layout_free_json(json)
Updates the one internal production caller (mcp.c SCC analysis) to
use cbm_store_free_call_edges instead of a bare free(), and the
existing unit tests for cbm_leiden/cbm_louvain/cbm_layout_to_json
to use the new paired frees. Adds fetch_call_edges_basic,
fetch_call_edges_truncated, and fetch_call_edges_empty_project to
test_store_arch.c, covering cbm_store_fetch_call_edges /
cbm_store_free_call_edges, which had no prior test coverage.
Fixes #1762
Signed-off-by: Jumaga2015 <jumaga2015@gmail.com>
This commit is contained in:
1 parent
17786374df
commit
5e328814fc
7 files changed
+100
-13
No files matched your search
+2
-4
@@ -5545,12 +5545,10 @@ static int arch_compute_cycles(cbm_store_t *store, const char *project, int64_t
|
||||
*scanned_edges = ecount;
|
||||
scc_graph_t g;
|
||||
if (!scc_build(src, tgt, ecount, &g)) {
|
||||
free(src);
|
||||
free(tgt);
|
||||
cbm_store_free_call_edges(src, tgt);
|
||||
return CBM_STORE_OK; /* no edges = no cycles, not an error */
|
||||
}
|
||||
free(src);
|
||||
free(tgt);
|
||||
cbm_store_free_call_edges(src, tgt);
|
||||
|
||||
int *comp = malloc((size_t)g.nverts * sizeof(int));
|
||||
if (!comp) {
|
||||
|
||||
@@ -3142,6 +3142,11 @@ int cbm_store_fetch_call_edges(cbm_store_t *s, const char *project, int max_edge
|
||||
return CBM_STORE_OK;
|
||||
}
|
||||
|
||||
void cbm_store_free_call_edges(int64_t *src, int64_t *tgt) {
|
||||
free(src);
|
||||
free(tgt);
|
||||
}
|
||||
|
||||
int cbm_store_find_edges_by_source(cbm_store_t *s, int64_t source_id, cbm_edge_t **out,
|
||||
int *count) {
|
||||
bind_id_t b = {source_id};
|
||||
@@ -8234,6 +8239,10 @@ int cbm_louvain(const int64_t *nodes, int node_count, const cbm_louvain_edge_t *
|
||||
return cbm_leiden(nodes, node_count, edges, edge_count, 1.0, out, out_count);
|
||||
}
|
||||
|
||||
void cbm_leiden_free(cbm_louvain_result_t *result) {
|
||||
free(result);
|
||||
}
|
||||
|
||||
/* ── Architecture: community clusters via Leiden ───────────────── */
|
||||
|
||||
enum {
|
||||
|
||||
@@ -574,6 +574,9 @@ int cbm_store_insert_edge_batch(cbm_store_t *s, const cbm_edge_t *edges, int cou
|
||||
int cbm_store_fetch_call_edges(cbm_store_t *s, const char *project, int max_edges,
|
||||
int64_t **out_src, int64_t **out_tgt, int *count, bool *truncated);
|
||||
|
||||
/* Free the (source_id, target_id) arrays returned by cbm_store_fetch_call_edges. */
|
||||
void cbm_store_free_call_edges(int64_t *src, int64_t *tgt);
|
||||
|
||||
/* Find edges by source node. */
|
||||
int cbm_store_find_edges_by_source(cbm_store_t *s, int64_t source_id, cbm_edge_t **out, int *count);
|
||||
|
||||
@@ -996,6 +999,9 @@ int cbm_leiden(const int64_t *nodes, int node_count, const cbm_louvain_edge_t *e
|
||||
int cbm_louvain(const int64_t *nodes, int node_count, const cbm_louvain_edge_t *edges,
|
||||
int edge_count, cbm_louvain_result_t **out, int *out_count);
|
||||
|
||||
/* Free the result array returned by cbm_leiden / cbm_louvain. */
|
||||
void cbm_leiden_free(cbm_louvain_result_t *result);
|
||||
|
||||
/* ── Memory management helpers ──────────────────────────────────── */
|
||||
|
||||
/* Free heap-allocated strings in a stack-allocated node (does NOT free the node itself). */
|
||||
|
||||
@@ -867,3 +867,7 @@ char *cbm_layout_to_json(const cbm_layout_result_t *r) {
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
void cbm_layout_free_json(char *json) {
|
||||
free(json);
|
||||
}
|
||||
@@ -71,4 +71,7 @@ void cbm_layout_free(cbm_layout_result_t *result);
|
||||
/* Serialize layout result to JSON string. Caller must free(). */
|
||||
char *cbm_layout_to_json(const cbm_layout_result_t *result);
|
||||
|
||||
/* Free the JSON string returned by cbm_layout_to_json. */
|
||||
void cbm_layout_free_json(char *json);
|
||||
|
||||
#endif /* CBM_UI_LAYOUT3D_H */
|
||||
+74
-7
@@ -1189,6 +1189,68 @@ TEST(adr_validate_keys_empty) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── cbm_store_fetch_call_edges / cbm_store_free_call_edges tests ─ */
|
||||
|
||||
TEST(fetch_call_edges_basic) {
|
||||
cbm_store_t *s = setup_arch_test_store();
|
||||
ASSERT_NOT_NULL(s);
|
||||
|
||||
int64_t *src = NULL;
|
||||
int64_t *tgt = NULL;
|
||||
int count = 0;
|
||||
bool truncated = true;
|
||||
ASSERT_EQ(cbm_store_fetch_call_edges(s, "test", 100, &src, &tgt, &count, &truncated),
|
||||
CBM_STORE_OK);
|
||||
/* setup_arch_test_store() wires 5 CALLS edges among Function nodes. */
|
||||
ASSERT_EQ(count, 5);
|
||||
ASSERT_FALSE(truncated);
|
||||
ASSERT_NOT_NULL(src);
|
||||
ASSERT_NOT_NULL(tgt);
|
||||
|
||||
cbm_store_free_call_edges(src, tgt);
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fetch_call_edges_truncated) {
|
||||
cbm_store_t *s = setup_arch_test_store();
|
||||
ASSERT_NOT_NULL(s);
|
||||
|
||||
int64_t *src = NULL;
|
||||
int64_t *tgt = NULL;
|
||||
int count = 0;
|
||||
bool truncated = false;
|
||||
/* Only 5 CALLS edges exist; capping at 2 must set truncated. */
|
||||
ASSERT_EQ(cbm_store_fetch_call_edges(s, "test", 2, &src, &tgt, &count, &truncated),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_TRUE(truncated);
|
||||
|
||||
cbm_store_free_call_edges(src, tgt);
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fetch_call_edges_empty_project) {
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
ASSERT_NOT_NULL(s);
|
||||
cbm_store_upsert_project(s, "empty", "/tmp/empty");
|
||||
|
||||
int64_t *src = NULL;
|
||||
int64_t *tgt = NULL;
|
||||
int count = 0;
|
||||
bool truncated = true;
|
||||
ASSERT_EQ(cbm_store_fetch_call_edges(s, "empty", 100, &src, &tgt, &count, &truncated),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_FALSE(truncated);
|
||||
|
||||
/* cbm_store_free_call_edges(NULL, NULL) must not crash. */
|
||||
cbm_store_free_call_edges(src, tgt);
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Louvain tests ──────────────────────────────────────────────── */
|
||||
|
||||
TEST(louvain_basic) {
|
||||
@@ -1214,7 +1276,7 @@ TEST(louvain_basic) {
|
||||
/* Triangle and pair different */
|
||||
ASSERT_TRUE(comm[1] != comm[4]);
|
||||
|
||||
free(result);
|
||||
cbm_leiden_free(result);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1223,7 +1285,7 @@ TEST(louvain_empty) {
|
||||
int count = 0;
|
||||
ASSERT_EQ(cbm_louvain(NULL, 0, NULL, 0, &result, &count), CBM_STORE_OK);
|
||||
ASSERT_EQ(count, 0);
|
||||
free(result);
|
||||
cbm_leiden_free(result);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1234,7 +1296,7 @@ TEST(louvain_single_node) {
|
||||
ASSERT_EQ(cbm_louvain(nodes, 1, NULL, 0, &result, &count), CBM_STORE_OK);
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(result[0].node_id, 42);
|
||||
free(result);
|
||||
cbm_leiden_free(result);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1297,7 +1359,7 @@ TEST(louvain_converges) {
|
||||
}
|
||||
ASSERT_TRUE(same_count >= 8);
|
||||
|
||||
free(result);
|
||||
cbm_leiden_free(result);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1431,7 +1493,7 @@ TEST(leiden_multilevel_collapses_noise) {
|
||||
}
|
||||
ASSERT_TRUE(same >= SZ - 1);
|
||||
}
|
||||
free(result);
|
||||
cbm_leiden_free(result);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1463,8 +1525,8 @@ TEST(leiden_resolution_controls_granularity) {
|
||||
ASSERT_TRUE(n_hi > n_lo);
|
||||
ASSERT_TRUE(leiden_all_communities_connected(lo, N, edges, ne));
|
||||
ASSERT_TRUE(leiden_all_communities_connected(hi, N, edges, ne));
|
||||
free(lo);
|
||||
free(hi);
|
||||
cbm_leiden_free(lo);
|
||||
cbm_leiden_free(hi);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1727,6 +1789,11 @@ SUITE(store_arch) {
|
||||
RUN_TEST(adr_splice_refuses_unterminated_fence);
|
||||
RUN_TEST(adr_validate_keys_empty);
|
||||
|
||||
/* Call-edge fetch / free */
|
||||
RUN_TEST(fetch_call_edges_basic);
|
||||
RUN_TEST(fetch_call_edges_truncated);
|
||||
RUN_TEST(fetch_call_edges_empty_project);
|
||||
|
||||
/* Louvain */
|
||||
RUN_TEST(louvain_basic);
|
||||
RUN_TEST(louvain_empty);
|
||||
|
||||
+2
-2
@@ -548,7 +548,7 @@ TEST(layout_to_json) {
|
||||
ASSERT(strstr(json, "\"hello\"") != NULL);
|
||||
ASSERT(strstr(json, "\"Function\"") != NULL);
|
||||
|
||||
free(json);
|
||||
cbm_layout_free_json(json);
|
||||
cbm_layout_free(r);
|
||||
cbm_store_close(store);
|
||||
PASS();
|
||||
@@ -719,7 +719,7 @@ TEST(layout_dead_code_classification) {
|
||||
ASSERT_NOT_NULL(json);
|
||||
ASSERT(strstr(json, "\"status\":\"dead\"") != NULL);
|
||||
ASSERT(strstr(json, "\"in_calls\":2") != NULL);
|
||||
free(json);
|
||||
cbm_layout_free_json(json);
|
||||
|
||||
cbm_layout_free(r);
|
||||
cbm_store_close(store);
|
||||
|
||||
Reference in new issue
Block a user