feat(p3-342): FluxEngine schreibt einen echten SCP-Behaelter - und dr… #897
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # UnifiedFloppyTool - Sanitizer Checks | |
| # | |
| # Runs ASan and UBSan builds to catch memory errors and undefined behaviour. | |
| # Uses the CMake build path (library targets + test suite). | |
| # | |
| # Fixes applied: | |
| # FAILURE-007: removed --target all, use default CMake target | |
| # Added: ctest --build-config for multi-config generators | |
| # ============================================================================ | |
| name: Sanitizer Checks | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| QT_VERSION: '6.10.1' | |
| jobs: | |
| asan: | |
| # MF-432: without this GitHub's default is 360 min. On 2026-08-19 | |
| # five Linux jobs hung in the Qt/apt download and would have burned | |
| # six hours each. A hang now dies here instead. | |
| timeout-minutes: 60 | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Qt | |
| uses: jurplel/install-qt-action@v4 | |
| with: | |
| version: ${{ env.QT_VERSION }} | |
| host: linux | |
| target: desktop | |
| modules: 'qtserialport' | |
| cache: true | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -y libusb-1.0-0-dev libgl1-mesa-dev | |
| - name: Build with ASan (CMake) | |
| # FIX-007: no --target all | |
| run: | | |
| cmake -B build \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DUFT_BUILD_TESTS=ON \ | |
| -DUFT_WITH_DTC_COMPONENTS=ON \ | |
| -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \ | |
| -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" | |
| cmake --build build --parallel $(nproc) 2>&1 || true | |
| # GUI may fail (missing headers) — tests are what matters | |
| # ───────────────────────────────────────────────────────────────── | |
| # SCHARF (MF-517). Dieser Schritt hat KEIN `|| true`. | |
| # | |
| # Er faehrt genau die Tests, die den Weg gehen, den ein Benutzer | |
| # geht: eine Datei oeffnen. Dort lagen fuenf Speicherfehler, die | |
| # 241 gruene Tests nicht gefunden haben (KNOWN_ISSUES FUZZ-1/FUZZ-2) | |
| # — Heap-Ueberlauf 44830 B, Stapel-Ueberlauf 1800 B, ein doppeltes | |
| # free bei jeder abgeschnittenen DSK-Datei, ein Lesen 29 KB hinter | |
| # ein Feld, und zwoelf read_track, die durch NULL schrieben. | |
| # | |
| # detect_leaks=0 mit Absicht: geleckt wird in diesem Baum an vielen | |
| # Stellen, und ein ungemessener Rueckstand darf kein Tor bewachen. | |
| # Speicher-FEHLER sind etwas anderes als Speicher-Verlust; die | |
| # ersten werden hier scharf geprueft, die zweiten weiter unten | |
| # gemeldet. | |
| # ───────────────────────────────────────────────────────────────── | |
| # MF-1110 — VOR dem Tor zaehlen, wie viele Prueflinge es ueberhaupt | |
| # hat. `--no-tests=error` unten feuert NUR, wenn die Regex GAR | |
| # KEINEN Test trifft. Faellt einer von neun weg — umbenannt, in | |
| # EXCLUDED_TESTS gewandert, oder schlicht nicht uebersetzt, weil | |
| # der Bauschritt darueber `|| true` traegt —, dann laeuft das Tor | |
| # mit ACHT und meldet GRUEN. Niemand zaehlt. | |
| # | |
| # Die Sollzahl wird aus der Regex ABGELEITET (Zahl der | |
| # `|`-Alternativen), nicht danebengeschrieben: eine zweite | |
| # gepflegte Zahl waere genau die Drift, die das hier verhindern | |
| # soll (MF-1077). Die statische Haelfte — existieren die Namen | |
| # ueberhaupt noch? — haelt `scripts/audit_tor_abdeckung.py`. | |
| - name: Gate coverage — all named tests present (ARMED) | |
| run: | | |
| cd build | |
| REGEX="test_convert_roundtrip_measured|test_convert_identity_lossless|test_track_add_sector_owns_copies|test_convert_fuzz|test_convert_roundtrip_lossless|test_disk_open_fuzz|test_disk_write_fuzz|test_dsk_open_bounds|test_format_probe_fuzz" | |
| SOLL=$(printf '%s' "$REGEX" | tr '|' '\n' | grep -c .) | |
| IST=$(ctest -N --tests-regex "$REGEX" | grep -cE '^[[:space:]]+Test[[:space:]]+#') || true | |
| echo "scharfes ASan-Tor: $IST von $SOLL Prueflingen registriert" | |
| if [ "$IST" -ne "$SOLL" ]; then | |
| echo "::error::Das scharfe Tor haette mit $IST statt $SOLL Tests laufen und GRUEN melden koennen." | |
| echo "Registriert sind:" | |
| ctest -N --tests-regex "$REGEX" | grep -E '^[[:space:]]+Test[[:space:]]+#' || true | |
| exit 1 | |
| fi | |
| - name: Memory-safety gate — open path (ARMED) | |
| run: | | |
| cd build | |
| ctest --output-on-failure --timeout 300 --no-tests=error \ | |
| --tests-regex "test_convert_roundtrip_measured|test_convert_identity_lossless|test_track_add_sector_owns_copies|test_convert_fuzz|test_convert_roundtrip_lossless|test_disk_open_fuzz|test_disk_write_fuzz|test_dsk_open_bounds|test_format_probe_fuzz" | |
| env: | |
| # halt_on_error=0: EIN Lauf meldet ALLE Funde statt beim ersten | |
| # abzubrechen. Sonst kostet jeder einzelne Fehler eine eigene | |
| # CI-Runde — beim ersten scharfen Lauf (MF-520) war genau das | |
| # der Fall, und hinter dem gemeldeten koennen weitere stehen. | |
| # ASan setzt den Exit-Code trotzdem, das Tor bleibt scharf. | |
| ASAN_OPTIONS: detect_leaks=0:halt_on_error=0:exitcode=1:print_stacktrace=1 | |
| # ───────────────────────────────────────────────────────────────── | |
| # BERICHTEND, nicht scharf. | |
| # | |
| # Bis MF-517 lief hier eine Liste von NEUN Testnamen, und das mit | |
| # `|| true`. Damit sah der Baum 232 seiner 241 Tests nie unter ASan, | |
| # und die neun, die er sah, konnten nichts roeten. Als "ASan- | |
| # Workflow" gefuehrt zu werden und beides zu sein, ist genau die | |
| # Sorte Zusage, gegen die DESIGN_PRINCIPLES geschrieben ist. | |
| # | |
| # Jetzt laeuft die ganze Suite, damit der Rueckstand GEMESSEN im Log | |
| # steht statt geschaetzt zu werden. `|| true` bleibt hier bewusst: | |
| # den Rueckstand scharf zu schalten, ohne ihn vorher zu kennen, waere | |
| # derselbe Fehler in die andere Richtung. | |
| - name: Full suite under ASan (reporting only, incl. leaks) | |
| # MF-530: `if: always()`, weil GitHub den Auftrag sonst | |
| # abbricht, sobald das scharfe Tor darueber rot wird — und | |
| # dann faellt genau die Messung aus, die den Rueckstand | |
| # sichtbar haelt. Beim d80-Fund (MF-529) ist das passiert: | |
| # ein Befund im Tor, und der Leck-Bericht des ganzen Laufs | |
| # war weg. Ein Tor, das die Messung mitreisst, misst nicht | |
| # mehr. | |
| if: always() | |
| run: | | |
| cd build | |
| ctest --output-on-failure --timeout 300 || true | |
| env: | |
| ASAN_OPTIONS: detect_leaks=1:abort_on_error=1:print_stacktrace=1 | |
| - name: Upload ASan findings | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: asan-findings-${{ github.sha }} | |
| path: | | |
| build/Testing/Temporary/ | |
| build/**/*.log | |
| retention-days: 7 | |
| ubsan: | |
| # MF-432: without this GitHub's default is 360 min. On 2026-08-19 | |
| # five Linux jobs hung in the Qt/apt download and would have burned | |
| # six hours each. A hang now dies here instead. | |
| timeout-minutes: 60 | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Qt | |
| uses: jurplel/install-qt-action@v4 | |
| with: | |
| version: ${{ env.QT_VERSION }} | |
| host: linux | |
| target: desktop | |
| modules: 'qtserialport' | |
| cache: true | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -y libusb-1.0-0-dev libgl1-mesa-dev | |
| - name: Build with UBSan (CMake) | |
| run: | | |
| cmake -B build \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DUFT_BUILD_TESTS=ON \ | |
| -DUFT_WITH_DTC_COMPONENTS=ON \ | |
| -DCMAKE_C_FLAGS="-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer" \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer" \ | |
| -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined" | |
| cmake --build build --parallel $(nproc) 2>&1 || true | |
| # GUI may fail (missing headers) — tests are what matters | |
| # MF-1110 — dieselbe Zaehlung wie im ASan-Auftrag; die Begruendung | |
| # steht dort. Sie gehoert in BEIDE Auftraege, weil jeder seinen | |
| # eigenen Bau hat und ein Ziel im einen uebersetzen kann und im | |
| # anderen nicht. | |
| - name: Gate coverage — all named tests present (ARMED) | |
| run: | | |
| cd build | |
| REGEX="test_convert_roundtrip_measured|test_convert_identity_lossless|test_track_add_sector_owns_copies|test_convert_fuzz|test_convert_roundtrip_lossless|test_disk_open_fuzz|test_disk_write_fuzz|test_dsk_open_bounds|test_format_probe_fuzz" | |
| SOLL=$(printf '%s' "$REGEX" | tr '|' '\n' | grep -c .) | |
| IST=$(ctest -N --tests-regex "$REGEX" | grep -cE '^[[:space:]]+Test[[:space:]]+#') || true | |
| echo "scharfes UBSan-Tor: $IST von $SOLL Prueflingen registriert" | |
| if [ "$IST" -ne "$SOLL" ]; then | |
| echo "::error::Das scharfe Tor haette mit $IST statt $SOLL Tests laufen und GRUEN melden koennen." | |
| echo "Registriert sind:" | |
| ctest -N --tests-regex "$REGEX" | grep -E '^[[:space:]]+Test[[:space:]]+#' || true | |
| exit 1 | |
| fi | |
| # SCHARF (MF-517) — siehe die Begruendung im ASan-Auftrag oben. | |
| - name: UB gate — open path (ARMED) | |
| run: | | |
| cd build | |
| ctest --output-on-failure --timeout 300 --no-tests=error \ | |
| --tests-regex "test_convert_roundtrip_measured|test_convert_identity_lossless|test_track_add_sector_owns_copies|test_convert_fuzz|test_convert_roundtrip_lossless|test_disk_open_fuzz|test_disk_write_fuzz|test_dsk_open_bounds|test_format_probe_fuzz" | |
| env: | |
| # MF-1102 — hier stand `halt_on_error=0` mit der Begruendung | |
| # "wie beim ASan-Tor: ein Lauf, alle Funde". Die Begruendung | |
| # stimmt fuer ASan und NICHT fuer UBSan, und der Unterschied | |
| # ist gemessen (WSL2, gcc 15.2.0, Rotbeweis mit einem | |
| # signed-integer-overflow): | |
| # | |
| # UBSan, genau diese Zeile -> Exit 0, 524 Byte Befund | |
| # dieselbe Zeile, halt_on_error=1 -> Exit 1 | |
| # -fno-sanitize-recover=all -> Exit 1, auch ohne Option | |
| # | |
| # ASan-Fehler sind von Haus aus nicht wiederaufsetzbar und | |
| # beenden den Prozess ohnehin (gemessen: Exit 1 auch voellig | |
| # ohne ASAN_OPTIONS). UBSan setzt dagegen wieder auf, meldet | |
| # und laeuft weiter — `exitcode` greift nur, wenn es ueberhaupt | |
| # abbricht. Dieses Tor konnte damit NICHT rot werden. | |
| # | |
| # Die Konfiguration war zudem VERTAUSCHT: der gatende Schritt | |
| # hier trug den milden Wert, der berichtende Schritt darunter | |
| # (mit `|| true`) den strengen. Der strenge stand dort, wo er | |
| # nichts bewirkt. | |
| # | |
| # Jetzt doppelt gehalten: das Uebersetzungs-Flag oben laesst | |
| # UBSan gar nicht erst wieder aufsetzen, und die Laufzeitoption | |
| # sagt dasselbe noch einmal — damit haengt das Tor nicht an | |
| # einer Umgebungsvariablen, die jemand streicht. Der Preis ist | |
| # benannt: ein Lauf zeigt nur noch den ERSTEN Fund. Die | |
| # vollstaendige Liste liefert der berichtende Schritt darunter, | |
| # und der ist genau dafuer da. | |
| UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1:exitcode=1 | |
| # BERICHTEND. Vorher liefen hier neun Testnamen mit `|| true`; die | |
| # restlichen 232 sah niemand unter UBSan. | |
| - name: Full suite under UBSan (reporting only) | |
| # MF-530: `if: always()`, weil GitHub den Auftrag sonst | |
| # abbricht, sobald das scharfe Tor darueber rot wird — und | |
| # dann faellt genau die Messung aus, die den Rueckstand | |
| # sichtbar haelt. Beim d80-Fund (MF-529) ist das passiert: | |
| # ein Befund im Tor, und der Leck-Bericht des ganzen Laufs | |
| # war weg. Ein Tor, das die Messung mitreisst, misst nicht | |
| # mehr. | |
| if: always() | |
| run: | | |
| cd build | |
| ctest --output-on-failure --timeout 300 || true | |
| env: | |
| UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 | |
| - name: Upload UBSan findings | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ubsan-findings-${{ github.sha }} | |
| path: | | |
| build/Testing/Temporary/ | |
| build/**/*.log | |
| retention-days: 7 |