Add asan support for various allocators

This commit is contained in:
Lucas Perlind
2025-05-06 14:55:50 +10:00
parent 8032db3484
commit 83bc2d3c4a
17 changed files with 308 additions and 100 deletions
@@ -1,6 +1,7 @@
package runtime package runtime
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset) block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base)) block.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed // Should be zeroed
assert(block.used == 0) assert(block.used == 0)
assert(block.prev == nil) assert(block.prev == nil)
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil { if block_to_free != nil {
allocator := block_to_free.allocator allocator := block_to_free.allocator
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc) mem_free(block_to_free, allocator, loc)
} }
} }
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return return
} }
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size block.used += size
return return
} }
@@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
} }
arena.total_used = 0 arena.total_used = 0
} }
@@ -226,6 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation // grow data in-place, adjusting next allocation
block.used = uint(new_end) block.used = uint(new_end)
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return return
} }
} }
@@ -299,6 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero arena.total_used -= amount_to_zero
} }
+10 -2
View File
@@ -1,5 +1,7 @@
package runtime package runtime
import "../sanitizer"
foreign import kernel32 "system:Kernel32.lib" foreign import kernel32 "system:Kernel32.lib"
@(private="file") @(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
HEAP_ZERO_MEMORY :: 0x00000008 HEAP_ZERO_MEMORY :: 0x00000008
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(ptr, size)
return ptr
} }
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
if new_size == 0 { if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
} }
HEAP_ZERO_MEMORY :: 0x00000008 HEAP_ZERO_MEMORY :: 0x00000008
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(new_ptr, new_size)
return new_ptr
} }
_heap_free :: proc "contextless" (ptr: rawptr) { _heap_free :: proc "contextless" (ptr: rawptr) {
if ptr == nil { if ptr == nil {
+9
View File
@@ -1106,3 +1106,12 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
dst[j>>3] |= the_bit<<(j&7) dst[j>>3] |= the_bit<<(j&7)
} }
} }
@(no_sanitize_address)
__asan_unpoison_memory_region :: #force_inline proc "contextless" (address: rawptr, size: uint) {
foreign {
__asan_unpoison_memory_region :: proc(address: rawptr, size: uint) ---
}
__asan_unpoison_memory_region(address, size)
}
+83 -1
View File
@@ -60,6 +60,7 @@ poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_poison_slice :: proc "contextless" (region: $T/[]$E) { address_poison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_poison_memory_region(raw_data(region), size_of(E) * len(region)) __asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
@@ -75,6 +76,7 @@ can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_unpoison_slice :: proc "contextless" (region: $T/[]$E) { address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region)) __asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
@@ -90,6 +92,7 @@ two threads can poison or unpoison memory in the same memory region region simul
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_poison_ptr :: proc "contextless" (ptr: ^$T) { address_poison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_poison_memory_region(ptr, size_of(T)) __asan_poison_memory_region(ptr, size_of(T))
@@ -106,6 +109,7 @@ region simultaneously.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_unpoison_ptr :: proc "contextless" (ptr: ^$T) { address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, size_of(T)) __asan_unpoison_memory_region(ptr, size_of(T))
@@ -121,6 +125,7 @@ poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED { when ASAN_ENABLED {
assert_contextless(len >= 0) assert_contextless(len >= 0)
@@ -128,6 +133,22 @@ address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
} }
} }
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_poison_memory_region(ptr, len)
}
}
/* /*
Marks the region covering `[ptr, ptr+len)` as addressable Marks the region covering `[ptr, ptr+len)` as addressable
@@ -137,6 +158,7 @@ threads can poison or unpoison memory in the same memory region region simultane
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED { when ASAN_ENABLED {
assert_contextless(len >= 0) assert_contextless(len >= 0)
@@ -144,16 +166,34 @@ address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
} }
} }
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, len)
}
}
address_poison :: proc { address_poison :: proc {
address_poison_slice, address_poison_slice,
address_poison_ptr, address_poison_ptr,
address_poison_rawptr, address_poison_rawptr,
address_poison_rawptr_uint,
} }
address_unpoison :: proc { address_unpoison :: proc {
address_unpoison_slice, address_unpoison_slice,
address_unpoison_ptr, address_unpoison_ptr,
address_unpoison_rawptr, address_unpoison_rawptr,
address_unpoison_rawptr_uint,
} }
/* /*
@@ -164,6 +204,7 @@ This can be used for logging and/or debugging purposes.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) { address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
when ASAN_ENABLED { when ASAN_ENABLED {
__sanitizer_set_death_callback(callback) __sanitizer_set_death_callback(callback)
@@ -178,7 +219,8 @@ in an asan error.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawptr { @(no_sanitize_address)
address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region)) return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
} else { } else {
@@ -194,6 +236,7 @@ in an asan error.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr { address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_region_is_poisoned(ptr, size_of(T)) return __asan_region_is_poisoned(ptr, size_of(T))
@@ -210,6 +253,7 @@ in an asan error.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr { address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
assert_contextless(len >= 0) assert_contextless(len >= 0)
@@ -219,10 +263,29 @@ address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: in
} }
} }
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(region, len)
} else {
return nil
}
}
address_region_is_poisoned :: proc { address_region_is_poisoned :: proc {
address_region_is_poisoned_slice, address_region_is_poisoned_slice,
address_region_is_poisoned_ptr, address_region_is_poisoned_ptr,
address_region_is_poisoned_rawptr, address_region_is_poisoned_rawptr,
address_region_is_poisoned_rawptr_uint,
} }
/* /*
@@ -233,6 +296,7 @@ If it is poisoned this procedure returns `true`, otherwise it returns
When asan is not enabled this procedure returns `false`. When asan is not enabled this procedure returns `false`.
*/ */
@(no_sanitize_address)
address_is_poisoned :: proc "contextless" (address: rawptr) -> bool { address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_address_is_poisoned(address) != 0 return __asan_address_is_poisoned(address) != 0
@@ -248,6 +312,7 @@ This procedure prints the description out to `stdout`.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_describe_address :: proc "contextless" (address: rawptr) { address_describe_address :: proc "contextless" (address: rawptr) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_describe_address(address) __asan_describe_address(address)
@@ -260,6 +325,7 @@ Returns `true` if an asan error has occured, otherwise it returns
When asan is not enabled this procedure returns `false`. When asan is not enabled this procedure returns `false`.
*/ */
@(no_sanitize_address)
address_report_present :: proc "contextless" () -> bool { address_report_present :: proc "contextless" () -> bool {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_report_present() != 0 return __asan_report_present() != 0
@@ -275,6 +341,7 @@ If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_get_report_pc :: proc "contextless" () -> rawptr { address_get_report_pc :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_pc() return __asan_get_report_pc()
@@ -290,6 +357,7 @@ If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_get_report_bp :: proc "contextless" () -> rawptr { address_get_report_bp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_bp() return __asan_get_report_bp()
@@ -305,6 +373,7 @@ If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_get_report_sp :: proc "contextless" () -> rawptr { address_get_report_sp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_sp() return __asan_get_report_sp()
@@ -320,6 +389,7 @@ If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_get_report_address :: proc "contextless" () -> rawptr { address_get_report_address :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_address() return __asan_get_report_address()
@@ -335,6 +405,7 @@ If no asan error has occurd `.none` is returned.
When asan is not enabled this procedure returns `.none`. When asan is not enabled this procedure returns `.none`.
*/ */
@(no_sanitize_address)
address_get_report_access_type :: proc "contextless" () -> Address_Access_Type { address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
when ASAN_ENABLED { when ASAN_ENABLED {
if ! address_report_present() { if ! address_report_present() {
@@ -353,6 +424,7 @@ If no asan error has occurd `0` is returned.
When asan is not enabled this procedure returns `0`. When asan is not enabled this procedure returns `0`.
*/ */
@(no_sanitize_address)
address_get_report_access_size :: proc "contextless" () -> uint { address_get_report_access_size :: proc "contextless" () -> uint {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_access_size() return __asan_get_report_access_size()
@@ -368,6 +440,7 @@ If no asan error has occurd an empty string is returned.
When asan is not enabled this procedure returns an empty string. When asan is not enabled this procedure returns an empty string.
*/ */
@(no_sanitize_address)
address_get_report_description :: proc "contextless" () -> string { address_get_report_description :: proc "contextless" () -> string {
when ASAN_ENABLED { when ASAN_ENABLED {
return string(__asan_get_report_description()) return string(__asan_get_report_description())
@@ -386,6 +459,7 @@ The information provided include:
When asan is not enabled this procedure returns zero initialised values. When asan is not enabled this procedure returns zero initialised values.
*/ */
@(no_sanitize_address)
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address { address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
when ASAN_ENABLED { when ASAN_ENABLED {
out_addr: rawptr out_addr: rawptr
@@ -404,6 +478,7 @@ The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns a zero initialised value. When asan is not enabled this procedure returns a zero initialised value.
*/ */
@(no_sanitize_address)
address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) { address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED { when ASAN_ENABLED {
out_thread: i32 out_thread: i32
@@ -421,6 +496,7 @@ The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns zero initialised values. When asan is not enabled this procedure returns zero initialised values.
*/ */
@(no_sanitize_address)
address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) { address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED { when ASAN_ENABLED {
out_thread: i32 out_thread: i32
@@ -436,6 +512,7 @@ Returns the current asan shadow memory mapping.
When asan is not enabled this procedure returns a zero initialised value. When asan is not enabled this procedure returns a zero initialised value.
*/ */
@(no_sanitize_address)
address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping { address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
when ASAN_ENABLED { when ASAN_ENABLED {
result: Address_Shadow_Mapping result: Address_Shadow_Mapping
@@ -451,6 +528,7 @@ Prints asan statistics to `stderr`
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_print_accumulated_stats :: proc "contextless" () { address_print_accumulated_stats :: proc "contextless" () {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_print_accumulated_stats() __asan_print_accumulated_stats()
@@ -464,6 +542,7 @@ This pointer can be then used for `address_is_in_fake_stack`.
When asan is not enabled this procedure returns `nil`. When asan is not enabled this procedure returns `nil`.
*/ */
@(no_sanitize_address)
address_get_current_fake_stack :: proc "contextless" () -> rawptr { address_get_current_fake_stack :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_current_fake_stack() return __asan_get_current_fake_stack()
@@ -477,6 +556,7 @@ Returns if an address belongs to a given fake stack and if so the region of the
When asan is not enabled this procedure returns zero initialised values. When asan is not enabled this procedure returns zero initialised values.
*/ */
@(no_sanitize_address)
address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) { address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
when ASAN_ENABLED { when ASAN_ENABLED {
begin: rawptr begin: rawptr
@@ -496,6 +576,7 @@ i.e. a procedure such as `panic` and `os.exit`.
When asan is not enabled this procedure does nothing. When asan is not enabled this procedure does nothing.
*/ */
@(no_sanitize_address)
address_handle_no_return :: proc "contextless" () { address_handle_no_return :: proc "contextless" () {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_handle_no_return() __asan_handle_no_return()
@@ -509,6 +590,7 @@ Returns `true` if successful, otherwise it returns `false`.
When asan is not enabled this procedure returns `false`. When asan is not enabled this procedure returns `false`.
*/ */
@(no_sanitize_address)
address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool { address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_update_allocation_context(addr) != 0 return __asan_update_allocation_context(addr) != 0
+3 -1
View File
@@ -14,12 +14,14 @@ related bugs. Typically asan interacts with libc but Odin code can be marked up
with the asan runtime to extend the memory error detection outside of libc using this package. with the asan runtime to extend the memory error detection outside of libc using this package.
For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
## Memory ## Memory
Enabled with `-sanitize:memory` when building an odin project. Enabled with `-sanitize:memory` when building an odin project.
The memory sanitizer is another runtime memory error detector with the sole purpose to catch the The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
use of uninitialized memory. This is not a very common bug in Odin as be default everything is use of uninitialized memory. This is not a very common bug in Odin as by default everything is
set to zero when initialised (ZII). set to zero when initialised (ZII).
For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
+31 -19
View File
@@ -1,6 +1,7 @@
package mem package mem
import "base:runtime" import "base:runtime"
import "base:sanitizer"
/* /*
Rollback stack default block size. Rollback stack default block size.
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
block_allocator: Allocator, block_allocator: Allocator,
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
start := raw_data(block.buffer) start := raw_data(block.buffer)
end := start[block.offset:] end := start[block.offset:]
return start < ptr && ptr <= end return start < ptr && ptr <= end
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
parent: ^Rollback_Stack_Block, parent: ^Rollback_Stack_Block,
block: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block,
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, nil, .Invalid_Pointer return nil, nil, nil, .Invalid_Pointer
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
block: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block,
header: ^Rollback_Stack_Header, header: ^Rollback_Stack_Header,
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, false return nil, nil, false
} }
@(private="file") @(private="file", no_sanitize_address)
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
header := header header := header
for block.offset > 0 && header.is_free { for block.offset > 0 && header.is_free {
block.offset = header.prev_offset block.offset = header.prev_offset
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
/* /*
Free memory to a rollback stack allocator. Free memory to a rollback stack allocator.
*/ */
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
parent, block, header := rb_find_ptr(stack, ptr) or_return parent, block, header := rb_find_ptr(stack, ptr) or_return
if header.is_free { if header.is_free {
return .Invalid_Pointer return .Invalid_Pointer
} }
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
/* /*
Free all memory owned by the rollback stack allocator. Free all memory owned by the rollback stack allocator.
*/ */
@(private="file") @(private="file", no_sanitize_address)
rb_free_all :: proc(stack: ^Rollback_Stack) { rb_free_all :: proc(stack: ^Rollback_Stack) {
for block := stack.head.next_block; block != nil; /**/ { for block := stack.head.next_block; block != nil; /**/ {
next_block := block.next_block next_block := block.next_block
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil stack.head.next_block = nil
stack.head.last_alloc = nil stack.head.last_alloc = nil
stack.head.offset = 0 stack.head.offset = 0
sanitizer.address_poison(stack.head.buffer)
} }
/* /*
Allocate memory using the rollback stack allocator. Allocate memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc :: proc( rb_alloc :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -153,7 +157,7 @@ rb_alloc :: proc(
/* /*
Allocate memory using the rollback stack allocator. Allocate memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_bytes :: proc( rb_alloc_bytes :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
/* /*
Allocate non-initialized memory using the rollback stack allocator. Allocate non-initialized memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_non_zeroed :: proc( rb_alloc_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
/* /*
Allocate non-initialized memory using the rollback stack allocator. Allocate non-initialized memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_bytes_non_zeroed :: proc( rb_alloc_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
assert(size >= 0, "Size must be positive or zero.", loc) assert(size >= 0, "Size must be positive or zero.", loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
parent: ^Rollback_Stack_Block parent: ^Rollback_Stack_Block
for block := stack.head; /**/; block = block.next_block { for block := stack.head; /**/; block = block.next_block {
when !ODIN_DISABLE_ASSERT { when !ODIN_DISABLE_ASSERT {
allocated_new_block: bool allocated_new_block: bool
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
// Prevent any further allocations on it. // Prevent any further allocations on it.
block.offset = cast(uintptr)len(block.buffer) block.offset = cast(uintptr)len(block.buffer)
} }
#no_bounds_check return ptr[:size], nil res := ptr[:size]
sanitizer.address_unpoison(res)
return res, nil
} }
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
/* /*
Resize an allocation owned by rollback stack allocator. Resize an allocation owned by rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize :: proc( rb_resize :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_ptr: rawptr, old_ptr: rawptr,
@@ -266,7 +273,7 @@ rb_resize :: proc(
/* /*
Resize an allocation owned by rollback stack allocator. Resize an allocation owned by rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_bytes :: proc( rb_resize_bytes :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_memory: []byte, old_memory: []byte,
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
Resize an allocation owned by rollback stack allocator without explicit Resize an allocation owned by rollback stack allocator without explicit
zero-initialization. zero-initialization.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_non_zeroed :: proc( rb_resize_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_ptr: rawptr, old_ptr: rawptr,
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
Resize an allocation owned by rollback stack allocator without explicit Resize an allocation owned by rollback stack allocator without explicit
zero-initialization. zero-initialization.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_bytes_non_zeroed :: proc( rb_resize_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_memory: []byte, old_memory: []byte,
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
if len(block.buffer) <= stack.block_size { if len(block.buffer) <= stack.block_size {
block.offset += cast(uintptr)size - cast(uintptr)old_size block.offset += cast(uintptr)size - cast(uintptr)old_size
} }
#no_bounds_check return (ptr)[:size], nil res := (ptr)[:size]
sanitizer.address_unpoison(res)
#no_bounds_check return res, nil
} }
} }
} }
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
return return
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
block = cast(^Rollback_Stack_Block)raw_data(buffer) block = cast(^Rollback_Stack_Block)raw_data(buffer)
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
/* /*
Initialize the rollback stack allocator using a fixed backing buffer. Initialize the rollback stack allocator using a fixed backing buffer.
*/ */
@(no_sanitize_address)
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
/* /*
Initialize the rollback stack alocator using a backing block allocator. Initialize the rollback stack alocator using a backing block allocator.
*/ */
@(no_sanitize_address)
rollback_stack_init_dynamic :: proc( rollback_stack_init_dynamic :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
/* /*
Destroy a rollback stack. Destroy a rollback stack.
*/ */
@(no_sanitize_address)
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
if stack.block_allocator.procedure != nil { if stack.block_allocator.procedure != nil {
rb_free_all(stack) rb_free_all(stack)
@@ -435,7 +447,7 @@ from the last allocation backwards.
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
the requested alignment. the requested alignment.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
return Allocator { return Allocator {
data = stack, data = stack,
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
} }
} }
@(require_results) @(require_results, no_sanitize_address)
rollback_stack_allocator_proc :: proc( rollback_stack_allocator_proc :: proc(
allocator_data: rawptr, allocator_data: rawptr,
mode: Allocator_Mode, mode: Allocator_Mode,
+1 -1
View File
@@ -198,4 +198,4 @@ fls :: proc "contextless" (word: u32) -> (bit: i32) {
fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
N :: (size_of(uint) * 8) - 1 N :: (size_of(uint) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(size)) return i32(N - intrinsics.count_leading_zeros(size))
} }
+52 -44
View File
@@ -10,6 +10,7 @@
package mem_tlsf package mem_tlsf
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
import "base:runtime" import "base:runtime"
// log2 of number of linear subdivisions of block sizes. // log2 of number of linear subdivisions of block sizes.
@@ -209,6 +210,8 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
sanitizer.address_poison(new_pool_buf)
// Allocate a new link in the `control.pool` tracking structure. // Allocate a new link in the `control.pool` tracking structure.
new_pool := new_clone(Pool{ new_pool := new_clone(Pool{
data = new_pool_buf, data = new_pool_buf,
@@ -254,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return block_prepare_used(control, block, adjust) return block_prepare_used(control, block, adjust)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align) res, err = alloc_bytes_non_zeroed(control, size, align)
if err == nil { if err == nil {
@@ -273,6 +276,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr) block := block_from_ptr(ptr)
assert(!block_is_free(block), "block already marked as free") // double free assert(!block_is_free(block), "block already marked as free") // double free
sanitizer.address_poison(ptr, block.size)
block_mark_as_free(block) block_mark_as_free(block)
block = block_merge_prev(control, block) block = block_merge_prev(control, block)
block = block_merge_next(control, block) block = block_merge_next(control, block)
@@ -316,6 +320,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
block_trim_used(control, block, adjust) block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size] res = ([^]byte)(ptr)[:new_size]
sanitizer.address_unpoison(res)
if min_size < new_size { if min_size < new_size {
to_zero := ([^]byte)(ptr)[min_size:new_size] to_zero := ([^]byte)(ptr)[min_size:new_size]
@@ -374,95 +379,96 @@ resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size:
NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31. NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
*/ */
@(private, require_results) @(private, require_results, no_sanitize_address)
block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) { block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE) return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
} }
@(private) @(private, no_sanitize_address)
block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) { block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
old_size := block.size old_size := block.size
block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)) block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) { block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
return block_size(block) == 0 return block_size(block) == 0
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) { block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_free :: proc "contextless" (block: ^Block_Header) { block_set_free :: proc "contextless" (block: ^Block_Header) {
block.size |= BLOCK_HEADER_FREE block.size |= BLOCK_HEADER_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_used :: proc "contextless" (block: ^Block_Header) { block_set_used :: proc "contextless" (block: ^Block_Header) {
block.size &~= BLOCK_HEADER_FREE block.size &~= BLOCK_HEADER_FREE
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) { block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_prev_free :: proc "contextless" (block: ^Block_Header) { block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
block.size |= BLOCK_HEADER_PREV_FREE block.size |= BLOCK_HEADER_PREV_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_prev_used :: proc "contextless" (block: ^Block_Header) { block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
block.size &~= BLOCK_HEADER_PREV_FREE block.size &~= BLOCK_HEADER_PREV_FREE
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) { block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET) return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) { block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
return rawptr(uintptr(block) + BLOCK_START_OFFSET) return rawptr(uintptr(block) + BLOCK_START_OFFSET)
} }
// Return location of next block after block of given size. // Return location of next block after block of given size.
@(private, require_results) @(private, require_results, no_sanitize_address)
offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) + uintptr(size)) return (^Block_Header)(uintptr(ptr) + uintptr(size))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) - uintptr(size)) return (^Block_Header)(uintptr(ptr) - uintptr(size))
} }
// Return location of previous block. // Return location of previous block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) { block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
assert(block_is_prev_free(block), "previous block must be free") assert(block_is_prev_free(block), "previous block must be free")
return block.prev_phys_block return block.prev_phys_block
} }
// Return location of next existing block. // Return location of next existing block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD) return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
} }
// Link a new block with its physical neighbor, return the neighbor. // Link a new block with its physical neighbor, return the neighbor.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
next = block_next(block) next = block_next(block)
next.prev_phys_block = block next.prev_phys_block = block
return return
} }
@(private) @(private, no_sanitize_address)
block_mark_as_free :: proc(block: ^Block_Header) { block_mark_as_free :: proc(block: ^Block_Header) {
// Link the block to the next block, first. // Link the block to the next block, first.
next := block_link_next(block) next := block_link_next(block)
@@ -470,26 +476,26 @@ block_mark_as_free :: proc(block: ^Block_Header) {
block_set_free(block) block_set_free(block)
} }
@(private) @(private, no_sanitize_address)
block_mark_as_used :: proc(block: ^Block_Header) { block_mark_as_used :: proc(block: ^Block_Header, ) {
next := block_next(block) next := block_next(block)
block_set_prev_used(next) block_set_prev_used(next)
block_set_used(block) block_set_used(block)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
align_up :: proc(x, align: uint) -> (aligned: uint) { align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1) return (x + (align - 1)) &~ (align - 1)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
align_down :: proc(x, align: uint) -> (aligned: uint) { align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1)) return x - (x & (align - 1))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
align_mask := uintptr(align) - 1 align_mask := uintptr(align) - 1
@@ -499,7 +505,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results) @(private, require_results, no_sanitize_address)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 0 { if size == 0 {
return 0 return 0
@@ -513,7 +519,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results) @(private, require_results, no_sanitize_address)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 { if size == 0 {
return 0, nil return 0, nil
@@ -531,7 +537,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err:
// TLSF utility functions. In most cases these are direct translations of // TLSF utility functions. In most cases these are direct translations of
// the documentation in the research paper. // the documentation in the research paper.
@(optimization_mode="favor_size", private, require_results) @(optimization_mode="favor_size", private, require_results, no_sanitize_address)
mapping_insert :: proc(size: uint) -> (fl, sl: i32) { mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
if size < SMALL_BLOCK_SIZE { if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list. // Store small blocks in first list.
@@ -544,7 +550,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
return return
} }
@(optimization_mode="favor_size", private, require_results) @(optimization_mode="favor_size", private, require_results, no_sanitize_address)
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
rounded = size rounded = size
if size >= SMALL_BLOCK_SIZE { if size >= SMALL_BLOCK_SIZE {
@@ -555,12 +561,12 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
} }
// This version rounds up to the next block size (for allocations) // This version rounds up to the next block size (for allocations)
@(optimization_mode="favor_size", private, require_results) @(optimization_mode="favor_size", private, require_results, no_sanitize_address)
mapping_search :: proc(size: uint) -> (fl, sl: i32) { mapping_search :: proc(size: uint) -> (fl, sl: i32) {
return mapping_insert(mapping_round(size)) return mapping_insert(mapping_round(size))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) { search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
// First, search for a block in the list associated with the given fl/sl index. // First, search for a block in the list associated with the given fl/sl index.
fl := fli^; sl := sli^ fl := fli^; sl := sli^
@@ -587,7 +593,7 @@ search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^B
} }
// Remove a free block from the free list. // Remove a free block from the free list.
@(private) @(private, no_sanitize_address)
remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
prev := block.prev_free prev := block.prev_free
next := block.next_free next := block.next_free
@@ -613,7 +619,7 @@ remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
} }
// Insert a free block into the free block list. // Insert a free block into the free block list.
@(private) @(private, no_sanitize_address)
insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
current := control.blocks[fl][sl] current := control.blocks[fl][sl]
assert(current != nil, "free lists cannot have a nil entry") assert(current != nil, "free lists cannot have a nil entry")
@@ -631,26 +637,26 @@ insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
} }
// Remove a given block from the free list. // Remove a given block from the free list.
@(private) @(private, no_sanitize_address)
block_remove :: proc(control: ^Allocator, block: ^Block_Header) { block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
fl, sl := mapping_insert(block_size(block)) fl, sl := mapping_insert(block_size(block))
remove_free_block(control, block, fl, sl) remove_free_block(control, block, fl, sl)
} }
// Insert a given block into the free list. // Insert a given block into the free list.
@(private) @(private, no_sanitize_address)
block_insert :: proc(control: ^Allocator, block: ^Block_Header) { block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
fl, sl := mapping_insert(block_size(block)) fl, sl := mapping_insert(block_size(block))
insert_free_block(control, block, fl, sl) insert_free_block(control, block, fl, sl)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) { block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
return block_size(block) >= size_of(Block_Header) + size return block_size(block) >= size_of(Block_Header) + size
} }
// Split a block into two, the second of which is free. // Split a block into two, the second of which is free.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
// Calculate the amount of space left in the remaining block. // Calculate the amount of space left in the remaining block.
remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD) remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
@@ -671,9 +677,10 @@ block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Head
} }
// Absorb a free block's storage into an adjacent previous free block. // Absorb a free block's storage into an adjacent previous free block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) { block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
assert(!block_is_last(prev), "previous block can't be last") assert(!block_is_last(prev), "previous block can't be last")
// Note: Leaves flags untouched. // Note: Leaves flags untouched.
prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
_ = block_link_next(prev) _ = block_link_next(prev)
@@ -681,7 +688,7 @@ block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^B
} }
// Merge a just-freed block with an adjacent previous free block. // Merge a just-freed block with an adjacent previous free block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
merged = block merged = block
if (block_is_prev_free(block)) { if (block_is_prev_free(block)) {
@@ -695,7 +702,7 @@ block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
} }
// Merge a just-freed block with an adjacent free block. // Merge a just-freed block with an adjacent free block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
merged = block merged = block
next := block_next(block) next := block_next(block)
@@ -710,7 +717,7 @@ block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
} }
// Trim any trailing block space off the end of a free block, return to pool. // Trim any trailing block space off the end of a free block, return to pool.
@(private) @(private, no_sanitize_address)
block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
assert(block_is_free(block), "block must be free") assert(block_is_free(block), "block must be free")
if (block_can_split(block, size)) { if (block_can_split(block, size)) {
@@ -722,7 +729,7 @@ block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
} }
// Trim any trailing block space off the end of a used block, return to pool. // Trim any trailing block space off the end of a used block, return to pool.
@(private) @(private, no_sanitize_address)
block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
assert(!block_is_free(block), "Block must be used") assert(!block_is_free(block), "Block must be used")
if (block_can_split(block, size)) { if (block_can_split(block, size)) {
@@ -736,7 +743,7 @@ block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
} }
// Trim leading block space, return to pool. // Trim leading block space, return to pool.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
remaining = block remaining = block
if block_can_split(block, size) { if block_can_split(block, size) {
@@ -750,7 +757,7 @@ block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size:
return remaining return remaining
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) { block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
fl, sl: i32 fl, sl: i32
if size != 0 { if size != 0 {
@@ -774,13 +781,14 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea
return block return block
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
if block != nil { if block != nil {
assert(size != 0, "Size must be non-zero") assert(size != 0, "Size must be non-zero")
block_trim_free(control, block, size) block_trim_free(control, block, size)
block_mark_as_used(block) block_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size] res = ([^]byte)(block_to_ptr(block))[:size]
sanitizer.address_unpoison(res)
} }
return return
} }
+10 -1
View File
@@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator
specified with `backing_allocator`. The `internals_allocator` will used to specified with `backing_allocator`. The `internals_allocator` will used to
allocate the tracked data. allocate the tracked data.
*/ */
@(no_sanitize_address)
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
t.backing = backing_allocator t.backing = backing_allocator
t.allocation_map.allocator = internals_allocator t.allocation_map.allocator = internals_allocator
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
/* /*
Destroy the tracking allocator. Destroy the tracking allocator.
*/ */
@(no_sanitize_address)
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
delete(t.allocation_map) delete(t.allocation_map)
delete(t.bad_free_array) delete(t.bad_free_array)
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
**Note**: This procedure clears only the current allocation data while keeping **Note**: This procedure clears only the current allocation data while keeping
the totals intact. the totals intact.
*/ */
@(no_sanitize_address)
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex) sync.mutex_lock(&t.mutex)
clear(&t.allocation_map) clear(&t.allocation_map)
@@ -103,6 +106,7 @@ Reset the tracking allocator.
Reset all of a Tracking Allocator's allocation data back to zero. Reset all of a Tracking Allocator's allocation data back to zero.
*/ */
@(no_sanitize_address)
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex) sync.mutex_lock(&t.mutex)
clear(&t.allocation_map) clear(&t.allocation_map)
@@ -124,6 +128,7 @@ 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 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. the tracking allocator to the old behavior, where the bad_free_array was used.
*/ */
@(no_sanitize_address)
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
runtime.print_caller_location(location) runtime.print_caller_location(location)
runtime.print_string(" Tracking allocator error: Bad free of pointer ") runtime.print_string(" Tracking allocator error: Bad free of pointer ")
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this, 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. then you must make sure to check Tracking_Allocator.bad_free_array at some point.
*/ */
@(no_sanitize_address)
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { 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 { append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
memory = memory, memory = memory,
@@ -175,7 +181,7 @@ Example:
} }
} }
*/ */
@(require_results) @(require_results, no_sanitize_address)
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
return Allocator{ return Allocator{
data = data, data = data,
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
} }
} }
@(no_sanitize_address)
tracking_allocator_proc :: proc( tracking_allocator_proc :: proc(
allocator_data: rawptr, allocator_data: rawptr,
mode: Allocator_Mode, mode: Allocator_Mode,
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
old_size: int, old_size: int,
loc := #caller_location, loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) { ) -> (result: []byte, err: Allocator_Error) {
@(no_sanitize_address)
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size) data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1 data.total_allocation_count += 1
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
} }
} }
@(no_sanitize_address)
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_freed += i64(entry.size) data.total_memory_freed += i64(entry.size)
data.total_free_count += 1 data.total_free_count += 1
+32 -11
View File
@@ -3,6 +3,8 @@ package mem_virtual
import "core:mem" import "core:mem"
import "core:sync" import "core:sync"
import "base:sanitizer"
Arena_Kind :: enum uint { Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list). Growing = 0, // Chained memory blocks (singly linked list).
Static = 1, // Fixed reservation sized. Static = 1, // Fixed reservation sized.
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
// Initialization of an `Arena` to be a `.Growing` variant. // Initialization of an `Arena` to be a `.Growing` variant.
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory. // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
@(require_results) @(require_results, no_sanitize_address)
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) { arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.kind = .Growing arena.kind = .Growing
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 { if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved arena.minimum_block_size = reserved
} }
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
// Initialization of an `Arena` to be a `.Static` variant. // Initialization of an `Arena` to be a `.Static` variant.
// A static arena contains a single `Memory_Block` allocated with virtual memory. // A static arena contains a single `Memory_Block` allocated with virtual memory.
@(require_results) @(require_results, no_sanitize_address)
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static arena.kind = .Static
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0 arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved arena.total_reserved = arena.curr_block.reserved
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
// Initialization of an `Arena` to be a `.Buffer` variant. // Initialization of an `Arena` to be a `.Buffer` variant.
// A buffer arena contains single `Memory_Block` created from a user provided []byte. // A buffer arena contains single `Memory_Block` created from a user provided []byte.
@(require_results) @(require_results, no_sanitize_address)
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) { arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
if len(buffer) < size_of(Memory_Block) { if len(buffer) < size_of(Memory_Block) {
return .Out_Of_Memory return .Out_Of_Memory
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer arena.kind = .Buffer
mem.zero_slice(buffer) sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer) block_base := raw_data(buffer)
block := (^Memory_Block)(block_base) block := (^Memory_Block)(block_base)
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
} }
// Allocates memory from the provided arena. // Allocates memory from the provided arena.
@(require_results) @(require_results, no_sanitize_address)
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0) data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
} }
sanitizer.address_unpoison(data)
return return
} }
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory. // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
@(no_sanitize_address)
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool { arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
} }
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true return true
} else if pos == 0 { } else if pos == 0 {
arena.total_used = 0 arena.total_used = 0
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
} }
// Frees the last memory block of a Growing Arena // Frees the last memory block of a Growing Arena
@(no_sanitize_address)
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
if free_block := arena.curr_block; free_block != nil { if free_block := arena.curr_block; free_block != nil {
assert(arena.kind == .Growing, "expected a .Growing arena", loc) assert(arena.kind == .Growing, "expected a .Growing arena", loc)
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev arena.curr_block = free_block.prev
sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block) memory_block_dealloc(free_block)
} }
} }
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0. // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
@(no_sanitize_address)
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
switch arena.kind { switch arena.kind {
case .Growing: case .Growing:
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used) curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used) mem.zero(arena.curr_block.base, curr_block_used)
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
} }
arena.total_used = 0 arena.total_used = 0
case .Static, .Buffer: case .Static, .Buffer:
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
// Frees all of the memory allocated by the arena and zeros all of the values of an arena. // Frees all of the memory allocated by the arena and zeros all of the values of an arena.
// A buffer based arena does not `delete` the provided `[]byte` bufffer. // A buffer based arena does not `delete` the provided `[]byte` bufffer.
@(no_sanitize_address)
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
switch arena.kind { switch arena.kind {
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena bootstrap: Arena
bootstrap.kind = .Growing bootstrap.kind = .Growing
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size) return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena bootstrap: Arena
bootstrap.kind = .Static bootstrap.kind = .Static
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved) return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
} }
// Create an `Allocator` from the provided `Arena` // Create an `Allocator` from the provided `Arena`
@(require_results) @(require_results, no_sanitize_address)
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator { arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
return mem.Allocator{arena_allocator_proc, arena} return mem.Allocator{arena_allocator_proc, arena}
} }
// The allocator procedure used by an `Allocator` produced by `arena_allocator` // The allocator procedure used by an `Allocator` produced by `arena_allocator`
@(no_sanitize_address)
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int, size, alignment: int,
old_memory: rawptr, old_size: int, old_memory: rawptr, old_size: int,
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size { if size < old_size {
// shrink data in-place // shrink data in-place
data = old_data[:size] data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size])
return return
} }
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used arena.total_used += block.used - prev_used
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return return
} }
} }
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return return
} }
copy(new_memory, old_data[:old_size]) copy(new_memory, old_data[:old_size])
sanitizer.address_poison(old_data[:old_size])
return new_memory, nil return new_memory, nil
case .Query_Features: case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory) set := (^mem.Allocator_Mode_Set)(old_memory)
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
} }
// Begins the section of temporary arena memory. // Begins the section of temporary arena memory.
@(require_results) @(require_results, no_sanitize_address)
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc) assert(arena != nil, "nil arena", loc)
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
} }
// Ends the section of temporary arena memory by resetting the memory to the stored position. // Ends the section of temporary arena memory by resetting the memory to the stored position.
@(no_sanitize_address)
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc) assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena arena := temp.arena
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
} }
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position. // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
@(no_sanitize_address)
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc) assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena arena := temp.arena
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
} }
// Asserts that all uses of `Arena_Temp` has been used by an `Arena` // Asserts that all uses of `Arena_Temp` has been used by an `Arena`
@(no_sanitize_address)
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
} }
+17 -8
View File
@@ -2,6 +2,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
Allocator_Error :: mem.Allocator_Error Allocator_Error :: mem.Allocator_Error
@(require_results) @(require_results, no_sanitize_address)
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
return _reserve(size) return _reserve(size)
} }
@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size)
return _commit(data, size) return _commit(data, size)
} }
@(require_results) @(require_results, no_sanitize_address)
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
data = reserve(size) or_return data = reserve(size) or_return
commit(raw_data(data), size) or_return commit(raw_data(data), size) or_return
return return
} }
@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) { decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size)
_decommit(data, size) _decommit(data, size)
} }
@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) { release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size)
_release(data, size) _release(data, size)
} }
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
Protect_Flags :: distinct bit_set[Protect_Flag; u32] Protect_Flags :: distinct bit_set[Protect_Flag; u32]
Protect_No_Access :: Protect_Flags{} Protect_No_Access :: Protect_Flags{}
@(no_sanitize_address)
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
return _protect(data, size, flags) return _protect(data, size, flags)
} }
Memory_Block :: struct { Memory_Block :: struct {
prev: ^Memory_Block, prev: ^Memory_Block,
base: [^]byte, base: [^]byte,
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32] Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
result := size + align-1 result := size + align-1
return result - result%align return result - result%align
} }
@(require_results) @(require_results, no_sanitize_address)
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) { memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
page_size := DEFAULT_PAGE_SIZE page_size := DEFAULT_PAGE_SIZE
assert(mem.is_power_of_two(uintptr(page_size))) assert(mem.is_power_of_two(uintptr(page_size)))
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
return &pmblock.block, nil return &pmblock.block, nil
} }
@(require_results) @(require_results, no_sanitize_address)
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) { alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
@(no_sanitize_address)
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
alignment_offset := uint(0) alignment_offset := uint(0)
ptr := uintptr(block.base[block.used:]) ptr := uintptr(block.base[block.used:])
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
return alignment_offset return alignment_offset
} }
@(no_sanitize_address)
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) { do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size { if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block) pmblock := (^Platform_Memory_Block)(block)
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
block.used += size block.used += size
sanitizer.address_unpoison(data)
return return
} }
@(no_sanitize_address)
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
if block := (^Platform_Memory_Block)(block_to_free); block != nil { if block := (^Platform_Memory_Block)(block_to_free); block != nil {
platform_memory_free(block) platform_memory_free(block)
+3
View File
@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
reserved: uint, reserved: uint,
} }
@(no_sanitize_address)
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) { platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
to_commit, to_reserve := to_commit, to_reserve to_commit, to_reserve := to_commit, to_reserve
to_reserve = max(to_commit, to_reserve) to_reserve = max(to_commit, to_reserve)
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
} }
@(no_sanitize_address)
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) { platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
if block != nil { if block != nil {
release(block, block.reserved) release(block, block.reserved)
} }
} }
@(no_sanitize_address)
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) { platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
if to_commit < block.committed { if to_commit < block.committed {
return nil return nil
+11 -1
View File
@@ -83,6 +83,8 @@ foreign Kernel32 {
dwNumberOfBytesToMap: uint, dwNumberOfBytesToMap: uint,
) -> rawptr --- ) -> rawptr ---
} }
@(no_sanitize_address)
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE) result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
if result == nil { if result == nil {
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
return return
} }
@(no_sanitize_address)
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE) result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
if result == nil { if result == nil {
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
} }
return nil return nil
} }
@(no_sanitize_address)
_decommit :: proc "contextless" (data: rawptr, size: uint) { _decommit :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, size, MEM_DECOMMIT) VirtualFree(data, size, MEM_DECOMMIT)
} }
@(no_sanitize_address)
_release :: proc "contextless" (data: rawptr, size: uint) { _release :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, 0, MEM_RELEASE) VirtualFree(data, 0, MEM_RELEASE)
} }
@(no_sanitize_address)
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
pflags: u32 pflags: u32
pflags = PAGE_NOACCESS pflags = PAGE_NOACCESS
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
} }
@(no_sanitize_address)
_platform_memory_init :: proc() { _platform_memory_init :: proc() {
sys_info: SYSTEM_INFO sys_info: SYSTEM_INFO
GetSystemInfo(&sys_info) GetSystemInfo(&sys_info)
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
} }
@(no_sanitize_address)
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
page_flags: u32 page_flags: u32
if flags == {.Read} { if flags == {.Read} {
+2
View File
@@ -383,6 +383,8 @@ struct lbProcedure {
PtrMap<Ast *, lbValue> selector_values; PtrMap<Ast *, lbValue> selector_values;
PtrMap<Ast *, lbAddr> selector_addr; PtrMap<Ast *, lbAddr> selector_addr;
PtrMap<LLVMValueRef, lbTupleFix> tuple_fix_map; PtrMap<LLVMValueRef, lbTupleFix> tuple_fix_map;
Array<lbValue> asan_stack_locals;
}; };
+7
View File
@@ -3070,6 +3070,13 @@ gb_internal lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero
if (e != nullptr) { if (e != nullptr) {
lb_add_entity(p->module, e, val); lb_add_entity(p->module, e, val);
lb_add_debug_local_variable(p, ptr, type, e->token); lb_add_debug_local_variable(p, ptr, type, e->token);
// NOTE(lucas): In LLVM 20 and below we do not have the option to have asan cleanup poisoned stack
// locals ourselves. So we need to manually track and unpoison these locals on proc return.
// LLVM 21 adds the 'use-after-scope' asan option which does this for us.
if (build_context.sanitizer_flags & SanitizerFlag_Address && !p->entity->Procedure.no_sanitize_address) {
array_add(&p->asan_stack_locals, val);
}
} }
if (zero_init) { if (zero_init) {
+13 -11
View File
@@ -115,12 +115,13 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
p->is_entry_point = false; p->is_entry_point = false;
gbAllocator a = heap_allocator(); gbAllocator a = heap_allocator();
p->children.allocator = a; p->children.allocator = a;
p->defer_stmts.allocator = a; p->defer_stmts.allocator = a;
p->blocks.allocator = a; p->blocks.allocator = a;
p->branch_blocks.allocator = a; p->branch_blocks.allocator = a;
p->context_stack.allocator = a; p->context_stack.allocator = a;
p->scope_stack.allocator = a; p->scope_stack.allocator = a;
p->asan_stack_locals.allocator = a;
// map_init(&p->selector_values, 0); // map_init(&p->selector_values, 0);
// map_init(&p->selector_addr, 0); // map_init(&p->selector_addr, 0);
// map_init(&p->tuple_fix_map, 0); // map_init(&p->tuple_fix_map, 0);
@@ -385,11 +386,12 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name
p->is_entry_point = false; p->is_entry_point = false;
gbAllocator a = permanent_allocator(); gbAllocator a = permanent_allocator();
p->children.allocator = a; p->children.allocator = a;
p->defer_stmts.allocator = a; p->defer_stmts.allocator = a;
p->blocks.allocator = a; p->blocks.allocator = a;
p->branch_blocks.allocator = a; p->branch_blocks.allocator = a;
p->context_stack.allocator = a; p->context_stack.allocator = a;
p->asan_stack_locals.allocator = a;
map_init(&p->tuple_fix_map, 0); map_init(&p->tuple_fix_map, 0);
+16
View File
@@ -2917,6 +2917,22 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo
} }
defer (p->branch_location_pos = prev_token_pos); defer (p->branch_location_pos = prev_token_pos);
// TODO(lucas): In LLVM 21 use the 'use-after-scope' asan option which does this for us.
#if LLVM_VERSION_MAJOR < 21
if (kind == lbDeferExit_Return) {
for_array(i, p->asan_stack_locals) {
lbValue local = p->asan_stack_locals[i];
auto args = array_make<lbValue>(temporary_allocator(), 2);
args[0] = lb_emit_conv(p, local, t_rawptr);
args[1] = lb_const_int(p->module, t_int, type_size_of(local.type->Pointer.elem));
lb_emit_runtime_call(p, "__asan_unpoison_memory_region", args);
}
}
#else
#error "Need to implement LLVM 21 'use-after-scope' asan option"
#endif
isize count = p->defer_stmts.count; isize count = p->defer_stmts.count;
isize i = count; isize i = count;
while (i --> 0) { while (i --> 0) {