• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aio-libs / multidict / 35592134197
87%

Build:
DEFAULT BRANCH: master
Ran 21 Sep 2026 11:15AM UTC
Jobs 1
Files 39
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

21 Sep 2026 11:04AM UTC coverage: 87.468% (+0.05%) from 87.419%
35592134197

push

github

web-flow
Stop items() iteration from reading a freed entry in CIMultiDict (#1496)

<!-- Thank you for your contribution! -->

## What do these changes do?

Fixes a use-after-free in the C extension's key conversion that crashed
the cp315t wheel test on master
([job](https://github.com/aio-libs/multidict/actions/runs/35579952505/job/106270507947),
`list(md.items())` in `test_race_condition_iterator_vs_mutation`).

When iterating a `CIMultiDict` whose stored key is a plain `str`,
`_md_ensure_key()` caches the `istr` in the entry, drops the old key,
and then read `entry->key` again. Dropping the old key can run a `str`
subclass's `__del__`, or on free-threaded builds suspend the iterator's
critical section; either lets the table be mutated and the entry freed
before that reload. Building the `istr` itself can run a `str`
subclass's `__str__`, which freed `entry->identity` while `IStr_New()`
was still using it.

`_md_ensure_key()` now holds its own references across the conversion,
only caches the `istr` if the version is unchanged, and returns the
computed key instead of reloading it. `md_clone_from_ht()` now assigns a
new version instead of copying the source's, so a `__str__` that calls
`d.__init__(d.copy())` can no longer restore the version that check
relies on; the pure Python implementation already behaved this way.
`md_next()`, `md_prev()` and both finders read the value before the key,
so the decrefs are the last access to the entry. The pure Python
implementation does not cache the converted key and is unaffected.

## Are there changes in behavior for the user?

No, apart from the crash being gone. `getversion()` of a `copy()` or of
a multidict re-initialized from another one now differs from the
source's on the C extension too, as it already did in pure Python. A
mutation from a key's `__str__`/`__del__` during iteration now surfaces
as the usual `RuntimeError: MultiDict is changed during iteration`.

## Is it a substantial burden for the maintainers to... (continued)

762 of 1524 branches covered (50.0%)

Branch coverage included in aggregate %.

46 of 48 new or added lines in 2 files covered. (95.83%)

6455 of 6727 relevant lines covered (95.96%)

1.92 hits per line

Uncovered Changes

Lines Coverage ∆ File
1
93.16
0.09% tests/test_multidict.py
1
99.03
-0.47% tests/test_version.py
Jobs
ID Job ID Ran Files Coverage
1 MyPy - 35592134197.1 21 Sep 2026 11:15AM UTC 78
87.46
GitHub Action Run
Source Files on build 35592134197
  • Tree
  • List 39
  • Changed 4
  • Source Changed 2
  • Coverage Changed 4
Coverage ∆ File Lines Relevant Covered Missed Hits/Line Branch Hits Branch Misses
  • Back to Repo
  • Github Actions Build #35592134197
  • 2a68472d on github
  • Prev Build on master (#35583362240)
  • Next Build on master (#35603248557)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc