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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,12 @@ bun test
## Correctness and API contracts

The [remediation notes](REMEDIATION.md) record the boundary cases covered by
the regression suites and the commands used to verify them. Two path APIs now
require an explicit bound: C `shortest_path_reconstruct` takes the output array
capacity, and Rust `reconstruct_path` takes the source vertex. Callers of those
APIs should update their arguments before using the revised implementations.
the regression suites and the commands used to verify them. C
`shortest_path_reconstruct` and `graph_floyd_warshall_path` take the output
array capacity; Rust `reconstruct_path` takes the source vertex. Callers of
those APIs should update their arguments before using the revised
implementations. C graph weights reserve `GRAPH_INF` (`INT_MAX`), and graph
algorithms reject unrepresentable path distances or MST totals.

### Git Hooks (Optional)
```bash
Expand Down
8 changes: 6 additions & 2 deletions REMEDIATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ possible input or deployment has been audited.
| Area | Current contract |
|------|------------------|
| C shortest paths | `shortest_path_reconstruct` takes `path_capacity` in elements and returns `false` for an insufficient buffer, invalid destination, unreachable path, or negative-cycle result. |
| C Floyd-Warshall paths | `graph_floyd_warshall_path` also takes `path_capacity` in elements and leaves the output array untouched when it is too small. A path from a vertex to itself contains that one vertex. Use the next-hop matrix only after `graph_floyd_warshall` returns `true`. |
| C graph weights | `GRAPH_INF` (`INT_MAX`) is reserved and `graph_add_edge` rejects it. Dijkstra and Bellman-Ford return `NULL`, and Floyd-Warshall returns `false`, when a computed distance cannot fit below `GRAPH_INF`. Kruskal and Prim return `NULL` if a total MST weight overflows `int`. |
| Undirected graph edge counts | Go, Java, and TypeScript count each self-loop once, alongside ordinary edges. |
| TypeScript graph paths | Dijkstra and Prim use an indexed binary min-heap, and unweighted BFS reconstructs its final path from parent links. |
| C segment tree | Both constructors return `NULL` when `4 * n` integer slots cannot fit in `size_t`; callers must check the result before querying or updating. |
| C B-tree | `btree_create` returns `NULL` when the minimum degree is below 2 or too large for signed arithmetic or node allocations. |
| Rust LRU cache | Eviction removes the map entry by node identity before freeing the node, including for keys whose clones compare differently. The map is a `BTreeMap`; eviction scans it, so `put` is O(n) in the worst case. |
Expand All @@ -35,8 +39,8 @@ possible input or deployment has been audited.
trie enumeration, search byte counters, graph path buffers, counting/radix
sort, hash tombstones, and B-tree root changes.
- Graph algorithms: absent destinations, negative vertex labels, self-loops,
parallel edges, object-valued vertices, and path reconstruction from a
nonzero source.
parallel edges, object-valued vertices, integer-range boundaries, bounded
path reconstruction, and path reconstruction from a nonzero source.
- String algorithms: delimiters occurring in input, Unicode boundaries, and
palindrome sentinels.
- Data structures: lazy segment-tree logical length, skip-list live length,
Expand Down
8 changes: 5 additions & 3 deletions by-language/c/algorithms/searching.c
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,9 @@ int kth_missing(const int *arr, size_t n, int k) {

/* Helper for sorting used by three_sum */
static int compare_ints(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
int left = *(const int *)a;
int right = *(const int *)b;
return (left > right) - (left < right);
}

bool two_sum_sorted(const int *arr, size_t n, int target, size_t *i_out, size_t *j_out) {
Expand All @@ -485,7 +487,7 @@ bool two_sum_sorted(const int *arr, size_t n, int target, size_t *i_out, size_t
size_t right = n - 1;

while (left < right) {
int sum = arr[left] + arr[right];
long long sum = (long long)arr[left] + arr[right];

if (sum == target) {
if (i_out) *i_out = left;
Expand Down Expand Up @@ -516,7 +518,7 @@ size_t three_sum(int *arr, size_t n, int target, int (*triplets)[3], size_t max_
size_t right = n - 1;

while (left < right && count < max_triplets) {
int sum = arr[i] + arr[left] + arr[right];
long long sum = (long long)arr[i] + arr[left] + arr[right];

if (sum == target) {
triplets[count][0] = arr[i];
Expand Down
110 changes: 85 additions & 25 deletions by-language/c/data-structures/graph_algorithms.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,17 @@ static bool uf_union(UnionFind *uf, int x, int y) {
/* ============== Edge Comparison for Kruskal ============== */

static int edge_compare(const void *a, const void *b) {
return ((EdgeItem *)a)->weight - ((EdgeItem *)b)->weight;
int left = ((const EdgeItem *)a)->weight;
int right = ((const EdgeItem *)b)->weight;
return (left > right) - (left < right);
}

/* GRAPH_INF is reserved for unreachable paths. A distance must fit below it. */
static bool add_distance(int left, int right, int *sum_out) {
long long sum = (long long)left + right;
if (sum < INT_MIN || sum >= GRAPH_INF) return false;
*sum_out = (int)sum;
return true;
}

/* ============== Result Allocation Helpers ============== */
Expand Down Expand Up @@ -377,7 +387,12 @@ ShortestPathResult *graph_dijkstra(const Graph *g, int source) {
for (Edge *e = g->adj_list[u]; e != NULL; e = e->next) {
int v = e->dest;
if (minpq_contains(pq, v)) {
int new_dist = result->distance[u] + e->weight;
int new_dist;
if (!add_distance(result->distance[u], e->weight, &new_dist)) {
minpq_destroy(pq);
shortest_path_result_free(result);
return NULL;
}
if (new_dist < result->distance[v]) {
result->distance[v] = new_dist;
result->parent[v] = u;
Expand Down Expand Up @@ -411,7 +426,11 @@ ShortestPathResult *graph_bellman_ford(const Graph *g, int source) {

for (Edge *e = g->adj_list[u]; e != NULL; e = e->next) {
int v = e->dest;
int new_dist = result->distance[u] + e->weight;
int new_dist;
if (!add_distance(result->distance[u], e->weight, &new_dist)) {
shortest_path_result_free(result);
return NULL;
}
if (new_dist < result->distance[v]) {
result->distance[v] = new_dist;
result->parent[v] = u;
Expand All @@ -426,7 +445,12 @@ ShortestPathResult *graph_bellman_ford(const Graph *g, int source) {

for (Edge *e = g->adj_list[u]; e != NULL; e = e->next) {
int v = e->dest;
if (result->distance[u] + e->weight < result->distance[v]) {
int new_dist;
if (!add_distance(result->distance[u], e->weight, &new_dist)) {
shortest_path_result_free(result);
return NULL;
}
if (new_dist < result->distance[v]) {
result->has_negative_cycle = true;
return result;
}
Expand Down Expand Up @@ -497,9 +521,11 @@ bool graph_floyd_warshall(const Graph *g, int **dist, int **next) {
/* Set distances from adjacency list */
for (int u = 0; u < n; u++) {
for (Edge *e = g->adj_list[u]; e != NULL; e = e->next) {
dist[u][e->dest] = e->weight;
if (next != NULL) {
next[u][e->dest] = e->dest;
if (e->weight < dist[u][e->dest]) {
dist[u][e->dest] = e->weight;
if (next != NULL) {
next[u][e->dest] = e->dest;
}
}
}
}
Expand All @@ -509,7 +535,10 @@ bool graph_floyd_warshall(const Graph *g, int **dist, int **next) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != GRAPH_INF && dist[k][j] != GRAPH_INF) {
int new_dist = dist[i][k] + dist[k][j];
int new_dist;
if (!add_distance(dist[i][k], dist[k][j], &new_dist)) {
return false;
}
if (new_dist < dist[i][j]) {
dist[i][j] = new_dist;
if (next != NULL) {
Expand All @@ -532,32 +561,45 @@ bool graph_floyd_warshall(const Graph *g, int **dist, int **next) {
}

bool graph_floyd_warshall_path(int **next, int n, int src, int dest,
int *path_out, int *path_len_out) {
int *path_out, size_t path_capacity,
int *path_len_out) {
if (path_len_out != NULL) *path_len_out = 0;
if (next == NULL || path_out == NULL || path_len_out == NULL) {
return false;
}
if (src < 0 || src >= n || dest < 0 || dest >= n) {
if (n <= 0 || src < 0 || src >= n || dest < 0 || dest >= n) {
return false;
}
if (src == dest) {
if (path_capacity == 0) return false;
path_out[0] = src;
*path_len_out = 1;
return true;
}
if (next[src][dest] == -1) {
*path_len_out = 0;
return false; /* No path */
}

int len = 0;
/* Validate the entire chain and capacity before writing to path_out. */
size_t len = 1;
int current = src;
path_out[len++] = current;

while (current != dest) {
current = next[current][dest];
if (current == -1 || len >= n) {
*path_len_out = 0;
if (current < 0 || current >= n || len >= (size_t)n) {
return false;
}
path_out[len++] = current;
len++;
}
if (len > path_capacity) return false;

*path_len_out = len;
current = src;
path_out[0] = current;
for (size_t i = 1; i < len; i++) {
current = next[current][dest];
path_out[i] = current;
}

*path_len_out = (int)len;
return true;
}

Expand Down Expand Up @@ -707,6 +749,7 @@ MSTResult *graph_mst_kruskal(const Graph *g) {
result->edges = NULL;
result->num_edges = 0;
result->total_weight = 0;
long long total_weight = 0;

/* Count edges and create edge array */
int edge_count = graph_edge_count(g);
Expand Down Expand Up @@ -771,13 +814,18 @@ MSTResult *graph_mst_kruskal(const Graph *g) {
result->edges[result->num_edges][0] = u;
result->edges[result->num_edges][1] = v;
result->edges[result->num_edges][2] = edges[i].weight;
result->total_weight += edges[i].weight;
total_weight += edges[i].weight;
result->num_edges++;
}
}

free(edges);
uf_destroy(uf);
if (total_weight < INT_MIN || total_weight > INT_MAX) {
mst_result_free(result);
return NULL;
}
result->total_weight = (int)total_weight;
return result;
}

Expand All @@ -793,6 +841,7 @@ MSTResult *graph_mst_prim(const Graph *g) {
result->edges = malloc((n - 1) * sizeof(int *));
result->num_edges = 0;
result->total_weight = 0;
long long total_weight = 0;

if (result->edges == NULL) {
free(result);
Expand Down Expand Up @@ -833,13 +882,19 @@ MSTResult *graph_mst_prim(const Graph *g) {
/* Add edge to MST */
if (parent[u] != -1) {
result->edges[result->num_edges] = malloc(3 * sizeof(int));
if (result->edges[result->num_edges] != NULL) {
result->edges[result->num_edges][0] = parent[u];
result->edges[result->num_edges][1] = u;
result->edges[result->num_edges][2] = key[u];
result->total_weight += key[u];
result->num_edges++;
if (result->edges[result->num_edges] == NULL) {
free(key);
free(parent);
free(in_mst);
minpq_destroy(pq);
mst_result_free(result);
return NULL;
}
result->edges[result->num_edges][0] = parent[u];
result->edges[result->num_edges][1] = u;
result->edges[result->num_edges][2] = key[u];
total_weight += key[u];
result->num_edges++;
}

for (Edge *e = g->adj_list[u]; e != NULL; e = e->next) {
Expand All @@ -858,6 +913,11 @@ MSTResult *graph_mst_prim(const Graph *g) {
free(parent);
free(in_mst);
minpq_destroy(pq);
if (total_weight < INT_MIN || total_weight > INT_MAX) {
mst_result_free(result);
return NULL;
}
result->total_weight = (int)total_weight;
return result;
}

Expand Down
19 changes: 12 additions & 7 deletions by-language/c/data-structures/graph_algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ TraversalResult *graph_dfs_full(const Graph *g);
* Requires non-negative edge weights.
* @param g Graph
* @param source Starting vertex
* @return Shortest path result (caller must free)
* @return Shortest path result (caller must free), or NULL if a computed
* distance cannot be represented below GRAPH_INF
*/
ShortestPathResult *graph_dijkstra(const Graph *g, int source);

Expand All @@ -78,7 +79,8 @@ ShortestPathResult *graph_dijkstra(const Graph *g, int source);
* Handles negative edge weights, detects negative cycles.
* @param g Graph
* @param source Starting vertex
* @return Shortest path result (caller must free)
* @return Shortest path result (caller must free), or NULL if a computed
* distance cannot be represented below GRAPH_INF
*/
ShortestPathResult *graph_bellman_ford(const Graph *g, int source);

Expand All @@ -88,7 +90,7 @@ ShortestPathResult *graph_bellman_ford(const Graph *g, int source);
* @param g Graph
* @param dist_out 2D array to store distances (caller allocates V*V ints)
* @param next_out 2D array for path reconstruction (caller allocates V*V ints, can be NULL)
* @return true on success, false if negative cycle detected
* @return true on success, false for a negative cycle or unrepresentable distance
*/
bool graph_floyd_warshall(const Graph *g, int **dist_out, int **next_out);

Expand All @@ -99,12 +101,15 @@ bool graph_floyd_warshall(const Graph *g, int **dist_out, int **next_out);
* @param src Source vertex
* @param dest Destination vertex
* @param path_out Array to store path (caller allocates)
* @param path_capacity Number of int elements in path_out
* @param path_len_out Pointer to store path length
* @return true if a path fits; false for an invalid or unreachable destination,
* a negative-cycle result, or insufficient output capacity
* a malformed next-hop chain, or insufficient output capacity. Use only
* after graph_floyd_warshall returns true.
*/
bool graph_floyd_warshall_path(int **next, int n, int src, int dest,
int *path_out, int *path_len_out);
int *path_out, size_t path_capacity,
int *path_len_out);

/**
* Reconstruct path from source to destination.
Expand Down Expand Up @@ -141,15 +146,15 @@ TopoSortResult *graph_topo_sort_dfs(const Graph *g);
* Kruskal's algorithm for MST.
* Uses Union-Find for cycle detection.
* @param g Graph (should be undirected)
* @return MST result (caller must free)
* @return MST result (caller must free), or NULL if the total weight overflows int
*/
MSTResult *graph_mst_kruskal(const Graph *g);

/**
* Prim's algorithm for MST.
* Uses min-heap for efficient implementation.
* @param g Graph (should be undirected)
* @return MST result (caller must free)
* @return MST result (caller must free), or NULL if the total weight overflows int
*/
MSTResult *graph_mst_prim(const Graph *g);

Expand Down
19 changes: 12 additions & 7 deletions by-language/c/data-structures/graph_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,29 @@ void graph_destroy(Graph *g) {

bool graph_add_edge(Graph *g, int src, int dest, int weight) {
if (g == NULL || src < 0 || src >= g->num_vertices ||
dest < 0 || dest >= g->num_vertices) {
dest < 0 || dest >= g->num_vertices || weight == GRAPH_INF) {
return false;
}

/* Add edge src -> dest */
Edge *e = malloc(sizeof(Edge));
if (e == NULL) return false;

Edge *e2 = NULL;
if (!g->directed) {
e2 = malloc(sizeof(Edge));
if (e2 == NULL) {
free(e);
return false;
}
}

/* Allocate both directions before changing the graph. */
e->dest = dest;
e->weight = weight;
e->next = g->adj_list[src];
g->adj_list[src] = e;

/* If undirected, add reverse edge */
if (!g->directed) {
Edge *e2 = malloc(sizeof(Edge));
if (e2 == NULL) return false;

if (e2 != NULL) {
e2->dest = src;
e2->weight = weight;
e2->next = g->adj_list[dest];
Expand Down
4 changes: 2 additions & 2 deletions by-language/c/data-structures/graph_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ void graph_destroy(Graph *g);
* @param g Graph
* @param src Source vertex
* @param dest Destination vertex
* @param weight Edge weight (use 1 for unweighted)
* @return true on success
* @param weight Edge weight (use 1 for unweighted); GRAPH_INF is reserved
* @return true on success, false for an invalid edge or allocation failure
*/
bool graph_add_edge(Graph *g, int src, int dest, int weight);

Expand Down
Loading
Loading