@@ -234,7 +234,7 @@ jobs:
234234 compiler-family : gcc
235235 c-compiler : gcc-14
236236 cc-compiler : g++-14
237- debug : nodebug # configure step passes --enable-debug unconditionally for valgrind-* lanes
237+ debug : nodebug
238238 coverage : nocoverage
239239 shell : bash
240240 - test-group : extra
@@ -244,7 +244,7 @@ jobs:
244244 compiler-family : gcc
245245 c-compiler : gcc-14
246246 cc-compiler : g++-14
247- debug : nodebug # configure step passes --enable-debug unconditionally for valgrind-* lanes
247+ debug : nodebug
248248 coverage : nocoverage
249249 shell : bash
250250 # Test build without digest auth support (issue #232)
@@ -674,13 +674,27 @@ jobs:
674674 uses : actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
675675 with :
676676 path : ${{ github.workspace }}/libcxx-msan
677- key : ${{ matrix.os }}-${{ matrix.c-compiler }}-libcxx-msan-origins-v1
677+ key : ${{ matrix.os }}-${{ matrix.c-compiler }}-libcxx-msan-llvmorg-18.1.8- origins-v1
678678 if : ${{ matrix.build-type == 'msan' }}
679679
680680 - name : Build instrumented libc++ (msan lane, if not cached)
681+ # Supply-chain: llvmorg-18.1.8 is a mutable git tag, not an immutable
682+ # commit SHA, so a post-clone SHA assertion guards against the tag
683+ # being force-moved upstream (CWE-829). LLVM_SHA was obtained via
684+ # `git ls-remote https://github.com/llvm/llvm-project refs/tags/llvmorg-18.1.8`
685+ # at pin time; to rotate the tag, update both the --branch value below
686+ # and LLVM_SHA in the same PR. Mirrors the IWYU SHA-pin pattern above.
681687 run : |
688+ set -euo pipefail
682689 sudo apt-get install -y ninja-build cmake ;
690+ LLVM_SHA=3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff ;
683691 git clone --depth 1 --branch llvmorg-18.1.8 https://github.com/llvm/llvm-project.git llvm-project ;
692+ HEAD_SHA="$(git -C llvm-project rev-parse --verify HEAD)" ;
693+ if [ "${HEAD_SHA}" != "${LLVM_SHA}" ]; then
694+ echo "LLVM SHA mismatch: expected ${LLVM_SHA}, got ${HEAD_SHA}" >&2 ;
695+ exit 1 ;
696+ fi
697+ echo "llvm-project pinned at SHA: ${HEAD_SHA}" ;
684698 cmake -G Ninja -S llvm-project/runtimes -B build-libcxx-msan \
685699 -DCMAKE_C_COMPILER=clang-18 \
686700 -DCMAKE_CXX_COMPILER=clang++-18 \
@@ -754,6 +768,11 @@ jobs:
754768 - name : Fetch libmicrohttpd from cache (msan lane)
755769 id : cache-libmicrohttpd-msan
756770 uses : actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
771+ # NOTE: the '-v1' suffix below must be bumped manually whenever the
772+ # CFLAGS/LDFLAGS in the "Build instrumented libmicrohttpd" step change
773+ # -- this key does not otherwise encode them, so a flag change alone
774+ # would silently keep reusing a stale (differently-instrumented)
775+ # cached artifact.
757776 with :
758777 path : libmicrohttpd-1.0.3
759778 key : ${{ matrix.os }}-${{ matrix.c-compiler }}-libmicrohttpd-1.0.3-msan-v1
@@ -772,7 +791,7 @@ jobs:
772791 export CFLAGS='-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -g -O1' ;
773792 export LDFLAGS='-fsanitize=memory' ;
774793 ./configure --disable-examples --disable-https ;
775- make ;
794+ make -j$(nproc) ;
776795 if : ${{ matrix.build-type == 'msan' && steps.cache-libmicrohttpd-msan.outputs.cache-hit != 'true' }}
777796
778797 - name : Install libmicrohttpd
@@ -911,14 +930,24 @@ jobs:
911930 - name : Run valgrind-lane structural gate
912931 # TASK-088: asserts the Valgrind CI lane is wired correctly (valid YAML,
913932 # valgrind matrix entry present, no --disable-valgrind-{helgrind,drd,
914- # sgcheck} flags remain, `make check-valgrind` still invoked, and the
915- # results-print step surfaces helgrind + drd logs). Valgrind is
933+ # sgcheck} flags remain, `make "check-valgrind-${TOOL}"` (per-tool
934+ # AX_VALGRIND_CHECK target) invoked, and the results-print step
935+ # surfaces helgrind + drd logs). Valgrind is
916936 # Linux-only, so the actual race-detector run + finding-triage happen on
917937 # the PR's Linux lane; this gate only guards the wiring. Mirrors the
918938 # check-msan-lane.sh gate idiom.
919939 run : ./scripts/check-valgrind-lane.sh
920940 if : ${{ matrix.build-type == 'lint' && matrix.os-type == 'ubuntu' }}
921941
942+ - name : Run DR-008 stress-lane structural gate
943+ # TASK-092: asserts the two DR-008 concurrency/deadlock stress-gate
944+ # steps (route_table_concurrency under TSan, stop()-from-handler
945+ # deadlock contract) stay wired correctly -- present, correctly `if:`
946+ # gated, and timeout-boxed. See test/PERFORMANCE.md ("CI wiring —
947+ # DR-008 stress gates"). Mirrors the check-msan-lane.sh gate idiom.
948+ run : ./scripts/check-dr008-lanes.sh
949+ if : ${{ matrix.build-type == 'lint' && matrix.os-type == 'ubuntu' }}
950+
922951 - name : Run parallel-install-lane structural gate
923952 # TASK-089: asserts the parallel-install CI gate is wired correctly
924953 # (valid YAML, a matrix entry opts in via parallel-install: check, a step
@@ -1158,8 +1187,27 @@ jobs:
11581187 export MSAN_OPTIONS="${MSAN_OPTIONS}:suppressions=$(pwd)/../test/msan.supp" ;
11591188 fi
11601189 # Derive the unit/ subset from the test Makefile so newly-added
1161- # unit tests are covered automatically.
1162- MSAN_TESTS="$(grep -oE '^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*unit/' ../test/Makefile.am | sed -E 's/_SOURCES.*//' | tr '\n' ' ')" ;
1190+ # unit tests are covered automatically, then further exclude any
1191+ # unit/-sourced target whose test file transitively links curl
1192+ # (e.g. via curl_easy_perform() round-trips) -- curl cannot
1193+ # practically be msan-instrumented, so its uninstrumented output
1194+ # would surface as false use-of-uninitialized-value reports.
1195+ # NOTE: this only matches a target's _SOURCES entry when it is
1196+ # written on a single line (`<target>_SOURCES = unit/<file>.cpp`);
1197+ # a future multi-line _SOURCES assignment, or a unit/ target whose
1198+ # first source is not itself under unit/, would be silently
1199+ # dropped without failing CI (the empty-set guard below only
1200+ # catches total loss, not partial narrowing).
1201+ MSAN_TESTS="" ;
1202+ while IFS= read -r src_line; do
1203+ target="$(printf '%s' "$src_line" | sed -E 's/_SOURCES.*//')" ;
1204+ source_rel="$(printf '%s' "$src_line" | sed -E 's/^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*//')" ;
1205+ if grep -q 'curl/curl.h' "../test/${source_rel}" 2>/dev/null; then
1206+ echo "msan: excluding ${target} (${source_rel} includes curl/curl.h)" ;
1207+ continue ;
1208+ fi
1209+ MSAN_TESTS="${MSAN_TESTS}${target} " ;
1210+ done <<< "$(grep -E '^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*unit/' ../test/Makefile.am)" ;
11631211 if [ -z "$MSAN_TESTS" ]; then echo "ERROR: msan: derived empty test set — grep pattern found no unit/ targets in test/Makefile.am" >&2; exit 1; fi ;
11641212 make check TESTS="$MSAN_TESTS" ;
11651213 else
@@ -1178,7 +1226,7 @@ jobs:
11781226 # Reuses the same TSAN_OPTIONS suppression file as "Run tests" to
11791227 # mask the benign libstdc++ std::ctype narrow-cache race; any
11801228 # libhttpserver-internal race stays fatal.
1181- timeout-minutes : 2
1229+ timeout-minutes : 5
11821230 run : |
11831231 cd build ;
11841232 export TSAN_OPTIONS="suppressions=$(pwd)/../test/tsan.supp" ;
@@ -1196,8 +1244,36 @@ jobs:
11961244 # is the safety net; a clean zero-exit child would be a regression.
11971245 timeout-minutes : 3
11981246 run : |
1247+ set -o pipefail ;
11991248 cd build ;
1200- make -C test check-stop-from-handler ;
1249+ make -C test check-stop-from-handler 2>&1 | tee test/stop_from_handler.log ;
1250+ if : ${{ matrix.os-type == 'ubuntu' && matrix.test-group == 'basic' && matrix.c-compiler == 'gcc' && matrix.debug == 'nodebug' && matrix.linking == 'dynamic' && matrix.build-type == 'classic' }}
1251+
1252+ - name : Verify stop()-from-handler contract fired (TASK-092)
1253+ # TASK-092: guards against Sub-test B
1254+ # (stop_from_handler_deadlocks_as_documented) silently reporting
1255+ # [SKIP] instead of exercising the DR-008 deadlock contract -- mirrors
1256+ # the "Verify [SKIP] markers fire on tls-no-cli lane (TASK-076)"
1257+ # grep-the-log pattern.
1258+ run : |
1259+ set -euo pipefail
1260+ cd build
1261+ LOG=test/stop_from_handler.log
1262+ if [ ! -f "$LOG" ]; then
1263+ echo "ERROR: $LOG missing — check-stop-from-handler did not run" >&2
1264+ exit 1
1265+ fi
1266+ if grep -q '^\[SKIP\] stop_from_handler_deadlocks_as_documented' "$LOG"; then
1267+ echo "ERROR: stop_from_handler_deadlocks_as_documented reported [SKIP] instead of exercising the deadlock contract" >&2
1268+ cat "$LOG"
1269+ exit 1
1270+ fi
1271+ if ! grep -q '^\[OK\] stop_from_handler' "$LOG"; then
1272+ echo "ERROR: expected an '[OK] stop_from_handler' marker in $LOG; none found" >&2
1273+ cat "$LOG"
1274+ exit 1
1275+ fi
1276+ echo "PASS — stop_from_handler_deadlocks_as_documented exercised the DR-008 deadlock contract"
12011277 if : ${{ matrix.os-type == 'ubuntu' && matrix.test-group == 'basic' && matrix.c-compiler == 'gcc' && matrix.debug == 'nodebug' && matrix.linking == 'dynamic' && matrix.build-type == 'classic' }}
12021278
12031279 - name : Fetch master ref for parallel-install
@@ -1249,7 +1325,7 @@ jobs:
12491325 echo "ERROR: $LOG missing — ws_start_stop did not run" >&2
12501326 exit 1
12511327 fi
1252- SKIP_COUNT=$(grep -c '^\[SKIP\].*gnutls-cli ' "$LOG" || true)
1328+ SKIP_COUNT=$(grep -c '^\[SKIP\].*psk_connection ' "$LOG" || true)
12531329 echo "Found $SKIP_COUNT [SKIP] markers attributable to gnutls-cli"
12541330 if [ "$SKIP_COUNT" -lt 5 ]; then
12551331 echo "ERROR: expected >=5 [SKIP] markers in $LOG; got $SKIP_COUNT" >&2
@@ -1329,6 +1405,8 @@ jobs:
13291405 if [ -f "test/$log" ]; then
13301406 echo "===== test/$log =====" ;
13311407 cat "test/$log" ;
1408+ else
1409+ echo "test/$log not produced by this lane" ;
13321410 fi
13331411 done
13341412 if : ${{ always() && startsWith(matrix.build-type, 'valgrind-') }}
0 commit comments