mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-23 05:10:50 +02:00
perf(redact-engine): line/col by binary search over a per-scan line index
lineColAt walked the input from offset 0 for every finding, so a match-dense input (a pasted log full of emails and IPs) cost O(findings x bytes): 128 KiB took ~400 ms and 900 KiB tens of seconds. The line starts are now indexed once per scan, on the first finding, and each finding is a binary search. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
36693efae9
commit
4285556925
+24
-12
@@ -163,18 +163,28 @@ export function normalizeWithMap(input: string): {
|
|||||||
|
|
||||||
// ── Offset → line/col on the ORIGINAL text ────────────────────────────────────
|
// ── Offset → line/col on the ORIGINAL text ────────────────────────────────────
|
||||||
|
|
||||||
function lineColAt(original: string, offset: number): { line: number; col: number } {
|
/** Start offset of every line, built once per scan and only when a finding needs it. */
|
||||||
let line = 1;
|
function lineStarts(original: string): number[] {
|
||||||
let col = 1;
|
const starts = [0];
|
||||||
for (let i = 0; i < offset && i < original.length; i++) {
|
for (let i = 0; i < original.length; i++) if (original[i] === "\n") starts.push(i + 1);
|
||||||
if (original[i] === "\n") {
|
return starts;
|
||||||
line += 1;
|
}
|
||||||
col = 1;
|
|
||||||
} else {
|
/**
|
||||||
col += 1;
|
* Binary search over lineStarts: O(log lines) per finding. The previous walk
|
||||||
}
|
* from offset 0 per finding made a match-dense input (a pasted log full of
|
||||||
|
* emails and IPs) cost O(findings x bytes) — seconds for a few hundred KiB.
|
||||||
|
*/
|
||||||
|
function lineColAt(starts: number[], original: string, offset: number): { line: number; col: number } {
|
||||||
|
const at = Math.min(Math.max(0, offset), original.length);
|
||||||
|
let lo = 0;
|
||||||
|
let hi = starts.length - 1;
|
||||||
|
while (lo < hi) {
|
||||||
|
const mid = (lo + hi + 1) >> 1;
|
||||||
|
if (starts[mid] <= at) lo = mid;
|
||||||
|
else hi = mid - 1;
|
||||||
}
|
}
|
||||||
return { line, col };
|
return { line: lo + 1, col: at - starts[lo] + 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Safe preview masking ──────────────────────────────────────────────────────
|
// ── Safe preview masking ──────────────────────────────────────────────────────
|
||||||
@@ -318,6 +328,7 @@ function emailAllowed(
|
|||||||
|
|
||||||
export function scan(input: string, opts: ScanOptions = {}): ScanResult {
|
export function scan(input: string, opts: ScanOptions = {}): ScanResult {
|
||||||
const repoVisibility: RepoVisibility = opts.repoVisibility ?? "unknown";
|
const repoVisibility: RepoVisibility = opts.repoVisibility ?? "unknown";
|
||||||
|
let starts: number[] | null = null; // line index, built on the first finding
|
||||||
// #1824: ?? only catches null/undefined, not NaN or <= 0. A bad value
|
// #1824: ?? only catches null/undefined, not NaN or <= 0. A bad value
|
||||||
// (NaN from a malformed --max-bytes, or a negative) would make `byteLen >
|
// (NaN from a malformed --max-bytes, or a negative) would make `byteLen >
|
||||||
// maxBytes` always false and silently disable the fail-closed oversize guard.
|
// maxBytes` always false and silently disable the fail-closed oversize guard.
|
||||||
@@ -395,7 +406,8 @@ export function scan(input: string, opts: ScanOptions = {}): ScanResult {
|
|||||||
if (seen.has(key)) continue;
|
if (seen.has(key)) continue;
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
|
|
||||||
const { line, col } = lineColAt(input, origOffset);
|
starts ??= lineStarts(input);
|
||||||
|
const { line, col } = lineColAt(starts, input, origOffset);
|
||||||
|
|
||||||
// Tool-fence degrade: only credential-category, only obvious doc examples.
|
// Tool-fence degrade: only credential-category, only obvious doc examples.
|
||||||
let severity: Severity = pat.tier;
|
let severity: Severity = pat.tier;
|
||||||
|
|||||||
Reference in New Issue
Block a user