1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! Line-based file scan against the forbidden-regex engine's batch API.
//!
//! Stage two of the engine swap (#384) replaces the aho-corasick/resharp per-file
//! scan with the engine's buffer-batch face. A file's bytes are split into lines and
//! handed to `RegexSet::line_matches(buf, starts)`, which resolves per-line rule ids
//! in one SIMD prefilter sweep. Findings emit as `PATH:LINE rule=N`, one per
//! line-and-rule pair, with no column segment (the engine reports per-line rule
//! indices, not spans).
//!
//! Line mechanics mirror the engine's contract: split on `\n`, one trailing `\r` and
//! the terminator excluded by the matcher, empty lines skipped. The batch call runs
//! under a `catch_unwind` boundary so an engine panic fails closed as a synthetic
//! finding rather than aborting the scan, preserving the scanner's fail-closed
//! guarantee against a secret-scanning gate exiting clean on an engine fault.
/// Imports the SIMD newline scan used to build the line-start offsets.
use memchr_iter;
/// Imports the unwind boundary that turns an engine panic into a fail-closed finding.
use ;
/// Imports the loaded rule sets scanned against each file.
use crateLoadedRules;
/// Builds the line-start offsets `RegexSet::line_matches` requires for `buf`.
///
/// The engine's precondition is that `starts` ascends, begins at 0, and every offset
/// indexes within `buf`. This returns 0 followed by the offset just past each `\n`,
/// dropping a final offset equal to `buf.len()` (the empty line after a trailing
/// newline): the matcher would skip it anyway, and omitting it keeps every offset a
/// valid in-bounds index. Interior empty lines keep their offset and the matcher
/// skips them, so line numbering stays aligned with the file.
/// Runs one set's batch matcher under a fail-closed unwind boundary.
///
/// On a normal return the `(line index, rule id)` pairs become `PATH:LINE rule=N`
/// findings, with `base` added to each rule id for cross-set disambiguation and the
/// 0-based line index rendered 1-based. On a panic the boundary catches it and emits
/// a single synthetic diagnostic so the file cannot exit clean, matching the
/// read-error diagnostic precedent; the panic never escapes the scan.
/// Scans one file's bytes against every loaded set, returning redacted findings.
///
/// Splits `buf` into lines once, then runs each set's `line_matches` under the
/// fail-closed boundary and collects `PATH:LINE rule=N` findings in set order (runtime
/// rules before the builtin baseline), each set's ids offset by its base. An empty
/// file yields no findings. A match on line 2 of `a.txt` from the runtime set renders
/// as `a.txt:2 rule=0`.
/// Registers the line-splitting edge-case and fail-closed tests (sidecar, lint-exempt).