Skip to content

[S2.14] Fix DEBUG DIGEST crash on compressed values#43

Merged
ikolomi merged 2 commits into
unstablefrom
ikolomi/s2-14-debug-digest-compressed
Jun 22, 2026
Merged

[S2.14] Fix DEBUG DIGEST crash on compressed values#43
ikolomi merged 2 commits into
unstablefrom
ikolomi/s2-14-debug-digest-compressed

Conversation

@ikolomi

@ikolomi ikolomi commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a pre-existing crash: DEBUG DIGEST / DEBUG DIGEST-VALUE panic the server on any OBJ_ENCODING_COMPRESSED value. The follow-up to the S2.13 audit finding; a blocker for transparency mode (§7.1 — the full Tcl corpus under --compression runs DEBUG DIGEST for consistency/replication checks). Product fix in debug.c + one external:skip test. No design behavior change.

Root cause

The DEBUG DIGEST family deliberately reads the kvstore directly so it can digest logically-expired keys — computeDatasetDigest via kvstoreIteratorNext, DEBUG DIGEST-VALUE via dbFind — bypassing the lookupKey() transient-view decompression hook. Both funnel a string value through xorObjectDigest's OBJ_STRING branch into mixStringObjectDigest / xorStringObjectDigest, which called getDecodedObject(). That panics on any encoding other than RAW/EMBSTR/INT (serverPanic("Unknown encoding type"), object.c) → crash on the first compressed value.

Same class PR #24 fixed for rdbSaveStringObject / rioWriteBulkObject, but the digest helpers were missed.

Fix

Route mixStringObjectDigest and xorStringObjectDigest through objectGetUncompressedView (R2.5.2) — the single decoder primitive, used exactly as PR #24 used it. For a compressed value the helper decompresses into a scratch sds, which is digested directly (not via getDecodedObjectincrRefCount on the OBJ_STATIC_REFCOUNT view would panic); otherwise it falls back to getDecodedObject (still handles INT). No-op passthrough for non-compressed values and for BUILD_ZSTD=no.

Consequence: the digest is defined over logical (decompressed) bytes — byte-identical whether the value is stored compressed or not.

Audit (full kvstore-direct reader sweep)

Only the two digest paths crash. Audited-safe:

Reader Why safe
OBJECT ENCODING/REFCOUNT/… objectCommandLookup passes LOOKUP_NO_BYTESstrEncoding returns "compressed"
DEBUG SDSLEN guarded by !sdsEncodedObject → "Not an sds encoded string"
DEBUG OBJECT strEncoding + rdbSavedObjectLen (PR#24-fixed path)
getDecodedObject on argv (AOF argv, module RM_Call, pubsub, cluster, LCS, t_list elements, sort, bitop sources) argv never compressed, or already lookupKey'd to RAW
dbUnshareStringValue (db.c) write path — lookupKeyWrite decompressed first (S2.13)

Deferred (not crashes, separate owners):

  • MEMORY USAGE decompresses via the transient view (no LOOKUP_NO_BYTES) → objectComputeSize sees RAW → no crash, but reports the uncompressed size (R2.7.3 wants compressed). That's S3.5 (@GilboaAWS).
  • DEBUG OBJECT missing the R2.7.2 dictID/compressedlength/uncompressedlength fields — a feature gap, separate.

Test

tests/unit/compression-debug-readers.tcl (external:skip, like the COW test — it churns global compression state):

  • DEBUG DIGEST over 20 compressed keys doesn't crash; digest is byte-identical compressed vs. decompressed (transparency).
  • DEBUG DIGEST-VALUE on a compressed key doesn't crash; identical to its uncompressed digest.
  • DEBUG RELOAD round-trip preserves both the digest and the values.

Verification

  • BUILD_ZSTD={yes,no} both build clean with -Werror.
  • New test: 3/3 in normal mode.
  • External simulation: the new test skips, unit/other (the digest path that crashed pre-fix) and unit/type/compression pass, server alive.
  • Standalone repro before/after: pre-fix DEBUG DIGESTGuru Meditation object.c:945; post-fix → identical digest compressed vs decompressed, server alive.
  • Full gtest suite (392) passes.

Docs

  • detailed-design.md R2.5.7: lists DEBUG DIGEST / DIGEST-VALUE among the kvstore-direct readers that call objectGetUncompressedView explicitly.
  • plan.md: S2.14 marked done.

ikolomi added 2 commits June 22, 2026 13:25
DEBUG DIGEST and DEBUG DIGEST-VALUE iterate the kvstore directly
(computeDatasetDigest via kvstoreIteratorNext; DIGEST-VALUE via dbFind)
so a debug command can digest logically-expired keys. They therefore
bypass the lookupKey() transient-view decompression hook and observe raw
OBJ_ENCODING_COMPRESSED robjs.

Both funnel a string value through xorObjectDigest's OBJ_STRING branch
into mixStringObjectDigest / xorStringObjectDigest, which called
getDecodedObject() — that panics on any encoding other than
RAW/EMBSTR/INT (serverPanic 'Unknown encoding type'), crashing the
server on the first compressed value.

Route the two digest helpers through objectGetUncompressedView (R2.5.2),
the single decoder primitive PR #24 used for rdbSaveStringObject /
rioWriteBulkObject. For a compressed value the helper decompresses into
a scratch sds (read directly — not via getDecodedObject, since
incrRefCount on the OBJ_STATIC_REFCOUNT view would panic); otherwise it
falls back to getDecodedObject (still handles INT). No-op passthrough
for non-compressed values and for BUILD_ZSTD=no.

The digest is thus defined over logical (decompressed) bytes: identical
whether a value is stored compressed or not. Pre-existing crash on
unstable; a blocker for transparency mode (full Tcl corpus under
--compression, which runs DEBUG DIGEST extensively).

Test tests/unit/compression-debug-readers.tcl (external:skip): compressed
values -> DEBUG DIGEST / DIGEST-VALUE don't crash, digest is byte-identical
compressed vs decompressed, and a DEBUG RELOAD round-trip preserves both
digest and values.
Mark S2.14 done in plan.md (the follow-up to the S2.13 audit finding) and
list DEBUG DIGEST / DIGEST-VALUE among R2.5.7's explicit-decompression
kvstore-direct readers in the design doc.
@ikolomi ikolomi merged commit 1dc7f72 into unstable Jun 22, 2026
80 checks passed
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