Origin. Assignment from the Yandex Practicum Rust course (debugging & profiling module): a deliberately broken app with 8 seeded defects. Developed locally, published as a snapshot. All measurements in REPORT.md are reproducible via
scripts/(Docker: Valgrind, TSan, perf).
broken-app was seeded with 8 defects (memory/UB/logic/asymptotics/
data race) and deliberately slow functions. The task is to fix them, prove the
absence of UB/leaks/races with tooling, and speed up the hot paths, checking
behavior against the reference-app baseline (which was left unchanged).
Detailed analysis with measurements - REPORT.md.
Map of artifacts - broken-app/artifacts/README.md.
-
All 8 defects fixed, all
unsaferemoved;cargo buildandclippy- no warnings. -
Correctness confirmed by 5 tools (real logs in
broken-app/artifacts/):Check Before After cargo testcrash on OOB + assert failures 14 passed Miri OOB, leak, use-after-free, race clean Valgrind --leak-check=fulldefinitely lost: 5,008 bytesdefinitely lost: 0AddressSanitizer heap-use-after-freeclean ThreadSanitizer data race @ concurrency.rs:15clean -
Regression tests added - one per bug (
broken-app/tests/regression.rs). -
Two optimizations (algorithmic + micro), measured with criterion on identical inputs:
Bench Before After Speedup slow_fib/n3213.38 ms 54 ns ≈ 249,000× slow_dedup/n1000087.8 ms 411 µs ≈ 214× slow_dedup/n500021.67 ms 280 µs ≈ 77× Callgrind profile of the same scenario: 2.27B -> 14.4M instructions (≈ 157×).
| Function | Before | After |
|---|---|---|
sum_even |
0..=len + get_unchecked (OOB, UB) |
safe iterator |
leak_buffer |
Box::into_raw without from_raw (leak) |
iter().filter().count() |
use_after_free |
read after drop (UAF) |
no raw pointers |
normalize |
stripped only spaces (not tabs) | split_whitespace() |
average_positive |
divided by all elements | mean over > 0 only |
slow_dedup |
O(n²·log n), a sort on every insert | O(n) via HashSet + a single sort (algorithm + with_capacity = micro) |
slow_fib |
O(φⁿ) recursion | iterative O(n) (algorithm) |
race_increment |
static mut from threads (race, UB) |
AtomicU64 |
src/lib.rs # sum_even, leak_buffer, normalize, average_positive, use_after_free
src/algo.rs # slow_dedup, slow_fib (optimized)
src/concurrency.rs # race_increment (AtomicU64)
src/bin/demo.rs # example run; `demo bench [N]` - heavy scenario for profiling
tests/integration.rs # tests checked against reference-app
tests/regression.rs # regressions for each of the 8 bugs (bug1..bug8)
benches/baseline.rs # criterion benches of the hot paths (harness=false)
scripts/ # mem_check / asan_check / tsan_check / profile / compare (Docker)
artifacts/ # "before/after" logs, criterion report, PNG profiles
REPORT.md # detailed report
cargo test # all tests (14)
cargo run --release --bin demo # example output
cargo run --release --bin demo bench 30 # heavy run of the hot paths
# UB detection (catches OOB, use-after-free, leaks AND data races):
rustup component add --toolchain nightly miri # once
cargo +nightly miri test
# Benchmarks (criterion prints change% relative to the previous run itself):
cargo bench --bench baselinevalgrind, perf, and ThreadSanitizer don't work on Windows, so a
container is used. All scripts are parameterized: <crate_dir> <target_dir> <out_file> [filter].
# Start the container (volume - this directory), install the tools (once):
docker run -d --name rustlab -v "$(pwd)":/work rustlang/rust:nightly sleep infinity
docker exec rustlab bash -c "apt-get update && apt-get install -y valgrind && rustup component add rust-src"
# Memory/race checks and profiling (write straight into artifacts/):
docker exec rustlab bash /work/scripts/mem_check.sh /work /tmp/tl /work/artifacts/after/valgrind_after.txt
docker exec rustlab bash /work/scripts/asan_check.sh /work /tmp/asan /work/artifacts/after/asan_after.txt
docker exec rustlab bash /work/scripts/tsan_check.sh /work /tmp/tsan /work/artifacts/after/tsan_after.txt bug8_race_increment
docker exec rustlab bash /work/scripts/profile.sh /work /tmp/prof /work/artifacts/after/callgrind_after 8
docker rm -f rustlab # stop the container when doneOn Windows, call
dockerfrom Git Bash withMSYS_NO_PATHCONV=1so that Unix paths (/work) are not converted.
./scripts/compare.sh before # on the slow version of algo.rs
# (apply the optimizations)
./scripts/compare.sh after # criterion shows the speedupReady-made HTML report with charts: broken-app/artifacts/criterion/report/index.html.
- Performance regressions are guarded by benchmarks (unit tests pin correctness, not speed);
a partial guard is
slow_fib(90/93)in the regressions (recursion can't handle that depth). - A leak/UAF/OOB is not always failed by plain
cargo test- they are caught by Miri/Valgrind/ASan (runs attached; there's no CI in the project, the sanitizers are run manually via scripts).
The full list with rationale is in REPORT.md.