authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2019-04-27 02:36:48-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-04-27 11:20:10-04:00
log205e501e42b9a2e1d35afa0873311165100863ae
tree99c684ac41ade859aa63a3e76c0b18f4f0b14e9e
parentbc1840e18f5456e3f9cc1f9c62e932e90ebb0d36

std.fmt: add max_depth to avoid infinite recursion from self-references


1 files changed, 96 insertions(+), 12 deletions(-)

std/fmt.zig+96-12
......@@ -8,6 +8,8 @@ const builtin = @import("builtin");
88const errol = @import("fmt/errol.zig");
99const lossyCast = std.math.lossyCast;
1010
11pub const default_max_depth = 3;
12
1113/// Renders fmt string with args, calling output with slices of bytes.
1214/// If `output` returns an error, the error is returned from `format` and
1315/// `output` is not called again.
......@@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
4951 start_index = i;
5052 },
5153 '}' => {
52 try formatType(args[next_arg], fmt[0..0], context, Errors, output);
54 try formatType(args[next_arg], fmt[0..0], context, Errors, output, default_max_depth);
5355 next_arg += 1;
5456 state = State.Start;
5557 start_index = i + 1;
......@@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
6971 State.FormatString => switch (c) {
7072 '}' => {
7173 const s = start_index + 1;
72 try formatType(args[next_arg], fmt[s..i], context, Errors, output);
74 try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth);
7375 next_arg += 1;
7476 state = State.Start;
7577 start_index = i + 1;
......@@ -108,6 +110,7 @@ pub fn formatType(
108110 context: var,
109111 comptime Errors: type,
110112 output: fn (@typeOf(context), []const u8) Errors!void,
113 max_depth: usize,
111114) Errors!void {
112115 const T = @typeOf(value);
113116 switch (@typeInfo(T)) {
......@@ -122,16 +125,16 @@ pub fn formatType(
122125 },
123126 builtin.TypeId.Optional => {
124127 if (value) |payload| {
125 return formatType(payload, fmt, context, Errors, output);
128 return formatType(payload, fmt, context, Errors, output, max_depth);
126129 } else {
127130 return output(context, "null");
128131 }
129132 },
130133 builtin.TypeId.ErrorUnion => {
131134 if (value) |payload| {
132 return formatType(payload, fmt, context, Errors, output);
135 return formatType(payload, fmt, context, Errors, output, max_depth);
133136 } else |err| {
134 return formatType(err, fmt, context, Errors, output);
137 return formatType(err, fmt, context, Errors, output, max_depth);
135138 }
136139 },
137140 builtin.TypeId.ErrorSet => {
......@@ -164,10 +167,13 @@ pub fn formatType(
164167 switch (comptime @typeId(T)) {
165168 builtin.TypeId.Enum => {
166169 try output(context, ".");
167 try formatType(@tagName(value), "", context, Errors, output);
170 try formatType(@tagName(value), "", context, Errors, output, max_depth);
168171 return;
169172 },
170173 builtin.TypeId.Struct => {
174 if (max_depth == 0) {
175 return output(context, "{ ... }");
176 }
171177 comptime var field_i = 0;
172178 inline while (field_i < @memberCount(T)) : (field_i += 1) {
173179 if (field_i == 0) {
......@@ -177,11 +183,14 @@ pub fn formatType(
177183 }
178184 try output(context, @memberName(T, field_i));
179185 try output(context, " = ");
180 try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output);
186 try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth-1);
181187 }
182188 try output(context, " }");
183189 },
184190 builtin.TypeId.Union => {
191 if (max_depth == 0) {
192 return output(context, "{ ... }");
193 }
185194 const info = @typeInfo(T).Union;
186195 if (info.tag_type) |UnionTagType| {
187196 try output(context, "{ .");
......@@ -189,7 +198,7 @@ pub fn formatType(
189198 try output(context, " = ");
190199 inline for (info.fields) |u_field| {
191200 if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) {
192 try formatType(@field(value, u_field.name), "", context, Errors, output);
201 try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth-1);
193202 }
194203 }
195204 try output(context, " }");
......@@ -210,7 +219,7 @@ pub fn formatType(
210219 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
211220 },
212221 builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => {
213 return formatType(value.*, fmt, context, Errors, output);
222 return formatType(value.*, fmt, context, Errors, output, max_depth);
214223 },
215224 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)),
216225 },
......@@ -986,17 +995,17 @@ test "fmt.format" {
986995 {
987996 var buf1: [32]u8 = undefined;
988997 var context = BufPrintContext{ .remaining = buf1[0..] };
989 try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite);
998 try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
990999 var res = buf1[0 .. buf1.len - context.remaining.len];
9911000 testing.expect(mem.eql(u8, res, "1234"));
9921001
9931002 context = BufPrintContext{ .remaining = buf1[0..] };
994 try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite);
1003 try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
9951004 res = buf1[0 .. buf1.len - context.remaining.len];
9961005 testing.expect(mem.eql(u8, res, "a"));
9971006
9981007 context = BufPrintContext{ .remaining = buf1[0..] };
999 try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite);
1008 try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
10001009 res = buf1[0 .. buf1.len - context.remaining.len];
10011010 testing.expect(mem.eql(u8, res, "1100"));
10021011 }
......@@ -1364,6 +1373,20 @@ test "fmt.format" {
13641373
13651374 try testFmt("E.Two", "{}", inst);
13661375 }
1376 //self-referential struct format
1377 {
1378 const S = struct {
1379 const SelfType = @This();
1380 a: ?*SelfType,
1381 };
1382
1383 var inst = S{
1384 .a = null,
1385 };
1386 inst.a = &inst;
1387
1388 try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst);
1389 }
13671390 //print bytes as hex
13681391 {
13691392 const some_bytes = "\xCA\xFE\xBA\xBE";
......@@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" {
14491472 try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append);
14501473 assert(mem.eql(u8, buf.toSlice(), "123456789123456789"));
14511474}
1475
1476test "fmt.formatType max_depth" {
1477 const Vec2 = struct {
1478 const SelfType = @This();
1479 x: f32,
1480 y: f32,
1481
1482 pub fn format(
1483 self: SelfType,
1484 comptime fmt: []const u8,
1485 context: var,
1486 comptime Errors: type,
1487 output: fn (@typeOf(context), []const u8) Errors!void,
1488 ) Errors!void {
1489 return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y);
1490 }
1491 };
1492 const E = enum {
1493 One,
1494 Two,
1495 Three,
1496 };
1497 const TU = union(enum) {
1498 const SelfType = @This();
1499 float: f32,
1500 int: u32,
1501 ptr: ?*SelfType,
1502 };
1503 const S = struct {
1504 const SelfType = @This();
1505 a: ?*SelfType,
1506 tu: TU,
1507 e: E,
1508 vec: Vec2,
1509 };
1510
1511 var inst = S{
1512 .a = null,
1513 .tu = TU{ .ptr = null },
1514 .e = E.Two,
1515 .vec = Vec2{ .x = 10.2, .y = 2.22 },
1516 };
1517 inst.a = &inst;
1518 inst.tu.ptr = &inst.tu;
1519
1520 var buf0 = try std.Buffer.init(std.debug.global_allocator, "");
1521 try formatType(inst, "", &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0);
1522 assert(mem.eql(u8, buf0.toSlice(), "S{ ... }"));
1523
1524 var buf1 = try std.Buffer.init(std.debug.global_allocator, "");
1525 try formatType(inst, "", &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1);
1526 assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }"));
1527
1528 var buf2 = try std.Buffer.init(std.debug.global_allocator, "");
1529 try formatType(inst, "", &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2);
1530 assert(mem.eql(u8, buf2.toSlice(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }"));
1531
1532 var buf3 = try std.Buffer.init(std.debug.global_allocator, "");
1533 try formatType(inst, "", &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3);
1534 assert(mem.eql(u8, buf3.toSlice(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }"));
1535}