Skip to content

reset encoder state after a failed encode - #348

Merged
agronholm merged 3 commits into
agronholm:masterfrom
sahvx655-wq:encoder-reset-after-error
Oct 1, 2026
Merged

agronholm merged 3 commits into
agronholm:masterfrom
sahvx655-wq:encoder-reset-after-error

Conversation

@sahvx655-wq

Copy link
Copy Markdown
Contributor

Changes

While checking how a reused CBOREncoder behaves once an encode() call has raised, I noticed that nothing reaches fp afterwards. encode() bumps encode_depth before dispatching and only decrements it on the success path, so after a CBOREncodeError (an unsupported type, a naive datetime with no default timezone, a default hook raising) the counter is stuck at 1 on that instance and every later top-level encode finishes at depth 1 instead of 0, skipping the flush and the reset of the shared container and string reference tables. enc.encode(object()) followed by enc.encode("hello") leaves fp empty, and the partial output of the failed item stays at the front of the buffer, so once a later message pushes it past the 4 KiB threshold the stream drains with the truncated item fused onto the next one: a failed [shared, object()], then encode(shared), then a larger message decodes on the receiving side as [['hello'], ['hello']], an item that was never encoded, with the trailing reference cut off. The stale tables are the other half of the risk, since a later message can emit tag 29 or tag 25 references against a registry the receiving decoder has never seen.

The depth decrement and the depth-zero reset now run whether encode_value succeeded or not, and on failure the buffered partial item is discarded rather than flushed, so the stream only ever carries complete items and the next encode starts from the same clean state a successful one leaves behind. encode() is the one place the depth is tracked, so this also covers encode_to_bytes, encode_semantic and re-entry from hooks; dump() and dumps() build a fresh encoder per call and are unaffected. The other state savers in the encoder (disable_value_sharing, disable_string_referencing, the namespace restore in encode_semantic) already restore on both paths, so this was the only one left. The regression test fails on master with an empty fp.

Checklist

  • You've added tests (in tests/) which would fail without your patch
  • You've updated the documentation (in docs/), in case of behavior changes or new features
  • You've added a new changelog entry (in docs/versionhistory.rst).

@coveralls

coveralls commented Sep 29, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 95.039% (+0.01%) from 95.027% — sahvx655-wq:encoder-reset-after-error into agronholm:master

@agronholm agronholm left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@agronholm
agronholm merged commit c895351 into agronholm:master Oct 1, 2026
16 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.

3 participants