compiler: Reject overflowing arena allocations
873a9fac96c0f47bb8e2a86a480d42104902079d735fd96c8a948c6b6ae03e13
1 parent
b4d4c376
lib/std/lang/alloc.rad
+8 -6
| 4 | 4 | //! byte buffer. Memory is never freed individually - the entire arena is |
|
| 5 | 5 | //! reset at once. This is ideal for compiler passes where all allocations |
|
| 6 | 6 | //! have the same lifetime. |
|
| 7 | 7 | @test mod tests; |
|
| 8 | 8 | ||
| 9 | - | use std::mem; |
|
| 10 | - | ||
| 11 | 9 | /// Error thrown by allocator. |
|
| 12 | 10 | export union AllocError: Copy { |
|
| 13 | 11 | /// Allocator is out of memory. |
|
| 14 | 12 | OutOfMemory, |
|
| 15 | 13 | } |
| 37 | 35 | /// appropriate type and initializing the memory. |
|
| 38 | 36 | export fn alloc(arena: &mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) { |
|
| 39 | 37 | assert alignment > 0; |
|
| 40 | 38 | assert size > 0; |
|
| 41 | 39 | ||
| 42 | - | let aligned = mem::alignUp(arena.offset, alignment); |
|
| 43 | - | let newOffset = aligned + size; |
|
| 44 | - | ||
| 45 | - | if newOffset > arena.data.len as u32 { |
|
| 40 | + | let aligned64 = (arena.offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1); |
|
| 41 | + | if aligned64 > arena.data.len as u64 or size as u64 > arena.data.len as u64 - aligned64 { |
|
| 46 | 42 | throw AllocError::OutOfMemory; |
|
| 47 | 43 | } |
|
| 44 | + | let aligned = aligned64 as u32; |
|
| 45 | + | let newOffset = aligned + size; |
|
| 46 | + | ||
| 48 | 47 | let base: *mut u8 = &mut arena.data[aligned]; |
|
| 49 | 48 | set arena.offset = newOffset; |
|
| 50 | 49 | ||
| 51 | 50 | return base as *mut opaque; |
|
| 52 | 51 | } |
| 97 | 96 | /// Throws `AllocError` if the arena is exhausted. |
|
| 98 | 97 | export unsafe fn allocSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) { |
|
| 99 | 98 | if count == 0 { |
|
| 100 | 99 | return &mut []; |
|
| 101 | 100 | } |
|
| 101 | + | if size > 0xffffffff / count { |
|
| 102 | + | throw AllocError::OutOfMemory; |
|
| 103 | + | } |
|
| 102 | 104 | let ptr = try alloc(arena, size * count, alignment); |
|
| 103 | 105 | ||
| 104 | 106 | return @sliceOf(ptr, count); |
|
| 105 | 107 | } |
|
| 106 | 108 |
lib/std/lang/alloc/tests.rad
+25 -0
| 118 | 118 | try testing::expect(super::used(&arena) == 24); |
|
| 119 | 119 | ||
| 120 | 120 | // Verify the pointers are distinct. |
|
| 121 | 121 | try testing::expect(p1 as u64 <> p2 as u64); |
|
| 122 | 122 | } |
|
| 123 | + | ||
| 124 | + | /// Large counts and offsets must fail before arithmetic wraps or storage changes. |
|
| 125 | + | @test unsafe fn testAllocOverflow() throws (testing::TestError) { |
|
| 126 | + | static bytes: [u8; 64] = [0; 64]; |
|
| 127 | + | let mut arena = super::new(&mut bytes[..]); |
|
| 128 | + | set arena.offset = 8; |
|
| 129 | + | let mut failed: u32 = 0; |
|
| 130 | + | try super::allocSlice(&mut arena, 8, 8, 0x20000000) catch { |
|
| 131 | + | set failed += 1; |
|
| 132 | + | }; |
|
| 133 | + | try super::alloc(&mut arena, 0xffffffff, 8) catch { |
|
| 134 | + | set failed += 1; |
|
| 135 | + | }; |
|
| 136 | + | assert failed == 2 and arena.offset == 8; |
|
| 137 | + | set arena.offset = 0xfffffff8; |
|
| 138 | + | try super::alloc(&mut arena, 16, 16) catch { |
|
| 139 | + | set failed += 1; |
|
| 140 | + | }; |
|
| 141 | + | assert failed == 3 and arena.offset == 0xfffffff8; |
|
| 142 | + | set arena.offset = 8; |
|
| 143 | + | let storage = try super::alloc(&mut arena, 8, 8) catch { |
|
| 144 | + | throw testing::TestError::Failed; |
|
| 145 | + | }; |
|
| 146 | + | assert arena.offset == 16; |
|
| 147 | + | } |