authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-10 19:34:27-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-11 02:01:32-04:00
log20510d209be44590f390c370f9e477d84ab46454
treed8d9cae0a71467a27d54292b1cfbb4aabfd094b4
parent900a897e90eca08f78ef632fd11e01ec9bcb3674

GeneralPurposeAllocator: use std.log instead of std.debug.print

`std.builtin.StackTrace` gains a `format` function. GeneralPurposeAllocator uses `std.log.err` instead of directly printing to stderr. Some errors are recoverable. The test runner is modified to fail the test run if any log messages of "err" or worse severity are encountered. self-hosted is modified to always print log messages of "err" severity or worse even if they have not been explicitly enabled. This makes GeneralPurposeAllocator available on the freestanding target.

4 files changed, 84 insertions(+), 37 deletions(-)

lib/std/builtin.zig+19
...@@ -52,6 +52,25 @@ pub const subsystem: ?SubSystem = blk: {...@@ -52,6 +52,25 @@ pub const subsystem: ?SubSystem = blk: {
52pub const StackTrace = struct {52pub const StackTrace = struct {
53 index: usize,53 index: usize,
54 instruction_addresses: []usize,54 instruction_addresses: []usize,
55
56 pub fn format(
57 self: StackTrace,
58 comptime fmt: []const u8,
59 options: std.fmt.FormatOptions,
60 writer: anytype,
61 ) !void {
62 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
63 defer arena.deinit();
64 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
65 return writer.print("\nUnable to print stack trace: Unable to open debug info: {}\n", .{@errorName(err)});
66 };
67 const tty_config = std.debug.detectTTYConfig();
68 try writer.writeAll("\n");
69 std.debug.writeStackTrace(self, writer, &arena.allocator, debug_info, tty_config) catch |err| {
70 try writer.print("Unable to print stack trace: {}\n", .{@errorName(err)});
71 };
72 try writer.writeAll("\n");
73 }
55};74};
5675
57/// This data structure is used by the Zig language code generation and76/// This data structure is used by the Zig language code generation and
lib/std/heap/general_purpose_allocator.zig+42-26
...@@ -4,12 +4,12 @@...@@ -4,12 +4,12 @@
4//!4//!
5//! ### `OptimizationMode.debug` and `OptimizationMode.release_safe`:5//! ### `OptimizationMode.debug` and `OptimizationMode.release_safe`:
6//!6//!
7//! * Detect double free, and print stack trace of:7//! * Detect double free, and emit stack trace of:
8//! - Where it was first allocated8//! - Where it was first allocated
9//! - Where it was freed the first time9//! - Where it was freed the first time
10//! - Where it was freed the second time10//! - Where it was freed the second time
11//!11//!
12//! * Detect leaks and print stack trace of:12//! * Detect leaks and emit stack trace of:
13//! - Where it was allocated13//! - Where it was allocated
14//!14//!
15//! * When a page of memory is no longer needed, give it back to resident memory15//! * When a page of memory is no longer needed, give it back to resident memory
...@@ -178,15 +178,18 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -178,15 +178,18 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
178 stack_addresses: [stack_n]usize,178 stack_addresses: [stack_n]usize,
179179
180 fn dumpStackTrace(self: *LargeAlloc) void {180 fn dumpStackTrace(self: *LargeAlloc) void {
181 std.debug.dumpStackTrace(self.getStackTrace());
182 }
183
184 fn getStackTrace(self: *LargeAlloc) std.builtin.StackTrace {
181 var len: usize = 0;185 var len: usize = 0;
182 while (len < stack_n and self.stack_addresses[len] != 0) {186 while (len < stack_n and self.stack_addresses[len] != 0) {
183 len += 1;187 len += 1;
184 }188 }
185 const stack_trace = StackTrace{189 return .{
186 .instruction_addresses = &self.stack_addresses,190 .instruction_addresses = &self.stack_addresses,
187 .index = len,191 .index = len,
188 };192 };
189 std.debug.dumpStackTrace(stack_trace);
190 }193 }
191 };194 };
192 const LargeAllocTable = std.AutoHashMapUnmanaged(usize, LargeAlloc);195 const LargeAllocTable = std.AutoHashMapUnmanaged(usize, LargeAlloc);
...@@ -282,15 +285,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -282,15 +285,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
282 while (true) : (bit_index += 1) {285 while (true) : (bit_index += 1) {
283 const is_used = @truncate(u1, used_byte >> bit_index) != 0;286 const is_used = @truncate(u1, used_byte >> bit_index) != 0;
284 if (is_used) {287 if (is_used) {
285 std.debug.print("\nMemory leak detected:\n", .{});
286 const slot_index = @intCast(SlotIndex, used_bits_byte * 8 + bit_index);288 const slot_index = @intCast(SlotIndex, used_bits_byte * 8 + bit_index);
287 const stack_trace = bucketStackTrace(289 const stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc);
288 bucket,290 std.log.err(.std, "Memory leak detected: {}", .{stack_trace});
289 size_class,
290 slot_index,
291 .alloc,
292 );
293 std.debug.dumpStackTrace(stack_trace);
294 leaks = true;291 leaks = true;
295 }292 }
296 if (bit_index == math.maxInt(u3))293 if (bit_index == math.maxInt(u3))
...@@ -301,8 +298,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -301,8 +298,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
301 return leaks;298 return leaks;
302 }299 }
303300
304 /// Returns whether there were leaks.301 /// Emits log messages for leaks and then returns whether there were any leaks.
305 pub fn deinit(self: *Self) bool {302 pub fn detectLeaks(self: *Self) bool {
306 var leaks = false;303 var leaks = false;
307 for (self.buckets) |optional_bucket, bucket_i| {304 for (self.buckets) |optional_bucket, bucket_i| {
308 const first_bucket = optional_bucket orelse continue;305 const first_bucket = optional_bucket orelse continue;
...@@ -317,10 +314,14 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -317,10 +314,14 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
317 }314 }
318 }315 }
319 for (self.large_allocations.items()) |*large_alloc| {316 for (self.large_allocations.items()) |*large_alloc| {
320 std.debug.print("\nMemory leak detected (0x{x}):\n", .{@ptrToInt(large_alloc.value.bytes.ptr)});317 std.log.err(.std, "Memory leak detected: {}", .{large_alloc.value.getStackTrace()});
321 large_alloc.value.dumpStackTrace();
322 leaks = true;318 leaks = true;
323 }319 }
320 return leaks;
321 }
322
323 pub fn deinit(self: *Self) bool {
324 const leaks = if (config.safety) self.detectLeaks() else false;
324 self.large_allocations.deinit(self.backing_allocator);325 self.large_allocations.deinit(self.backing_allocator);
325 self.* = undefined;326 self.* = undefined;
326 return leaks;327 return leaks;
...@@ -442,13 +443,18 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -442,13 +443,18 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
442 };443 };
443444
444 if (config.safety and old_mem.len != entry.value.bytes.len) {445 if (config.safety and old_mem.len != entry.value.bytes.len) {
445 std.debug.print("\nAllocation size {} bytes does not match free size {}. Allocated here:\n", .{446 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
447 var free_stack_trace = StackTrace{
448 .instruction_addresses = &addresses,
449 .index = 0,
450 };
451 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
452 std.log.err(.std, "Allocation size {} bytes does not match free size {}. Allocation: {} Free: {}", .{
446 entry.value.bytes.len,453 entry.value.bytes.len,
447 old_mem.len,454 old_mem.len,
455 entry.value.getStackTrace(),
456 free_stack_trace,
448 });457 });
449 entry.value.dumpStackTrace();
450
451 @panic("\nFree here:");
452 }458 }
453459
454 const result_len = try self.backing_allocator.resizeFn(self.backing_allocator, old_mem, old_align, new_size, len_align, ret_addr);460 const result_len = try self.backing_allocator.resizeFn(self.backing_allocator, old_mem, old_align, new_size, len_align, ret_addr);
...@@ -518,14 +524,24 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -518,14 +524,24 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
518 const is_used = @truncate(u1, used_byte.* >> used_bit_index) != 0;524 const is_used = @truncate(u1, used_byte.* >> used_bit_index) != 0;
519 if (!is_used) {525 if (!is_used) {
520 if (config.safety) {526 if (config.safety) {
521 // print allocation stack trace
522 std.debug.print("\nDouble free detected, allocated here:\n", .{});
523 const alloc_stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc);527 const alloc_stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc);
524 std.debug.dumpStackTrace(alloc_stack_trace);
525 std.debug.print("\nFirst free here:\n", .{});
526 const free_stack_trace = bucketStackTrace(bucket, size_class, slot_index, .free);528 const free_stack_trace = bucketStackTrace(bucket, size_class, slot_index, .free);
527 std.debug.dumpStackTrace(free_stack_trace);529 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
528 @panic("\nSecond free here:");530 var second_free_stack_trace = StackTrace{
531 .instruction_addresses = &addresses,
532 .index = 0,
533 };
534 std.debug.captureStackTrace(ret_addr, &second_free_stack_trace);
535 std.log.err(.std, "Double free detected. Allocation: {} First free: {} Second free: {}", .{
536 alloc_stack_trace,
537 free_stack_trace,
538 second_free_stack_trace,
539 });
540 if (new_size == 0) {
541 // Recoverable.
542 return @as(usize, 0);
543 }
544 @panic("Unrecoverable double free");
529 } else {545 } else {
530 unreachable;546 unreachable;
531 }547 }
lib/std/special/test_runner.zig+11-1
...@@ -4,6 +4,8 @@ const builtin = @import("builtin");...@@ -4,6 +4,8 @@ const builtin = @import("builtin");
44
5pub const io_mode: io.Mode = builtin.test_io_mode;5pub const io_mode: io.Mode = builtin.test_io_mode;
66
7var log_err_count: usize = 0;
8
7pub fn main() anyerror!void {9pub fn main() anyerror!void {
8 const test_fn_list = builtin.test_functions;10 const test_fn_list = builtin.test_functions;
9 var ok_count: usize = 0;11 var ok_count: usize = 0;
...@@ -75,8 +77,13 @@ pub fn main() anyerror!void {...@@ -75,8 +77,13 @@ pub fn main() anyerror!void {
75 } else {77 } else {
76 std.debug.print("{} passed; {} skipped.\n", .{ ok_count, skip_count });78 std.debug.print("{} passed; {} skipped.\n", .{ ok_count, skip_count });
77 }79 }
80 if (log_err_count != 0) {
81 std.debug.print("{} errors were logged.\n", .{log_err_count});
82 }
78 if (leaks != 0) {83 if (leaks != 0) {
79 std.debug.print("{} tests leaked memory\n", .{ok_count});84 std.debug.print("{} tests leaked memory.\n", .{ok_count});
85 }
86 if (leaks != 0 or log_err_count != 0) {
80 std.process.exit(1);87 std.process.exit(1);
81 }88 }
82}89}
...@@ -87,6 +94,9 @@ pub fn log(...@@ -87,6 +94,9 @@ pub fn log(
87 comptime format: []const u8,94 comptime format: []const u8,
88 args: anytype,95 args: anytype,
89) void {96) void {
97 if (@enumToInt(message_level) <= @enumToInt(std.log.Level.err)) {
98 log_err_count += 1;
99 }
90 if (@enumToInt(message_level) <= @enumToInt(std.testing.log_level)) {100 if (@enumToInt(message_level) <= @enumToInt(std.testing.log_level)) {
91 std.debug.print("[{}] ({}): " ++ format, .{ @tagName(scope), @tagName(message_level) } ++ args);101 std.debug.print("[{}] ({}): " ++ format, .{ @tagName(scope), @tagName(message_level) } ++ args);
92 }102 }
src-self-hosted/main.zig+12-10
...@@ -42,17 +42,19 @@ pub fn log(...@@ -42,17 +42,19 @@ pub fn log(
42 comptime format: []const u8,42 comptime format: []const u8,
43 args: anytype,43 args: anytype,
44) void {44) void {
45 if (@enumToInt(level) > @enumToInt(std.log.level))45 // Hide anything more verbose than warn unless it was added with `-Dlog=foo`.
46 return;46 if (@enumToInt(level) > @enumToInt(std.log.level) or
4747 @enumToInt(level) > @enumToInt(std.log.Level.warn))
48 const scope_name = @tagName(scope);48 {
49 const ok = comptime for (build_options.log_scopes) |log_scope| {49 const scope_name = @tagName(scope);
50 if (mem.eql(u8, log_scope, scope_name))50 const ok = comptime for (build_options.log_scopes) |log_scope| {
51 break true;51 if (mem.eql(u8, log_scope, scope_name))
52 } else false;52 break true;
53 } else false;
5354
54 if (!ok)55 if (!ok)
55 return;56 return;
57 }
5658
57 const prefix = "[" ++ @tagName(level) ++ "] " ++ "(" ++ @tagName(scope) ++ "): ";59 const prefix = "[" ++ @tagName(level) ++ "] " ++ "(" ++ @tagName(scope) ++ "): ";
5860