#!/usr/bin/env bash # Memory-safety check for the handle model. # # ASAN's leak detector is not usable here — CPython's arenas and OCCT's # process-lifetime singletons produce noise that would drown a real finding — # so this run targets *safety*: use-after-free, double-free, buffer overflow. # That is exactly the failure mode a wrong ownership rule produces (a stale # TShape is what segfaulted a process-global face memo under upstream OCP). # Leak *growth* is checked separately, by the RSS assertion in test_handles.py. set -euo pipefail cd /io . scripts/_env.sh BUILD="$CACHE/asan" mkdir -p "$BUILD" echo "--- configuring ASAN build ---" cmake -G Ninja -S . -B "$BUILD" \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \ -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" \ -DPython_EXECUTABLE="$PY" \ -Dnanobind_DIR="$("$PY" -c 'import nanobind; print(nanobind.cmake_dir())')" \ >/dev/null cmake --build "$BUILD" -j"$(nproc)" STAGE=$(mktemp -d) trap 'rm -rf "$STAGE"' EXIT cp -r python/OCP python/n3xd_ocp tests "$STAGE/" cp "$BUILD"/_OCP*.so "$STAGE/OCP/" cp -r tests/data "$STAGE/tests/" 2>/dev/null || true echo "--- running handle tests under ASAN ---" cd "$STAGE" # libasan.so is a linker script, not an ELF object — preloading it is silently # ignored and the run would look clean while ASAN was never active. Resolve # the real soname instead, and fail loudly if it is missing. ASAN_LIB=$(gcc -print-file-name=libasan.so.8) [ -f "$ASAN_LIB" ] || { echo "libasan.so.8 not found ($ASAN_LIB)" >&2; exit 1; } head -c 4 "$ASAN_LIB" | grep -q ELF || { echo "$ASAN_LIB is not an ELF object" >&2; exit 1; } LD_PRELOAD="$ASAN_LIB" \ N3XD_OCP_ASAN=1 \ ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1" \ "$PY" -m pytest tests/test_handles.py tests/test_shape_identity.py -q echo "ASAN: no memory-safety errors"