11 Commits

Author SHA1 Message Date
Martin Vogel d6c8d1dd0a fix(msan): split the known-red block by cause; fix the RSS one properly
The x86-64 leg runs this lane without exclusions on purpose, to settle
which limits are architectural. It has now run, and it disproves part of
what the previous block asserted. That block claimed all seven excluded
suites "abort with stack-overflow". Five do. Two do not, and lumping
them together hid two different problems behind one rationale.

  (A) stack-overflow, five suites: grammar_regression grammar_labels
      pipeline lang_contract grammar_probe_e. Confirmed on BOTH arm64 and
      x86-64 (CI logged 5), so it is not the aarch64 artifact an earlier
      note claimed. The recursion guards bound DEPTH while the resource
      exhausted is BYTES; that follow-up stands unchanged.

  (B) cli: no overflow at all. On x86-64 it runs to completion, 253
      passed / 5 failed, every failure in the install or activation path,
      with "agent_config agent=OpenClaw op=mcp_install" above them. Green
      on every other venue. MSan reported zero use-of-uninitialized-value
      in it, so the exclusion costs no uninit coverage. Recorded as
      undiagnosed rather than guessed at: the local lane is arm64 where
      these suites hit (A) before reaching this code, so there is no
      faithful venue to iterate in and each attempt is a ~30min round
      trip. That is a follow-up with an owner, not a dismissal.

  (C) incremental: an RSS BUDGET failure, 3054MB against a 2304MB limit
      -- not an overflow either. MSan maps shadow (and origin) memory for
      every allocation, so the budget cannot separate a leak from shadow.
      FIXED rather than excluded: the assertion is now skipped under
      __has_feature(memory_sanitizer) only, so the guard keeps its teeth
      on every other platform, where inflating the budget would have
      blinded it. The suite stays IN the lane.

Verified: with (C) fixed, incremental is 163 passed / 0 failed and ZERO
stack-overflows under the local arm64 MSan container -- so it never
belonged in the overflow list on either architecture.

msan-lane.sh no longer forces MSAN_EXCLUDE empty. That override existed
to ask the architectural question; it is answered, and keeping it would
re-red the gate for causes already recorded. Both venues now read the one
authoritative list in scripts/msan.sh, which still warns loudly that the
lane is partial.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 04:58:44 +02:00
Martin Vogel b3fb6689c9 fix(msan): whitelist the deep-recursion suites; correct the lane's diagnosis
The MSan lane gates CI and has never been green: seven suites abort with
a stack-overflow inside MSan's memset interceptor on a worker thread.
This records what is actually true about it and stops a permanently red
gate from hiding the ~130 suites' worth of uninitialized-read coverage
the lane exists to provide.

Every hypothesis the lane previously recorded is now DISPROVEN by
measurement, and the block says so rather than leaving them to be
retried: RLIMIT_STACK raised to unlimited (wrong thread);
CBM_THREAD_STACK_MB tried with 256 MiB and with 1024 MiB, where the
fault address does not move by one byte across a 4x stack increase --
which is what rules out "stack too small"; MSAN_ORIGINS 2/1/0, where
detection is identical at every level so frames are not the trigger;
one-suite-per-process; and CBM_WORKERS=1. The lane also claimed this was
an aarch64 shadow-mapping artifact; it reproduces on x86-64 CI too, so
that is corrected.

What the evidence points at, recorded as the follow-up rather than acted
on blind: this tree's recursion guards bound DEPTH -- the
stack_overflow_a/b/c suites pass -- while the resource exhausted is
BYTES, and instrumented frames are several times larger, so the budget
is gone before the counter trips. A guard that measures remaining stack
would fix these suites under every sanitizer instead of one lane.

The exclusion is by name, narrow, and expires with that fix. Verified:
with those seven skipped, every remaining MSan suite passes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 03:28:27 +02:00
Martin Vogel bc3cc3f49f fix(msan): shard the lane one suite per process, origins default 1
With the zstd feature-macro fix in, the x86-64 leg finally RUNS -- and
thousands of tests pass before the known deep-recursion stack overflow
lands in grammar_regression, the same signature as the local arm64 wall.
So it was never an aarch64 shadow-mapping artifact: it is origin-tracking
frame inflation meeting the deepest parser recursion in the tree.

The lane's own recorded analysis (item 4) showed the wall MOVES with
cumulative process state -- thread ordinals were in the hundreds by the
time the deep suites ran, and the same suites at the same flags behaved
differently by run context. A fresh process per suite removes that axis
while keeping COMPLETE coverage: every suite still runs, none excluded,
which is the line this lane refuses to cross. Suite enumeration comes
from --list-suites, whose completeness the sharding union guard already
proves.

