From a2eca3a29bbb6c656f388ca040ef1a162cc4852e Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jul 2026 08:47:35 -0500 Subject: [PATCH 1/2] fix(examples): allocate geo_z instead of geo_y in 3D readGeometry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary In the `readGeometry()` helper of the C API examples, the 3D branch allocated `*geo_y` a second time instead of allocating `*geo_z`. The subsequent `fscanf` then wrote through the (NULL) `*geo_z` pointer, causing a segmentation fault whenever 3D geometry input was used. ## Root cause ```c *geo_x = (double *)malloc(n * sizeof(double)); *geo_y = (double *)malloc(n * sizeof(double)); if (dimension == 3) { *geo_y = (double *)malloc(n * sizeof(double)); // leaks geo_y, // geo_z never allocated for (int i = 0; i < n; i ++) if (3 != fscanf(fin, "%lf %lf %lf\n", *geo_x + i, *geo_y + i, *geo_z + i)) // write to NULL ``` Callers initialize `gz = NULL`, pass `&gz`, and later `free(gz)` — so `*geo_z` is expected to be allocated here. ## Fix Allocate `*geo_z` in the 3D branch (one-word change in each of `examples/amgx_capi.c` and `examples/amgx_capi_multi.c`). ## Testing Full AMGX build/tests were not run (GPU/CUDA-heavy dependency). Instead, `readGeometry()` was extracted verbatim into a standalone C harness and compiled with `gcc -Wall -Wextra` (clean): - Fixed version: 3D and 2D geometry files parse correctly, `geo_z` values verified -> PASS (exit 0). - Original (pre-fix) version: segfaults on the 3D input (exit 139, SIGSEGV) — confirming the bug and the fix. ## Why existing tests missed it The example programs are not exercised with 3D geometry input files by any automated test; the 2D path (the common case) works fine because `geo_z` is unused there. --- examples/amgx_capi.c | 2 +- examples/amgx_capi_multi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/amgx_capi.c b/examples/amgx_capi.c index 9c002d89..17ebe108 100644 --- a/examples/amgx_capi.c +++ b/examples/amgx_capi.c @@ -106,7 +106,7 @@ void readGeometry( const char *fname, double **geo_x, double **geo_y, double **g if (dimension == 3) { - *geo_y = (double *)malloc(n * sizeof(double)); + *geo_z = (double *)malloc(n * sizeof(double)); for (int i = 0; i < n; i ++) if (3 != fscanf(fin, "%lf %lf %lf\n", *geo_x + i, *geo_y + i, *geo_z + i)) diff --git a/examples/amgx_capi_multi.c b/examples/amgx_capi_multi.c index ddd7e9f7..3b7956fd 100644 --- a/examples/amgx_capi_multi.c +++ b/examples/amgx_capi_multi.c @@ -104,7 +104,7 @@ void readGeometry( const char *fname, double **geo_x, double **geo_y, double **g if (dimension == 3) { - *geo_y = (double *)malloc(n * sizeof(double)); + *geo_z = (double *)malloc(n * sizeof(double)); for (int i = 0; i < n; i ++) if (3 != fscanf(fin, "%lf %lf %lf\n", *geo_x + i, *geo_y + i, *geo_z + i)) From 0f76448da6e1ac98f55fd384a3afbcbf9c0e7949 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jul 2026 12:11:18 -0500 Subject: [PATCH 2/2] test(examples): regression test for geo_z allocation in 3D readGeometry Flags the bug where the 3D branch of readGeometry() allocated *geo_y a second time instead of *geo_z, so fscanf wrote through the NULL *geo_z pointer and segfaulted on any 3D geometry input file. The test extracts readGeometry() verbatim from examples/amgx_capi.c and examples/amgx_capi_multi.c, compiles it with gcc into a small harness, and runs it on a 3D geometry file. Red-green verification: - sh tests/test_geo_z_alloc.sh -> PASS (exit 0) with the fix. - git show HEAD~1:examples/amgx_capi.c > examples/amgx_capi.c (and same for amgx_capi_multi.c), re-run -> FAIL (exit 139, SIGSEGV) on both. --- tests/test_geo_z_alloc.sh | 84 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 tests/test_geo_z_alloc.sh diff --git a/tests/test_geo_z_alloc.sh b/tests/test_geo_z_alloc.sh new file mode 100755 index 00000000..a5d3bebc --- /dev/null +++ b/tests/test_geo_z_alloc.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# Regression test: 3D readGeometry() must allocate geo_z (not geo_y twice). +# +# Bug: in the 3D branch of readGeometry() in examples/amgx_capi.c and +# examples/amgx_capi_multi.c, `*geo_y` was allocated a second time instead +# of `*geo_z`, so the subsequent fscanf wrote through the NULL *geo_z +# pointer -> segmentation fault on any 3D geometry input file. +# +# The test extracts readGeometry() verbatim from each example source, +# compiles it into a small harness with gcc, and runs it against a 3D +# geometry file. Pre-fix code segfaults (exit 139); fixed code parses and +# returns the correct z values. + +set -u + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +REPO_ROOT=$(dirname -- "$SCRIPT_DIR") +TMPDIR_TEST=$(mktemp -d) +trap 'rm -rf "$TMPDIR_TEST"' EXIT + +cat > "$TMPDIR_TEST/harness.c" <<'EOF' +#include +#include + +static void errAndExit(const char *err) +{ + fprintf(stderr, "%s", err); + exit(1); +} + +/* readGeometry() extracted verbatim from the example source under test */ +#include "readGeometry.inc" + +int main(void) +{ + /* 3 rows of 3D geometry */ + FILE *f = fopen("geo3.txt", "w"); + if (!f) return 10; + fprintf(f, "3 3\n1.0 2.0 3.0\n4.0 5.0 6.0\n7.0 8.0 9.0\n"); + fclose(f); + + double *gx = NULL, *gy = NULL, *gz = NULL; + int dim = 0, n = 0; + readGeometry("geo3.txt", &gx, &gy, &gz, &dim, &n); + + if (dim != 3 || n != 3) return 2; + if (gz == NULL) return 3; /* geo_z never allocated */ + if (gz[0] != 3.0 || gz[1] != 6.0 || gz[2] != 9.0) return 4; + if (gy[0] != 2.0 || gy[1] != 5.0 || gy[2] != 8.0) return 5; + + free(gx); + free(gy); + free(gz); + return 0; +} +EOF + +status=0 +for src in examples/amgx_capi.c examples/amgx_capi_multi.c; do + name=$(basename "$src" .c) + awk '/^void readGeometry/{f=1} f{print} f && /^}/{exit}' \ + "$REPO_ROOT/$src" > "$TMPDIR_TEST/readGeometry.inc" + if ! grep -q readGeometry "$TMPDIR_TEST/readGeometry.inc"; then + echo "FAIL: could not extract readGeometry() from $src" + status=1 + continue + fi + if ! gcc -Wall -Wextra -o "$TMPDIR_TEST/harness" "$TMPDIR_TEST/harness.c"; then + echo "FAIL: harness for $src did not compile" + status=1 + continue + fi + (cd "$TMPDIR_TEST" && ./harness) + rc=$? + if [ "$rc" -eq 0 ]; then + echo "PASS: $name readGeometry() handles 3D input (geo_z allocated)" + else + echo "FAIL: $name readGeometry() exited $rc on 3D input" \ + "(geo_z not allocated -> NULL write/segfault)" + status=1 + fi +done + +exit "$status"