attempt intra-record recovery for short length-prefixed strings, still produces ParseFailed at next record, with single diagnostic
All checks were successful
Generic zig build / build (push) Successful in 21s
All checks were successful
Generic zig build / build (push) Successful in 21s
This commit is contained in:
parent
e2b49957cd
commit
c5e83f7569
1 changed files with 57 additions and 10 deletions
67
src/srf.zig
67
src/srf.zig
|
|
@ -346,10 +346,11 @@ pub const Value = union(enum) {
|
||||||
_ = try state.reader.discardDelimiterExclusive('\n');
|
_ = try state.reader.discardDelimiterExclusive('\n');
|
||||||
}
|
}
|
||||||
} else if (past_val[0] != state.field_delimiter) {
|
} else if (past_val[0] != state.field_delimiter) {
|
||||||
|
const extra_bytes = std.mem.indexOfScalar(u8, past_val, state.field_delimiter) orelse past_val.len;
|
||||||
const msg = std.fmt.bufPrint(
|
const msg = std.fmt.bufPrint(
|
||||||
&buf,
|
&buf,
|
||||||
"field value has {} additional bytes. Perhaps length should be restated as {}?",
|
"field value has {} additional bytes. Perhaps length should be restated as {}?",
|
||||||
.{ past_val.len, past_val.len + declared_size },
|
.{ extra_bytes, extra_bytes + declared_size },
|
||||||
) catch "field value has additional bytes. Perhaps length should be restated";
|
) catch "field value has additional bytes. Perhaps length should be restated";
|
||||||
// we can try to advance the reader to the next field, but we might be cooked
|
// we can try to advance the reader to the next field, but we might be cooked
|
||||||
try parseError(msg, state);
|
try parseError(msg, state);
|
||||||
|
|
@ -943,15 +944,20 @@ pub const RecordIterator = struct {
|
||||||
return field;
|
return field;
|
||||||
} else {
|
} else {
|
||||||
if (state.current_line.?[0] != state.field_delimiter) {
|
if (state.current_line.?[0] != state.field_delimiter) {
|
||||||
var buf: [256]u8 = undefined;
|
// We're going to try to recover from this situation,
|
||||||
const msg = std.fmt.bufPrint(
|
// but we're dealing with invalid srf at this point,
|
||||||
&buf,
|
// so no guarantees. Note that our line/column counter
|
||||||
"expected '{c}' or end of line after value; check the length prefix",
|
// is already advanced in this situation, as this
|
||||||
.{state.field_delimiter},
|
// branch should only trigger in compact mode when
|
||||||
) catch @panic("bufPrint has no room but should absolutely have more room");
|
// length is short
|
||||||
|
if (std.mem.indexOfScalar(u8, state.current_line.?, state.field_delimiter)) |i| {
|
||||||
try parseError(msg, state);
|
log.info("attempting recovery from invalid srf. check diagnostics for more information", .{});
|
||||||
return error.ParseFailed; // this is fatal
|
log.debug(
|
||||||
|
"invalid srf recovery:\n\t\tcurrent_line: '{s}'\n\t\trevised line: '{s}'",
|
||||||
|
.{ state.current_line.?, state.current_line.?[i..] },
|
||||||
|
);
|
||||||
|
state.current_line = state.current_line.?[i..]; //reset to be on the delimiter as expected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
state.current_line = state.current_line.?[1..];
|
state.current_line = state.current_line.?[1..];
|
||||||
}
|
}
|
||||||
|
|
@ -2795,6 +2801,47 @@ test "BoundedDiagnostics.errors returns a view into the diagnostics, not a copy"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "compact format: a length prefix shorter than the line is reported exactly once" {
|
||||||
|
// const lvl = std.testing.log_level;
|
||||||
|
// defer std.testing.log_level = lvl;
|
||||||
|
// std.testing.log_level = .debug;
|
||||||
|
|
||||||
|
const data =
|
||||||
|
\\#!srfv1
|
||||||
|
\\k:3:hello,foo::bar
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var reader = std.Io.Reader.fixed(data);
|
||||||
|
var diags: BoundedDiagnostics(10) = .empty;
|
||||||
|
var diag: Diagnostics = diags.diagnostics();
|
||||||
|
|
||||||
|
var it = try iterator(&reader, allocator, .{ .diagnostics = &diag });
|
||||||
|
defer it.deinit();
|
||||||
|
const record = try it.next();
|
||||||
|
try std.testing.expect(record != null);
|
||||||
|
const k = try record.?.next();
|
||||||
|
try std.testing.expect(k != null);
|
||||||
|
try std.testing.expect(k.?.value != null);
|
||||||
|
try std.testing.expect(k.?.value.? == .string);
|
||||||
|
try std.testing.expectEqualStrings("hel", k.?.value.?.string);
|
||||||
|
|
||||||
|
const errors = diags.errors();
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), errors.len);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), errors[0].line);
|
||||||
|
|
||||||
|
// The parser should recover from this underflow on the length in this
|
||||||
|
// situation, but we should still see a ParseError at the end of the stream
|
||||||
|
const foo = try record.?.next();
|
||||||
|
try std.testing.expect(foo != null);
|
||||||
|
try std.testing.expect(foo.?.value != null);
|
||||||
|
try std.testing.expect(foo.?.value.? == .string);
|
||||||
|
try std.testing.expectEqualStrings("bar", foo.?.value.?.string);
|
||||||
|
|
||||||
|
// Any attempt to move to the next record should trigger parsefailed. But
|
||||||
|
// at least we've gotten to the point of consuming the full record
|
||||||
|
try std.testing.expectError(error.ParseFailed, it.next());
|
||||||
|
}
|
||||||
test "long format: a length prefix shorter than the line is reported" {
|
test "long format: a length prefix shorter than the line is reported" {
|
||||||
// In long format `next` takes the `field_delimiter == '\n'` branch and calls
|
// In long format `next` takes the `field_delimiter == '\n'` branch and calls
|
||||||
// nextLine() unconditionally, discarding whatever was left of the line. So
|
// nextLine() unconditionally, discarding whatever was left of the line. So
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue