Repository navigation
test: sanitizers, fuzzing and memory bounds - #10
Merged
Merged
Conversation
gudoshnikovn
force-pushed
the
feature/sanitizers
branch
3 times, most recently
from
October 13, 2026 11:17
5e7ae54 to
dcfbc9b
Compare
tools/sanitizers.py builds libphash, its decoders and the extension with Clang's AddressSanitizer + UndefinedBehaviorSanitizer (and libFuzzer's coverage hooks, so one build serves the tests and the fuzzers) or with ThreadSanitizer, runs a command with the runtime preloaded into the uninstrumented interpreter, and checks the logs. Any report fails; a leak fails when the extension itself allocated the block. CPython leaves thousands of unfreed blocks, and an LSan suppression cannot single out the extension: it matches any frame, and every stack passes the interpreter. The leak check runs when the tests are done (conftest), not at exit: the interpreter's teardown buries it there, and silencing the exit check through `exitcode` would also turn ASan aborts into success, as ASan, LSan and UBSan share that flag. Clang, not GCC: GCC's object-size check reports stb_image_resize2's scatter tables at -O3, which Clang does not and libphash's own sanitizer job (Clang) never sees. libphash is built in Release, as wheels are: its Debug install names -lpng16 in libphash.pc while the archive is libpng16d.a. The address build leaves out the function type check (-fno-sanitize=function): the fuzzers run under atheris' copy of the runtime, built by an older LLVM that has no handler for it, and the extension would not load. ci.yml runs the suite under both sanitizers on every push and PR, then each fuzz target for a minute; fuzz.yml gives them a quarter of an hour each. The fuzz targets cover what the binding adds over libphash, which fuzzes its decoders in C: encoded bytes, array layouts against their C-ordered copy (where ASan sees any read past a zero-copy view) and digests. The load target found that libphash 2.0.1 hashes a truncated TGA differently from load to load: stb_image leaves the missing rows uninitialized (16 loads of the same 74 bytes gave 11 hashes on a plain build). The target compares the two loads' outcome and dimensions only, and the input is in the corpus. tests/test_memory.py bounds the growth of Python objects and of resident memory over hundreds of repetitions of every path (success, load and hash errors, GC of an unclosed context, batch, a raising callback); a deliberate leak of one object or one context per step fails it. It also holds a batch's peak to libphash's estimate, four bytes per pixel and the file per worker (measured: exactly that for 1, 2 and 4 workers), plus the files the binding reads itself on Windows. Under a sanitizer it skips: CPython allocates through malloc there and some of its caches fill slowly enough to look like growth. mutmut leaves it out: it would multiply the run's time and catches no mutant the other tests miss. On a free-threaded build the two scenarios whose callbacks run on libphash's workers are a strict xfail: cffi 2.1.1 keeps a few objects of every C thread that enters an extern "Python" callback (an empty one included; a minimal cffi module without libphash shows it, a ctypes callback from the same threads does not, and GIL builds stay flat), and each batch starts its own workers. Valgrind is not run: ASan and LSan cover its findings here, and CPython under Valgrind is slow and noisy.
gudoshnikovn
force-pushed
the
feature/sanitizers
branch
from
October 13, 2026 11:41
dcfbc9b to
2cca660
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runs CI on the branch; it is integrated by a local rebase and
git merge --ff-only, not by merging this PR.tools/sanitizers.py): libphash, its decoders and the extension built with Clang under ASan + UBSan (with libFuzzer's coverage hooks) and under TSan; the runtime is preloaded into the uninstrumented interpreter.checkfails on any report and on a leak whose block the extension allocated;tests/conftest.pyasks LeakSanitizer for the check after the tests.tools/fuzz_targets.py,tools/fuzz.py, atheris):load,pixels,digest. The corpus intests/fuzz/is replayed by pytest on every platform. A minute per target inci.yml, a quarter of an hour weekly infuzz.yml.tests/test_memory.py): every path repeated, growth of Python objects and RSS bounded; a batch's peak held to libphash's estimate per worker.🤖 Generated with Claude Code