ADR-1081: vmaf_bench correctness — unchecked alloc returns and wall-clock timer¶
- Status: Accepted
- Date: 2026-06-06
- Deciders: Lusoris
- Tags:
tools,bench,correctness,clock
Context¶
Two correctness defects were identified in the benchmark and CLI tools during a focused audit of core/tools/vmaf_bench.c and core/tools/vmaf.c.
vmaf_bench.c — unchecked vmaf_picture_alloc returns. bench_feature (warm-up frame, lines 498-499 before this fix) and its timed loop (lines 517-518) called vmaf_picture_alloc without checking the return value. vmaf_picture_alloc returns a negative errno on -ENOMEM or -EINVAL; on failure it leaves the VmafPicture zero-initialised. The immediately-following yuv_pair_read_frame writes to ref->data[0], which is then a null pointer dereference. The same pattern repeated in run_feature_collect (lines 715-716 before this fix).
vmaf_bench.c — silently discarded flush return. Both bench_feature and run_feature_collect called vmaf_read_pictures(vmaf, NULL, NULL, 0) (the end-of-stream flush) and discarded the return value without a (void) cast or error check. A non-zero return from the flush signals a pooling or aggregation failure; swallowing it means validation mode can report PASS even when the extractor failed to finalise scores.
vmaf.c — clock() for wall-time FPS spinner. run_frame_loop used clock() / CLOCKS_PER_SEC to compute the FPS figure shown in the progress spinner. clock() measures aggregate CPU process time: under a multi-threaded run (n_threads > 1, the default) each worker thread's CPU time is summed, so clock() over-counts elapsed time by up to n_threads. The displayed FPS was therefore implausibly high for any multi-threaded invocation. vmaf_bench.c already uses CLOCK_MONOTONIC (and QueryPerformanceCounter on Windows) for its timing; vmaf.c should be consistent.
Decision¶
- In
bench_featureandrun_feature_collectinvmaf_bench.c: check everyvmaf_picture_allocreturn and propagate errors through the existingbench_cleanup/ early-return unwind paths. - Capture and log the
vmaf_read_pictures(vmaf, NULL, NULL, 0)flush return in both functions. - In
vmaf.c, introduce a file-staticwall_time_s()helper (CLOCK_MONOTONICon POSIX,QueryPerformanceCounteron Windows) and replace theclock()/CLOCKS_PER_SECexpression inrun_frame_loopwith calls towall_time_s().
No public API, no CLI flag, and no score computation path is changed. The FPS figure shown by the spinner will now reflect wall time rather than CPU time.
Alternatives considered¶
(void)-cast the alloc returns: rejected. The return encodes a real error (-ENOMEM,-EINVAL); silencing it with a cast perpetuates the latent null-deref.- Keep
clock()but divide byn_threads: rejected. The thread count is not available torun_frame_loop(it is buried incfgbeforevmaf_init), and the correction is approximate. Monotonic wall time is the correct primitive. - Add a
time.h-only portable fallback instead of QPC on Windows:time()has 1-second granularity andclock()has the CPU-time problem. QPC is the standard Windows high-resolution wall-clock primitive;<windows.h>is already conditionally included in the file forisatty/fileno.
Consequences¶
bench_featureandrun_feature_collectnow surface allocation failures instead of crashing with a null dereference.- Flush errors in validation mode are now surfaced on
stderrrather than silently ignored. - The FPS spinner in
vmaf(the main CLI) now reports wall-clock FPS, which is accurate regardless of--threads.
References¶
core/tools/vmaf_bench.c—bench_featureandrun_feature_collectcore/tools/vmaf.c—run_frame_loop,wall_time_shelper- ADR-1081 (this document)