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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user