From 2363f859442ed96f4b1b1e28cbc18569d9e2819d Mon Sep 17 00:00:00 2001 From: Mortimer Snerd Date: Wed, 25 Mar 2026 19:33:17 -0400 Subject: [PATCH 1/2] Fix for corner case in the scratch allocator. The scratch allocator could erroneously return a slice overlapping previously allocated memory for allocation requests that were smaller than the arena size, but larger than the amount of free space left in the arena. - fix the check in mem::scratch_alloc_bytes_non_zeroed that wasn't routing the request to the backup allocator. - added concrete reproduction test under tests/issues. --- core/mem/allocators.odin | 11 ++-------- tests/issues/run.bat | 1 + tests/issues/run.sh | 2 ++ tests/issues/test_pr_unknown.odin | 35 +++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 tests/issues/test_pr_unknown.odin diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index eea7a5b4f..68b6102b6 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -536,15 +536,8 @@ scratch_alloc_bytes_non_zeroed :: proc( // we don't need to be so strict about every byte. aligned_size += alignment - 1 } - if aligned_size <= len(s.data) { - offset := uintptr(0) - if s.curr_offset+aligned_size <= len(s.data) { - offset = uintptr(s.curr_offset) - } else { - // The allocation will cause an overflow past the boundary of the - // space available, so reset to the starting offset. - offset = 0 - } + if s.curr_offset+aligned_size <= len(s.data) { + offset := uintptr(s.curr_offset) start := uintptr(raw_data(s.data)) ptr := rawptr(offset+start) // We keep track of the original base pointer without extra alignment diff --git a/tests/issues/run.bat b/tests/issues/run.bat index f1a1c48c0..c1199ff73 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -34,6 +34,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_6401.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b ..\..\..\odin test ..\test_pr_6470.odin %COMMON% || exit /b ..\..\..\odin test ..\test_pr_6470.odin -define:TEST_EXPECT_FAILURE=true %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b +..\..\..\odin test ..\test_pr_unknown.odin %COMMON% || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 996007cd7..ef8c73351 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -37,6 +37,8 @@ $ODIN test ../test_issue_6068.odin $COMMON $ODIN test ../test_issue_6101.odin $COMMON $ODIN test ../test_issue_6165.odin $COMMON $ODIN test ../test_issue_6396.odin $COMMON +$ODIN test ../test_pr_unknown.odin $COMMON + if [[ $($ODIN build ../test_issue_6240.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_pr_unknown.odin b/tests/issues/test_pr_unknown.odin new file mode 100644 index 000000000..b63aec674 --- /dev/null +++ b/tests/issues/test_pr_unknown.odin @@ -0,0 +1,35 @@ +package test_issues + +import "core:testing" +import "core:mem" + +// Test for a problem encountered with the scratch allocator. +// Say you have a scratch allocator with an arena size of N. +// If you make an allocation whose size is <= N but greater than +// the amount of free space left in the arena, the allocator +// will return a slice of memory from the start of the arena, +// overlapping previous allocations. (the expected +// behavior is it satisfies the request with the backup allocator) + +@test +test_scratch_smash :: proc(t: ^testing.T) { + // setup + frAlloc: mem.Scratch + err := mem.scratch_init(&frAlloc, 1 * mem.Kilobyte) + testing.expect(t, err == nil) + + talloc := mem.scratch_allocator(&frAlloc) + defer mem.scratch_destroy(&frAlloc) + + // First allocation fits in arena. + a1 := make([]byte, 512, talloc) + + // Second allocation does not fit in the free space, but is + // <= the arena size. + a2 := make([]byte, 1024, talloc) + + // Should be true, but bug in scratch allocator returns space + // overlapping a1 when allocating a2. + testing.expect(t, &a1[0] != &a2[0]) +} + From 6aceb7639f55d40390d7540ade7d07fd2c78ba1f Mon Sep 17 00:00:00 2001 From: Mortimer Snerd Date: Wed, 25 Mar 2026 19:55:38 -0400 Subject: [PATCH 2/2] Clean up the name of the test file for this PR --- tests/issues/run.bat | 2 +- tests/issues/run.sh | 2 +- tests/issues/{test_pr_unknown.odin => test_pr_6476.odin} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/issues/{test_pr_unknown.odin => test_pr_6476.odin} (100%) diff --git a/tests/issues/run.bat b/tests/issues/run.bat index c1199ff73..2f42e3e21 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -34,7 +34,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_6401.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b ..\..\..\odin test ..\test_pr_6470.odin %COMMON% || exit /b ..\..\..\odin test ..\test_pr_6470.odin -define:TEST_EXPECT_FAILURE=true %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b -..\..\..\odin test ..\test_pr_unknown.odin %COMMON% || exit /b +..\..\..\odin test ..\test_pr_6476.odin %COMMON% || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index ef8c73351..8a7800cd7 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -37,7 +37,7 @@ $ODIN test ../test_issue_6068.odin $COMMON $ODIN test ../test_issue_6101.odin $COMMON $ODIN test ../test_issue_6165.odin $COMMON $ODIN test ../test_issue_6396.odin $COMMON -$ODIN test ../test_pr_unknown.odin $COMMON +$ODIN test ../test_pr_6476.odin $COMMON if [[ $($ODIN build ../test_issue_6240.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]] ; then echo "SUCCESSFUL 1/1" diff --git a/tests/issues/test_pr_unknown.odin b/tests/issues/test_pr_6476.odin similarity index 100% rename from tests/issues/test_pr_unknown.odin rename to tests/issues/test_pr_6476.odin