@@ -298,6 +298,72 @@ impl<'tcx> GotocHook<'tcx> for SliceFromRawPart {
298
298
}
299
299
}
300
300
301
+ /// This hook intercepts calls to `memcmp` and skips CBMC's pointer checks if the number of bytes to be compared is zero.
302
+ /// See issue <https://github.com/model-checking/kani/issues/1489>
303
+ ///
304
+ /// This compiles `memcmp(first, second, count)` to:
305
+ /// ```c
306
+ /// count_var = count;
307
+ /// first_var = first;
308
+ /// second_var = second;
309
+ /// count_var == 0 && first_var != NULL && second_var != NULL ? 0 : memcmp(first_var, second_var, count_var)
310
+ /// ```
311
+ pub struct MemCmp ;
312
+
313
+ impl < ' tcx > GotocHook < ' tcx > for MemCmp {
314
+ fn hook_applies ( & self , tcx : TyCtxt < ' tcx > , instance : Instance < ' tcx > ) -> bool {
315
+ let name = with_no_trimmed_paths ! ( tcx. def_path_str( instance. def_id( ) ) ) ;
316
+ name == "core::slice::cmp::memcmp" || name == "std::slice::cmp::memcmp"
317
+ }
318
+
319
+ fn handle (
320
+ & self ,
321
+ tcx : & mut GotocCtx < ' tcx > ,
322
+ instance : Instance < ' tcx > ,
323
+ mut fargs : Vec < Expr > ,
324
+ assign_to : Place < ' tcx > ,
325
+ target : Option < BasicBlock > ,
326
+ span : Option < Span > ,
327
+ ) -> Stmt {
328
+ let loc = tcx. codegen_span_option ( span) ;
329
+ let target = target. unwrap ( ) ;
330
+ let first = fargs. remove ( 0 ) ;
331
+ let second = fargs. remove ( 0 ) ;
332
+ let count = fargs. remove ( 0 ) ;
333
+ let ( count_var, count_decl) = tcx. decl_temp_variable ( count. typ ( ) . clone ( ) , Some ( count) , loc) ;
334
+ let ( first_var, first_decl) = tcx. decl_temp_variable ( first. typ ( ) . clone ( ) , Some ( first) , loc) ;
335
+ let ( second_var, second_decl) =
336
+ tcx. decl_temp_variable ( second. typ ( ) . clone ( ) , Some ( second) , loc) ;
337
+ let is_count_zero = count_var. clone ( ) . is_zero ( ) ;
338
+ // We have to ensure that the pointers are valid even if we're comparing zero bytes.
339
+ // According to Rust's current definition (see https://github.com/model-checking/kani/issues/1489),
340
+ // this means they have to be non-null and aligned.
341
+ // But alignment is automatically satisfied because `memcmp` takes `*const u8` pointers.
342
+ let is_first_ok = first_var. clone ( ) . is_nonnull ( ) ;
343
+ let is_second_ok = second_var. clone ( ) . is_nonnull ( ) ;
344
+ let should_skip_pointer_checks = is_count_zero. and ( is_first_ok) . and ( is_second_ok) ;
345
+ let place_expr =
346
+ unwrap_or_return_codegen_unimplemented_stmt ! ( tcx, tcx. codegen_place( & assign_to) )
347
+ . goto_expr ;
348
+ let rhs = should_skip_pointer_checks. ternary (
349
+ Expr :: int_constant ( 0 , place_expr. typ ( ) . clone ( ) ) , // zero bytes are always equal (as long as pointers are nonnull and aligned)
350
+ tcx. codegen_func_expr ( instance, span. as_ref ( ) )
351
+ . call ( vec ! [ first_var, second_var, count_var] ) ,
352
+ ) ;
353
+ let code = place_expr. assign ( rhs, loc) . with_location ( loc) ;
354
+ Stmt :: block (
355
+ vec ! [
356
+ count_decl,
357
+ first_decl,
358
+ second_decl,
359
+ code,
360
+ Stmt :: goto( tcx. current_fn( ) . find_label( & target) , loc) ,
361
+ ] ,
362
+ loc,
363
+ )
364
+ }
365
+ }
366
+
301
367
pub fn fn_hooks < ' tcx > ( ) -> GotocHooks < ' tcx > {
302
368
GotocHooks {
303
369
hooks : vec ! [
@@ -308,6 +374,7 @@ pub fn fn_hooks<'tcx>() -> GotocHooks<'tcx> {
308
374
Rc :: new( Nondet ) ,
309
375
Rc :: new( RustAlloc ) ,
310
376
Rc :: new( SliceFromRawPart ) ,
377
+ Rc :: new( MemCmp ) ,
311
378
] ,
312
379
}
313
380
}
0 commit comments