@@ -36,17 +36,23 @@ impl IsolatedAlloc {
3636 page_ptrs : Vec :: new ( ) ,
3737 huge_ptrs : Vec :: new ( ) ,
3838 page_infos : Vec :: new ( ) ,
39+ // SAFETY: `sysconf(_SC_PAGESIZE)` is always safe to call at runtime
40+ // See https://www.man7.org/linux/man-pages/man3/sysconf.3.html
3941 page_size : unsafe { libc:: sysconf ( libc:: _SC_PAGESIZE) . try_into ( ) . unwrap ( ) } ,
4042 }
4143 }
4244
4345 /// Expands the available memory pool by adding one page.
44- fn add_page ( & mut self , page_size : usize ) -> ( * mut u8 , & mut DenseBitSet < usize > ) {
45- let page_layout = unsafe { Layout :: from_size_align_unchecked ( page_size, page_size) } ;
46+ fn add_page ( & mut self ) -> ( * mut u8 , & mut DenseBitSet < usize > ) {
47+ // SAFETY: the system page size must always be a power of 2 and nonzero,
48+ // and cannot overflow an isize on the host.
49+ let page_layout =
50+ unsafe { Layout :: from_size_align_unchecked ( self . page_size , self . page_size ) } ;
51+ // SAFETY: The system page size, which is the layout size, cannot be 0
4652 let page_ptr = unsafe { alloc:: alloc ( page_layout) } ;
4753 // `page_infos` has to have one bit for each `COMPRESSION_FACTOR`-sized chunk of bytes in the page.
48- assert ! ( page_size % COMPRESSION_FACTOR == 0 ) ;
49- self . page_infos . push ( DenseBitSet :: new_empty ( page_size / COMPRESSION_FACTOR ) ) ;
54+ assert ! ( self . page_size % COMPRESSION_FACTOR == 0 ) ;
55+ self . page_infos . push ( DenseBitSet :: new_empty ( self . page_size / COMPRESSION_FACTOR ) ) ;
5056 self . page_ptrs . push ( page_ptr) ;
5157 ( page_ptr, self . page_infos . last_mut ( ) . unwrap ( ) )
5258 }
@@ -85,13 +91,15 @@ impl IsolatedAlloc {
8591 ///
8692 /// SAFETY: See `alloc::alloc()`
8793 pub unsafe fn alloc ( & mut self , layout : Layout ) -> * mut u8 {
94+ // SAFETY: Upheld by caller
8895 unsafe { self . allocate ( layout, false ) }
8996 }
9097
9198 /// Same as `alloc`, but zeroes out the memory.
9299 ///
93100 /// SAFETY: See `alloc::alloc_zeroed()`
94101 pub unsafe fn alloc_zeroed ( & mut self , layout : Layout ) -> * mut u8 {
102+ // SAFETY: Upheld by caller
95103 unsafe { self . allocate ( layout, true ) }
96104 }
97105
@@ -103,9 +111,13 @@ impl IsolatedAlloc {
103111 unsafe fn allocate ( & mut self , layout : Layout , zeroed : bool ) -> * mut u8 {
104112 let ( size, align) = IsolatedAlloc :: normalized_layout ( layout) ;
105113 if IsolatedAlloc :: is_huge_alloc ( size, align, self . page_size ) {
114+ // SAFETY: Validity of `layout` upheld by caller; we checked that
115+ // the size and alignment are appropriate for being a huge alloc
106116 unsafe { self . alloc_huge ( layout, zeroed) }
107117 } else {
108118 for ( & mut page, pinfo) in std:: iter:: zip ( & mut self . page_ptrs , & mut self . page_infos ) {
119+ // SAFETY: The value in `self.page_size` is used to allocate
120+ // `page`, with page alignment
109121 if let Some ( ptr) =
110122 unsafe { Self :: alloc_from_page ( self . page_size , layout, page, pinfo, zeroed) }
111123 {
@@ -117,7 +129,9 @@ impl IsolatedAlloc {
117129 let page_size = self . page_size ;
118130 // Add another page and allocate from it; this cannot fail since the
119131 // new page is empty and we already asserted it fits into a page
120- let ( page, pinfo) = self . add_page ( page_size) ;
132+ let ( page, pinfo) = self . add_page ( ) ;
133+
134+ // SAFETY: See comment on `alloc_from_page` above
121135 unsafe { Self :: alloc_from_page ( page_size, layout, page, pinfo, zeroed) . unwrap ( ) }
122136 }
123137 }
@@ -149,6 +163,11 @@ impl IsolatedAlloc {
149163 let range_avail = !( idx_pinfo..idx_pinfo + size_pinfo) . any ( |idx| pinfo. contains ( idx) ) ;
150164 if range_avail {
151165 pinfo. insert_range ( idx_pinfo..idx_pinfo + size_pinfo) ;
166+ // SAFETY: We checked the available bytes after `idx` in the call
167+ // to `domain_size` above and asserted there are at least `idx +
168+ // layout.size()` bytes available and unallocated after it.
169+ // `page` must point to the start of the page, so adding `idx`
170+ // is safe per the above.
152171 unsafe {
153172 let ptr = page. add ( idx) ;
154173 if zeroed {
@@ -168,6 +187,7 @@ impl IsolatedAlloc {
168187 /// SAFETY: Same as `alloc()`.
169188 unsafe fn alloc_huge ( & mut self , layout : Layout , zeroed : bool ) -> * mut u8 {
170189 let layout = IsolatedAlloc :: huge_normalized_layout ( layout, self . page_size ) ;
190+ // SAFETY: Upheld by caller
171191 let ret =
172192 unsafe { if zeroed { alloc:: alloc_zeroed ( layout) } else { alloc:: alloc ( layout) } } ;
173193 self . huge_ptrs . push ( ( ret, layout. size ( ) ) ) ;
@@ -183,6 +203,8 @@ impl IsolatedAlloc {
183203 let ( size, align) = IsolatedAlloc :: normalized_layout ( layout) ;
184204
185205 if IsolatedAlloc :: is_huge_alloc ( size, align, self . page_size ) {
206+ // SAFETY: Partly upheld by caller, and we checked that the size
207+ // and align mean this must have been allocated via `alloc_huge`
186208 unsafe {
187209 self . dealloc_huge ( ptr, layout) ;
188210 }
@@ -195,8 +217,9 @@ impl IsolatedAlloc {
195217 // Find the page this allocation belongs to.
196218 // This could be made faster if the list was sorted -- the allocator isn't fully optimized at the moment.
197219 let pinfo = std:: iter:: zip ( & mut self . page_ptrs , & mut self . page_infos )
198- . find ( |( page, _) | page. addr ( ) == page_addr) ;
199- let Some ( ( _, pinfo) ) = pinfo else {
220+ . enumerate ( )
221+ . find ( |( _, ( page, _) ) | page. addr ( ) == page_addr) ;
222+ let Some ( ( idx_of_pinfo, ( _, pinfo) ) ) = pinfo else {
200223 panic ! (
201224 "Freeing in an unallocated page: {ptr:?}\n Holding pages {:?}" ,
202225 self . page_ptrs
@@ -209,15 +232,18 @@ impl IsolatedAlloc {
209232 pinfo. remove ( idx) ;
210233 }
211234
212- // We allocated all the pages with this layout
213- let page_layout =
214- unsafe { Layout :: from_size_align_unchecked ( self . page_size , self . page_size ) } ;
215235 // Only 1 page might have been freed after a dealloc, so if it exists,
216236 // find it and free it (and adjust the vectors)
217- if let Some ( free_idx) = self . page_infos . iter ( ) . position ( |pinfo| pinfo. is_empty ( ) ) {
218- self . page_infos . remove ( free_idx) ;
237+ if pinfo. is_empty ( ) {
238+ // SAFETY: `self.page_size` is always a power of 2 and does not overflow an isize
239+ let page_layout =
240+ unsafe { Layout :: from_size_align_unchecked ( self . page_size , self . page_size ) } ;
241+ self . page_infos . remove ( idx_of_pinfo) ;
242+ // SAFETY: We checked that there are no outstanding allocations
243+ // from us pointing to this page, and we know it was allocated
244+ // with this layout
219245 unsafe {
220- alloc:: dealloc ( self . page_ptrs . remove ( free_idx ) , page_layout) ;
246+ alloc:: dealloc ( self . page_ptrs . remove ( idx_of_pinfo ) , page_layout) ;
221247 }
222248 }
223249 }
@@ -235,6 +261,7 @@ impl IsolatedAlloc {
235261 . expect ( "Freeing unallocated pages" ) ;
236262 // And kick it from the list
237263 self . huge_ptrs . remove ( idx) ;
264+ // SAFETY: Caller ensures validity of the layout
238265 unsafe {
239266 alloc:: dealloc ( ptr, layout) ;
240267 }
@@ -247,7 +274,10 @@ mod tests {
247274
248275 /// Helper function to assert that all bytes from `ptr` to `ptr.add(layout.size())`
249276 /// are zeroes.
250- fn assert_zeroes ( ptr : * mut u8 , layout : Layout ) {
277+ ///
278+ /// SAFETY: `ptr` must have been allocated with `layout`.
279+ unsafe fn assert_zeroes ( ptr : * mut u8 , layout : Layout ) {
280+ // SAFETY: Caller ensures this is valid
251281 unsafe {
252282 for ofs in 0 ..layout. size ( ) {
253283 assert_eq ! ( 0 , ptr. add( ofs) . read( ) ) ;
@@ -261,9 +291,11 @@ mod tests {
261291 let mut alloc = IsolatedAlloc :: new ( ) ;
262292 // 256 should be less than the pagesize on *any* system
263293 let layout = Layout :: from_size_align ( 256 , 32 ) . unwrap ( ) ;
294+ // SAFETY: layout size is the constant above, not 0
264295 let ptr = unsafe { alloc. alloc_zeroed ( layout) } ;
265- assert_zeroes ( ptr, layout) ;
296+ // SAFETY: ` ptr` was just allocated with ` layout`
266297 unsafe {
298+ assert_zeroes ( ptr, layout) ;
267299 alloc. dealloc ( ptr, layout) ;
268300 }
269301 }
@@ -274,9 +306,11 @@ mod tests {
274306 let mut alloc = IsolatedAlloc :: new ( ) ;
275307 // 16k is about as big as pages get e.g. on macos aarch64
276308 let layout = Layout :: from_size_align ( 16 * 1024 , 128 ) . unwrap ( ) ;
309+ // SAFETY: layout size is the constant above, not 0
277310 let ptr = unsafe { alloc. alloc_zeroed ( layout) } ;
278- assert_zeroes ( ptr, layout) ;
311+ // SAFETY: ` ptr` was just allocated with ` layout`
279312 unsafe {
313+ assert_zeroes ( ptr, layout) ;
280314 alloc. dealloc ( ptr, layout) ;
281315 }
282316 }
@@ -289,24 +323,21 @@ mod tests {
289323 // Try both sub-pagesize allocs and those larger than / equal to a page
290324 for sz in ( 1 ..=( 16 * 1024 ) ) . step_by ( 128 ) {
291325 let layout = Layout :: from_size_align ( sz, 1 ) . unwrap ( ) ;
326+ // SAFETY: all sizes in the range above are nonzero as we start from 1
292327 let ptr = unsafe { alloc. alloc_zeroed ( layout) } ;
293- assert_zeroes ( ptr, layout) ;
328+ // SAFETY: `ptr` was just allocated with `layout`, which was used
329+ // to bound the access size
294330 unsafe {
331+ assert_zeroes ( ptr, layout) ;
295332 ptr. write_bytes ( 255 , sz) ;
296333 alloc. dealloc ( ptr, layout) ;
297334 }
298335 }
299336 }
300337
301- /// Checks that allocations of different sizes do not overlap.
302- #[ test]
303- fn no_overlaps ( ) {
304- let mut alloc = IsolatedAlloc :: new ( ) ;
305- no_overlaps_inner ( & mut alloc) ;
306- }
307-
308- /// Allows us to reuse this bit for `no_overlaps` and `check_leaks`.
309- fn no_overlaps_inner ( alloc : & mut IsolatedAlloc ) {
338+ /// Checks that allocations of different sizes do not overlap. Not a freestanding
339+ /// test because we use it as part of `check_leaks()` also.
340+ fn no_overlaps ( alloc : & mut IsolatedAlloc ) {
310341 // Some random sizes and aligns
311342 let mut sizes = vec ! [ 32 ; 10 ] ;
312343 sizes. append ( & mut vec ! [ 15 ; 4 ] ) ;
@@ -327,13 +358,18 @@ mod tests {
327358 let layouts: Vec < _ > = std:: iter:: zip ( sizes, aligns)
328359 . map ( |( sz, al) | Layout :: from_size_align ( sz, al) . unwrap ( ) )
329360 . collect ( ) ;
361+ // SAFETY: all sizes specified in `sizes` are nonzero
330362 let ptrs: Vec < _ > =
331363 layouts. iter ( ) . map ( |layout| unsafe { alloc. alloc_zeroed ( * layout) } ) . collect ( ) ;
332364
333365 for ( & ptr, & layout) in std:: iter:: zip ( & ptrs, & layouts) {
334366 // We requested zeroed allocations, so check that that's true
335367 // Then write to the end of the current size, so if the allocs
336- // overlap (or the zeroing is wrong) then `assert_zeroes` will panic
368+ // overlap (or the zeroing is wrong) then `assert_zeroes` will panic.
369+ // Also check that the alignment we asked for was respected
370+ assert_eq ! ( ptr. addr( ) . rem_euclid( layout. align( ) ) , 0 ) ;
371+ // SAFETY: each `ptr` was allocated with its corresponding `layout`,
372+ // which is used to bound the access size
337373 unsafe {
338374 assert_zeroes ( ptr, layout) ;
339375 ptr. write_bytes ( 255 , layout. size ( ) ) ;
@@ -346,9 +382,9 @@ mod tests {
346382 #[ test]
347383 fn check_leaks ( ) {
348384 let mut alloc = IsolatedAlloc :: new ( ) ;
349-
350385 // Generate some noise first so leaks can manifest
351- no_overlaps_inner ( & mut alloc) ;
386+ no_overlaps ( & mut alloc) ;
387+
352388 // And then verify that no memory was leaked
353389 assert ! ( alloc. page_ptrs. is_empty( ) && alloc. huge_ptrs. is_empty( ) ) ;
354390 }
0 commit comments