ADR-1060: Round 10 C++23 wave error-path cleanup¶
- Status: Accepted
- Date: 2026-06-06
- Deciders: Lusoris
- Tags:
cpp23,correctness,memory,error-handling,fork-local
Context¶
The C++23 wave files (read_json_model.cpp, opt.cpp, log.cpp, gpu_dispatch_env.cpp, feature_extractor.cpp) introduced new code paths that require careful error handling. A round-10 audit identified five concrete error-path defects:
-
vmaf_feature_extractor_context_create(feature_extractor.cpp:491): Whenvmaf_fex_ctx_parse_optionsfails, the function returned the error code without freeingf->fex->priv,f->fex(x), andf. Three heap allocations leaked on every failed option-parse. -
pthread_mutex_initreturn unchecked (feature_extractor.cpp:736):vmaf_fex_ctx_pool_createcalledpthread_mutex_initbut discarded its return value. On failure (e.g.ENOMEMon low-memory systems) the pool would be returned as if successfully initialised. The existingfree_plabel also did not freep->fex_listwhen reached from the mutex path. -
pthread_cond_initunchecked +vmaf_dictionary_copyunchecked (feature_extractor.cpp:791, 797):get_fex_list_entrydiscarded both return values. A cond-init or dict-copy failure left a partially-constructed slot whose resources (cond object, ctx_list allocation) were neither cleaned up nor recorded. -
vmaf_fex_ctx_pool_flushdiscards flush errors (feature_extractor.cpp:933): The innervmaf_feature_extractor_context_flushcall result was silently discarded. Callers received 0 even when temporal-extractor flush failed. -
model_parsedoes not checkjson_get_errorafter the key loop (read_json_model.cpp:543): If the JSON stream entered an error state whilejson_skip-ing unknown keys after a successfulmodel_dictparse,model_parsereturned 0 (success). Callers would then proceed with a partially-parsed model.
opt.cpp, log.cpp, and gpu_dispatch_env.cpp are clean — no actionable error-path gaps found.
Decision¶
Fix all five defects in-place with minimal, targeted changes:
- Add
free(f->fex->priv); goto free_x;in the context-create parse-options error path. - Check
pthread_mutex_initreturn; addfree_fex_listlabel to freep->fex_listbeforefree_p. - Check
pthread_cond_initreturn; roll backslot->ctx_listand destroy the cond onvmaf_dictionary_copyfailure. - Propagate the first non-zero error from
vmaf_feature_extractor_context_flushinvmaf_fex_ctx_pool_flush. - Check
json_get_error(s)after themodel_parsekey loop and return-EINVALwhen the stream is in an error state.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| RAII wrappers for the pool slot | Would prevent entire class of partial-init leaks | Requires broader C++23 refactor of pool struct | Deferred to a future wave; this PR targets minimal, safe fixes |
(void)-cast discards for pthread | Matches pre-existing pattern for some C functions | Hides real failures on resource-constrained hosts | Not acceptable per SEI CERT ERR33-C |
std::expected for model_parse | Type-safe, composable | Requires touching many more call sites | Out of scope for a bug-fix PR |
Consequences¶
- Positive: No more heap leak on failed option parse. Pool creation fails cleanly on pthread failures. Flush errors surface to callers. Malformed JSON files after
model_dictare rejected. - Negative:
vmaf_fex_ctx_pool_flushnow returns non-zero on flush failure — callers that unconditionally ignored the return value now see an error. In practice all callers already check flush error codes. - Neutral / follow-ups: Remaining POSIX pthread API call sites elsewhere in the C sources (
*.c) are out of scope for this C++-wave PR.
References¶
- Round-10 agent brief: r10-error-paths cluster.
- ADR-0772: C++23 atomic + placement-new policy in feature_extractor.cpp.
- SEI CERT ERR33-C: Detect and handle standard-library errors.
- Power of 10 Rule 7: Check return value of every non-void function.