milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
This commit is contained in:
+9
-18
@@ -61,30 +61,17 @@ pub fn compile(
|
||||
var wild: Entries = .{};
|
||||
defer wild.deinit(gpa);
|
||||
|
||||
while (true) {
|
||||
const raw = r.takeDelimiter('\n') catch |err| switch (err) {
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
// `takeDelimiter` leaves the stream unmodified on `StreamTooLong`
|
||||
// (Reader.zig:885). Without this discard the loop re-reads the same
|
||||
// bytes forever.
|
||||
error.StreamTooLong => {
|
||||
while (try parsers.nextBoundedLine(r, max_line_len)) |event| {
|
||||
const raw = switch (event) {
|
||||
.long_line => {
|
||||
counts.long_lines += 1;
|
||||
_ = r.discardDelimiterInclusive('\n') catch |discard_err| switch (discard_err) {
|
||||
error.EndOfStream => break,
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
};
|
||||
continue;
|
||||
},
|
||||
} orelse break;
|
||||
.line => |line| line,
|
||||
};
|
||||
|
||||
var line = raw;
|
||||
if (line.len != 0 and line[line.len - 1] == '\r') line = line[0 .. line.len - 1];
|
||||
// A reader whose buffer is larger than `max_line_len` reports the
|
||||
// over-long line here instead of through `error.StreamTooLong`.
|
||||
if (line.len > max_line_len) {
|
||||
counts.long_lines += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const parsed = parsers.parseLine(format, line);
|
||||
switch (parsed.kind) {
|
||||
@@ -120,6 +107,10 @@ pub fn compile(
|
||||
|
||||
/// Normalizes one whitespace-separated candidate and files it under `.list`,
|
||||
/// `.wild`, or neither.
|
||||
///
|
||||
/// The normalization below is deliberately not `dns.name.normalizeText`: this
|
||||
/// one adds the two-label minimum, rejects control bytes, and reports every
|
||||
/// rejection through `counts.invalid` rather than an error.
|
||||
fn addCandidate(
|
||||
gpa: std.mem.Allocator,
|
||||
field: []const u8,
|
||||
|
||||
+5
-13
@@ -1530,20 +1530,12 @@ fn destroySnapshot(gpa: Allocator, snapshot: *matcher.Snapshot) void {
|
||||
fn collectSample(r: *std.Io.Reader, w: *std.Io.Writer) error{ ReadFailed, WriteFailed }!void {
|
||||
var considered: usize = 0;
|
||||
while (considered < parsers.sample_lines) {
|
||||
const raw = r.takeDelimiter('\n') catch |err| switch (err) {
|
||||
// The stream is left unmodified here, so the line has to be stepped
|
||||
// over or this loop never advances.
|
||||
error.StreamTooLong => {
|
||||
_ = r.discardDelimiterInclusive('\n') catch |discard_err| switch (discard_err) {
|
||||
error.EndOfStream => return,
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
};
|
||||
continue;
|
||||
},
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
} orelse return;
|
||||
const event = (try parsers.nextBoundedLine(r, compiler.max_line_len)) orelse return;
|
||||
const raw = switch (event) {
|
||||
.long_line => continue,
|
||||
.line => |line| line,
|
||||
};
|
||||
|
||||
if (raw.len > compiler.max_line_len) continue;
|
||||
const line = std.mem.trim(u8, raw, &std.ascii.whitespace);
|
||||
if (line.len == 0) continue;
|
||||
if (parsers.isComment(line)) continue;
|
||||
|
||||
@@ -48,6 +48,50 @@ pub fn parseLine(format: Format, line: []const u8) Line {
|
||||
};
|
||||
}
|
||||
|
||||
pub const LineEvent = union(enum) {
|
||||
/// One line without its delimiter, borrowed from the reader's buffer and
|
||||
/// valid only until the next call. A trailing '\r' is left on: whether it
|
||||
/// belongs to the line is the caller's decision.
|
||||
line: []const u8,
|
||||
/// A line longer than `max_len`. It has already been stepped over.
|
||||
long_line,
|
||||
};
|
||||
|
||||
/// One line, or `null` at end of stream. `max_len` bounds a line; anything
|
||||
/// longer comes back as `.long_line` with the stream positioned on the line
|
||||
/// after it, so a caller that keeps calling always advances.
|
||||
///
|
||||
/// The bound is a parameter because this file may not import `compiler.zig`:
|
||||
/// the compiler imports this one, and this file is the root of a separate fuzz
|
||||
/// module. Both callers pass `compiler.max_line_len`.
|
||||
///
|
||||
/// The two over-long paths exist because a `Reader` reports an over-long line
|
||||
/// two different ways. A reader whose buffer is smaller than `max_len` reports
|
||||
/// `error.StreamTooLong` and — this is the hazard — leaves the stream
|
||||
/// unmodified (Reader.zig:895-919), so without the discard a caller re-reads
|
||||
/// the same bytes forever. A reader whose buffer is larger hands the whole line
|
||||
/// over and the length check catches it.
|
||||
///
|
||||
/// An over-long final line with no delimiter ends the stream inside the
|
||||
/// discard. That still counts as a line, so it comes back as `.long_line`; the
|
||||
/// discard drained the stream (Reader.zig:1042), so the next call returns
|
||||
/// `null`.
|
||||
pub fn nextBoundedLine(r: *std.Io.Reader, max_len: usize) error{ReadFailed}!?LineEvent {
|
||||
const raw = r.takeDelimiter('\n') catch |err| switch (err) {
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
error.StreamTooLong => {
|
||||
_ = r.discardDelimiterInclusive('\n') catch |discard_err| switch (discard_err) {
|
||||
error.EndOfStream => return .long_line,
|
||||
error.ReadFailed => return error.ReadFailed,
|
||||
};
|
||||
return .long_line;
|
||||
},
|
||||
} orelse return null;
|
||||
|
||||
if (raw.len > max_len) return .long_line;
|
||||
return .{ .line = raw };
|
||||
}
|
||||
|
||||
pub const sample_lines = 64;
|
||||
|
||||
/// Picks a format from the first `sample_lines` lines that are not blank and
|
||||
@@ -309,6 +353,58 @@ test "parseLine dispatches to the abp parser" {
|
||||
try testing.expect(line.covers_apex);
|
||||
}
|
||||
|
||||
fn expectLine(expected: []const u8, event: ?LineEvent) !void {
|
||||
const got = event orelse return error.TestExpectedLine;
|
||||
switch (got) {
|
||||
.line => |line| try testing.expectEqualStrings(expected, line),
|
||||
.long_line => return error.TestExpectedLine,
|
||||
}
|
||||
}
|
||||
|
||||
test "nextBoundedLine walks lines and ends at the stream" {
|
||||
var r: std.Io.Reader = .fixed("a\nbb\n\nccc");
|
||||
try expectLine("a", try nextBoundedLine(&r, 16));
|
||||
try expectLine("bb", try nextBoundedLine(&r, 16));
|
||||
try expectLine("", try nextBoundedLine(&r, 16));
|
||||
// A final line with no delimiter is still a line.
|
||||
try expectLine("ccc", try nextBoundedLine(&r, 16));
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(&r, 16));
|
||||
}
|
||||
|
||||
test "nextBoundedLine reports an over-long line when the reader buffer is large" {
|
||||
var r: std.Io.Reader = .fixed("a\nxxxxxxxx\nb\n");
|
||||
try expectLine("a", try nextBoundedLine(&r, 4));
|
||||
try testing.expectEqual(LineEvent.long_line, (try nextBoundedLine(&r, 4)).?);
|
||||
try expectLine("b", try nextBoundedLine(&r, 4));
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(&r, 4));
|
||||
}
|
||||
|
||||
test "nextBoundedLine steps over a line that does not fit the reader buffer" {
|
||||
// A buffer smaller than the long line makes `takeDelimiter` report
|
||||
// `error.StreamTooLong` and leave the stream where it was, which is the
|
||||
// path that loops forever without the discard.
|
||||
var backing: std.Io.Reader = .fixed("a\n" ++ "x" ** 64 ++ "\nb\n");
|
||||
var buf: [16]u8 = undefined;
|
||||
var limited = backing.limited(.unlimited, &buf);
|
||||
const r = &limited.interface;
|
||||
|
||||
try expectLine("a", try nextBoundedLine(r, 16));
|
||||
try testing.expectEqual(LineEvent.long_line, (try nextBoundedLine(r, 16)).?);
|
||||
try expectLine("b", try nextBoundedLine(r, 16));
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(r, 16));
|
||||
}
|
||||
|
||||
test "nextBoundedLine reports an over-long final line that ends inside the discard" {
|
||||
var backing: std.Io.Reader = .fixed("a\n" ++ "x" ** 64);
|
||||
var buf: [16]u8 = undefined;
|
||||
var limited = backing.limited(.unlimited, &buf);
|
||||
const r = &limited.interface;
|
||||
|
||||
try expectLine("a", try nextBoundedLine(r, 16));
|
||||
try testing.expectEqual(LineEvent.long_line, (try nextBoundedLine(r, 16)).?);
|
||||
try testing.expectEqual(@as(?LineEvent, null), try nextBoundedLine(r, 16));
|
||||
}
|
||||
|
||||
test "looksLikeIpLiteral separates addresses from names" {
|
||||
try testing.expect(looksLikeIpLiteral("0.0.0.0"));
|
||||
try testing.expect(looksLikeIpLiteral("127.0.0.1"));
|
||||
|
||||
@@ -192,6 +192,10 @@ const NameError = error{BadName};
|
||||
/// Lowercases over ASCII and strips one trailing dot. A byte ≥ 0x80 is
|
||||
/// rejected: query names reach the matcher ASCII-lowercased, so a pattern
|
||||
/// carrying a high byte could never match anything.
|
||||
///
|
||||
/// Deliberately not `dns.name.normalizeText`: a pattern may hold `*`, which
|
||||
/// `name.fromText` would reject, so this variant skips that check and rejects
|
||||
/// control bytes and space instead.
|
||||
fn normalize(text: []const u8, buf: *[types.max_name_len]u8) NameError![]const u8 {
|
||||
var rest = text;
|
||||
if (rest.len > 0 and rest[rest.len - 1] == '.') rest = rest[0 .. rest.len - 1];
|
||||
|
||||
Reference in New Issue
Block a user