Skip to content

Commit 7abffbb

Browse files
fix(cross-repo): harden Maven library cleanup and growth
1 parent 4bf45f8 commit 7abffbb

3 files changed

Lines changed: 144 additions & 15 deletions

File tree

src/pipeline/pass_cross_repo.c

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,25 @@ static const char *cr_itoa(int v) {
4747
return cr_ibuf;
4848
}
4949

50+
bool cbm_cross_repo_project_list_alloc(char ***out, int cap, void *(*malloc_fn)(size_t)) {
51+
if (!out) {
52+
return false;
53+
}
54+
*out = NULL;
55+
if (cap <= 0 || !malloc_fn) {
56+
return false;
57+
}
58+
if ((size_t)cap > SIZE_MAX / sizeof(char *)) {
59+
return false;
60+
}
61+
char **items = malloc_fn((size_t)cap * sizeof(*items));
62+
if (!items) {
63+
return false;
64+
}
65+
*out = items;
66+
return true;
67+
}
68+
5069
/* ── Helpers ─────────────────────────────────────────────────────── */
5170

5271
static const char *cr_cache_dir(void) {
@@ -114,16 +133,14 @@ static void delete_cross_edges(cbm_store_t *store, const char *project) {
114133
cbm_store_delete_edges_by_type(store, project, "CROSS_GRAPHQL_CALLS");
115134
cbm_store_delete_edges_by_type(store, project, "CROSS_TRPC_CALLS");
116135
cbm_store_delete_edges_by_type(store, project, "CROSS_LIBRARY_DEPENDS_ON");
117-
cbm_store_delete_edges_by_type(store, project, "CROSS_LIBRARY_USED_BY");
118136
struct sqlite3 *db = cbm_store_get_db(store);
119137
if (!db) {
120138
return;
121139
}
122140
sqlite3_stmt *st = NULL;
123141
if (sqlite3_prepare_v2(db,
124-
"DELETE FROM nodes WHERE project=?1 AND "
125-
"(qualified_name GLOB '__library__*' OR "
126-
"qualified_name GLOB '__library_consumer__*')",
142+
"DELETE FROM nodes WHERE project=?1 AND label='Library' "
143+
"AND qualified_name GLOB '__library__*'",
127144
CBM_NOT_FOUND, &st, NULL) == SQLITE_OK) {
128145
sqlite3_bind_text(st, SKIP_ONE, project, CBM_NOT_FOUND, SQLITE_STATIC);
129146
sqlite3_step(st);
@@ -578,7 +595,12 @@ static int collect_all_projects(char ***out) {
578595

579596
int cap = CR_INIT_CAP;
580597
int count = 0;
581-
char **projects = malloc((size_t)cap * sizeof(char *));
598+
char **projects = NULL;
599+
if (!cbm_cross_repo_project_list_alloc(&projects, cap, malloc)) {
600+
cbm_closedir(d);
601+
*out = NULL;
602+
return 0;
603+
}
582604

583605
cbm_dirent_t *ent;
584606
while ((ent = cbm_readdir(d)) != NULL) {
@@ -593,15 +615,25 @@ static int collect_all_projects(char ***out) {
593615
continue;
594616
}
595617
if (count >= cap) {
596-
cap *= PAIR_LEN;
597-
char **tmp = realloc(projects, (size_t)cap * sizeof(char *));
618+
if (cap > INT32_MAX / PAIR_LEN) {
619+
break;
620+
}
621+
int new_cap = cap * PAIR_LEN;
622+
if ((size_t)new_cap > SIZE_MAX / sizeof(*projects)) {
623+
break;
624+
}
625+
char **tmp = realloc(projects, (size_t)new_cap * sizeof(*projects));
598626
if (!tmp) {
599627
break;
600628
}
629+
cap = new_cap;
601630
projects = tmp;
602631
}
603632
/* Strip .db extension */
604633
projects[count] = malloc(len - PAIR_LEN);
634+
if (!projects[count]) {
635+
break;
636+
}
605637
memcpy(projects[count], ent->name, len - CR_DB_EXT_LEN);
606638
projects[count][len - CR_DB_EXT_LEN] = '\0';
607639
count++;

src/pipeline/pass_cross_repo_maven.c

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ bool cbm_cross_repo_maven_grow_array(void **items, int *cap, size_t elem_size,
8686
return false;
8787
}
8888
int new_cap = *cap * PAIR_LEN;
89+
if ((size_t)new_cap > SIZE_MAX / elem_size) {
90+
return false;
91+
}
8992
void *tmp = realloc_fn(*items, (size_t)new_cap * elem_size);
9093
if (!tmp) {
9194
return false;
@@ -370,12 +373,11 @@ static int list_pom_paths(cbm_store_t *store, const char *project, char ***out)
370373
continue;
371374
}
372375
if (count >= cap) {
373-
cap *= PAIR_LEN;
374-
char **tmp = realloc(paths, (size_t)cap * sizeof(char *));
375-
if (!tmp) {
376+
void *grown = paths;
377+
if (!cbm_cross_repo_maven_grow_array(&grown, &cap, sizeof(*paths), realloc)) {
376378
break;
377379
}
378-
paths = tmp;
380+
paths = grown;
379381
}
380382
paths[count] = malloc(strlen(path) + SKIP_ONE);
381383
if (paths[count]) {
@@ -438,16 +440,15 @@ static int collect_artifacts(cbm_store_t *store, const char *project, mcr_artifa
438440
}
439441
if (group && group[0] && artifact && artifact[0]) {
440442
if (count >= cap) {
441-
cap *= PAIR_LEN;
442-
mcr_artifact_t *tmp = realloc(items, (size_t)cap * sizeof(*items));
443-
if (!tmp) {
443+
void *grown = items;
444+
if (!cbm_cross_repo_maven_grow_array(&grown, &cap, sizeof(*items), realloc)) {
444445
free(parent_group);
445446
free(group);
446447
free(artifact);
447448
free(xml);
448449
break;
449450
}
450-
items = tmp;
451+
items = grown;
451452
}
452453
memset(&items[count], 0, sizeof(items[count]));
453454
items[count].group_id = group;

tests/test_pipeline.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <stdlib.h>
1919
#include <string.h>
20+
#include <stdint.h>
2021
#include <stdatomic.h>
2122
#include "foundation/compat_thread.h"
2223
#include <fcntl.h>
@@ -1284,13 +1285,41 @@ static void cleanup_cross_maven_fixture(cross_maven_fixture_t *fx) {
12841285

12851286
extern bool cbm_cross_repo_maven_grow_array(void **items, int *cap, size_t elem_size,
12861287
void *(*realloc_fn)(void *, size_t));
1288+
extern bool cbm_cross_repo_project_list_alloc(char ***out, int cap,
1289+
void *(*malloc_fn)(size_t));
12871290

12881291
static void *test_maven_realloc_fails(void *ptr, size_t size) {
12891292
(void)ptr;
12901293
(void)size;
12911294
return NULL;
12921295
}
12931296

1297+
static int g_test_maven_realloc_calls;
1298+
1299+
static void *test_maven_realloc_records_call(void *ptr, size_t size) {
1300+
(void)size;
1301+
g_test_maven_realloc_calls++;
1302+
return ptr;
1303+
}
1304+
1305+
static void *test_project_list_malloc_fails(size_t size) {
1306+
(void)size;
1307+
return NULL;
1308+
}
1309+
1310+
TEST(cross_repo_project_list_initial_alloc_failure_returns_empty) {
1311+
char **projects = (char **)1;
1312+
1313+
ASSERT_FALSE(cbm_cross_repo_project_list_alloc(&projects, 32, test_project_list_malloc_fails));
1314+
ASSERT_NULL(projects);
1315+
1316+
projects = (char **)1;
1317+
ASSERT_FALSE(cbm_cross_repo_project_list_alloc(&projects, 0, test_project_list_malloc_fails));
1318+
ASSERT_NULL(projects);
1319+
1320+
PASS();
1321+
}
1322+
12941323
TEST(cross_repo_maven_failed_growth_preserves_capacity) {
12951324
int cap = 32;
12961325
char *items = malloc((size_t)cap);
@@ -1307,6 +1336,24 @@ TEST(cross_repo_maven_failed_growth_preserves_capacity) {
13071336
PASS();
13081337
}
13091338

1339+
TEST(cross_repo_maven_growth_rejects_byte_overflow_before_realloc) {
1340+
int cap = 2;
1341+
char *items = malloc(1);
1342+
ASSERT_NOT_NULL(items);
1343+
void *original = items;
1344+
g_test_maven_realloc_calls = 0;
1345+
1346+
ASSERT_FALSE(cbm_cross_repo_maven_grow_array(
1347+
(void **)&items, &cap, (SIZE_MAX / (size_t)(cap * PAIR_LEN)) + 1,
1348+
test_maven_realloc_records_call));
1349+
ASSERT_EQ(g_test_maven_realloc_calls, 0);
1350+
ASSERT_EQ(cap, 2);
1351+
ASSERT_EQ(items == original, 1);
1352+
1353+
free(items);
1354+
PASS();
1355+
}
1356+
13101357
TEST(cross_repo_maven_dependency_creates_library_edges) {
13111358
const char *provider_pom = "<project><modelVersion>4.0.0</modelVersion>"
13121359
"<groupId>com.example.platform</groupId>"
@@ -1493,7 +1540,16 @@ TEST(cross_repo_maven_cleanup_preserves_unrelated_nodes) {
14931540
.start_line = 1,
14941541
.end_line = 1,
14951542
.properties_json = "{}"};
1543+
cbm_node_t prefixed_function = {.project = "consumer",
1544+
.label = "Function",
1545+
.name = "PrefixedFunction",
1546+
.qualified_name = "__library__manual_function_should_stay",
1547+
.file_path = "src/manual.c",
1548+
.start_line = 1,
1549+
.end_line = 1,
1550+
.properties_json = "{}"};
14961551
ASSERT_GT(cbm_store_upsert_node(consumer, &unrelated), 0);
1552+
ASSERT_GT(cbm_store_upsert_node(consumer, &prefixed_function), 0);
14971553
cbm_store_close(consumer);
14981554

14991555
const char *targets[] = {"provider"};
@@ -1503,12 +1559,49 @@ TEST(cross_repo_maven_cleanup_preserves_unrelated_nodes) {
15031559
consumer = cbm_store_open_path(fx.consumer_db);
15041560
ASSERT_NOT_NULL(consumer);
15051561
ASSERT_TRUE(node_exists_by_qn(consumer, "consumer", "xxlibraryzz_should_stay"));
1562+
ASSERT_TRUE(node_exists_by_qn(consumer, "consumer", "__library__manual_function_should_stay"));
15061563
cbm_store_close(consumer);
15071564

15081565
cleanup_cross_maven_fixture(&fx);
15091566
PASS();
15101567
}
15111568

1569+
TEST(cross_repo_maven_provider_rerun_preserves_incoming_used_by) {
1570+
const char *provider_pom = "<project><modelVersion>4.0.0</modelVersion>"
1571+
"<groupId>com.example.platform</groupId>"
1572+
"<artifactId>shared-library</artifactId>"
1573+
"<version>1.0.0</version></project>";
1574+
const char *consumer_pom =
1575+
"<project><modelVersion>4.0.0</modelVersion>"
1576+
"<groupId>app</groupId><artifactId>consumer</artifactId>"
1577+
"<dependencies><dependency><groupId>com.example.platform</groupId>"
1578+
"<artifactId>shared-library</artifactId><version>1.0.0</version>"
1579+
"</dependency></dependencies></project>";
1580+
cross_maven_fixture_t fx;
1581+
ASSERT_EQ(setup_cross_maven_fixture(&fx, provider_pom, consumer_pom), 0);
1582+
1583+
const char *provider_targets[] = {"provider"};
1584+
cbm_cross_repo_result_t result = cbm_cross_repo_match("consumer", provider_targets, 1);
1585+
ASSERT_EQ(result.library_edges, 1);
1586+
1587+
cbm_store_t *provider = cbm_store_open_path(fx.provider_db);
1588+
ASSERT_NOT_NULL(provider);
1589+
ASSERT_EQ(count_edges_by_type(provider, "provider", "CROSS_LIBRARY_USED_BY"), 1);
1590+
cbm_store_close(provider);
1591+
1592+
const char *consumer_targets[] = {"consumer"};
1593+
result = cbm_cross_repo_match("provider", consumer_targets, 1);
1594+
ASSERT_EQ(result.library_edges, 0);
1595+
1596+
provider = cbm_store_open_path(fx.provider_db);
1597+
ASSERT_NOT_NULL(provider);
1598+
ASSERT_EQ(count_edges_by_type(provider, "provider", "CROSS_LIBRARY_USED_BY"), 1);
1599+
cbm_store_close(provider);
1600+
1601+
cleanup_cross_maven_fixture(&fx);
1602+
PASS();
1603+
}
1604+
15121605
TEST(cross_repo_maven_removed_dependency_clears_provider_used_by) {
15131606
const char *provider_pom = "<project><modelVersion>4.0.0</modelVersion>"
15141607
"<groupId>com.example.platform</groupId>"
@@ -6591,13 +6684,16 @@ SUITE(pipeline) {
65916684
/* FastAPI Depends edge tracking (PR #66 port) */
65926685
RUN_TEST(pipeline_fastapi_depends_edges);
65936686
/* Cross-repo library dependency linking */
6687+
RUN_TEST(cross_repo_project_list_initial_alloc_failure_returns_empty);
65946688
RUN_TEST(cross_repo_maven_failed_growth_preserves_capacity);
6689+
RUN_TEST(cross_repo_maven_growth_rejects_byte_overflow_before_realloc);
65956690
RUN_TEST(cross_repo_maven_dependency_creates_library_edges);
65966691
RUN_TEST(cross_repo_maven_dependency_escapes_library_edge_props);
65976692
RUN_TEST(cross_repo_maven_dependency_management_does_not_create_library_edge);
65986693
RUN_TEST(cross_repo_maven_commented_dependency_does_not_create_library_edge);
65996694
RUN_TEST(cross_repo_maven_plugin_dependency_does_not_create_library_edge);
66006695
RUN_TEST(cross_repo_maven_cleanup_preserves_unrelated_nodes);
6696+
RUN_TEST(cross_repo_maven_provider_rerun_preserves_incoming_used_by);
66016697
RUN_TEST(cross_repo_maven_removed_dependency_clears_provider_used_by);
66026698
RUN_TEST(cross_repo_maven_long_coordinates_do_not_collide);
66036699
RUN_TEST(cross_repo_maven_fixture_restores_cache_dir);

0 commit comments

Comments
 (0)