Skip to content

Remove shift/overflow UB in Rice and mid/side encode; add Rice tests#35

Merged
audexdev merged 1 commit into
mainfrom
fix/codec-arith-ub
Jun 5, 2026
Merged

Remove shift/overflow UB in Rice and mid/side encode; add Rice tests#35
audexdev merged 1 commit into
mainfrom
fix/codec-arith-ub

Conversation

@audexdev

@audexdev audexdev commented Jun 5, 2026

Copy link
Copy Markdown
Owner

What

Phase 2 of docs/refactor-roadmap.md (#8): remove undefined behaviour in the codec hot paths and lock it in with sanitizer-backed tests.

Changes

  • Rice::encode — guard the u >> k and 1u << k shifts so k >= 32 is well-defined. k is <= 31 by construction (adaptive k clamps to 31; static k is a u5 field) and Rice::decode already rejects k > 31, but the shifts were undefined behaviour for k >= 32.
  • ms_encode_scalar_impl — compute l + r and l - r in uint32 instead of int32, removing signed-overflow UB. This reproduces the wrapping semantics of the NEON vaddq_s32 / vsubq_s32 path bit-for-bit, and is identical to the previous int32 form for the validated 16/24-bit sample domain (which cannot overflow).
  • tests/test_rice.cpp (new lac_rice_tests, built with -UNDEBUG) — round-trip across all k, full-range boundary values (incl. INT32_MIN → unsigned 0xFFFFFFFF) at the k == 31 shift boundary, and the k > 31 decode-rejection contract. Runs under the existing ASan/UBSan CI job.

Audit of the remaining #8 items

  • mid/side decode reconstruction (decoder.cpp, main.cpp) already uses int64 + range checks.
  • fixed/FIR/LPC residual (block/encoder.cpp) and restore (block/decoder.cpp, lpc.cpp) already accumulate in int64 and range-check before narrowing to int32.

So those paths are left unchanged. Marking Refs #8 (not Closes): a follow-up could add full-range synthetic-input tests for the predictor/restore primitives, though they are already range-checked.

Verification (local, macOS)

  • Debug build: ctest 3/3 pass (incl. new lac_rice_tests).
  • ASan + UBSan build: ctest 3/3 pass and lac_cli selftest clean — no sanitizer reports.

Refs #8

- Rice::encode: guard the `u >> k` / `1u << k` shifts so k >= 32 is well
  defined (it is unreachable for valid streams, but was undefined behaviour).
  Rice::decode already rejects k > 31.
- ms_encode_scalar_impl: compute l+r / l-r in uint32 to avoid signed-overflow
  UB. This reproduces the NEON vaddq_s32/vsubq_s32 wrapping bit-for-bit and is
  identical to the int32 form for the validated 16/24-bit domain.
- Add tests/test_rice.cpp (lac_rice_tests): round-trip across k, full-range
  boundary values (incl INT32_MIN -> 0xFFFFFFFF) at k == 31, and the k > 31
  decode rejection. Built with -UNDEBUG and run under the ASan/UBSan CI job.

Audited the other arithmetic in #8: fixed/FIR/LPC residual + restore already
use int64 accumulation with int32 range checks, so they are left unchanged.

Refs #8

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@audexdev audexdev merged commit 86909d9 into main Jun 5, 2026
8 checks passed
@audexdev audexdev deleted the fix/codec-arith-ub branch June 5, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant