diff --git a/lib/std/json/scanner.zig b/lib/std/json/scanner.zig index 80d908ac69..c256f385e9 100644 --- a/lib/std/json/scanner.zig +++ b/lib/std/json/scanner.zig @@ -204,6 +204,8 @@ pub const Diagnostics = struct { cursor_in_current_input: usize = undefined, current_input: []const u8 = undefined, + // updated setMessage(). + message: []const u8 = "", // updated by recordContext(). context_stack: BoundedArray([]const u8, 8) = .{}, @@ -220,6 +222,13 @@ pub const Diagnostics = struct { return self.total_bytes_before_current_input + self.cursor_in_current_input; } + /// Set the headline message of the problem. + /// Must not be called multiple times. + pub fn setMessage(self: *@This(), message: []const u8) void { + assert(message.len != 0); + assert(self.message.len == 0); // already set message. + self.message = message; + } /// Attemps to push a human-readable string onto the context stack. /// Only works up to a maximum number of times, after which this does nothing. pub fn recordContext(self: *@This(), context: []const u8) void { @@ -270,11 +279,11 @@ pub const Diagnostics = struct { try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start); try writer.writeAll("^\n"); - if (self.context_stack.len > 0) { - try writer.print("{s}\n", .{self.context_stack.slice()[0]}); - for (self.context_stack.slice()[1..]) |item| { - try writer.print(" in {s}\n", .{item}); - } + if (self.message.len > 0) { + try writer.print("{s}\n", .{self.message}); + } + for (self.context_stack.slice()) |item| { + try writer.print(" in {s}\n", .{item}); } } }; diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig index 4cccf2247a..b504beb821 100644 --- a/lib/std/json/static.zig +++ b/lib/std/json/static.zig @@ -287,7 +287,7 @@ pub fn innerParse( const field_name = switch (name_token.?) { inline .string, .allocated_string => |slice| slice, else => { - if (options.diagnostics) |diag| diag.recordContext("tagged union requires a field to identify the active tag"); + if (options.diagnostics) |diag| diag.setMessage("tagged union requires a field to identify the active tag"); return error.MissingField; }, }; @@ -305,7 +305,7 @@ pub fn innerParse( else => |t| return typeError(options.diagnostics, t, "void payload ('{}')"), } if (.object_end != try source.next()) { - if (options.diagnostics) |diag| diag.recordContext("void payload '{}' should have no fields"); + if (options.diagnostics) |diag| diag.setMessage("void payload '{}' should have no fields"); return error.UnknownField; } result = @unionInit(T, u_field.name, {}); @@ -317,12 +317,12 @@ pub fn innerParse( } } else { // Didn't match anything. - if (options.diagnostics) |diag| diag.recordContext("unrecognized tag name"); + if (options.diagnostics) |diag| diag.setMessage("unrecognized tag name"); return error.UnknownField; } if (.object_end != try source.next()) { - if (options.diagnostics) |diag| diag.recordContext("tagged union requires only one field"); + if (options.diagnostics) |diag| diag.setMessage("tagged union requires only one field"); return error.UnknownField; } @@ -339,7 +339,7 @@ pub fn innerParse( var r: T = undefined; inline for (0..structInfo.fields.len) |i| { if (.array_end == try source.peekNextTokenType()) { - if (options.diagnostics) |diag| diag.recordContext(std.fmt.comptimePrint( + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint( "tuple too short. expected length: {}", .{structInfo.fields.len}, )); @@ -349,7 +349,7 @@ pub fn innerParse( } if (.array_end != try source.next()) { - if (options.diagnostics) |diag| diag.recordContext(std.fmt.comptimePrint( + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint( "tuple too long. expected length: {}", .{structInfo.fields.len}, )); @@ -398,7 +398,7 @@ pub fn innerParse( break; }, .@"error" => { - if (options.diagnostics) |diag| diag.recordContext("duplicate field name: " ++ field.name); + if (options.diagnostics) |diag| diag.setMessage("duplicate field name: " ++ field.name); return error.DuplicateField; }, .use_last => {}, @@ -414,7 +414,7 @@ pub fn innerParse( if (options.ignore_unknown_fields) { try source.skipValue(); } else { - if (options.diagnostics) |diag| diag.recordContext("unrecognized field name"); + if (options.diagnostics) |diag| diag.setMessage("unrecognized field name"); return error.UnknownField; } } @@ -438,32 +438,54 @@ pub fn innerParse( while (true) { switch (try source.next()) { .string => |slice| { - if (i + slice.len != r.len) return error.LengthMismatch; + if (i + slice.len > r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } + if (i + slice.len < r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too short for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } @memcpy(r[i..][0..slice.len], slice); break; }, .partial_string => |slice| { - if (i + slice.len > r.len) return error.LengthMismatch; + if (i + slice.len > r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } @memcpy(r[i..][0..slice.len], slice); i += slice.len; }, .partial_string_escaped_1 => |arr| { - if (i + arr.len > r.len) return error.LengthMismatch; + if (i + arr.len > r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } @memcpy(r[i..][0..arr.len], arr[0..]); i += arr.len; }, .partial_string_escaped_2 => |arr| { - if (i + arr.len > r.len) return error.LengthMismatch; + if (i + arr.len > r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } @memcpy(r[i..][0..arr.len], arr[0..]); i += arr.len; }, .partial_string_escaped_3 => |arr| { - if (i + arr.len > r.len) return error.LengthMismatch; + if (i + arr.len > r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } @memcpy(r[i..][0..arr.len], arr[0..]); i += arr.len; }, .partial_string_escaped_4 => |arr| { - if (i + arr.len > r.len) return error.LengthMismatch; + if (i + arr.len > r.len) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len})); + return error.LengthMismatch; + } @memcpy(r[i..][0..arr.len], arr[0..]); i += arr.len; }, @@ -568,11 +590,17 @@ fn internalParseArray( var r: T = undefined; var i: usize = 0; while (i < len) : (i += 1) { - if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch; + if (.array_end == try source.peekNextTokenType()) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("not enough items for fixed length array. expected length: {}", .{len})); + return error.LengthMismatch; + } r[i] = try innerParse(Child, allocator, source, options); } - if (.array_end != try source.next()) return error.LengthMismatch; + if (.array_end != try source.next()) { + if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("too many items for fixed length array. expected length: {}", .{len})); + return error.LengthMismatch; + } return r; } @@ -622,7 +650,7 @@ fn typeError(diagnostics: ?*Diagnostics, token: anytype, comptime expected: []co .array_end => unreachable, // type errors happen at the start of a value. .end_of_document => unreachable, // type errors happen at the start of a value. }; - diag.recordContext(s); + diag.setMessage(s); } return error.UnexpectedToken; } @@ -880,7 +908,7 @@ fn fillDefaultStructValues(comptime T: type, options: ParseOptions, r: *T, field const default = @as(*align(1) const field.type, @ptrCast(default_ptr)).*; @field(r, field.name) = default; } else { - if (options.diagnostics) |diag| diag.recordContext("missing field: " ++ @typeName(T) ++ "." ++ field.name); + if (options.diagnostics) |diag| diag.setMessage("missing field: " ++ @typeName(T) ++ "." ++ field.name); return error.MissingField; } }