Phase 10A/10B Inc 0: build system, handle model, first module surface
Builds n3xd-ocp end to end and publishes 7.9.3.1.dev1 to the Gitea registry, where it installs anonymously and passes its suite. - occt/Dockerfile: OCCT 7.9.3 compiled once into a manylinux_2_28 builder image (base digest + tarball sha256 pinned), Draw/VTK/Tk/Xlib/OpenGL off, FreeType on, -O2 without fast-math or march=native. A final layer asserts TKService/TKV3d exist with no libGL/libX11 DT_NEEDED, which is what lets the app image drop libgl1/libx11-6. Mounted into, never built FROM. - scikit-build-core + nanobind STABLE_ABI -> one cp312-abi3 extension that registers every OCP.* submodule via PyImport_AddModule, so `import OCP.TopoDS` needs no shim and cls.__module__ is right. Version <occt>.N is asserted against the OCCT found, keeping occt_version() truthful. - occt_handle.h: type caster for opencascade::handle<T> over OCCT's intrusive refcount. Wrappers are non-owning instances holding exactly one handle in their keep-alive list, reusing an existing wrapper so identity survives a round trip. Transient constructors go through ocp_new (never nb::init<>, which would let OCCT delete nanobind's storage); the caster refuses a refcount-0 object rather than corrupt the heap. Verified under ASAN with no memory-safety errors, plus an RSS bound over 50k create/destroy cycles. - Sub-shapes are returned by value everywhere, making the TShape lifetime class that segfaulted a process-global face memo unrepresentable. - Standard_Failure derives RuntimeError, with ~20 concrete types dispatched on the dynamic OCCT type (cad_pool marshals failures home by type name). - Inc 0 surface: gp subset, TopAbs, TopoDS (+ downcasts), TopExp, TopLoc, TopTools, BRep, BinTools, Poly, Standard. 34 of the app's 139 symbols. - n3xd_ocp: additive APIs kept out of the OCP namespace so parity testing stays meaningful. bintools (shape <-> bytes, GIL-free, byte-identical) and _debug. Two findings worth the record, both verified against the stock wheel rather than assumed: upstream binds __hash__ but leaves __eq__ at identity, which is exactly what geom_memo.py's hash-bucket + IsSame scan is built around, so we match it instead of "fixing" it; and BinTools can release the GIL after all, by slurping the file-like object instead of bridging a streambuf that would call back into Python. Gate: BREP round-trips are byte-identical to cadquery-ocp-novtk across six fixtures (the generator asserts stock idempotency first). That matters beyond IPC — derive.py content-addresses BREP payloads by sha256 and stores the ref.
This commit is contained in:
49
scripts/asan.sh
Executable file
49
scripts/asan.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user