mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 07:29:33 +00:00
perf(csharp): resume past the \$/@ prefix run so malformed files stay linear (#3376)
(cherry picked from commit 7f0fd6ddfe)
This commit is contained in:
1 parent
709bba316b
commit
1704777e39
2 files changed
+37
-8
No files matched your search
+15
-7
@@ -3167,8 +3167,14 @@ def extract_ruby(path: Path) -> dict:
|
||||
return _extract_generic(path, _RUBY_CONFIG)
|
||||
|
||||
|
||||
def _csharp_string_opener(source: bytes, i: int) -> "tuple[int, int, bool, bool] | None":
|
||||
"""If a string literal starts at ``i``, return ``(quote_pos, quote_run, interpolated, verbatim)``.
|
||||
def _csharp_string_opener(source: bytes, i: int) -> "tuple[int, int, bool, bool]":
|
||||
"""Read a possible string-literal opener at ``i``.
|
||||
|
||||
Returns ``(prefix_end, quote_run, interpolated, verbatim)``. ``quote_run`` is
|
||||
0 when no string starts here, and ``prefix_end`` is always where the run of
|
||||
``$``/``@`` characters stopped — the caller needs that even on a miss, so it
|
||||
can resume past the run instead of re-walking it from the next byte (a long
|
||||
run in malformed source would otherwise cost O(n^2)).
|
||||
|
||||
C# lets the ``$`` and ``@`` prefixes appear in either order and repeat, and
|
||||
a raw string literal opens with a run of three or more quotes, so the prefix
|
||||
@@ -3187,7 +3193,7 @@ def _csharp_string_opener(source: bytes, i: int) -> "tuple[int, int, bool, bool]
|
||||
break
|
||||
j += 1
|
||||
if source[j:j + 1] != b'"':
|
||||
return None
|
||||
return j, 0, interpolated, verbatim
|
||||
run = 0
|
||||
while source[j + run:j + run + 1] == b'"':
|
||||
run += 1
|
||||
@@ -3229,11 +3235,13 @@ def _csharp_broken_escape_offsets(source: bytes) -> list[int]:
|
||||
j += 1
|
||||
i = j + 1
|
||||
continue
|
||||
opener = _csharp_string_opener(source, i)
|
||||
if opener is None:
|
||||
i += 1
|
||||
quote, run, interpolated, verbatim = _csharp_string_opener(source, i)
|
||||
if run == 0:
|
||||
# No string opens here. Resume at the end of the `$`/`@` run rather
|
||||
# than at i + 1: re-walking a long run from every offset is what
|
||||
# makes a malformed file quadratic.
|
||||
i = quote if quote > i else i + 1
|
||||
continue
|
||||
quote, run, interpolated, verbatim = opener
|
||||
if run >= 3:
|
||||
# Raw string literal: closed by a quote run of the same length.
|
||||
end = source.find(b'"' * run, quote + run)
|
||||
|
||||
@@ -16,7 +16,11 @@ from __future__ import annotations
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.extract import _normalize_csharp_verbatim_interpolation, extract
|
||||
from graphify.extract import (
|
||||
_csharp_broken_escape_offsets,
|
||||
_normalize_csharp_verbatim_interpolation,
|
||||
extract,
|
||||
)
|
||||
|
||||
# The offending literal, kept in one place: `}` then an escaped quote then the
|
||||
# terminator. Written as a plain string so the C# quoting stays readable.
|
||||
@@ -164,3 +168,20 @@ def test_two_offending_literals_in_one_file(tmp_path):
|
||||
labels = {str(n.get("label", "")).lstrip(".") for n in result["nodes"]}
|
||||
for member in ("First()", "Between()", "Second()", "Last()"):
|
||||
assert member in labels, f"{member} missing from {sorted(labels)}"
|
||||
|
||||
|
||||
def test_long_prefix_run_does_not_blow_up(tmp_path):
|
||||
"""A malformed file is the normal input here -- the scan only runs on files
|
||||
the grammar already rejected -- so a long run of `$`/`@` must stay linear.
|
||||
|
||||
Resuming one byte past the start of such a run re-walks it from every offset:
|
||||
4k characters took 0.8s that way, and the cost quadruples each doubling.
|
||||
"""
|
||||
import time
|
||||
|
||||
src = b"class C { " + (b"$" * 1_000_000) + b" }"
|
||||
started = time.perf_counter()
|
||||
assert _csharp_broken_escape_offsets(src) == []
|
||||
elapsed = time.perf_counter() - started
|
||||
# Linear finishes in well under a second; the quadratic form needs hours.
|
||||
assert elapsed < 5.0, f"prefix scan took {elapsed:.1f}s -- quadratic again?"
|
||||
Reference in new issue
Block a user