From 9b2c7acfaea57695db3948323e741327501d319e Mon Sep 17 00:00:00 2001 From: WHC Date: Wed, 7 Oct 2026 02:39:36 +0800 Subject: [PATCH] ci: fix test_subagent strcasestr crash; llvm-mingw Windows zip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clang under strict C11 left strcasestr undeclared (returns int → pointer truncation → SIGSEGV). Parse Content-Length portably; keep -Werror on make check. Drop Nix for windows-cross (root /nix fails) and use pinned llvm-mingw UCRT instead; Alpine musl gets linux-headers for audit.h. --- .gitea/workflows/ci.yml | 33 +++++++-------------- scripts/ci_windows_cross.sh | 59 +++++++++++++++++++++++++++++++++++++ tests/test_subagent.c | 21 +++++++++++-- 3 files changed, 87 insertions(+), 26 deletions(-) create mode 100755 scripts/ci_windows_cross.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 71e8aa0..12aa86f 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -37,7 +37,11 @@ jobs: - name: Build (warnings are errors) run: make CC=${{ matrix.cc }} CFLAGS="-std=c11 -Wall -Wextra -O2 -g -Werror" -j"$(nproc)" - name: Unit tests - run: make CC=${{ matrix.cc }} check + run: | + # Same -Werror as the build step so undeclared libc helpers cannot + # silently return int and truncate pointers (see test_subagent). + make CC=${{ matrix.cc }} \ + CFLAGS="-std=c11 -Wall -Wextra -O2 -g -Werror" check - name: CMake build + ctest run: | cmake -B build -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCST_BUILD_TUI=ON @@ -69,7 +73,10 @@ jobs: -o /tmp/ci_checkout.sh bash /tmp/ci_checkout.sh - name: Install build dependencies - run: apk add --no-cache build-base pkgconf curl-dev ncurses-dev git nodejs bash python3 + run: | + # linux-headers provides linux/audit.h for the seccomp probe path + apk add --no-cache build-base pkgconf curl-dev ncurses-dev \ + linux-headers git nodejs bash python3 - name: Build + unit tests run: | make CFLAGS="-std=c11 -Wall -Wextra -O2 -Werror" -j"$(nproc)" @@ -97,28 +104,8 @@ jobs: $SUDO apt-get install -y --no-install-recommends \ ca-certificates curl git cmake ninja-build make pkg-config \ python3 zip unzip perl xz-utils - - name: Install Nix (TUNA) - run: | - if ! command -v nix-shell >/dev/null; then - curl -L https://mirrors.tuna.tsinghua.edu.cn/nix/latest/install | sh -s -- --no-daemon - fi - mkdir -p "$HOME/.config/nix" - printf '%s\n' \ - 'substituters = https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store https://cache.nixos.org/' \ - 'trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=' \ - > "$HOME/.config/nix/nix.conf" - # shellcheck source=/dev/null - . "$HOME/.nix-profile/etc/profile.d/nix.sh" - nix --version - - name: Proxy GitHub for third_party clones - run: | - # curl/busybox-w32/ncurses sources; skip if the proxy is down - git config --global url."https://gitclone.com/github.com/".insteadOf "https://github.com/" - name: Cross-build Windows zip - run: | - # shellcheck source=/dev/null - . "$HOME/.nix-profile/etc/profile.d/nix.sh" - nix-shell shell-win.nix --run 'scripts/bootstrap_windows.sh .' + run: bash scripts/ci_windows_cross.sh - name: Verify zip run: | ls -lah dist/windows-x86_64/ diff --git a/scripts/ci_windows_cross.sh b/scripts/ci_windows_cross.sh new file mode 100755 index 0000000..f1b6308 --- /dev/null +++ b/scripts/ci_windows_cross.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# CI helper: MinGW-w64 UCRT toolchain via llvm-mingw (no Nix / no /nix). +# Downloads a pinned release, puts it on PATH, then runs bootstrap_windows.sh. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +VER="${CST_LLVM_MINGW_VER:-20241030}" +NAME="llvm-mingw-${VER}-ucrt-ubuntu-20.04-x86_64" +TGZ="/tmp/${NAME}.tar.xz" +# Prefer /opt when writable (root CI); else unpack under $HOME. +if [[ -w /opt || "$(id -u)" -eq 0 ]]; then + DIR="/opt/${NAME}" + PARENT=/opt +else + DIR="${HOME}/${NAME}" + PARENT="$HOME" +fi + +if [[ ! -x "$DIR/bin/x86_64-w64-mingw32-gcc" ]]; then + echo "==> download $NAME" + urls=( + "https://gitclone.com/github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz" + "https://mirror.ghproxy.com/https://github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz" + "https://github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz" + ) + ok=0 + for u in "${urls[@]}"; do + echo " try $u" + if curl -fL --retry 2 --connect-timeout 20 --retry-delay 2 -o "$TGZ" "$u"; then + ok=1 + break + fi + done + [[ $ok -eq 1 ]] || { echo "failed to download llvm-mingw" >&2; exit 1; } + rm -rf "$DIR" + mkdir -p "$PARENT" + tar -C "$PARENT" -xf "$TGZ" +fi + +export PATH="$DIR/bin:$PATH" +export MINGW_HOST=x86_64-w64-mingw32 +export BUILD_CC="${BUILD_CC:-cc}" + +need() { command -v "$1" >/dev/null || { echo "missing $1" >&2; exit 1; }; } +need x86_64-w64-mingw32-gcc +need x86_64-w64-mingw32-windres +need cmake; need ninja; need perl; need zip; need git; need make + +if ! printf '#include \n' | x86_64-w64-mingw32-gcc -dM -E - | grep -qw _UCRT; then + echo "toolchain is not UCRT" >&2 + exit 1 +fi + +# Third-party sources live on GitHub; prefer a CN-friendly rewrite. +git config --global url."https://gitclone.com/github.com/".insteadOf "https://github.com/" || true + +echo "==> bootstrap_windows.sh" +exec bash scripts/bootstrap_windows.sh "$ROOT" diff --git a/tests/test_subagent.c b/tests/test_subagent.c index 06af50b..1df48e3 100644 --- a/tests/test_subagent.c +++ b/tests/test_subagent.c @@ -18,6 +18,7 @@ #define CST_TEST_SOCKETS #include "cst_test_util.h" +#include #include #include #include @@ -52,6 +53,20 @@ static void send_all(test_sock_t fd, const char *s, size_t n) { } } +/* Portable Content-Length lookup (avoid GNU strcasestr; undeclared under + * strict C11 it is assumed to return int and the pointer truncates). */ +static const char *find_content_length(const char *hdrs) { + static const char key[] = "content-length:"; + for (const char *p = hdrs; *p; p++) { + size_t i = 0; + while (key[i] && tolower((unsigned char)p[i]) == (unsigned char)key[i]) + i++; + if (key[i] == '\0') + return p + (sizeof(key) - 1); + } + return NULL; +} + static void sse(test_sock_t fd, cJSON *delta, const char *finish) { cJSON *o = cJSON_CreateObject(); cJSON *ch = cJSON_AddArrayToObject(o, "choices"); @@ -205,7 +220,7 @@ static void handle(test_sock_t fd, const char *body_txt) { const char *rep = cJSON_IsString(c) ? strstr(c->valuestring, "child report ") : NULL; - if (strcmp(r->valuestring, "tool") == 0 && rep) + if (cJSON_IsString(r) && strcmp(r->valuestring, "tool") == 0 && rep) cst_buf_printf(&b, " [%.14s]", rep); } reply_text(fd, b.buf); @@ -230,8 +245,8 @@ static void *conn_main(void *p) { char *e = strstr(req.buf, "\r\n\r\n"); if (e) { hdr_end = (size_t)(e - req.buf) + 4; - char *cl = strcasestr(req.buf, "Content-Length:"); - need = cl ? atol(cl + 15) : 0; + const char *cl = find_content_length(req.buf); + need = cl ? atol(cl) : 0; } } if (hdr_end && (long)(req.len - hdr_end) >= need)