authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2024-06-01 23:58:05-04:00
committergravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2024-06-02 00:22:24-04:00
logce828bdc8947d0d4817acea1bf7624ccf5455ab3
tree7e3bf53e1ea85df4a0a562a4be7a3aa4f9b61888
parent8559d5dbd6f14916a1fa757a3a75a987bf697868

fix unsound allocator usage


2 files changed, 44 insertions(+), 46 deletions(-)

lib/std/json/scanner.zig+15-13
......@@ -35,6 +35,7 @@ const ArrayList = std.ArrayList;
3535const ArrayListUnmanaged = std.ArrayListUnmanaged;
3636const assert = std.debug.assert;
3737const BitStack = std.BitStack;
38const BoundedArray = std.BoundedArray;
3839
3940/// Scan the input and check for malformed JSON.
4041/// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`.
......@@ -204,7 +205,7 @@ pub const Diagnostics = struct {
204205 current_input: []const u8 = undefined,
205206
206207 // updated by recordContext().
207 context_stack: ArrayListUnmanaged([]const u8) = .{},
208 context_stack: BoundedArray([]const u8, 8) = .{},
208209
209210 /// Starts at 1.
210211 pub fn getLine(self: *const @This()) u64 {
......@@ -219,15 +220,22 @@ pub const Diagnostics = struct {
219220 return self.total_bytes_before_current_input + self.cursor_in_current_input;
220221 }
221222
222 pub fn recordContext(self: *@This(), allocator: Allocator, context: []const u8) Allocator.Error!void {
223 return self.context_stack.append(allocator, context);
223 /// Attemps to push a human-readable string onto the context stack.
224 /// Only works up to a maximum number of times, after which this does nothing.
225 pub fn recordContext(self: *@This(), context: []const u8) void {
226 self.context_stack.append(context) catch {};
224227 }
225228
226229 /// Pretty-print diagnostic information to the given writer, such as `std.io.getStdErr().writer()`.
227 /// file_name if non-null will be printed in a line with the line and column numbers;
230 /// displayed_file_name if non-null will be printed in a line with the line and column numbers;
228231 /// it is purely aesthetic and is not touched on any actual file system.
229 pub fn dump(self: *const @This(), writer: anytype, err: anyerror, file_name: ?[]const u8) !void {
230 try writer.print("{s}:{}:{}: {s}\n", .{ file_name orelse "<json>", self.getLine(), self.getColumn(), @errorName(err) });
232 pub fn dump(self: *const @This(), writer: anytype, err: anyerror, displayed_file_name: ?[]const u8) !void {
233 try writer.print("{s}:{}:{}: {s}\n", .{
234 displayed_file_name orelse "<json>",
235 self.getLine(),
236 self.getColumn(),
237 @errorName(err),
238 });
231239
232240 // Show a "line" of context, or in case of very long lines, just an excerpt of the line.
233241 // (Very long lines are common in minified JSON such as in an HTTP API or other machine-to-machine contexts.)
......@@ -262,18 +270,12 @@ pub const Diagnostics = struct {
262270 try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);
263271 try writer.writeAll("^\n");
264272
265 for (self.context_stack.items) |item| {
273 for (self.context_stack.slice()) |item| {
266274 try writer.print(" in {s}\n", .{item});
267275 }
268276 }
269277};
270278
271pub inline fn maybeRecordDiagnosticContext(allocator: Allocator, maybe_diagnostics: ?*Diagnostics, context: []const u8) void {
272 if (maybe_diagnostics) |diag| {
273 diag.recordContext(allocator, context) catch {};
274 }
275}
276
277279/// See the documentation for `std.json.Token`.
278280pub const AllocWhen = enum { alloc_if_needed, alloc_always };
279281
lib/std/json/static.zig+29-33
......@@ -11,7 +11,6 @@ const AllocWhen = @import("./scanner.zig").AllocWhen;
1111const Diagnostics = @import("./scanner.zig").Diagnostics;
1212const default_max_value_len = @import("./scanner.zig").default_max_value_len;
1313const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
14const maybeRecordDiagnosticContext = @import("./scanner.zig").maybeRecordDiagnosticContext;
1514
1615const Value = @import("./dynamic.zig").Value;
1716const Array = @import("./dynamic.zig").Array;
......@@ -220,13 +219,13 @@ pub fn innerParse(
220219 options: ParseOptions,
221220) ParseError(@TypeOf(source.*))!T {
222221 errdefer source.saveDiagnostics();
223 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T));
222 errdefer if (options.diagnostics) |diag| diag.recordContext(@typeName(T));
224223 switch (@typeInfo(T)) {
225224 .Bool => {
226225 return switch (try source.next()) {
227226 .true => true,
228227 .false => false,
229 else => |t| return typeError(allocator, options.diagnostics, t, "bool"),
228 else => |t| return typeError(options.diagnostics, t, "bool"),
230229 };
231230 },
232231 .Float, .ComptimeFloat => {
......@@ -234,7 +233,7 @@ pub fn innerParse(
234233 defer freeAllocated(allocator, token);
235234 const slice = switch (token) {
236235 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
237 else => |t| return typeError(allocator, options.diagnostics, t, "float"),
236 else => |t| return typeError(options.diagnostics, t, "float"),
238237 };
239238 return try std.fmt.parseFloat(T, slice);
240239 },
......@@ -243,7 +242,7 @@ pub fn innerParse(
243242 defer freeAllocated(allocator, token);
244243 const slice = switch (token) {
245244 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
246 else => |t| return typeError(allocator, options.diagnostics, t, "int"),
245 else => |t| return typeError(options.diagnostics, t, "int"),
247246 };
248247 return sliceToInt(T, slice);
249248 },
......@@ -267,7 +266,7 @@ pub fn innerParse(
267266 defer freeAllocated(allocator, token);
268267 const slice = switch (token) {
269268 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
270 else => |t| return typeError(allocator, options.diagnostics, t, "enum (number or string)"),
269 else => |t| return typeError(options.diagnostics, t, "enum (number or string)"),
271270 };
272271 return sliceToEnum(T, slice);
273272 },
......@@ -280,7 +279,7 @@ pub fn innerParse(
280279
281280 switch (try source.next()) {
282281 .object_begin => {},
283 else => |t| return typeError(allocator, options.diagnostics, t, "union (object with one field)"),
282 else => |t| return typeError(options.diagnostics, t, "union (object with one field)"),
284283 }
285284
286285 var result: ?T = null;
......@@ -300,7 +299,7 @@ pub fn innerParse(
300299 // void isn't really a json type, but we can support void payload union tags with {} as a value.
301300 switch (try source.next()) {
302301 .object_begin => {},
303 else => |t| return typeError(allocator, options.diagnostics, t, "void payload ('{}')"),
302 else => |t| return typeError(options.diagnostics, t, "void payload ('{}')"),
304303 }
305304 if (.object_end != try source.next()) return error.UnknownField;
306305 result = @unionInit(T, u_field.name, {});
......@@ -324,7 +323,7 @@ pub fn innerParse(
324323 if (structInfo.is_tuple) {
325324 switch (try source.next()) {
326325 .array_begin => {},
327 else => |t| return typeError(allocator, options.diagnostics, t, "tuple (array of values)"),
326 else => |t| return typeError(options.diagnostics, t, "tuple (array of values)"),
328327 }
329328
330329 var r: T = undefined;
......@@ -344,7 +343,7 @@ pub fn innerParse(
344343
345344 switch (try source.next()) {
346345 .object_begin => {},
347 else => |t| return typeError(allocator, options.diagnostics, t, "struct ('{...}')"),
346 else => |t| return typeError(options.diagnostics, t, "struct ('{...}')"),
348347 }
349348
350349 var r: T = undefined;
......@@ -366,7 +365,7 @@ pub fn innerParse(
366365 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
367366 // (Recursing into innerParse() might trigger more allocations.)
368367 freeAllocated(allocator, name_token.?);
369 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T) ++ "." ++ field.name);
368 errdefer if (options.diagnostics) |diag| diag.recordContext(@typeName(T) ++ "." ++ field.name);
370369 name_token = null;
371370 if (fields_seen[i]) {
372371 switch (options.duplicate_field_behavior) {
......@@ -405,7 +404,7 @@ pub fn innerParse(
405404 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
406405 },
407406 .string => {
408 if (arrayInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");
407 if (arrayInfo.child != u8) return typeError(options.diagnostics, .string, "array");
409408 // Fixed-length string.
410409
411410 var r: T = undefined;
......@@ -449,7 +448,7 @@ pub fn innerParse(
449448 return r;
450449 },
451450
452 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
451 else => |t| return typeError(options.diagnostics, t, "array"),
453452 }
454453 },
455454
......@@ -458,7 +457,7 @@ pub fn innerParse(
458457 .array_begin => {
459458 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
460459 },
461 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
460 else => |t| return typeError(options.diagnostics, t, "array"),
462461 }
463462 },
464463
......@@ -497,7 +496,7 @@ pub fn innerParse(
497496 return try arraylist.toOwnedSlice();
498497 },
499498 .string => {
500 if (ptrInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");
499 if (ptrInfo.child != u8) return typeError(options.diagnostics, .string, "array");
501500
502501 // Dynamic length string.
503502 if (ptrInfo.sentinel) |sentinel_ptr| {
......@@ -519,7 +518,7 @@ pub fn innerParse(
519518 }
520519 }
521520 },
522 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
521 else => |t| return typeError(options.diagnostics, t, "array"),
523522 }
524523 },
525524 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
......@@ -582,24 +581,21 @@ fn coerceToTokenType(token: anytype) TokenType {
582581 .end_of_document => .end_of_document,
583582 };
584583}
585fn typeError(allocator: Allocator, diagnostics: ?*Diagnostics, token: anytype, expected: []const u8) error{UnexpectedToken} {
584fn typeError(diagnostics: ?*Diagnostics, token: anytype, comptime expected: []const u8) error{UnexpectedToken} {
586585 if (diagnostics) |diag| {
587 if (std.fmt.allocPrint(allocator, "expected: {s}, found: {s}", .{
588 expected,
589 switch (coerceToTokenType(token)) {
590 .object_begin => "'{'",
591 .array_begin => "'['",
592 .true, .false => "bool",
593 .null => "null",
594 .number => "number",
595 .string => "string",
596 .object_end => unreachable, // type errors happen at the start of a value.
597 .array_end => unreachable, // type errors happen at the start of a value.
598 .end_of_document => unreachable, // type errors happen at the start of a value.
599 },
600 })) |s| {
601 diag.recordContext(allocator, s) catch {};
602 } else |_| {}
586 const prefix = "expected: " ++ expected ++ ", found: ";
587 const s = switch (coerceToTokenType(token)) {
588 .object_begin => prefix ++ "'{'",
589 .array_begin => prefix ++ "'['",
590 .true, .false => prefix ++ "bool",
591 .null => prefix ++ "null",
592 .number => prefix ++ "number",
593 .string => prefix ++ "string",
594 .object_end => unreachable, // type errors happen at the start of a value.
595 .array_end => unreachable, // type errors happen at the start of a value.
596 .end_of_document => unreachable, // type errors happen at the start of a value.
597 };
598 diag.recordContext(s);
603599 }
604600 return error.UnexpectedToken;
605601}