Add Leak Detection to Ona Heap Allocator #15
|
@ -7,6 +7,7 @@ pub const AllocationError = error {
|
|||
};
|
||||
|
||||
pub const AllocationOptions = struct {
|
||||
return_address: usize,
|
||||
allocation: ?[]u8 = null,
|
||||
size: usize,
|
||||
};
|
||||
|
@ -181,9 +182,10 @@ pub fn allocate_many(allocator: Allocator, amount: usize, comptime Type: type) A
|
|||
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
|
||||
}
|
||||
|
||||
return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse {
|
||||
return error.OutOfMemory;
|
||||
}))[0 .. amount];
|
||||
return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{
|
||||
.size = @sizeOf(Type) * amount,
|
||||
.return_address = @returnAddress(),
|
||||
}) orelse return error.OutOfMemory))[0 .. amount];
|
||||
}
|
||||
|
||||
pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) {
|
||||
|
@ -193,9 +195,10 @@ pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@Type
|
|||
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
|
||||
}
|
||||
|
||||
const allocation = @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type)}) orelse {
|
||||
return error.OutOfMemory;
|
||||
}));
|
||||
const allocation = @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{
|
||||
.size = @sizeOf(Type),
|
||||
.return_address = @returnAddress(),
|
||||
}) orelse return error.OutOfMemory));
|
||||
|
||||
allocation.* = value;
|
||||
|
||||
|
@ -241,6 +244,7 @@ pub fn deallocate(allocator: Allocator, allocation: anytype) void {
|
|||
.Many, .C => @compileError("length of allocation must be known to deallocate"),
|
||||
},
|
||||
|
||||
.return_address = @returnAddress(),
|
||||
.size = 0,
|
||||
});
|
||||
},
|
||||
|
|
|
@ -4,17 +4,11 @@ const ext = @import("./ext.zig");
|
|||
|
||||
const std = @import("std");
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
const stack_trace_frame_size = if (std.debug.sys_can_stack_trace) 16 else 0;
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
const AllocationInfo = struct {
|
||||
stack_frames: [stack_trace_frame_size]usize,
|
||||
stack_trace: std.builtin.StackTrace,
|
||||
trace: std.debug.Trace,
|
||||
next_info: ?*AllocationInfo,
|
||||
size: usize,
|
||||
};
|
||||
|
@ -26,9 +20,16 @@ const Context = struct {
|
|||
allocation_info_head: ?*AllocationInfo = null,
|
||||
|
||||
///
|
||||
/// Attempts to allocate a buffer of `size` length from `self`, with `return_address` as the location of the
|
||||
/// allocation request origin.
|
||||
///
|
||||
/// A reference to the allocated buffer is returned via a slice if the allocation was successful, otherwise `null`
|
||||
/// is returned.
|
||||
///
|
||||
fn allocate(self: *Context, size: usize) ?[]u8 {
|
||||
/// *Note* the returned buffer must be deallocated with [deallocate] before program exit or it will cause a memory
|
||||
/// leak.
|
||||
///
|
||||
fn allocate(self: *Context, size: usize, return_address: usize) ?[]u8 {
|
||||
const allocation_info_size = @sizeOf(AllocationInfo);
|
||||
const total_allocation_size = allocation_info_size + size;
|
||||
const allocation = ext.SDL_malloc(total_allocation_size) orelse return null;
|
||||
|
@ -37,15 +38,10 @@ const Context = struct {
|
|||
allocation_info.* = .{
|
||||
.size = size,
|
||||
.next_info = self.allocation_info_head,
|
||||
.stack_frames = [_]usize{0} ** stack_trace_frame_size,
|
||||
|
||||
.stack_trace = .{
|
||||
.instruction_addresses = &allocation_info.stack_frames,
|
||||
.index = 0,
|
||||
},
|
||||
.trace = .{},
|
||||
};
|
||||
|
||||
std.debug.captureStackTrace(null, &allocation_info.stack_trace);
|
||||
allocation_info.trace.addAddr(return_address, "");
|
||||
|
||||
self.allocation_info_head = allocation_info;
|
||||
|
||||
|
@ -53,7 +49,10 @@ const Context = struct {
|
|||
}
|
||||
|
||||
///
|
||||
/// Deallocates a the allocation buffer referenced by `allocation`.
|
||||
///
|
||||
/// *Note* the pointer and length of `allocation` must match valid values known to `allocator` otherwise safety-
|
||||
/// checked behavior will occur.
|
||||
///
|
||||
fn deallocate(self: *Context, allocation: []u8) void {
|
||||
const target_allocation_info = @intToPtr(*AllocationInfo, @ptrToInt(allocation.ptr) - @sizeOf(AllocationInfo));
|
||||
|
@ -92,7 +91,19 @@ const Context = struct {
|
|||
}
|
||||
|
||||
///
|
||||
/// Attempts to reallocate the buffer referenced by `allocation` to be `size` length from `self`.
|
||||
///
|
||||
/// A reference to the reallocated buffer is returned via a slice if the reallocation was successful, otherwise
|
||||
/// `null` is returned.
|
||||
///
|
||||
/// *Note* the returned buffer must be deallocated with [deallocate] before program exit or it will cause a memory
|
||||
/// leak.
|
||||
///
|
||||
/// *Note* the pointer and length of `allocation` must match valid values known to `allocator` otherwise safety-
|
||||
/// checked behavior will occur.
|
||||
///
|
||||
/// *Note* the allocation referenced by `allocation` should be considered invalid once the function returns,
|
||||
/// discarding it in favor of the return value.
|
||||
///
|
||||
fn reallocate(self: *Context, allocation: []u8, size: usize) ?[]u8 {
|
||||
const allocation_info_size = @sizeOf(AllocationInfo);
|
||||
|
@ -133,7 +144,7 @@ const Context = struct {
|
|||
};
|
||||
|
||||
///
|
||||
///
|
||||
/// Heap context.
|
||||
///
|
||||
var context = Context{};
|
||||
|
||||
|
@ -149,28 +160,30 @@ pub const allocator = coral.io.Allocator.bind(Context, &context, struct {
|
|||
return null;
|
||||
}
|
||||
|
||||
return self.allocate(0);
|
||||
return self.allocate(0, options.return_address);
|
||||
}
|
||||
|
||||
if (options.allocation) |allocation| {
|
||||
return self.reallocate(allocation, options.size);
|
||||
}
|
||||
|
||||
return self.allocate(options.size);
|
||||
return self.allocate(options.size, options.return_address);
|
||||
}
|
||||
}.reallocate);
|
||||
|
||||
///
|
||||
/// Checks for any allocations belonging to the process heap allocated through the [allocator] interface that are still
|
||||
/// alive and reports the stack traces of any detected allocations to stderr along with the allocation address and
|
||||
/// length.
|
||||
///
|
||||
///
|
||||
pub fn trace_allocations() void {
|
||||
pub fn trace_leaks() void {
|
||||
var current_allocation_info = context.allocation_info_head;
|
||||
|
||||
while (current_allocation_info) |allocation_info| : (current_allocation_info = allocation_info.next_info) {
|
||||
std.debug.print("{d} byte leak at 0x{x} detected: {}", .{
|
||||
allocation_info.size,
|
||||
@ptrToInt(allocation_info) + @sizeOf(AllocationInfo),
|
||||
allocation_info.stack_trace
|
||||
allocation_info.trace
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ const AppManifest = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub fn run_app(_: file.System) void {
|
||||
defer heap.trace_allocations();
|
||||
pub fn run_app(base_file_system: file.System) void {
|
||||
defer heap.trace_leaks();
|
||||
|
||||
const Logger = struct {
|
||||
const Self = @This();
|
||||
|
@ -76,12 +76,12 @@ pub fn run_app(_: file.System) void {
|
|||
|
||||
defer script_environment.deinit();
|
||||
|
||||
// const app_file_name = "app.ona";
|
||||
const app_file_name = "app.ona";
|
||||
var app_manifest = AppManifest{};
|
||||
|
||||
// app_manifest.load_script(&script_environment, base_file_system, app_file_name) catch {
|
||||
// return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to load %s\n", app_file_name);
|
||||
// };
|
||||
app_manifest.load_script(&script_environment, base_file_system, app_file_name) catch {
|
||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to load %s\n", app_file_name);
|
||||
};
|
||||
|
||||
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
|
||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
|
||||
|
|
Loading…
Reference in New Issue