Skip to content

[3.15] gh-98820: Fix quadratic time in csv.Sniffer for quoted fields - #154867

Merged
serhiy-storchaka merged 3 commits into
python:3.15from
serhiy-storchaka:sniff-315-gh98820
Aug 3, 2026
Merged

[3.15] gh-98820: Fix quadratic time in csv.Sniffer for quoted fields#154867
serhiy-storchaka merged 3 commits into
python:3.15from
serhiy-storchaka:sniff-315-gh98820

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Jul 29, 2026

Copy link
Copy Markdown
Member

The four regular expressions which look for a quoted field match its body lazily. A closing quote which is not followed by a delimiter is therefore retried with every following quote, to the very end of the sample, and findall() repeats that from every start position -- so the search is quadratic. On a single column of quoted fields none of them ever matches, so the whole cost is a failing scan.

>>> sample = '"abcdefghijklmnopqrstuvwxyz"\n' * 30000
>>> csv.Sniffer().sniff(sample, delimiters=',:|\t')   # minutes

The body now ends at the first quote which is not doubled, which is the only closing quote a reader would accept, and is matched possessively.

rows      before      after
 4000    3.1264 s    0.0427 s
30000        --      0.3190 s

A minimal reproducer needs no quoted fields at all -- ',"x' * n is quadratic too, because two of the four patterns are anchored on a delimiter rather than a line.

Being unambiguous is what makes it linear, so a few matches necessarily change: 16 of 560 files in the CSVsniffer corpora. Against their ground truth the net is positive -- delimiter 73.3% -> 74.5%, quotechar 81.3% -> 81.1%, doublequote unchanged.

main is not affected: the sniffer was rewritten there in gh-83273.

The regular expressions which look for a quoted field matched its body
lazily, so a closing quote which was not followed by a delimiter was
retried with every following quote, to the end of the sample.  Match
the body possessively instead: it ends at the first quote which is not
doubled, as it does for a reader.
serhiy-storchaka and others added 2 commits August 3, 2026 13:33
sniff() now replaces \r\n and \r with \n before these regular
expressions are used.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@serhiy-storchaka
serhiy-storchaka enabled auto-merge (squash) August 3, 2026 11:49
@serhiy-storchaka serhiy-storchaka added the type-security A security issue label Aug 3, 2026
@serhiy-storchaka
serhiy-storchaka merged commit 476fb09 into python:3.15 Aug 3, 2026
147 of 152 checks passed
@miss-islington-app

Copy link
Copy Markdown

Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14.
🐍🍒⛏🤖 I'm not a witch! I'm not a witch!

@bedevere-app

bedevere-app Bot commented Aug 3, 2026

Copy link
Copy Markdown

GH-155117 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Aug 3, 2026
@bedevere-app

bedevere-app Bot commented Aug 3, 2026

Copy link
Copy Markdown

GH-155118 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.13 bugs and security fixes label Aug 3, 2026
Yhg1s pushed a commit that referenced this pull request Aug 4, 2026
…H-154867) (#155118)

[3.15] gh-98820: Fix quadratic time in csv.Sniffer for quoted fields (GH-154867)

The regular expressions which look for a quoted field matched its body
lazily, so a closing quote which was not followed by a delimiter was
retried with every following quote, to the end of the sample.  Match
the body possessively instead: it ends at the first quote which is not
doubled, as it does for a reader.
(cherry picked from commit 476fb09)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bedevere-app

bedevere-app Bot commented Aug 4, 2026

Copy link
Copy Markdown

GH-155166 is a backport of this pull request to the 3.12 branch.

serhiy-storchaka added a commit that referenced this pull request Aug 4, 2026
…H-154867) (GH-155117)

The regular expressions which look for a quoted field matched its body
lazily, so a closing quote which was not followed by a delimiter was
retried with every following quote, to the end of the sample.  Match
the body possessively instead: it ends at the first quote which is not
doubled, as it does for a reader.
(cherry picked from commit 476fb09)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Yhg1s pushed a commit that referenced this pull request Aug 4, 2026
…H-154867) (#155166)

(cherry picked from commit b30c7fa)

Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-security A security issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant