Repository navigation
BUG: Detect a duplicate dictionary key whose first value is falsy - #4024
Merged
stefan6419846 merged 1 commit intoAug 27, 2026
Merged
Conversation
read_from_stream() used "if not data.get(key)" to decide whether a key had already been seen. A first value of NumberObject(0) is falsy, so the second occurrence looked like a new key. The duplicate went unreported and the second value replaced the first, even with strict=True. Checking "key not in data" makes the falsy case behave like every other duplicate: strict raises, non-strict warns, and the first value is kept.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4024 +/- ##
=======================================
Coverage 98.00% 98.00%
=======================================
Files 57 57
Lines 11128 11128
Branches 2087 2087
=======================================
Hits 10906 10906
Misses 124 124
Partials 98 98 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
stefan6419846
approved these changes
Aug 27, 2026
stefan6419846
left a comment
Collaborator
There was a problem hiding this comment.
Thanks. I do not know why #69 implemented it like this, but your change seems reasonable.
stefan6419846
added a commit
that referenced
this pull request
Sep 4, 2026
## What's new ### Security (SEC) - Limit value for Roman numerals (#4047) by @stefan6419846 ### New Features (ENH) - _cmap.py: Also parse encoding for embedded CFF Type1 fonts (#4032) by @PJBrs ### Performance Improvements (PI) - Cache repeated text extraction character lookups (#4036) by @petermik68-sudo ### Bug Fixes (BUG) - Treat an empty /Filter array as no filter when extracting images (#4026) by @Anai-Guo - Detect a duplicate dictionary key whose first value is falsy (#4024) by @devYRPauli - Make is_open=False collapse outline items (#3998) by @SomSamantray ### Robustness (ROB) - Multiple changes for wrong inputs by @RavSinghChandan - Skip trailing duplicate %%EOF markers when locating startxref (#4015) by @Anai-Guo - Do not crash on a non-array destination (#3976) by @eeshsaxena - Handle annotations without subtype during merge (#3999) by @Nexlu1 ### Documentation (DOC) - Use AnnotationFlag enum instead of plain integers (#3997) by @RavSinghChandan ### Code Style (STY) - Multiple small changes detected from test runs by @RavSinghChandan [Full Changelog](6.16.2...6.17.0)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
DictionaryObject.read_from_stream()decides whether a key has already been seen with:The test checks truthiness, not presence. A first value of
NumberObject(0)is falsy. On the second occurrencedata.get(key)returns0, sonot 0isTrue. The branch then treats the key as new. It does not report the duplicate, and the second value replaces the first.This happens even with
strict=True, where the reader is supposed to raise.Behaviour on current main:
<< /Count 7 /Count 5 >>7<< /Count 0 /Count 5 >>5The second row is the bug. pypdf does not raise, and it does not log. It also keeps the opposite value from every other duplicate.
Change
Presence, not truthiness. The falsy case now behaves like every other duplicate:
strict=Trueraises, non-strict warns, and the first value is kept.<< /Count 7 /Count 5 >>7<< /Count 0 /Count 5 >>50<< /Count 0 /Size 5 >>The non-strict result for the second row changes from
5to0. That change is the point. It now matches the first row, which has always kept the first value.Tests
Four tests in
tests/generic/test_data_structures.py, all from bytes in memory, no fixture download:strict=True0parse normally, so there is no false positiveReverting the one-line change fails the first two and leaves the last two passing.
tests/generic/reports 90 passed with the change and 86 without it. One test,test_files.py::test_embedded_file__basic, fails either way in my environment because a fixture is missing locally.ruff checkpasses on both files.AI usage disclosure
Per the AI policy in CONTRIBUTING.md: I used Claude Code (Anthropic, Opus 5) while working on this. I reviewed the change, reproduced the behaviour myself before and after, and understand and stand behind it. Happy to explain any part of it or adjust the approach.