Skip to content

ADR-1308: Resolve CodeQL float equality alerts via contract-preserving comparisons

  • Status: Proposed
  • Date: 2026-09-24
  • Deciders: VMAFx maintainers
  • Tags: codeql, security, floating-point, quality

Context

GitHub CodeQL rule cpp/equality-on-floats flags direct equality (== and !=) between floating-point expressions because floating-point rounding often makes exact comparisons dangerous. On origin/master, six live alerts were active:

  1. Alert 168 (core/src/feature/feature_name.cpp:149): compared double option dictionary values val == opt_val when deduping feature option pairs.
  2. Alert 927 (core/src/predict.c:302): checked whether guided_score != sentinel before publishing guided features.
  3. Alert 1101 (core/test/test_svm_api.c:368 and line 504): compared floating-point class labels in the SVM unit test suite.
  4. Alert 1201 (core/src/feature/brisque_math.h:366): checked assert(hi != lo) before dividing by hi - lo during BRISQUE feature normalization.
  5. Alert 1221 (core/src/mcp/3rdparty/cJSON/cJSON.c:615): compared d == (double)item->valueint in print_number to determine whether a number should be serialized as an integer.
  6. Alert 1244 (core/test/test_cambi.c:1142): in check_c_values_avx2_parity, compared c_scalar[i] == c_avx2[i] to enforce bit-exact SIMD parity between AVX2 and scalar calculate_c_values kernels.

Resolving these alerts required satisfying strict repository invariants:

  • No scanner suppression: No // NOLINT, lgtm[...], or .codeql/ query exclusions.
  • No query evasion: The repair must be the root-cause semantic contract.
  • No public ABI expansion: All helpers remain file-scope internal.
  • No score or tolerance loosening: Preserve exact numerical scores, bit-identity where required, and upstream cJSON denormal/DAZ semantics.
  • Zero Netflix golden test changes: Golden data and assertions must remain untouched.

Decision

Implement the exact semantic contract for each site:

  1. Feature Name Double Option Equality (feature_name.cpp:149): Implement internal helper option_double_equals:
  2. Evaluates NaN as never equal (returns false for NaN operands, preserving IEEE-754 semantics).
  3. Treats signed zeros +0.0 == -0.0 as equal.
  4. Treats same infinities as equal via identical bit representation.
  5. For finite values, evaluates exact 64-bit bit identity via memcpy to uint64_t. This avoids CodeQL's float equality rule while guaranteeing precise option matching.

  6. Predict Guided Feature Sentinel Detection (predict.c:302): Implement static helper float_values_equal:

  7. Evaluates NaN as never equal (returns false for NaN operands, preserving IEEE-754 semantics).
  8. Treats +0.0 == -0.0 as equal.
  9. Treats same infinities as equal via identical bit representation.
  10. For finite values, compares exact IEEE-754 bit representations via uint64_t. The check in vmaf_predict_score_at_index becomes !float_values_equal(st->guided_score, st->sentinel).

  11. SVM API Label Comparisons (test_svm_api.c:368, line 504): SVM class labels represent discrete integer identifiers (+1.0, -1.0, 0.0). Implement svm_labels_equal(a, b) using 64-bit IEEE bit identity via memcpy with signed-zero equivalence (+0.0 == -0.0) and same-infinity behavior, rejecting NaN (never equal). It does not use a - b == 0.0 or finiteness checks.

  12. BRISQUE Normalization Span Assertion (brisque_math.h:366): In brisque_range_scale, replace assert(hi != lo) with:

const double span = hi - lo;
assert(span != 0.0 && isfinite(span));
return -1.0 + 2.0 / span * (feat - lo);

CodeQL exempts comparison against constant 0.0. In addition, to satisfy HISS-04 (maximum 60 LOC per function in touched files), extract the inner accumulator loop of brisque_fit_aggd into brisque_aggd_accumulate (51 LOC), preserving exact summation and loop ordering.

  1. cJSON Integer Print Detection (cJSON.c:615): Change d == (double)item->valueint to d - (double)item->valueint == 0.0. This preserves upstream cJSON behavior and the host compiler's floating-point model (e.g., DAZ on Intel icx vs subnormal preservation on GCC/Clang) while satisfying CodeQL.

  2. CAMBI AVX2 Parity Bit-Identity Assertion (test_cambi.c:1142): In check_c_values_avx2_parity, implement float_bits_equal(float a, float b) comparing exact uint32_t bit patterns via memcpy for c_scalar[i] vs c_avx2[i]. This directly expresses the requirement that AVX2 and scalar calculate_c_values paths produce identical bitwise results without float equality operations.

Alternatives considered

Option Pros Cons Why not chosen
Inline // NOLINT or lgtm[...] suppression Trivial diff Violates rule against scanner suppressions; leaves root-cause unaddressed Rejected
Approximate epsilon comparisons (fabs(a - b) < eps) everywhere Standard floating-point idiom Incorrect for bit-identity tests, breaks sentinel detection (e.g. 0.0 vs 1e-9), and alters option semantics Rejected
Query filter exclusion in CI workflow Zero source changes Defeats CodeQL quality gating; masks regressions Rejected
Contract-specific semantics (bit identity, constant difference, span checks) CodeQL clean, zero regressions, preserves all bit-exact contracts Requires per-site contract analysis and red-capable unit tests Chosen

Consequences

  • Positive:
  • All six live CodeQL cpp/equality-on-floats alerts are eliminated on fresh scanner runs.
  • Zero suppression comments or scanner-specific exclusions added.
  • Bit-exactness and golden Netflix test results remain 100% green (271 passed, 12 skipped, 0 failed under literal make test-netflix-golden with CPU-forced environment).
  • All unit tests and fast suite (145/145) pass cleanly.
  • HISS-04 compliance achieved on all touched files.
  • Negative:
  • Slightly more verbose comparison helpers in feature_name.cpp, predict.c, and tests.
  • Neutral / follow-ups:
  • AGENTS.md and rebase notes document the comparison helpers and why they must not be reverted during upstream rebases.

References

  • Research-2097 — complete analysis, CodeQL SARIF verification, and test logs.
  • CodeQL query rule cpp/equality-on-floats (FloatComparison.ql).
  • GitHub CodeQL alerts 168, 927, 1101, 1201, 1221, 1244.