Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
**vmaf_init double-init guard + vmaf_close pointer-contract + DNN fp32 fallback** (ADR-1032)

- `vmaf_init` now returns `-EINVAL` immediately when `*vmaf` is non-NULL,
preventing silent context leaks caused by accidental double-initialisation.
- `vmaf_close` pointer-invalidity contract documented in
`core/include/libvmaf/libvmaf.h` with a `@code` example showing the
recommended null-after-close pattern.
- DNN `vmaf_dnn_session_open`: when the `.int8.onnx` sidecar is absent or
fails validation, the session now falls through to the fp32 baseline model
instead of returning an error, matching the stated "better degraded than
dead" design intent. A `VMAF_LOG_LEVEL_DEBUG` message makes the degradation
observable.
- Unit test `test_vmaf_init_double_init_guard` added to the `fast` suite in
`core/test/test_context.c`.
14 changes: 14 additions & 0 deletions core/include/libvmaf/libvmaf.h
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,20 @@ VMAF_EXPORT int vmaf_fetch_preallocated_picture(VmafContext *vmaf, VmafPicture *
* Close a VMAF instance and free all associated memory.
*
* @param vmaf The VMAF instance to close.
* The pointer becomes invalid after this call returns.
* Callers must not dereference or pass @p vmaf to any libvmaf
* function after `vmaf_close()` returns. To guard against
* accidental use-after-free, set the pointer to NULL immediately
* after calling this function:
*
* @code
* vmaf_close(ctx);
* ctx = NULL;
* @endcode
*
* Calling `vmaf_close(NULL)` returns `-EINVAL` harmlessly;
* however passing a dangling (already-freed) pointer is
* undefined behaviour and is **not** detected.
*
*
* @return 0 on success, or < 0 (a negative errno code) on error.
Expand Down
21 changes: 14 additions & 7 deletions core/src/dnn/dnn_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "libvmaf/dnn.h"
#include "libvmaf/vmaf_assert.h"

#include "log.h"
#include "model_loader.h"
#include "ort_backend.h"
#include "tensor_io.h"
Expand Down Expand Up @@ -105,14 +106,20 @@ int vmaf_dnn_session_open(VmafDnnSession **out, const char *onnx_path, const Vma
memcpy(int8_path + base_len, ".int8.onnx", sizeof(".int8.onnx"));
rc = vmaf_dnn_validate_onnx(int8_path, max_bytes);
if (rc < 0) {
/* int8 file missing or fails the allowlist — fall back to
* fp32 with a debug log; better degraded than dead. */
if (s->has_sidecar)
vmaf_dnn_sidecar_free(&s->meta);
free(s);
return rc;
/* int8 file missing or fails the allowlist — fall back to fp32;
* better degraded than dead. Keep has_sidecar / meta intact so
* the caller can still read quant_mode; only the load_path stays
* as the fp32 baseline. */
vmaf_log(VMAF_LOG_LEVEL_DEBUG,
"dnn: int8 sidecar unavailable (%s, rc=%d); "
"falling back to fp32 path\n",
int8_path, rc);
/* load_path already equals onnx_path — no assignment needed;
* fall through to vmaf_ort_open below. */
rc = 0;
} else {
load_path = int8_path;
}
load_path = int8_path;
}

rc = vmaf_ort_open(&s->ort, load_path, cfg);
Expand Down
6 changes: 6 additions & 0 deletions core/src/libvmaf.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ int vmaf_init(VmafContext **vmaf, VmafConfiguration cfg)
{
if (!vmaf)
return -EINVAL;
/* Guard against double-init: if the caller passes a non-NULL *vmaf the
* old context would be silently overwritten and leak. Returning -EINVAL
* surfaces the bug immediately instead of leaking memory on every
* subsequent initialisation path. */
if (*vmaf)
return -EINVAL;
int err = 0;

VmafContext *const v = *vmaf = malloc(sizeof(*v));
Expand Down
30 changes: 28 additions & 2 deletions core/test/test_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
*
*/

#include <errno.h>

#include "test.h"
#include "libvmaf/libvmaf.h"

static char *test_context_init_and_close()
{
int err = 0;
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};

err = vmaf_init(&vmaf, cfg);
Expand All @@ -36,7 +38,7 @@ static char *test_context_init_and_close()
static char *test_get_feature_score()
{
int err = 0;
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};

err = vmaf_init(&vmaf, cfg);
Expand Down Expand Up @@ -68,9 +70,33 @@ static char *test_get_feature_score()
return NULL;
}

static char *test_vmaf_init_double_init_guard()
{
/* vmaf_init on an already-initialised (non-NULL) pointer must return
* -EINVAL without leaking the existing context. This exercises the
* double-init guard added in ADR-1032. */
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};

int err = vmaf_init(&vmaf, cfg);
mu_assert("first vmaf_init failed unexpectedly", !err);
mu_assert("vmaf pointer still NULL after successful init", vmaf != NULL);

/* Second call on the same (non-NULL) pointer must fail. */
int err2 = vmaf_init(&vmaf, cfg);
mu_assert("vmaf_init on non-NULL pointer must return -EINVAL", err2 == -EINVAL);

/* The original context must still be valid and closeable. */
err = vmaf_close(vmaf);
mu_assert("vmaf_close after rejected double-init failed", !err);

return NULL;
}

