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;...@@ -35,6 +35,7 @@ const ArrayList = std.ArrayList;
35const ArrayListUnmanaged = std.ArrayListUnmanaged;35const ArrayListUnmanaged = std.ArrayListUnmanaged;
36const assert = std.debug.assert;36const assert = std.debug.assert;
37const BitStack = std.BitStack;37const BitStack = std.BitStack;
38const BoundedArray = std.BoundedArray;
3839
39/// Scan the input and check for malformed JSON.40/// Scan the input and check for malformed JSON.
40/// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`.41/// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`.
...@@ -204,7 +205,7 @@ pub const Diagnostics = struct {...@@ -204,7 +205,7 @@ pub const Diagnostics = struct {
204 current_input: []const u8 = undefined,205 current_input: []const u8 = undefined,
205206
206 // updated by recordContext().207 // updated by recordContext().
207 context_stack: ArrayListUnmanaged([]const u8) = .{},208 context_stack: BoundedArray([]const u8, 8) = .{},
208209
209 /// Starts at 1.210 /// Starts at 1.
210 pub fn getLine(self: *const @This()) u64 {211 pub fn getLine(self: *const @This()) u64 {
...@@ -219,15 +220,22 @@ pub const Diagnostics = struct {...@@ -219,15 +220,22 @@ pub const Diagnostics = struct {
219 return self.total_bytes_before_current_input + self.cursor_in_current_input;220 return self.total_bytes_before_current_input + self.cursor_in_current_input;
220 }221 }
221222
222 pub fn recordContext(self: *@This(), allocator: Allocator, context: []const u8) Allocator.Error!void {223 /// Attemps to push a human-readable string onto the context stack.
223 return self.context_stack.append(allocator, context);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 {};
224 }227 }
225228
226 /// Pretty-print diagnostic information to the given writer, such as `std.io.getStdErr().writer()`.229 /// 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;
228 /// it is purely aesthetic and is not touched on any actual file system.231 /// 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 {232 pub fn dump(self: *const @This(), writer: anytype, err: anyerror, displayed_file_name: ?[]const u8) !void {
230 try writer.print("{s}:{}:{}: {s}\n", .{ file_name orelse "<json>", self.getLine(), self.getColumn(), @errorName(err) });233 try writer.print("{s}:{}:{}: {s}\n", .{
234 displayed_file_name orelse "<json>",
235 self.getLine(),
236 self.getColumn(),
237 @errorName(err),
238 });
231239
232 // Show a "line" of context, or in case of very long lines, just an excerpt of the line.240 // Show a "line" of context, or in case of very long lines, just an excerpt of the line.
233 // (Very long lines are common in minified JSON such as in an HTTP API or other machine-to-machine contexts.)241 // (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 {...@@ -262,18 +270,12 @@ pub const Diagnostics = struct {
262 try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);270 try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);
263 try writer.writeAll("^\n");271 try writer.writeAll("^\n");
264272
265 for (self.context_stack.items) |item| {273 for (self.context_stack.slice()) |item| {
266 try writer.print(" in {s}\n", .{item});274 try writer.print(" in {s}\n", .{item});
267 }275 }
268 }276 }
269};277};
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
277/// See the documentation for `std.json.Token`.279/// See the documentation for `std.json.Token`.
278pub const AllocWhen = enum { alloc_if_needed, alloc_always };280pub 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;...@@ -11,7 +11,6 @@ const AllocWhen = @import("./scanner.zig").AllocWhen;
11const Diagnostics = @import("./scanner.zig").Diagnostics;11const Diagnostics = @import("./scanner.zig").Diagnostics;
12const default_max_value_len = @import("./scanner.zig").default_max_value_len;12const default_max_value_len = @import("./scanner.zig").default_max_value_len;
13const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;13const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
14const maybeRecordDiagnosticContext = @import("./scanner.zig").maybeRecordDiagnosticContext;
1514
16const Value = @import("./dynamic.zig").Value;15const Value = @import("./dynamic.zig").Value;
17const Array = @import("./dynamic.zig").Array;16const Array = @import("./dynamic.zig").Array;
...@@ -220,13 +219,13 @@ pub fn innerParse(...@@ -220,13 +219,13 @@ pub fn innerParse(
220 options: ParseOptions,219 options: ParseOptions,
221) ParseError(@TypeOf(source.*))!T {220) ParseError(@TypeOf(source.*))!T {
222 errdefer source.saveDiagnostics();221 errdefer source.saveDiagnostics();
223 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T));222 errdefer if (options.diagnostics) |diag| diag.recordContext(@typeName(T));
224 switch (@typeInfo(T)) {223 switch (@typeInfo(T)) {
225 .Bool => {224 .Bool => {
226 return switch (try source.next()) {225 return switch (try source.next()) {
227 .true => true,226 .true => true,
228 .false => false,227 .false => false,
229 else => |t| return typeError(allocator, options.diagnostics, t, "bool"),228 else => |t| return typeError(options.diagnostics, t, "bool"),
230 };229 };
231 },230 },
232 .Float, .ComptimeFloat => {231 .Float, .ComptimeFloat => {
...@@ -234,7 +233,7 @@ pub fn innerParse(...@@ -234,7 +233,7 @@ pub fn innerParse(
234 defer freeAllocated(allocator, token);233 defer freeAllocated(allocator, token);
235 const slice = switch (token) {234 const slice = switch (token) {
236 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,235 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"),
238 };237 };
239 return try std.fmt.parseFloat(T, slice);238 return try std.fmt.parseFloat(T, slice);
240 },239 },
...@@ -243,7 +242,7 @@ pub fn innerParse(...@@ -243,7 +242,7 @@ pub fn innerParse(
243 defer freeAllocated(allocator, token);242 defer freeAllocated(allocator, token);
244 const slice = switch (token) {243 const slice = switch (token) {
245 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,244 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"),
247 };246 };
248 return sliceToInt(T, slice);247 return sliceToInt(T, slice);
249 },248 },
...@@ -267,7 +266,7 @@ pub fn innerParse(...@@ -267,7 +266,7 @@ pub fn innerParse(
267 defer freeAllocated(allocator, token);266 defer freeAllocated(allocator, token);
268 const slice = switch (token) {267 const slice = switch (token) {
269 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,268 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)"),
271 };270 };
272 return sliceToEnum(T, slice);271 return sliceToEnum(T, slice);
273 },272 },
...@@ -280,7 +279,7 @@ pub fn innerParse(...@@ -280,7 +279,7 @@ pub fn innerParse(
280279
281 switch (try source.next()) {280 switch (try source.next()) {
282 .object_begin => {},281 .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)"),
284 }283 }
285284
286 var result: ?T = null;285 var result: ?T = null;
...@@ -300,7 +299,7 @@ pub fn innerParse(...@@ -300,7 +299,7 @@ pub fn innerParse(
300 // void isn't really a json type, but we can support void payload union tags with {} as a value.299 // void isn't really a json type, but we can support void payload union tags with {} as a value.
301 switch (try source.next()) {300 switch (try source.next()) {
302 .object_begin => {},301 .object_begin => {},
303 else => |t| return typeError(allocator, options.diagnostics, t, "void payload ('{}')"),302 else => |t| return typeError(options.diagnostics, t, "void payload ('{}')"),
304 }303 }
305 if (.object_end != try source.next()) return error.UnknownField;304 if (.object_end != try source.next()) return error.UnknownField;
306 result = @unionInit(T, u_field.name, {});305 result = @unionInit(T, u_field.name, {});
...@@ -324,7 +323,7 @@ pub fn innerParse(...@@ -324,7 +323,7 @@ pub fn innerParse(
324 if (structInfo.is_tuple) {323 if (structInfo.is_tuple) {
325 switch (try source.next()) {324 switch (try source.next()) {
326 .array_begin => {},325 .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)"),
328 }327 }
329328
330 var r: T = undefined;329 var r: T = undefined;
...@@ -344,7 +343,7 @@ pub fn innerParse(...@@ -344,7 +343,7 @@ pub fn innerParse(
344343
345 switch (try source.next()) {344 switch (try source.next()) {
346 .object_begin => {},345 .object_begin => {},
347 else => |t| return typeError(allocator, options.diagnostics, t, "struct ('{...}')"),346 else => |t| return typeError(options.diagnostics, t, "struct ('{...}')"),
348 }347 }
349348
350 var r: T = undefined;349 var r: T = undefined;
...@@ -366,7 +365,7 @@ pub fn innerParse(...@@ -366,7 +365,7 @@ pub fn innerParse(
366 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.365 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
367 // (Recursing into innerParse() might trigger more allocations.)366 // (Recursing into innerParse() might trigger more allocations.)
368 freeAllocated(allocator, name_token.?);367 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);
370 name_token = null;369 name_token = null;
371 if (fields_seen[i]) {370 if (fields_seen[i]) {
372 switch (options.duplicate_field_behavior) {371 switch (options.duplicate_field_behavior) {
...@@ -405,7 +404,7 @@ pub fn innerParse(...@@ -405,7 +404,7 @@ pub fn innerParse(
405 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);404 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
406 },405 },
407 .string => {406 .string => {
408 if (arrayInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");407 if (arrayInfo.child != u8) return typeError(options.diagnostics, .string, "array");
409 // Fixed-length string.408 // Fixed-length string.
410409
411 var r: T = undefined;410 var r: T = undefined;
...@@ -449,7 +448,7 @@ pub fn innerParse(...@@ -449,7 +448,7 @@ pub fn innerParse(
449 return r;448 return r;
450 },449 },
451450
452 else => |t| return typeError(allocator, options.diagnostics, t, "array"),451 else => |t| return typeError(options.diagnostics, t, "array"),
453 }452 }
454 },453 },
455454
...@@ -458,7 +457,7 @@ pub fn innerParse(...@@ -458,7 +457,7 @@ pub fn innerParse(
458 .array_begin => {457 .array_begin => {
459 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);458 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
460 },459 },
461 else => |t| return typeError(allocator, options.diagnostics, t, "array"),460 else => |t| return typeError(options.diagnostics, t, "array"),
462 }461 }
463 },462 },
464463
...@@ -497,7 +496,7 @@ pub fn innerParse(...@@ -497,7 +496,7 @@ pub fn innerParse(
497 return try arraylist.toOwnedSlice();496 return try arraylist.toOwnedSlice();
498 },497 },
499 .string => {498 .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
502 // Dynamic length string.501 // Dynamic length string.
503 if (ptrInfo.sentinel) |sentinel_ptr| {502 if (ptrInfo.sentinel) |sentinel_ptr| {
...@@ -519,7 +518,7 @@ pub fn innerParse(...@@ -519,7 +518,7 @@ pub fn innerParse(
519 }518 }
520 }519 }
521 },520 },
522 else => |t| return typeError(allocator, options.diagnostics, t, "array"),521 else => |t| return typeError(options.diagnostics, t, "array"),
523 }522 }
524 },523 },
525 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),524 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
...@@ -582,24 +581,21 @@ fn coerceToTokenType(token: anytype) TokenType {...@@ -582,24 +581,21 @@ fn coerceToTokenType(token: anytype) TokenType {
582 .end_of_document => .end_of_document,581 .end_of_document => .end_of_document,
583 };582 };
584}583}
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} {
586 if (diagnostics) |diag| {585 if (diagnostics) |diag| {
587 if (std.fmt.allocPrint(allocator, "expected: {s}, found: {s}", .{586 const prefix = "expected: " ++ expected ++ ", found: ";
588 expected,587 const s = switch (coerceToTokenType(token)) {
589 switch (coerceToTokenType(token)) {588 .object_begin => prefix ++ "'{'",
590 .object_begin => "'{'",589 .array_begin => prefix ++ "'['",
591 .array_begin => "'['",590 .true, .false => prefix ++ "bool",
592 .true, .false => "bool",591 .null => prefix ++ "null",
593 .null => "null",592 .number => prefix ++ "number",
594 .number => "number",593 .string => prefix ++ "string",
595 .string => "string",594 .object_end => unreachable, // type errors happen at the start of a value.
596 .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.
597 .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.
598 .end_of_document => unreachable, // type errors happen at the start of a value.597 };
599 },598 diag.recordContext(s);
600 })) |s| {
601 diag.recordContext(allocator, s) catch {};
602 } else |_| {}
603 }599 }
604 return error.UnexpectedToken;600 return error.UnexpectedToken;
605}601}