Skip to content

Commit 8348dc2

Browse files
authored
Fix static variable initialization when they have the same value (#2469)
We were incorrectly deduping the allocation of constant static variables. Static variables should each have their own memory allocation, and it should be correctly initialized. The fix to this issue was basically the change to static_var.rs. The changes to operand.rs was a bit of cleanup to make it explicit that we should only try to dedupe constant allocations.
1 parent c87fb24 commit 8348dc2

3 files changed

Lines changed: 47 additions & 66 deletions

File tree

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

Lines changed: 21 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ impl<'tcx> GotocCtx<'tcx> {
130130
}
131131
ConstValue::ByRef { alloc, offset } => {
132132
debug!("ConstValue by ref {:?} {:?}", alloc, offset);
133-
let mem_var = self
134-
.codegen_allocation_auto_imm_name(alloc.inner(), |tcx| tcx.next_global_name());
133+
let mem_var = self.codegen_const_allocation(alloc.inner(), None);
135134
mem_var
136135
.cast_to(Type::unsigned_int(8).to_pointer())
137136
.plus(Expr::int_constant(offset.bytes(), Type::unsigned_int(64)))
@@ -165,8 +164,7 @@ impl<'tcx> GotocCtx<'tcx> {
165164
// These seem to always start at 0
166165
assert_eq!(start, 0);
167166
// Create a static variable that holds its value
168-
let mem_var =
169-
self.codegen_allocation_auto_imm_name(data, |tcx| tcx.next_global_name());
167+
let mem_var = self.codegen_const_allocation(data, None);
170168

171169
// Extract identifier for static variable.
172170
// codegen_allocation_auto_imm_name returns the *address* of
@@ -464,15 +462,15 @@ impl<'tcx> GotocCtx<'tcx> {
464462
// crates do not conflict. The name alone is insufficient because Rust
465463
// allows different versions of the same crate to be used.
466464
let name = format!("{}::{alloc_id:?}", self.full_crate_name());
467-
self.codegen_allocation(alloc.inner(), |_| name.clone(), Some(name.clone()))
465+
self.codegen_const_allocation(alloc.inner(), Some(name))
468466
}
469467
GlobalAlloc::VTable(ty, trait_ref) => {
470468
// This is similar to GlobalAlloc::Memory but the type is opaque to rust and it
471469
// requires a bit more logic to get information about the allocation.
472470
let alloc_id = self.tcx.vtable_allocation((ty, trait_ref));
473471
let alloc = self.tcx.global_alloc(alloc_id).unwrap_memory();
474472
let name = format!("{}::{alloc_id:?}", self.full_crate_name());
475-
self.codegen_allocation(alloc.inner(), |_| name.clone(), Some(name.clone()))
473+
self.codegen_const_allocation(alloc.inner(), Some(name))
476474
}
477475
};
478476
assert!(res_t.is_pointer() || res_t.is_transparent_type(&self.symbol_table));
@@ -530,77 +528,36 @@ impl<'tcx> GotocCtx<'tcx> {
530528
sym.clone().to_expr().address_of()
531529
}
532530

533-
/// `codegen_allocation` but without an `imm_name`
531+
/// Generate an expression that represents the address for a constant allocation.
534532
///
535-
/// TODO: This could use some refactoring. This function is only ever invoked as:
536-
/// `self.codegen_allocation_auto_imm_name(alloc, |tcx| tcx.next_global_name())`
537-
fn codegen_allocation_auto_imm_name<F: FnOnce(&mut GotocCtx<'tcx>) -> String>(
538-
&mut self,
539-
alloc: &'tcx Allocation,
540-
mut_name: F,
541-
) -> Expr {
542-
self.codegen_allocation(alloc, mut_name, None)
543-
}
544-
545-
/// Add a new static allocation to the symbol table (if not already present) and
546-
/// generate a goto expression that points to it.
547-
///
548-
/// This is *the* mechanism for generating (and ensuring the intialization of) a global
549-
/// (often but not necessarily immutable) variable in the symbol table.
550-
///
551-
/// We need to codegen allocations from two sources:
552-
/// 1. `codegen_static` which supplies the initialization code for top-level statics.
553-
/// 2. Constants, scattered throughout the source, which need to be initialized statically,
554-
/// so those functions can reference them. (These should always be immutable.)
555-
/// (These also require de-duplication.)
556-
///
557-
/// TODO: This function could use some refactoring. Outside of `codegen_allocation_auto_imm_name`
558-
/// above, this function is only ever invoked as:
559-
/// `self.codegen_allocation(alloc, |_| name.clone(), Some(name.clone()))`
560-
pub fn codegen_allocation<F: FnOnce(&mut GotocCtx<'tcx>) -> String>(
561-
&mut self,
562-
alloc: &'tcx Allocation,
563-
mut_name: F,
564-
imm_name: Option<String>,
565-
) -> Expr {
566-
debug!("codegen_allocation imm_name {:?}", imm_name);
567-
let mem_var = match alloc.mutability {
568-
Mutability::Mut => {
569-
let name = mut_name(self);
570-
self.codegen_alloc_in_memory(alloc, name.clone());
571-
// here we know name must be in the symbol table
572-
self.symbol_table.lookup(&name).unwrap().clone().to_expr()
573-
}
574-
Mutability::Not => self.codegen_immutable_allocation(alloc, imm_name),
575-
};
576-
mem_var.address_of()
577-
}
578-
579-
/// Generate a goto expression for an immutable (constant) allocation.
533+
/// This function will only allocate a new memory location if necessary. The standard does
534+
/// not offer any guarantees over the location of a constant.
580535
///
581-
/// This function's primary purpose is to de-duplicate immutable global constants.
582-
/// If it already has been codegen'd, use that.
583-
/// If not, we otherwise proceed identically to the mutable case and call `codegen_alloc_in_memory`
584-
fn codegen_immutable_allocation(
585-
&mut self,
586-
alloc: &'tcx Allocation,
587-
name: Option<String>,
588-
) -> Expr {
536+
/// These constants can be named constants which are declared by the user, or constant values
537+
/// used scattered throughout the source
538+
fn codegen_const_allocation(&mut self, alloc: &'tcx Allocation, name: Option<String>) -> Expr {
539+
debug!(?name, "codegen_const_allocation");
540+
assert_eq!(
541+
alloc.mutability,
542+
Mutability::Not,
543+
"Expected constant allocation for `{name:?}`, but got a mutable instead"
544+
);
589545
if !self.alloc_map.contains_key(&alloc) {
590546
let name = if let Some(name) = name { name } else { self.next_global_name() };
591547
self.codegen_alloc_in_memory(alloc, name);
592548
}
593549

594-
self.symbol_table.lookup(&self.alloc_map.get(&alloc).unwrap()).unwrap().to_expr()
550+
let mem_place =
551+
self.symbol_table.lookup(&self.alloc_map.get(&alloc).unwrap()).unwrap().to_expr();
552+
mem_place.address_of()
595553
}
596554

597555
/// Insert an allocation into the goto symbol table, and generate a goto function that will
598556
/// initialize it.
599557
///
600558
/// This function is ultimately responsible for creating new statically initialized global variables
601-
/// in our goto binaries. These may be actual (potentially mutable) globals or (more often) constants
602-
/// that were encountered during code generation (that are of course immutable).
603-
fn codegen_alloc_in_memory(&mut self, alloc: &'tcx Allocation, name: String) {
559+
/// in our goto binaries.
560+
pub fn codegen_alloc_in_memory(&mut self, alloc: &'tcx Allocation, name: String) {
604561
debug!("codegen_alloc_in_memory name: {}", name);
605562
let struct_name = &format!("{name}::struct");
606563

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ use tracing::debug;
1212

1313
impl<'tcx> GotocCtx<'tcx> {
1414
/// Ensures a static variable is initialized.
15+
///
16+
/// Note that each static variable have their own location in memory. Per Rust documentation:
17+
/// "statics declare global variables. These represent a memory address."
18+
/// Source: <https://rust-lang.github.io/rfcs/0246-const-vs-static.html>
1519
pub fn codegen_static(&mut self, def_id: DefId, item: MonoItem<'tcx>) {
1620
debug!("codegen_static");
1721
let alloc = self.tcx.eval_static_initializer(def_id).unwrap();
1822
let symbol_name = item.symbol_name(self.tcx).to_string();
19-
// This is an `Expr` constructing function, but it mutates the symbol table to ensure initialization.
20-
self.codegen_allocation(alloc.inner(), |_| symbol_name.clone(), Some(symbol_name.clone()));
23+
self.codegen_alloc_in_memory(alloc.inner(), symbol_name);
2124
}
2225

2326
/// Mutates the Goto-C symbol table to add a forward-declaration of the static variable.

tests/kani/Static/static_value.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! Ensure that every static variable has a unique address and it is correctly
4+
//! initialized.
5+
//! This test reproduces the issue reported in <https://github.com/model-checking/kani/issues/2455>
6+
7+
static VAL_2_A: u8 = 2;
8+
static VAL_2_B: u8 = 2;
9+
static VAL_4: u8 = 4;
10+
11+
#[kani::proof]
12+
fn check_same_value_diff_address() {
13+
assert_eq!(VAL_2_A, VAL_2_B);
14+
assert_ne!(&VAL_2_A as *const u8, &VAL_2_B as *const u8);
15+
}
16+
17+
#[kani::proof]
18+
fn check_diff_value_diff_address() {
19+
assert_ne!(VAL_2_A, VAL_4);
20+
assert_ne!(&VAL_2_A as *const u8, &VAL_4 as *const u8);
21+
}

0 commit comments

Comments
 (0)