mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -10:00
CI: build full RetroArch with ASan + UBSan and run headless smoke
Stands up a complementary CI job to the per-sample regression-test
workflows shipped in v3-v6 (Linux-samples-gfx.yml). Builds full
RetroArch with -fsanitize=address,undefined via the existing top-
level Makefile SANITIZER= plumbing, then runs `./retroarch --help`
and `./retroarch --features` under AddressSanitizer's abort-on-error
mode and UBSan in soft-fail mode.
The per-sample harnesses regression-test specific predicates that
had bugs and by design only cover code that can be exercised by a
small standalone test program with mocked dependencies. This job
is complementary -- it sanitizer-instruments the entire RetroArch
binary as it actually links and runs, so any future heap-corruption
/ use-after-free / signed-overflow / null-deref bug in code
reachable from main() is caught for free as a side effect of any
change. Same template as c82db9f's libretro-db samples build under
sanitizer in CI, scaled up to the full retroarch binary.
* .github/workflows/Linux-asan-ubsan.yml
- Build matches Linux-Headless.yml's apt set and --disable-menu
choice. Adds --disable-discord --disable-cheevos
--disable-networking on the first iteration to shrink the
third-party surface so that any sanitizer hit is a RetroArch-
internal bug rather than noise from a vendored subsystem.
Each can be re-enabled in follow-ups as the baseline stays
green.
- Build with `make SANITIZER=address,undefined`, which the top-
level Makefile (line 153) propagates into CFLAGS / CXXFLAGS /
LDFLAGS for every TU and the final link. No new apt packages:
libasan / libubsan ship with the default gcc on ubuntu-latest.
- Smoke runs --help and --features. Both exit(0) directly after
printing. Coverage scope is honest in the inline comment: libc
init, main(), frontend driver bootstrap, argv duplication, the
getopt walk over the full option table, the print functions
themselves, and the static feature-table walks --features
performs. Doesn't reach core loading, video init, or cleanup-
on-shutdown -- a follow-up step running with --max-frames=N
against a noop core can extend coverage to the full lifecycle,
but adding the noop-core dependency to CI is a separate step
better handled in its own patch.
- Sanitizer settings:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0:
print_stacktrace=1:strict_string_checks=1
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=0
LSAN_OPTIONS=exitcode=0
ASan in abort-on-error: any heap corruption fails the job
immediately. Leak detection off for now -- libGL / Mesa /
Wayland symbol-resolution drives a non-trivial baseline of
"leaks" at process shutdown that aren't RetroArch bugs; can
revisit with a suppression file once the rest is clean.
UBSan in report-only: the first iteration is a discovery run.
Pre-existing signed-overflow / alignment / shift-too-large
diagnostics in audio resampling, color math, and vendored deps
are likely to be present. The "Summarize UBSan baseline"
step prints the deduplicated list so follow-up patches have
a target to drive to zero. Once that count reaches zero,
flip halt_on_error=1 in the smoke steps to enforce.
Each smoke step independently greps stderr for
"AddressSanitizer:" and exits 1 on any match -- belt-and-
suspenders to abort_on_error in case a future ASan release
changes its default exit semantics.
- Kept as its own workflow rather than folded into Linux-Headless
so the existing green-path build smoke stays fast (no ~3-5x
ASan slowdown), and so this one can own its longer 25-minute
timeout independently.
- Trigger matches the per-sample workflows: push to master,
pull_request to master, workflow_dispatch.
This commit is contained in:
1 parent
aa4a196115
commit
b9777c8307
1 file changed
+170
@@ -0,0 +1,170 @@
|
||||
name: CI Linux ASan + UBSan [No Menu]
|
||||
|
||||
# Builds full RetroArch with -fsanitize=address,undefined and runs
|
||||
# headless smoke invocations (--help, --features) so AddressSanitizer
|
||||
# and UndefinedBehaviorSanitizer instrument the startup config-loading,
|
||||
# argument parsing, default-driver init, and cleanup-on-exit paths.
|
||||
#
|
||||
# The per-sample tests under .github/workflows/Linux-samples-gfx.yml,
|
||||
# Linux-samples-tasks.yml, and Linux-libretro-{db,common}-samples.yml
|
||||
# regression-test specific predicates that previously had bugs. This
|
||||
# job is complementary -- it covers everything those harnesses can't
|
||||
# reach because the code only runs from main(), and catches future
|
||||
# heap-corruption / UB regressions across the whole code base for
|
||||
# free as a side effect of any change that can be exercised by a
|
||||
# headless run.
|
||||
#
|
||||
# Build configuration matches Linux-Headless.yml (--disable-menu) with
|
||||
# additional --disable-discord --disable-cheevos --disable-networking
|
||||
# to shrink the third-party surface on this first iteration. Each can
|
||||
# be re-enabled once the baseline is green.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
||||
|
||||
jobs:
|
||||
asan-ubsan:
|
||||
name: Build with ASan+UBSan and run headless smoke
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
# Mirrors Linux-Headless.yml's apt set so the build matches a
|
||||
# known-good headless configuration. No sanitizer-specific
|
||||
# packages required; libasan / libubsan ship with the gcc that
|
||||
# ubuntu-latest has installed by default.
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
libxkbcommon-dev libx11-xcb-dev \
|
||||
zlib1g-dev libfreetype6-dev \
|
||||
libegl1-mesa-dev libgles2-mesa-dev libgbm-dev \
|
||||
nvidia-cg-toolkit nvidia-cg-dev \
|
||||
libavcodec-dev libsdl2-dev libsdl-image1.2-dev \
|
||||
libxml2-dev yasm
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Configure (no menu, no discord/cheevos/networking)
|
||||
# Trim the build surface for the first iteration so any
|
||||
# sanitizer hit is a RetroArch-internal bug rather than noise
|
||||
# from a vendored third-party subsystem. The disabled
|
||||
# subsystems will be re-enabled in follow-up patches as the
|
||||
# baseline stays green.
|
||||
run: |
|
||||
./configure \
|
||||
--disable-menu \
|
||||
--disable-discord \
|
||||
--disable-cheevos \
|
||||
--disable-networking
|
||||
|
||||
- name: Build with -fsanitize=address,undefined
|
||||
# The top-level Makefile (line 153) propagates SANITIZER into
|
||||
# CFLAGS / CXXFLAGS / LDFLAGS for every translation unit and
|
||||
# the final link. ASan defaults to abort-on-heap-corruption;
|
||||
# UBSan recovery is controlled at runtime via UBSAN_OPTIONS
|
||||
# (see the smoke-run steps below).
|
||||
run: |
|
||||
make -j$(getconf _NPROCESSORS_ONLN) \
|
||||
SANITIZER=address,undefined
|
||||
test -x retroarch
|
||||
file retroarch
|
||||
|
||||
- name: Smoke run --help (ASan strict, UBSan soft-fail)
|
||||
# `--help` exits cleanly via exit(0) after printing the usage
|
||||
# banner. Coverage scope: libc init, main(), frontend
|
||||
# driver bootstrap, argv duplication, the getopt walk over
|
||||
# the full option table, and retroarch_print_help() itself.
|
||||
# Doesn't reach into core loading / video init / cleanup-on-
|
||||
# shutdown -- a follow-up step running with --max-frames=N
|
||||
# against a noop core will extend coverage to the full
|
||||
# lifecycle. ASan runs in abort-on-error mode so any heap
|
||||
# corruption fails the job; UBSan reports stacktraces but
|
||||
# does not fail (first iteration -- we want to see the
|
||||
# baseline before deciding which UBSan classes to enforce).
|
||||
env:
|
||||
ASAN_OPTIONS: abort_on_error=1:detect_leaks=0:print_stacktrace=1:strict_string_checks=1
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=0
|
||||
# Avoid noise from libGL / Mesa / Wayland symbol-resolution
|
||||
# leaks at process shutdown; those are not RetroArch bugs.
|
||||
LSAN_OPTIONS: exitcode=0
|
||||
run: |
|
||||
set -eu
|
||||
timeout 30 ./retroarch --help > /tmp/help.out 2> /tmp/help.err || rc=$?
|
||||
echo "exit=${rc:-0}"
|
||||
echo "=== stdout (head) ==="
|
||||
head -40 /tmp/help.out
|
||||
echo "=== stderr ==="
|
||||
cat /tmp/help.err
|
||||
# ASan abort_on_error sends the process to exit code 1 + a
|
||||
# SUMMARY: line on stderr. Treat any "AddressSanitizer:"
|
||||
# marker as fatal regardless of exit code.
|
||||
if grep -q "AddressSanitizer:" /tmp/help.err; then
|
||||
echo "[FAIL] AddressSanitizer reported a finding"
|
||||
exit 1
|
||||
fi
|
||||
echo "[pass] retroarch --help under ASan+UBSan"
|
||||
|
||||
- name: Smoke run --features (ASan strict, UBSan soft-fail)
|
||||
# `--features` exits cleanly via exit(0) after printing the
|
||||
# compile-time feature list. Coverage scope is the same as
|
||||
# --help (libc init / main / frontend bootstrap / arg parse)
|
||||
# plus retroarch_print_features(), which walks the static
|
||||
# video / audio / input / camera / location / record / cheats
|
||||
# / network / database / overlay / hid feature tables. Each
|
||||
# such walk reads pointers into a static-string table -- low
|
||||
# corruption surface but a useful sanity check that the link-
|
||||
# time feature flags resolve consistently. Doesn't reach
|
||||
# core loading or cleanup-on-shutdown.
|
||||
env:
|
||||
ASAN_OPTIONS: abort_on_error=1:detect_leaks=0:print_stacktrace=1:strict_string_checks=1
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=0
|
||||
LSAN_OPTIONS: exitcode=0
|
||||
run: |
|
||||
set -eu
|
||||
timeout 30 ./retroarch --features > /tmp/features.out 2> /tmp/features.err || rc=$?
|
||||
echo "exit=${rc:-0}"
|
||||
echo "=== stdout (head) ==="
|
||||
head -40 /tmp/features.out
|
||||
echo "=== stderr ==="
|
||||
cat /tmp/features.err
|
||||
if grep -q "AddressSanitizer:" /tmp/features.err; then
|
||||
echo "[FAIL] AddressSanitizer reported a finding"
|
||||
exit 1
|
||||
fi
|
||||
echo "[pass] retroarch --features under ASan+UBSan"
|
||||
|
||||
- name: Summarize UBSan baseline
|
||||
# Both runtime steps are non-fatal on UBSan diagnostics. This
|
||||
# step prints a count of distinct UBSan messages observed so
|
||||
# follow-up patches have a numerical target to drive to zero.
|
||||
# If this step ever shows zero, flip
|
||||
# UBSAN_OPTIONS=halt_on_error=1 in the runtime steps above to
|
||||
# enforce the clean baseline.
|
||||
if: always()
|
||||
run: |
|
||||
set +e
|
||||
ub_count=$( {
|
||||
grep -h "runtime error:" /tmp/help.err /tmp/features.err 2>/dev/null
|
||||
} | sort -u | wc -l)
|
||||
echo "UBSan distinct diagnostics: ${ub_count}"
|
||||
if [ "${ub_count}" != "0" ]; then
|
||||
echo "=== unique UBSan diagnostics ==="
|
||||
grep -h "runtime error:" /tmp/help.err /tmp/features.err 2>/dev/null \
|
||||
| sort -u
|
||||
fi
|
||||
Reference in new issue
Block a user