Skip to content

Commit dfd05f7

Browse files
authored
Fix contract handling of promoted constants and constant static (rust-lang#3305)
When verifying contracts, CBMC initializes all static variables to non-deterministic values, except for those with constant types or with types / values annotated with `ID_C_no_nondet_initialization`. Kani compiler never set these flags, which caused spurious failures when verification depended on promoted constants or constant static variables. This fix changes that. First, I did a bit of refactoring since we may need to set this `Symbol` property at a later time for static variables. I also got rid of the initialization function, since the allocation initialization can be done directly from an expression. Then, I added the new property to the `Symbol` type. In CBMC, this is a property of the type or expression. However, I decided to add it to `Symbol` to avoid having to add this attribute to all variants of `Type` and `Expr`. Resolves rust-lang#3228
1 parent ea0f54a commit dfd05f7

File tree

18 files changed

+461
-108
lines changed

18 files changed

+461
-108
lines changed

cprover_bindings/src/goto_program/symbol.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use std::fmt::Display;
88

99
/// Based off the CBMC symbol implementation here:
1010
/// <https://github.com/diffblue/cbmc/blob/develop/src/util/symbol.h>
11+
///
12+
/// TODO: We should consider using BitFlags for all the boolean flags.
1113
#[derive(Clone, Debug)]
1214
pub struct Symbol {
1315
/// Unique identifier. Mangled name from compiler `foo12_bar17_x@1`
@@ -46,6 +48,14 @@ pub struct Symbol {
4648
pub is_thread_local: bool,
4749
pub is_volatile: bool,
4850
pub is_weak: bool,
51+
52+
/// This flag marks a variable as constant (IrepId: `ID_C_constant`).
53+
///
54+
/// In CBMC, this is a property of the type or expression. However, we keep it here to avoid
55+
/// having to propagate the attribute to all variants of `Type` and `Expr`.
56+
///
57+
/// During contract verification, CBMC will not havoc static variables marked as constant.
58+
pub is_static_const: bool,
4959
}
5060

5161
/// The equivalent of a "mathematical function" in CBMC. Semantically this is an
@@ -157,6 +167,7 @@ impl Symbol {
157167
is_lvalue: false,
158168
is_parameter: false,
159169
is_static_lifetime: false,
170+
is_static_const: false,
160171
is_thread_local: false,
161172
is_volatile: false,
162173
is_weak: false,
@@ -363,6 +374,11 @@ impl Symbol {
363374
self
364375
}
365376

377+
pub fn set_is_static_const(&mut self, v: bool) -> &mut Symbol {
378+
self.is_static_const = v;
379+
self
380+
}
381+
366382
pub fn with_is_state_var(mut self, v: bool) -> Symbol {
367383
self.is_state_var = v;
368384
self
@@ -383,11 +399,21 @@ impl Symbol {
383399
self
384400
}
385401

402+
pub fn set_pretty_name<T: Into<InternedString>>(&mut self, pretty_name: T) -> &mut Symbol {
403+
self.pretty_name = Some(pretty_name.into());
404+
self
405+
}
406+
386407
pub fn with_is_hidden(mut self, hidden: bool) -> Symbol {
387408
self.is_auxiliary = hidden;
388409
self
389410
}
390411

412+
pub fn set_is_hidden(&mut self, hidden: bool) -> &mut Symbol {
413+
self.is_auxiliary = hidden;
414+
self
415+
}
416+
391417
/// Set `is_property`.
392418
pub fn with_is_property(mut self, v: bool) -> Self {
393419
self.is_property = v;

cprover_bindings/src/goto_program/symbol_table.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ impl SymbolTable {
107107
self.symbol_table.get(&name)
108108
}
109109

110+
pub fn lookup_mut<T: Into<InternedString>>(&mut self, name: T) -> Option<&mut Symbol> {
111+
let name = name.into();
112+
self.symbol_table.get_mut(&name)
113+
}
114+
110115
pub fn machine_model(&self) -> &MachineModel {
111116
&self.machine_model
112117
}

cprover_bindings/src/irep/to_irep.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,10 @@ impl goto_program::Symbol {
598598
Irep::just_sub(contract.assigns.iter().map(|req| req.to_irep(mm)).collect()),
599599
);
600600
}
601+
if self.is_static_const {
602+
// Add a `const` to the type.
603+
typ = typ.with_named_sub(IrepId::CConstant, Irep::just_id(IrepId::from_int(1)))
604+
}
601605
super::Symbol {
602606
typ,
603607
value: match &self.value {

kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
use crate::codegen_cprover_gotoc::utils::slice_fat_ptr;
44
use crate::codegen_cprover_gotoc::GotocCtx;
55
use crate::unwrap_or_return_codegen_unimplemented;
6-
use cbmc::goto_program::{DatatypeComponent, Expr, ExprValue, Location, Stmt, Symbol, Type};
6+
use cbmc::goto_program::{DatatypeComponent, Expr, ExprValue, Location, Symbol, Type};
77
use rustc_middle::ty::Const as ConstInternal;
88
use rustc_smir::rustc_internal;
99
use rustc_span::Span as SpanInternal;
1010
use stable_mir::mir::alloc::{AllocId, GlobalAlloc};
1111
use stable_mir::mir::mono::{Instance, StaticDef};
12-
use stable_mir::mir::Operand;
12+
use stable_mir::mir::{Mutability, Operand};
1313
use stable_mir::ty::{
1414
Allocation, ConstantKind, FloatTy, FnDef, GenericArgs, IntTy, MirConst, RigidTy, Size, Ty,
1515
TyConst, TyConstKind, TyKind, UintTy,
@@ -470,11 +470,17 @@ impl<'tcx> GotocCtx<'tcx> {
470470
name: Option<String>,
471471
loc: Location,
472472
) -> Expr {
473-
debug!(?name, "codegen_const_allocation");
473+
debug!(?name, ?alloc, "codegen_const_allocation");
474474
let alloc_name = match self.alloc_map.get(alloc) {
475475
None => {
476476
let alloc_name = if let Some(name) = name { name } else { self.next_global_name() };
477-
self.codegen_alloc_in_memory(alloc.clone(), alloc_name.clone(), loc);
477+
let has_interior_mutabity = false; // Constants cannot be mutated.
478+
self.codegen_alloc_in_memory(
479+
alloc.clone(),
480+
alloc_name.clone(),
481+
loc,
482+
has_interior_mutabity,
483+
);
478484
alloc_name
479485
}
480486
Some(name) => name.clone(),
@@ -484,13 +490,18 @@ impl<'tcx> GotocCtx<'tcx> {
484490
mem_place.address_of()
485491
}
486492

487-
/// Insert an allocation into the goto symbol table, and generate a goto function that will
488-
/// initialize it.
493+
/// Insert an allocation into the goto symbol table, and generate an init value.
489494
///
490-
/// This function is ultimately responsible for creating new statically initialized global variables
491-
/// in our goto binaries.
492-
pub fn codegen_alloc_in_memory(&mut self, alloc: Allocation, name: String, loc: Location) {
493-
debug!(?alloc, ?name, "codegen_alloc_in_memory");
495+
/// This function is ultimately responsible for creating new statically initialized global
496+
/// variables.
497+
pub fn codegen_alloc_in_memory(
498+
&mut self,
499+
alloc: Allocation,
500+
name: String,
501+
loc: Location,
502+
has_interior_mutabity: bool,
503+
) {
504+
debug!(?name, ?alloc, "codegen_alloc_in_memory");
494505
let struct_name = &format!("{name}::struct");
495506

496507
// The declaration of a static variable may have one type and the constant initializer for
@@ -513,50 +524,40 @@ impl<'tcx> GotocCtx<'tcx> {
513524
.collect()
514525
});
515526

527+
// Create the allocation from a byte array.
528+
let init_fn = |gcx: &mut GotocCtx, var: Symbol| {
529+
let val = Expr::struct_expr_from_values(
530+
alloc_typ_ref.clone(),
531+
alloc_data
532+
.iter()
533+
.map(|d| match d {
534+
AllocData::Bytes(bytes) => Expr::array_expr(
535+
Type::unsigned_int(8).array_of(bytes.len()),
536+
bytes
537+
.iter()
538+
// We should consider adding a poison / undet where we have none
539+
// This mimics the behaviour before StableMIR though.
540+
.map(|b| Expr::int_constant(b.unwrap_or(0), Type::unsigned_int(8)))
541+
.collect(),
542+
),
543+
AllocData::Expr(e) => e.clone(),
544+
})
545+
.collect(),
546+
&gcx.symbol_table,
547+
);
548+
if val.typ() == &var.typ { val } else { val.transmute_to(var.typ, &gcx.symbol_table) }
549+
};
550+
516551
// The global static variable may not be in the symbol table if we are dealing
517-
// with a literal that can be statically allocated.
518-
// We need to make a constructor whether it was in the table or not, so we can't use the
519-
// closure argument to ensure_global_var to do that here.
520-
let var = self.ensure_global_var(
552+
// with a promoted constant.
553+
let _var = self.ensure_global_var_init(
521554
&name,
522555
false, //TODO is this correct?
556+
alloc.mutability == Mutability::Not && !has_interior_mutabity,
523557
alloc_typ_ref.clone(),
524558
loc,
525-
|_, _| None,
526-
);
527-
let var_typ = var.typ().clone();
528-
529-
// Assign the initial value `val` to `var` via an intermediate `temp_var` to allow for
530-
// transmuting the allocation type to the global static variable type.
531-
let val = Expr::struct_expr_from_values(
532-
alloc_typ_ref.clone(),
533-
alloc_data
534-
.iter()
535-
.map(|d| match d {
536-
AllocData::Bytes(bytes) => Expr::array_expr(
537-
Type::unsigned_int(8).array_of(bytes.len()),
538-
bytes
539-
.iter()
540-
// We should consider adding a poison / undet where we have none
541-
// This mimics the behaviour before StableMIR though.
542-
.map(|b| Expr::int_constant(b.unwrap_or(0), Type::unsigned_int(8)))
543-
.collect(),
544-
),
545-
AllocData::Expr(e) => e.clone(),
546-
})
547-
.collect(),
548-
&self.symbol_table,
549-
);
550-
let fn_name = Self::initializer_fn_name(&name);
551-
let temp_var = self.gen_function_local_variable(0, &fn_name, alloc_typ_ref, loc).to_expr();
552-
let body = Stmt::block(
553-
vec![
554-
Stmt::decl(temp_var.clone(), Some(val), loc),
555-
var.assign(temp_var.transmute_to(var_typ, &self.symbol_table), loc),
556-
],
557-
loc,
559+
init_fn,
558560
);
559-
self.register_initializer(&name, body);
560561

561562
self.alloc_map.insert(alloc, name);
562563
}
@@ -663,12 +664,6 @@ impl<'tcx> GotocCtx<'tcx> {
663664
let fn_item_struct_ty = self.codegen_fndef_type_stable(instance);
664665
// This zero-sized object that a function name refers to in Rust is globally unique, so we create such a global object.
665666
let fn_singleton_name = format!("{mangled_name}::FnDefSingleton");
666-
self.ensure_global_var(
667-
&fn_singleton_name,
668-
false,
669-
fn_item_struct_ty,
670-
loc,
671-
|_, _| None, // zero-sized, so no initialization necessary
672-
)
667+
self.ensure_global_var(&fn_singleton_name, false, fn_item_struct_ty, loc).to_expr()
673668
}
674669
}

kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,9 +1444,10 @@ impl<'tcx> GotocCtx<'tcx> {
14441444
let vtable_name = self.vtable_name_stable(trait_type).intern();
14451445
let vtable_impl_name = format!("{vtable_name}_impl_for_{src_name}");
14461446

1447-
self.ensure_global_var(
1447+
self.ensure_global_var_init(
14481448
vtable_impl_name,
14491449
true,
1450+
true,
14501451
Type::struct_tag(vtable_name),
14511452
loc,
14521453
|ctx, var| {
@@ -1487,11 +1488,10 @@ impl<'tcx> GotocCtx<'tcx> {
14871488
vtable_fields,
14881489
&ctx.symbol_table,
14891490
);
1490-
let body = var.assign(vtable, loc);
1491-
let block = Stmt::block(vec![size_assert, body], loc);
1492-
Some(block)
1491+
Expr::statement_expression(vec![size_assert, vtable.as_stmt(loc)], var.typ, loc)
14931492
},
14941493
)
1494+
.to_expr()
14951495
}
14961496

14971497
/// Cast a pointer to a fat pointer.

kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,8 @@ impl<'tcx> GotocCtx<'tcx> {
317317
fn codegen_ret_unit(&mut self, loc: Location) -> Stmt {
318318
let is_file_local = false;
319319
let ty = self.codegen_ty_unit();
320-
let var =
321-
self.ensure_global_var(FN_RETURN_VOID_VAR_NAME, is_file_local, ty, loc, |_, _| None);
322-
Stmt::ret(Some(var), loc)
320+
let var = self.ensure_global_var(FN_RETURN_VOID_VAR_NAME, is_file_local, ty, loc);
321+
Stmt::ret(Some(var.to_expr()), loc)
323322
}
324323

325324
/// Generates Goto-C for MIR [TerminatorKind::Drop] calls. We only handle code _after_ Rust's "drop elaboration"

kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! This file contains functions related to codegenning MIR static variables into gotoc
55
66
use crate::codegen_cprover_gotoc::GotocCtx;
7-
use cbmc::goto_program::Symbol;
7+
use crate::kani_middle::is_interior_mut;
88
use stable_mir::mir::mono::{Instance, StaticDef};
99
use stable_mir::CrateDef;
1010
use tracing::debug;
@@ -19,7 +19,12 @@ impl<'tcx> GotocCtx<'tcx> {
1919
debug!("codegen_static");
2020
let alloc = def.eval_initializer().unwrap();
2121
let symbol_name = Instance::from(def).mangled_name();
22-
self.codegen_alloc_in_memory(alloc, symbol_name, self.codegen_span_stable(def.span()));
22+
self.codegen_alloc_in_memory(
23+
alloc,
24+
symbol_name,
25+
self.codegen_span_stable(def.span()),
26+
is_interior_mut(self.tcx, def.ty()),
27+
);
2328
}
2429

2530
/// Mutates the Goto-C symbol table to add a forward-declaration of the static variable.
@@ -37,9 +42,8 @@ impl<'tcx> GotocCtx<'tcx> {
3742
// havoc static variables. Kani uses the location and pretty name to identify
3843
// the correct variables. If the wrong name is used, CBMC may fail silently.
3944
// More details at https://github.com/diffblue/cbmc/issues/8225.
40-
let symbol = Symbol::static_variable(symbol_name.clone(), symbol_name, typ, location)
41-
.with_is_hidden(false) // Static items are always user defined.
42-
.with_pretty_name(pretty_name);
43-
self.symbol_table.insert(symbol);
45+
self.ensure_global_var(symbol_name, false, typ, location)
46+
.set_is_hidden(false) // Static items are always user defined.
47+
.set_pretty_name(pretty_name);
4448
}
4549
}

0 commit comments

Comments
 (0)