Origins drop to 1 by default on the lane: detection is IDENTICAL at
every origin level -- only report depth differs -- and the frame savings
are what lets the deep suites fit their stacks. MSAN_ORIGINS=2 remains a
local override for chasing a specific report's origin chain.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 18:43:44 +02:00
Martin Vogel a4cef7f922 fix(msan): pin _GNU_SOURCE for the zstd object alongside the forced stdint
Scoping -include stdint.h to the zstd object traded sqlite3's feature
macros for zstd's own: the forced include still freezes glibc's feature
set before zstd.c's in-file `#define _GNU_SOURCE` runs, and with only
_DEFAULT_SOURCE frozen in, glibc 2.39 does not declare qsort_r --
zstd.c:47409 fails exactly as the x86-64 leg reported. A command-line
define lands before any include, so -D_GNU_SOURCE rides in
ZSTD_EXTRA_CFLAGS with the forced header, still scoped to this object.

Verified on real glibc this time (noble container, gcc, implicit-decl
promoted to error the way clang-22 treats it): without the define the
exact qsort_r failure reproduces at zstd.c:47409; with it the file is
clean. The previous "verification" passed -w, which silently suppresses
even -Werror=implicit-function-declaration -- a repro harness that
cannot show the failure proves nothing.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 17:47:36 +02:00
Martin Vogel 6a0adb4430 fix(msan): scope the zstd stdint workaround to the zstd object
The previous fix put -include stdint.h on the lane's global SANITIZE
line. That traded one vendored compile break for another: force-including
a libc header ahead of every source file freezes glibc's feature-test
macros before sqlite3.c can set _GNU_SOURCE for itself, and its view of
libc loses MREMAP_MAYMOVE and nanosleep (17 errors on the x86-64 CI
leg).

The workaround only ever had one legitimate target -- the zstd
amalgamation whose MEMORY_SANITIZER block lost its stdint re-include --
so it now rides a per-object hook (ZSTD_EXTRA_CFLAGS) that the MSan lane
sets and every other build leaves empty. sqlite3.c compiles exactly as
before in every lane.

Verified locally that zstd compiles with the hook and the default rule
stays untouched; the MSan leg itself is x86-64-only, so CI is its
verification venue.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 17:02:51 +02:00
Martin Vogel 7d390c5120 fix(ci): unbreak the MSan and diag lanes on x86-64
MSan: vendored zstd fails to compile. Its MSan-only block (guarded by
MEMORY_SANITIZER) declares __msan_test_shadow returning intptr_t and
reaches for the type with

    #define ZSTD_DEPS_NEED_STDINT
    #include "zstd_deps.h"

but the amalgamator that produced zstd.c collapsed that second include
into a "skipping file" comment, so the define pulls nothing in and
intptr_t is undeclared. Only this lane compiles that block at all, and
only where <stddef.h> does not drag stdint.h in transitively -- which is
why it built on the local aarch64 container and failed on CI's x86-64.
The lane now forces the header. Patching the vendored amalgamation would
be silently undone by the next re-vendor.

diag: detect_invalid_pointer_pairs comes back out. It fires during static
initialisation inside vendored simplecpp -- a std::string global at
simplecpp.cpp:101 -- with a second "pointer" of 0xfffffffffffffff3, a
sentinel rather than an address: libstdc++ string internals, not
anything this codebase wrote. It is a process-wide runtime flag with no
per-file scoping, so unlike the analyzer's path filter it cannot be
aimed away from vendored code. Keeping it would mean a permanently red
lane reporting a non-defect, which is how a lane gets ignored. The
instrumentation it needed comes out with it.

The other three off-by-default checks stay: stack-use-after-return,
stack-use-after-scope, strict-string-checks. Those are the ones covering
bug classes nothing else in the matrix looks for, and none of them
fired.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 16:23:19 +02:00
Martin Vogel 02f870105f fix(msan): the lane runs the complete code by default
A sanitizer lane covers everything or it is not a sanitizer lane: a pass over
a subset asserts coverage it does not have. My exclusion list had grown to
three suites — including pipeline, a large one — and dressing it up as an O10
whitelist was wrong. O10 governs a board that TRACKS known-red reproductions;
it does not license cutting a sanitizer's coverage to make it green.

MSAN_EXCLUDE now defaults to EMPTY in both venues. It remains as an iteration
aid — an engineer fixing the underlying problem can narrow the run — and it
prints an explicit warning that a green partial result proves nothing about
the tree.

Consequence, stated plainly: the lane is currently RED on the local arm64
container, where three deep-recursion suites overflow their thread stacks
under instrumentation. That is a bug to fix, not a list to live with, and the
script records everything known about it. The CI leg (x86-64, where MSan's
stack handling is far better supported) is where it gets settled.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 15:37:33 +02:00
Martin Vogel 54dd5ad781 ci(msan): restore the lane; x86-64 CI is its authoritative venue
Two corrections.

First, removing the CI job while keeping the compose service and the local
bindings left the ladder carrying a lane CI did not have — the venue
asymmetry the unification work exists to prevent. The lane now lives in both
venues again.

