|
9 | 9 | import os.path as osp |
10 | 10 | import subprocess |
11 | 11 | import sys |
| 12 | +import time |
12 | 13 | from unittest import mock |
13 | 14 |
|
14 | 15 | import pytest |
@@ -262,6 +263,25 @@ def test_inline_comments_are_stripped_like_git(self): |
262 | 263 | with self.subTest(content=content): |
263 | 264 | self.assertEqual(config.get_value("a", "k"), expected) |
264 | 265 |
|
| 266 | + def test_option_line_with_long_whitespace_run_is_not_quadratic(self): |
| 267 | + """A key followed by a long whitespace run and no indicator must not make |
| 268 | + the option regex backtrack quadratically. |
| 269 | +
|
| 270 | + `.gitmodules` and other config files are fully controlled by any repository |
| 271 | + that is inspected, so a crafted line must stay cheap to parse. Keys that come |
| 272 | + before the malformed line are still read. |
| 273 | + """ |
| 274 | + malformed = b'[submodule "x"]\n\tpath = x\n\tbranch' + b" " * 200_000 + b"\n" |
| 275 | + config_file = io.BytesIO(malformed) |
| 276 | + config_file.name = ".gitmodules" |
| 277 | + config = GitConfigParser(config_file) |
| 278 | + start = time.process_time() |
| 279 | + config.read() |
| 280 | + elapsed = time.process_time() - start |
| 281 | + # Leave ample CPU time for slow runners, but catch quadratic backtracking. |
| 282 | + self.assertLess(elapsed, 1.0) |
| 283 | + self.assertEqual(config.get_value('submodule "x"', "path"), "x") |
| 284 | + |
265 | 285 | @with_rw_directory |
266 | 286 | def test_inline_comments_preserve_balanced_quotes_and_following_settings(self, rw_dir): |
267 | 287 | config_path = osp.join(rw_dir, "config") |
|
0 commit comments