@@ -232,27 +232,53 @@ impl<T: Copy, const FIXED_BLOCK_SIZE: bool> BlockedVec<T, FIXED_BLOCK_SIZE> {
232232 /// `group_indices` must only point to the first `total_num_groups`
233233 /// groups, which is the contract of
234234 /// [`BlockedGroupsAccumulator::update_batch`] and
235- /// [`BlockedGroupsAccumulator::merge_batch`]. It is only checked in debug
236- /// builds, like the flat accumulators' unchecked indexing.
235+ /// [`BlockedGroupsAccumulator::merge_batch`].
237236 ///
238237 /// [`BlockedGroupsAccumulator::update_batch`]: datafusion_expr_common::blocked_groups_accumulator::BlockedGroupsAccumulator::update_batch
239238 /// [`BlockedGroupsAccumulator::merge_batch`]: datafusion_expr_common::blocked_groups_accumulator::BlockedGroupsAccumulator::merge_batch
239+ ///
240+ /// # Panics
241+ /// If an index in `group_indices` is not below `total_num_groups`.
242+ /// if there is an index in block that is larger than block size
240243 pub fn update < F > (
241244 & mut self ,
242245 total_num_groups : usize ,
243246 starting_value : T ,
244247 group_indices : & [ BlocksIndex ] ,
245248 nulls : Option < & NullBuffer > ,
246249 opt_filter : Option < & BooleanArray > ,
247- mut update_fn : F ,
250+ update_fn : F ,
248251 ) where
249252 F : FnMut ( & mut T ) ,
250253 {
251254 self . grow_to ( total_num_groups, starting_value) ;
252- self . debug_assert_in_bounds ( group_indices) ;
255+ self . assert_in_bounds ( group_indices) ;
256+
257+ // SAFETY: grown to `total_num_groups` and checked above
258+ unsafe {
259+ self . update_unchecked ( group_indices, nulls, opt_filter, update_fn) ;
260+ }
261+ }
262+
263+ /// Same as [`Self::update`], but does not grow the vector and the caller guarantees that indices are in bounds.
264+ ///
265+ /// # Safety
266+ /// The behavior is undefined if any of the following are true:
267+ /// 1. `groups_indices` contain block index outside the number of blocks (e.g. you did not call `grow_to` first to the expected number of values)
268+ /// 2. `groups_indices` index in block is larger than block size
269+ /// 3. `groups_indices` index in block for last blocks is larger than the number of elements in the last block
270+ pub unsafe fn update_unchecked < F > (
271+ & mut self ,
272+ group_indices : & [ BlocksIndex ] ,
273+ nulls : Option < & NullBuffer > ,
274+ opt_filter : Option < & BooleanArray > ,
275+ mut update_fn : F ,
276+ ) where
277+ F : FnMut ( & mut T ) ,
278+ {
253279 if let Some ( block) = self . as_single_block_mut ( ) {
254280 accumulate_blocked_indices ( group_indices, nulls, opt_filter, |index| {
255- // SAFETY: indices are in bounds, see the method docs
281+ // SAFETY: indices are in bounds, guaranteed by the caller
256282 update_fn ( unsafe { block. get_unchecked_mut ( index. index_in_block ( ) ) } )
257283 } ) ;
258284 return ;
@@ -263,7 +289,7 @@ impl<T: Copy, const FIXED_BLOCK_SIZE: bool> BlockedVec<T, FIXED_BLOCK_SIZE> {
263289 let mut addrs = [ std:: ptr:: null_mut :: < T > ( ) ; RESOLVE_CHUNK ] ;
264290 for chunk in group_indices. chunks ( RESOLVE_CHUNK ) {
265291 for ( addr, & index) in addrs. iter_mut ( ) . zip ( chunk) {
266- // SAFETY: indices are in bounds, see the method docs
292+ // SAFETY: indices are in bounds, guaranteed by the caller
267293 * addr = unsafe { ptrs. ptr ( index) } ;
268294 }
269295 for & addr in & addrs[ ..chunk. len ( ) ] {
@@ -274,7 +300,7 @@ impl<T: Copy, const FIXED_BLOCK_SIZE: bool> BlockedVec<T, FIXED_BLOCK_SIZE> {
274300 }
275301 } else {
276302 accumulate_blocked_indices ( group_indices, nulls, opt_filter, |index| {
277- // SAFETY: indices are in bounds, see the method docs
303+ // SAFETY: indices are in bounds, guaranteed by the caller
278304 update_fn ( unsafe { & mut * ptrs. ptr ( index) } )
279305 } ) ;
280306 }
@@ -289,31 +315,52 @@ impl<T: Copy, const FIXED_BLOCK_SIZE: bool> BlockedVec<T, FIXED_BLOCK_SIZE> {
289315 /// `group_indices` must only point to the first `total_num_groups`
290316 /// groups, which is the contract of
291317 /// [`BlockedGroupsAccumulator::update_batch`] and
292- /// [`BlockedGroupsAccumulator::merge_batch`]. It is only checked in debug
293- /// builds, like the flat accumulators' unchecked indexing.
318+ /// [`BlockedGroupsAccumulator::merge_batch`].
294319 ///
295320 /// [`BlockedGroupsAccumulator::update_batch`]: datafusion_expr_common::blocked_groups_accumulator::BlockedGroupsAccumulator::update_batch
296321 /// [`BlockedGroupsAccumulator::merge_batch`]: datafusion_expr_common::blocked_groups_accumulator::BlockedGroupsAccumulator::merge_batch
297322 ///
298323 /// # Panics
299- /// If `values` and `group_indices` have different lengths.
324+ /// If `values` and `group_indices` have different lengths, or an index in
325+ /// `group_indices` is not below `total_num_groups`.
300326 pub fn update_with < V , F > (
301327 & mut self ,
302328 total_num_groups : usize ,
303329 starting_value : T ,
304330 group_indices : & [ BlocksIndex ] ,
305331 values : & [ V ] ,
332+ update_fn : F ,
333+ ) where
334+ V : Copy ,
335+ F : FnMut ( & mut T , V ) ,
336+ {
337+ self . grow_to ( total_num_groups, starting_value) ;
338+ self . assert_in_bounds ( group_indices) ;
339+
340+ // SAFETY: grown to `total_num_groups` and checked above
341+ unsafe { self . update_with_unchecked ( group_indices, values, update_fn) }
342+ }
343+
344+ /// Same as [`Self::update_with`], but does not grow the vector and the caller guarantees that indices are in bounds.
345+ ///
346+ /// # Safety
347+ /// The behavior is undefined if any of the following are true:
348+ /// 1. `groups_indices` contain block index outside the number of blocks (e.g. you did not call `grow_to` first to the expected number of values)
349+ /// 2. `groups_indices` index in block is larger than block size
350+ /// 3. `groups_indices` index in block for last block is larger than the number of elements in the last block (e.g. you did not call `grow_to` first to the expected number of values)
351+ pub unsafe fn update_with_unchecked < V , F > (
352+ & mut self ,
353+ group_indices : & [ BlocksIndex ] ,
354+ values : & [ V ] ,
306355 mut update_fn : F ,
307356 ) where
308357 V : Copy ,
309358 F : FnMut ( & mut T , V ) ,
310359 {
311360 assert_eq ! ( group_indices. len( ) , values. len( ) ) ;
312- self . grow_to ( total_num_groups, starting_value) ;
313- self . debug_assert_in_bounds ( group_indices) ;
314361 if let Some ( block) = self . as_single_block_mut ( ) {
315362 for ( & index, & value) in group_indices. iter ( ) . zip ( values) {
316- // SAFETY: indices are in bounds, see the method docs
363+ // SAFETY: indices are in bounds, guaranteed by the caller
317364 update_fn (
318365 unsafe { block. get_unchecked_mut ( index. index_in_block ( ) ) } ,
319366 value,
@@ -329,7 +376,7 @@ impl<T: Copy, const FIXED_BLOCK_SIZE: bool> BlockedVec<T, FIXED_BLOCK_SIZE> {
329376 . zip ( values. chunks ( RESOLVE_CHUNK ) )
330377 {
331378 for ( addr, & index) in addrs. iter_mut ( ) . zip ( indices) {
332- // SAFETY: indices are in bounds, see the method docs
379+ // SAFETY: indices are in bounds, guaranteed by the caller
333380 * addr = unsafe { ptrs. ptr ( index) } ;
334381 }
335382 for ( & addr, & value) in addrs. iter ( ) . zip ( values) {
@@ -340,15 +387,13 @@ impl<T: Copy, const FIXED_BLOCK_SIZE: bool> BlockedVec<T, FIXED_BLOCK_SIZE> {
340387 }
341388 }
342389
343- /// Checks, in debug builds only, that every index points to an element.
390+ /// Checks that every index points to an element, so the update loops
391+ /// can index without bounds checks. Every block but the last holds
392+ /// `block_size` elements, see [`BlocksIndex::all_in_bounds`].
344393 #[ inline]
345- fn debug_assert_in_bounds ( & self , group_indices : & [ BlocksIndex ] ) {
346- debug_assert ! (
347- group_indices. iter( ) . all( |index| {
348- self . blocks
349- . get( index. block_index( ) )
350- . is_some_and( |block| index. index_in_block( ) < block. len( ) )
351- } ) ,
394+ fn assert_in_bounds ( & self , group_indices : & [ BlocksIndex ] ) {
395+ assert ! (
396+ BlocksIndex :: all_in_bounds( group_indices, self . block_size, self . len) ,
352397 "group index out of bounds"
353398 ) ;
354399 }
@@ -673,4 +718,18 @@ mod tests {
673718 fn zero_block_size_panics ( ) {
674719 let _ = BlockedVec :: < i64 > :: new ( 0 ) ;
675720 }
721+
722+ #[ test]
723+ #[ should_panic( expected = "group index out of bounds" ) ]
724+ fn update_out_of_bounds_panics ( ) {
725+ let mut v = BlockedVec :: < u64 > :: new ( 4 ) ;
726+ v. update ( 1 , 0 , & [ BlocksIndex :: new ( 0 , 1 ) ] , None , None , |v| * v += 1 ) ;
727+ }
728+
729+ #[ test]
730+ #[ should_panic( expected = "group index out of bounds" ) ]
731+ fn update_with_out_of_bounds_panics ( ) {
732+ let mut v = BlockedVec :: < u64 > :: new ( 4 ) ;
733+ v. update_with ( 6 , 0 , & [ BlocksIndex :: new ( 0 , 4 ) ] , & [ 1 ] , |v, x| * v += x) ;
734+ }
676735}
0 commit comments