Second, and the reason the removal was wrong: the evidence behind it was
entirely from the LOCAL arm64 container, while the job that got deleted would
have run on x86-64. MSan's shadow and stack handling are materially better
supported on x86-64, so the thread-stack overflows that drove the exclusions
may well be architectural. I never tested the architecture CI uses, and the
local ladder cannot emulate it faithfully — so 'it cannot run in CI' was an
inference from the wrong platform stated as a fact.

The CI leg therefore runs with MSAN_EXCLUDE="" — no exclusions — so the
first run settles the question with real evidence on the real architecture.
The script's exclusion list stays as the LOCAL default, documented as such.
An accepted local/CI divergence for this lane specifically.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 15:37:33 +02:00
Martin Vogel f96ddf5f93 chore(msan): keep the lane local and exploratory, not in CI
Running the lane to completion does not currently work: with three suites
already excluded for thread-stack overflow it now dies with a plain SIGSEGV
elsewhere. Wiring an auto-running job that cannot finish would be exactly the
structurally-red lane O10 forbids, so the test-msan CI job is removed and the
lane is documented as exploratory and local-only.

What it IS worth, and why the infrastructure stays: every suite it does run
is clean under MSan, including the C++ preprocessing path that justified
building the instrumented-libc++ image in the first place, and it correctly
identified one convincing-looking report as a mixed-build artifact rather
than a bug. The image, the script, the compose service, the MSAN_ORIGINS and
CBM_THREAD_STACK_MB knobs, and the full record of what was tried all remain,
so picking this up is a continuation rather than a restart.

The blocker is one problem, stated at the exclusion site: threads whose
stacks are sized outside cbm_thread_create overflow under instrumentation,
and the failure follows cumulative process state rather than any single
suite's depth — which points at per-suite process sharding or finding that
thread creator as the fix.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 15:37:33 +02:00
Martin Vogel 7ad0003c95 fix(msan): stack-size floor for sanitized builds; document the excluded suites
Two follow-ups to the MSan lane, both from running it for real.

1. compat_thread.c gains a sanitized-build-only stack FLOOR
   (CBM_THREAD_STACK_MB). Thread stacks here are sized in code, so a
   sanitizer lane cannot raise them with ulimit -- RLIMIT_STACK at 8/64/256
   MiB provably had no effect. The first version overrode only the DEFAULT
   size, which silently did nothing for worker_pool/runtime/main because they
   all pass an explicit size; it is now a floor applied to every thread.
   Shipping builds are untouched (the whole hook is behind
   CBM_SANITIZED_BUILD).

2. Two grammar-corpus suites are EXCLUDED from the lane, with the full
   rationale, evidence, and everything tried recorded at the exclusion site
   per O10 -- including that the floor above does NOT fix them, which narrows
   the next person's search to a thread creator outside cbm_thread_create.
   The exclusion list now names SUITES (an earlier version named a TEST and
   therefore excluded nothing) and fails loudly on an entry that matches no
   suite, so that silent-no-op cannot recur.

Also restores the exec bit on scripts/msan.sh, which the image ENTRYPOINT
needs.

worker_pool + parallel + pipeline + mcp on macOS: 526 passed, 2 skipped.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 15:37:33 +02:00
Martin Vogel c9886d4f25 feat(ci): MemorySanitizer lane — instrumented libc++/zlib image, full C++ coverage
Stage 2 of the memory-diagnostics program (user decision: go directly to the
instrumented image rather than a C-only probe). MSan detects uninitialized
READS, the one memory-error class no other lane covers dynamically, and it
requires every linked library to be instrumented -- vendored C deps compile
in-tree and instrument for free; the two external links do not:

- test-infrastructure/Dockerfile.msan: pinned-base image building
  libc++/libc++abi/libunwind (llvmorg-18.1.8, LLVM_USE_SANITIZER=
  MemoryWithOrigins) and static zlib v1.3.1 into /opt/msan, with the
  symbolizer and MSan runtime in a separate last layer so tool additions
  never invalidate the ~30-min libc++ build.
- scripts/msan.sh: the canonical lane entry. ALWAYS clean-builds its
  BUILD_DIR: make does not encode flags into dependencies, and a stage-1
  probe's libstdc++ objects surviving into the libc++ lane produced a
  convincing-looking uninitialized-value report at preprocessor.cpp:168 --
  the uninstrumented .so string constructor wrote the temporary, the
  instrumented move constructor read it. The clean rebuild proved it an
  artifact: extraction (incl. the C++ preprocessing path) runs 272/272 with
  zero reports.
- Makefile.cbm: CXX_STDLIB / CXX_STDLIB_FLAGS hooks so the lane can swap
  libstdc++ for the instrumented libc++ (defaults identical; the shipping
  build is byte-for-byte unaffected).
- docker-compose test-msan service: same aarch64 seccomp/setarch remedy as
  the TSan service (MSan's shadow layout hits the same personality() block).
- CI test-msan job (_test.yml): buildx local-cache via the repo's existing
  pinned actions/cache -- no new third-party action pins; a warm run skips
  the libc++ build entirely.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 15:37:33 +02:00