Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/mcp/mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -8280,12 +8280,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) {
Expand Down
42 changes: 36 additions & 6 deletions src/store/store.c
Original file line number Diff line number Diff line change
Expand Up @@ -4165,10 +4165,18 @@ int cbm_store_fetch_call_edges(cbm_store_t *s, const char *project, int max_edge
bind_text(stmt, SKIP_ONE, project);
sqlite3_bind_int(stmt, 2, max_edges > 0 ? max_edges + SKIP_ONE : CBM_NOT_FOUND);

/* Both arrays are STORE-class memory-core blocks: the caller releases them
* with cbm_store_free_call_edges, the only free that matches. */
int cap = ST_INIT_CAP_16;
int n = 0;
int64_t *src = malloc((size_t)cap * sizeof(int64_t));
int64_t *tgt = malloc((size_t)cap * sizeof(int64_t));
int64_t *src = cbm_alloc(CBM_MEM_CLASS_STORE, (size_t)cap * sizeof(int64_t));
int64_t *tgt = cbm_alloc(CBM_MEM_CLASS_STORE, (size_t)cap * sizeof(int64_t));
if (!src || !tgt) {
sqlite3_finalize(stmt);
cbm_store_free_call_edges(src, tgt);
store_set_error(s, "fetch_call_edges: out of memory");
return CBM_STORE_ERR;
}
int scan_rc;
while ((scan_rc = sqlite3_step(stmt)) == SQLITE_ROW) {
if (max_edges > 0 && n >= max_edges) {
Expand All @@ -4178,18 +4186,31 @@ int cbm_store_fetch_call_edges(cbm_store_t *s, const char *project, int max_edge
break;
}
if (n >= cap) {
size_t grown_bytes = (size_t)cap * ST_GROWTH * sizeof(int64_t);
int64_t *grown_src = cbm_realloc(CBM_MEM_CLASS_STORE, src, grown_bytes);
if (grown_src) {
src = grown_src;
}
int64_t *grown_tgt =
grown_src ? cbm_realloc(CBM_MEM_CLASS_STORE, tgt, grown_bytes) : NULL;
if (grown_tgt) {
tgt = grown_tgt;
}
if (!grown_src || !grown_tgt) {
sqlite3_finalize(stmt);
cbm_store_free_call_edges(src, tgt);
store_set_error(s, "fetch_call_edges: out of memory");
return CBM_STORE_ERR;
}
cap *= ST_GROWTH;
src = safe_realloc(src, (size_t)cap * sizeof(int64_t));
tgt = safe_realloc(tgt, (size_t)cap * sizeof(int64_t));
}
src[n] = sqlite3_column_int64(stmt, 0);
tgt[n] = sqlite3_column_int64(stmt, 1);
n++;
}
sqlite3_finalize(stmt);
if (scan_rc != SQLITE_DONE && scan_rc != SQLITE_ROW) {
free(src);
free(tgt);
cbm_store_free_call_edges(src, tgt);
store_set_error_sqlite(s, "fetch_call_edges scan");
return CBM_STORE_ERR;
}
Expand All @@ -4199,6 +4220,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) {
cbm_free(CBM_MEM_CLASS_STORE, src);
cbm_free(CBM_MEM_CLASS_STORE, 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};
Expand Down Expand Up @@ -10647,6 +10673,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) {
cbm_free(CBM_MEM_CLASS_OTHER, result); /* cbm_leiden allocates OTHER */
}

/* ── Architecture: community clusters via Leiden ───────────────── */

enum {
Expand Down
11 changes: 9 additions & 2 deletions src/store/store.h
Original file line number Diff line number Diff line change
Expand Up @@ -708,12 +708,16 @@ int64_t cbm_store_insert_edge(cbm_store_t *s, const cbm_edge_t *e);
int cbm_store_insert_edge_batch(cbm_store_t *s, const cbm_edge_t *edges, int count);

/* Fetch all CALLS edges among Function/Method nodes for a project as parallel
* (source_id, target_id) arrays (caller frees both). For SCC / cycle analysis.
* (source_id, target_id) arrays, released together with
* cbm_store_free_call_edges. For SCC / cycle analysis.
* Stops at max_edges and sets *truncated — never a silent cap. Returns
* CBM_STORE_OK (or _ERR); *count is the number returned. */
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);

Expand Down Expand Up @@ -1306,14 +1310,17 @@ typedef struct {
* reported community is internally connected. The resolution parameter
* controls granularity (higher -> more, smaller communities); 1.0 is standard.
* Allocates *out (length *out_count == node_count) in CBM_MEM_CLASS_OTHER;
* the caller releases it with cbm_free(CBM_MEM_CLASS_OTHER, *out). */
* the caller releases it with cbm_leiden_free(*out). */
int cbm_leiden(const int64_t *nodes, int node_count, const cbm_louvain_edge_t *edges,
int edge_count, double resolution, cbm_louvain_result_t **out, int *out_count);

/* Convenience wrapper: cbm_leiden with resolution 1.0. */
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). */
Expand Down
10 changes: 5 additions & 5 deletions src/ui/http_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,7 @@ static bool attach_missed_graph(yyjson_mut_doc *mdoc, yyjson_mut_val *mroot, cbm
return false;
}
yyjson_doc *mldoc = yyjson_read(mjson, strlen(mjson), 0);
free(mjson);
cbm_layout_free_json(mjson);
if (!mldoc) {
return false;
}
Expand Down Expand Up @@ -2164,7 +2164,7 @@ static void handle_layout(cbm_http_conn_t *c, const cbm_http_req_t *req) {
return;
}
cbm_http_replyf(c, 200, g_cors_json, "%s", scoped_json);
free(scoped_json);
cbm_layout_free_json(scoped_json);
return;
}

Expand Down Expand Up @@ -2200,13 +2200,13 @@ static void handle_layout(cbm_http_conn_t *c, const cbm_http_req_t *req) {
if (linked_count == 0 && missed_graph) {
cbm_store_close(store);
cbm_http_replyf(c, 200, g_cors_json, "%s", primary_json);
free(primary_json);
cbm_layout_free_json(primary_json);
return;
}

/* Parse primary JSON and append missed_graph + linked_projects */
yyjson_doc *pdoc = yyjson_read(primary_json, strlen(primary_json), 0);
free(primary_json);
cbm_layout_free_json(primary_json);
if (!pdoc) {
cbm_store_close(store);
cbm_http_replyf(c, 500, g_cors_json, "{\"error\":\"JSON parse failed\"}");
Expand Down Expand Up @@ -2258,7 +2258,7 @@ static void handle_layout(cbm_http_conn_t *c, const cbm_http_req_t *req) {

/* Parse linked project layout */
yyjson_doc *lpdoc = yyjson_read(lp_json, strlen(lp_json), 0);
free(lp_json);
cbm_layout_free_json(lp_json);
if (!lpdoc) {
cbm_store_close(lp_store);
free(linked[li]);
Expand Down
11 changes: 9 additions & 2 deletions src/ui/layout3d.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "ui/layout3d.h"
#include "ui/layout_internal.h"
#include "foundation/log.h"
#include "foundation/json_writer.h"
#include "foundation/mem_core.h"
#include "foundation/platform.h"

Expand Down Expand Up @@ -899,10 +900,12 @@ char *cbm_layout_to_json(const cbm_layout_result_t *r) {
yyjson_mut_obj_add_val(doc, root, "edges", ea);
yyjson_mut_obj_add_int(doc, root, "total_nodes", r->total_nodes);

/* The output string is an OTHER-class memory-core block, released by
* cbm_layout_free_json; the document keeps its own allocator. */
size_t len = 0;
yyjson_write_err write_err = {0};
char *json =
yyjson_mut_write_opts(doc, YYJSON_WRITE_ALLOW_INVALID_UNICODE, NULL, &len, &write_err);
char *json = yyjson_mut_write_opts(doc, YYJSON_WRITE_ALLOW_INVALID_UNICODE,
cbm_json_writer_allocator(), &len, &write_err);
yyjson_mut_doc_free(doc);
if (!json) {
char code[CBM_SZ_32];
Expand All @@ -912,3 +915,7 @@ char *cbm_layout_to_json(const cbm_layout_result_t *r) {
}
return json;
}

void cbm_layout_free_json(char *json) {
cbm_free(CBM_MEM_CLASS_OTHER, json);
}
5 changes: 4 additions & 1 deletion src/ui/layout3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ cbm_layout_result_t *cbm_layout_compute(cbm_store_t *store, const char *project,
/* Release a computed layout result and its fields through their matching allocators. */
void cbm_layout_free(cbm_layout_result_t *result);

/* Serialize layout result to JSON string. Caller must free(). */
/* Serialize layout result to JSON string. Release with cbm_layout_free_json(). */
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);

/* ── CBM Atlas region level ───────────────────────────────────── */

/* level=regions payload: one body per region (Leiden communities voted per
Expand Down
Loading
Loading