char *run_tests()
{
mu_run_test(test_context_init_and_close);
mu_run_test(test_get_feature_score);
mu_run_test(test_vmaf_init_double_init_guard);
return NULL;
}
10 changes: 5 additions & 5 deletions core/test/test_cuda_pic_preallocation.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static char *test_cuda_no_init()

VmafConfiguration vmaf_cfg = {0};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", vmaf);

Expand Down Expand Up @@ -68,7 +68,7 @@ static char *test_cuda_picture_preallocation_method_none()

VmafConfiguration vmaf_cfg = {0};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", vmaf);

Expand Down Expand Up @@ -120,7 +120,7 @@ static char *test_cuda_picture_preallocation_method_host()

VmafConfiguration vmaf_cfg = {0};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", vmaf);

Expand Down Expand Up @@ -186,7 +186,7 @@ static char *test_cuda_picture_preallocation_method_host_pinned()

VmafConfiguration vmaf_cfg = {0};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", vmaf);

Expand Down Expand Up @@ -252,7 +252,7 @@ static char *test_cuda_picture_preallocation_method_device()

VmafConfiguration vmaf_cfg = {0};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", vmaf);

Expand Down
2 changes: 1 addition & 1 deletion core/test/test_feature_collector.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static char *test_model_mount_with_use_features()

VmafConfiguration vmaf_cfg = {0};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", vmaf);

Expand Down
4 changes: 2 additions & 2 deletions core/test/test_locale_handling.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ static char *test_output_xml_with_comma_locale(void)
}

int err;
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {
.log_level = VMAF_LOG_LEVEL_NONE,
.n_threads = 0,
Expand Down Expand Up @@ -174,7 +174,7 @@ static char *test_output_json_with_comma_locale(void)
}

