Set up CI, with an opt lane and a sanitizer lane #1
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
bcox/tymbal#1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
There is no CI configuration in the repository — no
.github,.forgejo,.woodpeckeror.drone. Everything is verified by runningbazel test //...by hand, at the default
fastbuildconfiguration.That gap has already hidden a real bug for an unknown length of time.
What it cost
//dsp:lfo_testfailed under-c optwhile passing atfastbuild. The causewas a lifetime bug, not a numerical one:
Lfo::NoteOnkeeps a pointer to thepatch rather than copying it, and twelve call sites in the test passed a
temporary, so
patch_referred to dead stack for the rest of the note.AddressSanitizer named it exactly —
stack-use-after-scope.It survived at
-O0, where the stack slot happens to still hold the values,and failed at
-O3, where it does not. Nothing caught it because the suiteonly ever ran at one optimisation level.
It also meant
.bazelrc's stated invariant was false the whole time:Fixed in
80db42a. The suite now passes atfastbuild,-c optand--config=opt, andNoteOn(Patch&&)is deleted onLfo,LayerandFmPairLayerso the same mistake is a compile error rather than aconfiguration-dependent miscompile.
What is wanted
Three lanes, because each catches a different class:
bazel test //...— the defaultfastbuildlane.bazel test //... --config=opt— this is the one that would have caughtthe above. Worth treating as non-optional given the bit-reproducibility
claim the project makes about
dsp/.bazel test //... --copt=-fsanitize=address,undefined --copt=-fno-sanitize-recover=all --copt=-fsanitize-address-use-after-return=always --linkopt=-fsanitize=address,undefined— ASan is what actually named thebug once the opt lane exposed it. UBSan is nearly free alongside it.
Notes for whoever picks this up:
toolchains_llvm), so a runner needs only Bazeland network access to fetch it; no system compiler or dev packages.
--expungebuild is expensive. Caching Bazel's repository cachebetween runs is worth setting up early.
//tools:patch_editorlinks GLFW/OpenGL but is not run by any test, so aheadless runner can still
bazel buildit. It should be built, since it isthe one target the test suite does not cover.
27e06bf/08c4b3a, so--copt=-Werroris worth considering for our own code — note
.bazelrcdeliberatelysuppresses warnings for
external/.ThreadSanitizer is deliberately not in this list; see the separate issue.
test comment
Not implementing this, but the lane design above needs one input it did not have: the default build is now
-O3(07a725d), where it was unoptimized when this was written.That changes the relationship between lanes 1 and 2 specifically. Lane 2 is described here as "the one that would have caught the above" -- the
//dsp:lfo_teststack-use-after-scopethat survived at-O0because the stack slot happened to still hold the values. Lane 1 now catches that class too, because lane 1 is optimized. The two lanes differ only by-c opt, and therefore only byNDEBUG.Still worth running both, but for a different reason than the one written above: what lane 2 uniquely covers now is the
NDEBUGbuild -- the eight runtimeassert()s this tree relies on are compiled out there and live everywhere else, so lane 2 is the only lane that exercises the code a shipped bundle actually runs. That is a real difference and a smaller one than an optimization level.The new gap, stated so whoever picks this up decides it deliberately rather than discovering it: no lane runs unoptimized any more. For the bug in this issue's own story that is a straight improvement, since
-O0is what hid it. It cuts the other way for UBSan, which sees undefined behavior an optimizer may have deleted before instrumentation -- a computation whose result is unused is the usual shape.--cxxopt=-O0on the command line is how to get that view for a run, and a fourth lane pinning it is worth considering if the sanitizer lanes are meant to be exhaustive rather than fast.Context on why the default moved: the suite ran about 30x slower unoptimized, which had tests being written around the sanitizer budget rather than around what they needed to measure -- see #61, which closed because its subject went from 665 s under asan to 11 s. The whole suite is now 28 s uncached at the default.
One practical note for the caching advice above: the sanitizer lanes share bazel's fastbuild output directory, so an asan run's testlogs are overwritten by the next default-config run. Anything that needs to read per-target timings or failures out of a lane has to do it before the next lane starts.