]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-148370: prevent quadratic behavior in `configparser.ParsingError.combine...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 13 Apr 2026 23:05:38 +0000 (01:05 +0200)
committerGitHub <noreply@github.com>
Mon, 13 Apr 2026 23:05:38 +0000 (23:05 +0000)
gh-148370: prevent quadratic behavior in `configparser.ParsingError.combine` (GH-148452)
(cherry picked from commit 2662db0c45aa16232136628457a53681b6683c25)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Lib/configparser.py
Lib/test/test_configparser.py
Misc/NEWS.d/next/Library/2026-04-12-16-40-11.gh-issue-148370.0Li2EK.rst [new file with mode: 0644]

index 05b86acb919bfdc0be7e2b72c5d9acb807a95695..71f842b3902e8cbab4f6600ba93256168730d7d2 100644 (file)
@@ -316,12 +316,15 @@ class ParsingError(Error):
 
     def append(self, lineno, line):
         self.errors.append((lineno, line))
-        self.message += '\n\t[line %2d]: %s' % (lineno, repr(line))
+        self.message += f'\n\t[line {lineno:2d}]: {line!r}'
 
     def combine(self, others):
+        messages = [self.message]
         for other in others:
-            for error in other.errors:
-                self.append(*error)
+            for lineno, line in other.errors:
+                self.errors.append((lineno, line))
+                messages.append(f'\n\t[line {lineno:2d}]: {line!r}')
+        self.message = "".join(messages)
         return self
 
     @staticmethod
index d4fdd92bdc6b6ac85a1411ba58dd723dee40b98b..bef3c11b941242f956c50b856378b0d479afba55 100644 (file)
@@ -1729,6 +1729,19 @@ class ExceptionPicklingTestCase(unittest.TestCase):
             self.assertEqual(e1.message, e2.message)
             self.assertEqual(repr(e1), repr(e2))
 
+    def test_combine_error_linear_complexity(self):
+        # Ensure that ParsingError.combine() has linear complexity.
+        # See https://github.com/python/cpython/issues/148370.
+        n = 50000
+        s = '[*]\n' + (err_line := '=\n') * n
+        p = configparser.ConfigParser(strict=False)
+        with self.assertRaises(configparser.ParsingError) as cm:
+            p.read_string(s)
+        errlines = cm.exception.message.splitlines()
+        self.assertEqual(len(errlines), n + 1)
+        self.assertTrue(errlines[0].startswith("Source contains parsing errors: "))
+        self.assertEqual(errlines[42], f"\t[line {43:2d}]: {err_line!r}")
+
     def test_nosectionerror(self):
         import pickle
         e1 = configparser.NoSectionError('section')
diff --git a/Misc/NEWS.d/next/Library/2026-04-12-16-40-11.gh-issue-148370.0Li2EK.rst b/Misc/NEWS.d/next/Library/2026-04-12-16-40-11.gh-issue-148370.0Li2EK.rst
new file mode 100644 (file)
index 0000000..3bb6623
--- /dev/null
@@ -0,0 +1,2 @@
+:mod:`configparser`: prevent quadratic behavior when a :exc:`~configparser.ParsingError`
+is raised after a parser fails to parse multiple lines. Patch by Bénédikt Tran.