Skip to content

Commit

Permalink
Merge pull request #4605 from karl-zylinski/tracking-allocator-bad-fr…
Browse files Browse the repository at this point in the history
…ee-default-to-crash

Make tracking allocator default to crashing on a bad free instead of adding to bad_free_array
  • Loading branch information
gingerBill authored Jan 8, 2025
2 parents bc2e577 + e5f32e1 commit 2a29322
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 9 deletions.
50 changes: 41 additions & 9 deletions core/mem/tracking_allocator.odin
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@ Tracking_Allocator_Bad_Free_Entry :: struct {
location: runtime.Source_Code_Location,
}

/*
Callback type for when tracking allocator runs into a bad free.
*/
Tracking_Allocator_Bad_Free_Callback :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location)

/*
Tracking allocator data.
*/
Tracking_Allocator :: struct {
backing: Allocator,
allocation_map: map[rawptr]Tracking_Allocator_Entry,
bad_free_callback: Tracking_Allocator_Bad_Free_Callback,
bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
mutex: sync.Mutex,
clear_on_free_all: bool,
Expand All @@ -61,6 +67,7 @@ allocate the tracked data.
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
t.backing = backing_allocator
t.allocation_map.allocator = internals_allocator
t.bad_free_callback = tracking_allocator_bad_free_callback_panic
t.bad_free_array.allocator = internals_allocator
if .Free_All in query_features(t.backing) {
t.clear_on_free_all = true
Expand Down Expand Up @@ -109,15 +116,44 @@ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
sync.mutex_unlock(&t.mutex)
}

/*
Default behavior for a bad free: Crash with error message that says where the
bad free happened.
Override Tracking_Allocator.bad_free_callback to have something else happen. For
example, you can use tracking_allocator_bad_free_callback_add_to_array to return
the tracking allocator to the old behavior, where the bad_free_array was used.
*/
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
runtime.print_caller_location(location)
runtime.print_string(" Tracking allocator error: Bad free of pointer ")
runtime.print_uintptr(uintptr(memory))
runtime.print_string("\n")
runtime.trap()
}

/*
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
then you must make sure to check Tracking_Allocator.bad_free_array at some point.
*/
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
memory = memory,
location = location,
})
}

/*
Tracking allocator.
The tracking allocator is an allocator wrapper that tracks memory allocations.
This allocator stores all the allocations in a map. Whenever a pointer that's
not inside of the map is freed, the `bad_free_array` entry is added.
An example of how to use the `Tracking_Allocator` to track subsequent allocations
in your program and report leaks and bad frees:
Here follows an example of how to use the `Tracking_Allocator` to track
subsequent allocations in your program and report leaks. By default, the
tracking allocator will crash on bad frees. You can override that behavior by
overriding `track.bad_free_callback`.
Example:
Expand All @@ -137,9 +173,6 @@ Example:
for _, leak in track.allocation_map {
fmt.printf("%v leaked %m\n", leak.location, leak.size)
}
for bad_free in track.bad_free_array {
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
}
}
*/
@(require_results)
Expand Down Expand Up @@ -191,10 +224,9 @@ tracking_allocator_proc :: proc(
}

if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map {
append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{
memory = old_memory,
location = loc,
})
if data.bad_free_callback != nil {
data.bad_free_callback(data, old_memory, loc)
}
} else {
result = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc) or_return
}
Expand Down
1 change: 1 addition & 0 deletions core/testing/runner.odin
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error)
when TRACKING_MEMORY {
mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i]))
task_memory_trackers[i].bad_free_callback = mem.tracking_allocator_bad_free_callback_add_to_array
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/core/container/test_core_rbtree.odin
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "core:log"
test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
track.bad_free_callback = mem.tracking_allocator_bad_free_callback_add_to_array
defer mem.tracking_allocator_destroy(&track)
context.allocator = mem.tracking_allocator(&track)

Expand Down

0 comments on commit 2a29322

Please sign in to comment.