int err;
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {
.log_level = VMAF_LOG_LEVEL_NONE,
.n_threads = 0,
Expand Down
24 changes: 12 additions & 12 deletions core/test/test_output.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ static int seed_normal(VmafContext **out_vmaf)

static char *test_csv_basic()
{
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand Down Expand Up @@ -205,7 +205,7 @@ static char *test_csv_basic()

static char *test_csv_subsample_and_custom_format()
{
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand Down Expand Up @@ -236,7 +236,7 @@ static char *test_csv_subsample_and_custom_format()

static char *test_sub_basic()
{
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand Down Expand Up @@ -267,7 +267,7 @@ static char *test_sub_basic()
static char *test_xml_einval_guards()
{
/* All three guards live at the head of vmaf_write_output_xml. */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand Down Expand Up @@ -295,7 +295,7 @@ static char *test_xml_einval_guards()
* SIGSEGV on the very first fprintf instead of returning -EINVAL. */
static char *test_csv_sub_einval_guards()
{
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand Down Expand Up @@ -328,7 +328,7 @@ static char *test_xml_basic()
* index. Use a dense 2-frame x 2-feature collector here. The
* count_written_at skip branch is already covered by test_csv_basic /
* test_sub_basic (which use seed_normal). */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};
int err = vmaf_init(&vmaf, cfg);
mu_assert("vmaf_init failed", !err);
Expand Down Expand Up @@ -377,7 +377,7 @@ static char *test_json_basic_and_format()
/* Dense 2x2 collector so json_write_pooled_entry / json_write_pool_score
* produce per-method numbers (otherwise vmaf_feature_score_pooled
* returns -EAGAIN and the writer emits an empty per-feature block). */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};
int err = vmaf_init(&vmaf, cfg);
mu_assert("vmaf_init failed", !err);
Expand Down Expand Up @@ -437,7 +437,7 @@ static char *test_json_nan_and_inf()
/* Force NaN / +Inf into both frame metrics, pooled (via mean over the
* frame values), and aggregates / fps. The writers route every numeric
* branch through fpclassify(); NaN + Inf must serialize as JSON null. */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};
int err = vmaf_init(&vmaf, cfg);
mu_assert("vmaf_init failed", !err);
Expand Down Expand Up @@ -482,7 +482,7 @@ static char *test_json_empty_collector()
/* Zero features, zero frames — exercises the "no frames" branch where
* max_capacity returns 0 and the for-loop body never executes. The
* writer must still emit valid JSON skeleton. */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
VmafConfiguration cfg = {0};
int err = vmaf_init(&vmaf, cfg);
mu_assert("vmaf_init failed", !err);
Expand Down Expand Up @@ -543,7 +543,7 @@ static char *test_write_output_json_path()
{
/* vmaf_write_output() — public path-based dispatcher — must produce a
* well-formed JSON file for VMAF_OUTPUT_FORMAT_JSON. */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand All @@ -570,7 +570,7 @@ static char *test_write_output_with_format_custom()
/* vmaf_write_output_with_format() must honour a caller-supplied printf
* format string. "%.3f" of 80.0 yields "80.000"; the default "%.17g"
* would yield "80" or "80.000000000000000" — never "80.000" exactly. */
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = seed_normal(&vmaf);
mu_assert("seed_normal failed", !err);

Expand Down Expand Up @@ -658,7 +658,7 @@ static char *test_write_output_pic_cnt_zero_xml(VmafContext *vmaf)
/* Top-level ADR-0602 regression entry point. */
static char *test_write_output_pic_cnt_zero()
{
VmafContext *vmaf;
VmafContext *vmaf = NULL;
int err = make_pic_cnt_zero_ctx(&vmaf);
mu_assert("make_pic_cnt_zero_ctx failed", !err);

Expand Down
16 changes: 8 additions & 8 deletions core/test/test_pic_preallocation.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ static char *test_picture_pool_basic()
.n_threads = 4,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -98,7 +98,7 @@ static char *test_picture_pool_small()
.n_threads = 2,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -157,7 +157,7 @@ static char *test_picture_pool_fetch_unref_cycle()
.n_threads = 4,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -207,7 +207,7 @@ static char *test_picture_pool_yuv444()
.n_threads = 4,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -270,7 +270,7 @@ static char *test_picture_pool_exhaustion()
.n_threads = 4,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -376,7 +376,7 @@ static char *test_picture_pool_multithreaded()
.n_threads = 8,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -452,7 +452,7 @@ static char *test_picture_pool_close_waits()
.n_threads = 2,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down Expand Up @@ -500,7 +500,7 @@ static char *test_picture_pool_stress()
.n_threads = 16,
};

VmafContext *vmaf;
VmafContext *vmaf = NULL;
err = vmaf_init(&vmaf, vmaf_cfg);
mu_assert("problem during vmaf_init", !err);

Expand Down
Loading
Loading