Skip to content

Copy list-valued info entries independently in Image.copy() - #9964

Merged
radarhere merged 5 commits into
python-pillow:mainfrom
lazerg:fix/issue-9963-info-copy-list-alias
Sep 5, 2026
Merged

radarhere merged 5 commits into
python-pillow:mainfrom
lazerg:fix/issue-9963-info-copy-list-alias

Conversation

@lazerg

@lazerg lazerg commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

ImImagePlugin and IptcImagePlugin both store repeated header/tag values as a list in .info, and Image._new() only shallow-copies the info dict, so a copy shares the same list object as the original. Appending to the copy's list silently mutates the original too, breaking the independence copy() is meant to give you. transform() (and anything routed through it, like a non-90-degree rotate()) had the same shallow copy and the same problem.

Both spots now copy list values individually when building the new info dict, so the resulting images have independent lists. Added regression tests in Tests/test_image_copy.py covering ImImagePlugin, IptcImagePlugin, and transform().

Fixes #9963

@lazerg lazerg closed this Sep 4, 2026
@lazerg lazerg reopened this Sep 4, 2026
@lazerg

lazerg commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

The ubuntu-latest Python 3.13 failure is an X11/Xwayland crash in test_qt_image_qapplication, unrelated to this change (it never touches Qt code). Same environmental flake seen on other unrelated PRs in this repo.

@radarhere

Copy link
Copy Markdown
Member

I've created lazerg#3 with suggestions for the tests.

@tritsystem

Copy link
Copy Markdown

Independently re-verified this fix (not affiliated with the PR, filed the original issue):

  • Reproduced the bug fresh against unpatched Pillow 12.2.0 for all three cases: Image.copy() on an IM file with two Comment: header lines, rotate(45) (goes through transform()), and Image.copy() on an IPTC file with a duplicate tag (2, 105). All three show is-identity True and a mutation of the copy leaking into the original.
  • Applied this PR's diff to a local copy of the installed package and reran the same three repros — all three now show is-identity False and no leak.

_copy_info() centralizing the fix in _new() and transform() matches the root cause exactly (both paths funnel through Image.info construction, not the individual plugins). LGTM from an independent check.

@lazerg

lazerg commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Cherry-picked all three commits from lazerg#3 (e71bb59, 5784152, d9e0e0d), no changes made to them.

  • (2, 25) is the better tag. iptc.jpg carries 7 keywords, and Headline is not repeatable per the spec, so the old fixture was testing a tag that would not realistically be a list.
  • Calling transform() directly is clearer than reaching it through rotate().
  • Agreed that one format is enough; the IM fixture was covering the same code path.

Re-verified the consolidated test still catches both halves of the bug: reverting _copy_info() at _new() and at transform() separately each trips its own assertion. Full local suite passes (4930 passed, 294 skipped, 3 xfailed), black/ruff clean.

Thanks @tritsystem for the independent verification.

@radarhere
radarhere enabled auto-merge (squash) September 5, 2026 09:27
@radarhere
radarhere merged commit 7327771 into python-pillow:main Sep 5, 2026
49 checks passed
@radarhere

Copy link
Copy Markdown
Member

Just a note that IM has now been deprecated.

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.

Image.copy() aliases mutable .info values (e.g. list-valued "Comment" in IM/IPTC) between original and copy

3 participants