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");...@@ -8,6 +8,8 @@ const builtin = @import("builtin");
8const errol = @import("fmt/errol.zig");8const errol = @import("fmt/errol.zig");
9const lossyCast = std.math.lossyCast;9const lossyCast = std.math.lossyCast;
1010
11pub const default_max_depth = 3;
12
11/// Renders fmt string with args, calling output with slices of bytes.13/// Renders fmt string with args, calling output with slices of bytes.
12/// If `output` returns an error, the error is returned from `format` and14/// If `output` returns an error, the error is returned from `format` and
13/// `output` is not called again.15/// `output` is not called again.
...@@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),...@@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
49 start_index = i;51 start_index = i;
50 },52 },
51 '}' => {53 '}' => {
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);
53 next_arg += 1;55 next_arg += 1;
54 state = State.Start;56 state = State.Start;
55 start_index = i + 1;57 start_index = i + 1;
...@@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),...@@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
69 State.FormatString => switch (c) {71 State.FormatString => switch (c) {
70 '}' => {72 '}' => {
71 const s = start_index + 1;73 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);
73 next_arg += 1;75 next_arg += 1;
74 state = State.Start;76 state = State.Start;
75 start_index = i + 1;77 start_index = i + 1;
...@@ -108,6 +110,7 @@ pub fn formatType(...@@ -108,6 +110,7 @@ pub fn formatType(
108 context: var,110 context: var,
109 comptime Errors: type,111 comptime Errors: type,
110 output: fn (@typeOf(context), []const u8) Errors!void,112 output: fn (@typeOf(context), []const u8) Errors!void,
113 max_depth: usize,
111) Errors!void {114) Errors!void {
112 const T = @typeOf(value);115 const T = @typeOf(value);
113 switch (@typeInfo(T)) {116 switch (@typeInfo(T)) {
...@@ -122,16 +125,16 @@ pub fn formatType(...@@ -122,16 +125,16 @@ pub fn formatType(
122 },125 },
123 builtin.TypeId.Optional => {126 builtin.TypeId.Optional => {
124 if (value) |payload| {127 if (value) |payload| {
125 return formatType(payload, fmt, context, Errors, output);128 return formatType(payload, fmt, context, Errors, output, max_depth);
126 } else {129 } else {
127 return output(context, "null");130 return output(context, "null");
128 }131 }
129 },132 },
130 builtin.TypeId.ErrorUnion => {133 builtin.TypeId.ErrorUnion => {
131 if (value) |payload| {134 if (value) |payload| {
132 return formatType(payload, fmt, context, Errors, output);135 return formatType(payload, fmt, context, Errors, output, max_depth);
133 } else |err| {136 } else |err| {
134 return formatType(err, fmt, context, Errors, output);137 return formatType(err, fmt, context, Errors, output, max_depth);
135 }138 }
136 },139 },
137 builtin.TypeId.ErrorSet => {140 builtin.TypeId.ErrorSet => {
...@@ -164,10 +167,13 @@ pub fn formatType(...@@ -164,10 +167,13 @@ pub fn formatType(
164 switch (comptime @typeId(T)) {167 switch (comptime @typeId(T)) {
165 builtin.TypeId.Enum => {168 builtin.TypeId.Enum => {
166 try output(context, ".");169 try output(context, ".");
167 try formatType(@tagName(value), "", context, Errors, output);170 try formatType(@tagName(value), "", context, Errors, output, max_depth);
168 return;171 return;
169 },172 },
170 builtin.TypeId.Struct => {173 builtin.TypeId.Struct => {
174 if (max_depth == 0) {
175 return output(context, "{ ... }");
176 }
171 comptime var field_i = 0;177 comptime var field_i = 0;
172 inline while (field_i < @memberCount(T)) : (field_i += 1) {178 inline while (field_i < @memberCount(T)) : (field_i += 1) {
173 if (field_i == 0) {179 if (field_i == 0) {
...@@ -177,11 +183,14 @@ pub fn formatType(...@@ -177,11 +183,14 @@ pub fn formatType(
177 }183 }
178 try output(context, @memberName(T, field_i));184 try output(context, @memberName(T, field_i));
179 try output(context, " = ");185 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);
181 }187 }
182 try output(context, " }");188 try output(context, " }");
183 },189 },
184 builtin.TypeId.Union => {190 builtin.TypeId.Union => {
191 if (max_depth == 0) {
192 return output(context, "{ ... }");
193 }
185 const info = @typeInfo(T).Union;194 const info = @typeInfo(T).Union;
186 if (info.tag_type) |UnionTagType| {195 if (info.tag_type) |UnionTagType| {
187 try output(context, "{ .");196 try output(context, "{ .");
...@@ -189,7 +198,7 @@ pub fn formatType(...@@ -189,7 +198,7 @@ pub fn formatType(
189 try output(context, " = ");198 try output(context, " = ");
190 inline for (info.fields) |u_field| {199 inline for (info.fields) |u_field| {
191 if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) {200 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);
193 }202 }
194 }203 }
195 try output(context, " }");204 try output(context, " }");
...@@ -210,7 +219,7 @@ pub fn formatType(...@@ -210,7 +219,7 @@ pub fn formatType(
210 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));219 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
211 },220 },
212 builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => {221 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);
214 },223 },
215 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)),224 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)),
216 },225 },
...@@ -986,17 +995,17 @@ test "fmt.format" {...@@ -986,17 +995,17 @@ test "fmt.format" {
986 {995 {
987 var buf1: [32]u8 = undefined;996 var buf1: [32]u8 = undefined;
988 var context = BufPrintContext{ .remaining = buf1[0..] };997 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);
990 var res = buf1[0 .. buf1.len - context.remaining.len];999 var res = buf1[0 .. buf1.len - context.remaining.len];
991 testing.expect(mem.eql(u8, res, "1234"));1000 testing.expect(mem.eql(u8, res, "1234"));
9921001
993 context = BufPrintContext{ .remaining = buf1[0..] };1002 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);
995 res = buf1[0 .. buf1.len - context.remaining.len];1004 res = buf1[0 .. buf1.len - context.remaining.len];
996 testing.expect(mem.eql(u8, res, "a"));1005 testing.expect(mem.eql(u8, res, "a"));
9971006
998 context = BufPrintContext{ .remaining = buf1[0..] };1007 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);
1000 res = buf1[0 .. buf1.len - context.remaining.len];1009 res = buf1[0 .. buf1.len - context.remaining.len];
1001 testing.expect(mem.eql(u8, res, "1100"));1010 testing.expect(mem.eql(u8, res, "1100"));
1002 }1011 }
...@@ -1364,6 +1373,20 @@ test "fmt.format" {...@@ -1364,6 +1373,20 @@ test "fmt.format" {
13641373
1365 try testFmt("E.Two", "{}", inst);1374 try testFmt("E.Two", "{}", inst);
1366 }1375 }
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 }
1367 //print bytes as hex1390 //print bytes as hex
1368 {1391 {
1369 const some_bytes = "\xCA\xFE\xBA\xBE";1392 const some_bytes = "\xCA\xFE\xBA\xBE";
...@@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" {...@@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" {
1449 try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append);1472 try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append);
1450 assert(mem.eql(u8, buf.toSlice(), "123456789123456789"));1473 assert(mem.eql(u8, buf.toSlice(), "123456789123456789"));
1451}1474}
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}