Skip to content

Commit d1bb464

Browse files
committed
Add support for anonymous nested statics (#3953)
rust-lang/rust#121644 added support for anonymous nested allocations to statics. This PR adds support for such statics to Kani. The idea is to treat an anonymous `GlobalAlloc::Static` the same as we would treat a `GlobalAlloc::Memory`, since an anonymous static is a nested memory allocation. To frame this change in terms of the tests: `pointer_to_const_alloc.rs` contains a test for the `GlobalAlloc::Memory` case, which we could already handle prior to this PR. The MIR looks like: ``` alloc3 (size: 4, align: 4) { 2a 00 00 00 │ *... } alloc1 (static: FOO, size: 16, align: 8) { ╾─────alloc3<imm>─────╼ 01 00 00 00 00 00 00 00 │ ╾──────╼........ } ``` meaning that `FOO` contains a pointer to the *immutable* allocation alloc3 (note the `alloc3<imm>`, imm standing for "immutable"). `anon_static.rs` tests the code introduced in this PR. The MIR from `example_1` looks almost identical: ``` alloc2 (static: FOO::{constant#0}, size: 4, align: 4) { 2a 00 00 00 │ *... } alloc1 (static: FOO, size: 16, align: 8) { ╾───────alloc2────────╼ 01 00 00 00 00 00 00 00 │ ╾──────╼........ } ``` Note, however, that `alloc2` is mutable, and is thus an anonymous nested static rather than a constant allocation. But we can just call `codegen_const_allocation` anyway, since it ends up checking if the allocation is indeed constant before declaring the global variable in the symbol table: https://github.com/model-checking/kani/blob/319040b8cd2cb72ec0603653fad7a8d934857d57/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs#L556 Resolves #3904 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 158f1af commit d1bb464

File tree

5 files changed

+153
-23
lines changed

5 files changed

+153
-23
lines changed

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33
use crate::codegen_cprover_gotoc::GotocCtx;
44
use crate::codegen_cprover_gotoc::utils::slice_fat_ptr;
5+
use crate::kani_middle::is_anon_static;
56
use crate::unwrap_or_return_codegen_unimplemented;
67
use cbmc::goto_program::{DatatypeComponent, Expr, ExprValue, Location, Symbol, Type};
78
use rustc_middle::ty::Const as ConstInternal;
@@ -372,7 +373,15 @@ impl<'tcx> GotocCtx<'tcx> {
372373
// We want to return the function pointer (not to be confused with function item)
373374
self.codegen_func_expr(instance, loc).address_of()
374375
}
375-
GlobalAlloc::Static(def) => self.codegen_static_pointer(def),
376+
GlobalAlloc::Static(def) => {
377+
if is_anon_static(self.tcx, def.def_id()) {
378+
let alloc = def.eval_initializer().unwrap();
379+
let name = format!("{}::{alloc_id:?}", self.full_crate_name());
380+
self.codegen_nested_static_allocation(&alloc, Some(name), loc)
381+
} else {
382+
self.codegen_static_pointer(def)
383+
}
384+
}
376385
GlobalAlloc::Memory(alloc) => {
377386
// Full (mangled) crate name added so that allocations from different
378387
// crates do not conflict. The name alone is insufficient because Rust
@@ -490,6 +499,19 @@ impl<'tcx> GotocCtx<'tcx> {
490499
mem_place.address_of()
491500
}
492501

502+
/// Generate an expression that represents the address of a nested static allocation.
503+
fn codegen_nested_static_allocation(
504+
&mut self,
505+
alloc: &Allocation,
506+
name: Option<String>,
507+
loc: Location,
508+
) -> Expr {
509+
// The memory behind this allocation isn't constant, but codegen_alloc_in_memory (which codegen_const_allocation calls)
510+
// uses alloc's mutability field to set the const-ness of the allocation in CBMC's symbol table,
511+
// so we can reuse the code and without worrying that the allocation is set as immutable.
512+
self.codegen_const_allocation(alloc, name, loc)
513+
}
514+
493515
/// Insert an allocation into the goto symbol table, and generate an init value.
494516
///
495517
/// This function is ultimately responsible for creating new statically initialized global

kani-compiler/src/kani_middle/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use crate::kani_queries::QueryDb;
99
use rustc_hir::{def::DefKind, def_id::DefId as InternalDefId, def_id::LOCAL_CRATE};
1010
use rustc_middle::ty::TyCtxt;
1111
use rustc_smir::rustc_internal;
12-
use stable_mir::CrateDef;
1312
use stable_mir::mir::mono::MonoItem;
1413
use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, Ty, TyKind};
1514
use stable_mir::visitor::{Visitable, Visitor as TyVisitor};
15+
use stable_mir::{CrateDef, DefId};
1616
use std::ops::ControlFlow;
1717

1818
use self::attributes::KaniAttributes;
@@ -146,6 +146,15 @@ impl SourceLocation {
146146
}
147147
}
148148

149+
/// Return whether `def_id` refers to a nested static allocation.
150+
pub fn is_anon_static(tcx: TyCtxt, def_id: DefId) -> bool {
151+
let int_def_id = rustc_internal::internal(tcx, def_id);
152+
match tcx.def_kind(int_def_id) {
153+
rustc_hir::def::DefKind::Static { nested, .. } => nested,
154+
_ => false,
155+
}
156+
}
157+
149158
/// Try to convert an internal `DefId` to a `FnDef`.
150159
pub fn stable_fn_def(tcx: TyCtxt, def_id: InternalDefId) -> Option<FnDef> {
151160
if let TyKind::RigidTy(RigidTy::FnDef(def, _)) =

kani-compiler/src/kani_middle/reachability.rs

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use std::{
4242

4343
use crate::kani_middle::coercion;
4444
use crate::kani_middle::coercion::CoercionBase;
45+
use crate::kani_middle::is_anon_static;
4546
use crate::kani_middle::transform::BodyTransformation;
4647

4748
/// Collect all reachable items starting from the given starting points.
@@ -211,20 +212,25 @@ impl<'tcx, 'a> MonoItemsCollector<'tcx, 'a> {
211212
let _guard = debug_span!("visit_static", ?def).entered();
212213
let mut next_items = vec![];
213214

214-
// Collect drop function.
215-
let static_ty = def.ty();
216-
let instance = Instance::resolve_drop_in_place(static_ty);
217-
next_items
218-
.push(CollectedItem { item: instance.into(), reason: CollectionReason::StaticDrop });
215+
// Collect drop function, unless it's an anonymous static.
216+
if !is_anon_static(self.tcx, def.def_id()) {
217+
let static_ty = def.ty();
218+
let instance = Instance::resolve_drop_in_place(static_ty);
219+
next_items.push(CollectedItem {
220+
item: instance.into(),
221+
reason: CollectionReason::StaticDrop,
222+
});
219223

220-
// Collect initialization.
221-
let alloc = def.eval_initializer().unwrap();
222-
for (_, prov) in alloc.provenance.ptrs {
223-
next_items.extend(
224-
collect_alloc_items(prov.0)
225-
.into_iter()
226-
.map(|item| CollectedItem { item, reason: CollectionReason::Static }),
227-
);
224+
// Collect initialization.
225+
let alloc = def.eval_initializer().unwrap();
226+
debug!(?alloc, "visit_static: initializer");
227+
for (_, prov) in alloc.provenance.ptrs {
228+
next_items.extend(
229+
collect_alloc_items(self.tcx, prov.0)
230+
.into_iter()
231+
.map(|item| CollectedItem { item, reason: CollectionReason::Static }),
232+
);
233+
}
228234
}
229235

230236
next_items
@@ -323,7 +329,7 @@ impl MonoItemsFnCollector<'_, '_> {
323329
debug!(?alloc, "collect_allocation");
324330
for (_, id) in &alloc.provenance.ptrs {
325331
self.collected.extend(
326-
collect_alloc_items(id.0)
332+
collect_alloc_items(self.tcx, id.0)
327333
.into_iter()
328334
.map(|item| CollectedItem { item, reason: CollectionReason::Static }),
329335
)
@@ -512,27 +518,38 @@ fn should_codegen_locally(instance: &Instance) -> bool {
512518
!instance.is_foreign_item()
513519
}
514520

515-
fn collect_alloc_items(alloc_id: AllocId) -> Vec<MonoItem> {
521+
fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec<MonoItem> {
516522
trace!(?alloc_id, "collect_alloc_items");
517523
let mut items = vec![];
518524
match GlobalAlloc::from(alloc_id) {
519525
GlobalAlloc::Static(def) => {
520-
// This differ from rustc's collector since rustc does not include static from
521-
// upstream crates.
522-
let instance = Instance::try_from(CrateItem::from(def)).unwrap();
523-
should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def)));
526+
if is_anon_static(tcx, def.def_id()) {
527+
let alloc = def.eval_initializer().unwrap();
528+
items.extend(
529+
alloc
530+
.provenance
531+
.ptrs
532+
.iter()
533+
.flat_map(|(_, prov)| collect_alloc_items(tcx, prov.0)),
534+
);
535+
} else {
536+
// This differ from rustc's collector since rustc does not include static from
537+
// upstream crates.
538+
let instance = Instance::try_from(CrateItem::from(def)).unwrap();
539+
should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def)));
540+
}
524541
}
525542
GlobalAlloc::Function(instance) => {
526543
should_codegen_locally(&instance).then(|| items.push(MonoItem::from(instance)));
527544
}
528545
GlobalAlloc::Memory(alloc) => {
529546
items.extend(
530-
alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(prov.0)),
547+
alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(tcx, prov.0)),
531548
);
532549
}
533550
vtable_alloc @ GlobalAlloc::VTable(..) => {
534551
let vtable_id = vtable_alloc.vtable_allocation().unwrap();
535-
items = collect_alloc_items(vtable_id);
552+
items = collect_alloc_items(tcx, vtable_id);
536553
}
537554
};
538555
items

tests/kani/Static/anon_static.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Test that Kani can codegen statics that contain pointers to nested statics.
5+
// See https://github.com/model-checking/kani/issues/3904
6+
7+
mod example_1 {
8+
// FOO contains a pointer to the anonymous nested static alloc2.
9+
// The MIR is:
10+
// alloc1 (static: FOO, size: 8, align: 8) {
11+
// ╾───────alloc2────────╼ │ ╾──────╼
12+
// }
13+
14+
// alloc2 (static: FOO::{constant#0}, size: 4, align: 4) {
15+
// 2a 00 00 00 │ *...
16+
// }
17+
static mut FOO: &mut u32 = &mut 42;
18+
19+
#[kani::proof]
20+
fn main() {
21+
unsafe {
22+
*FOO = 43;
23+
}
24+
}
25+
}
26+
27+
mod example_2 {
28+
// FOO and BAR both point to the anonymous nested static alloc2.
29+
// The MIR is:
30+
// alloc3 (static: BAR, size: 8, align: 8) {
31+
// ╾───────alloc2────────╼ │ ╾──────╼
32+
// }
33+
34+
// alloc2 (static: FOO::{constant#0}, size: 4, align: 4) {
35+
// 2a 00 00 00 │ *...
36+
// }
37+
38+
// alloc1 (static: FOO, size: 8, align: 8) {
39+
// ╾───────alloc2────────╼ │ ╾──────╼
40+
// }
41+
42+
static mut FOO: &mut i32 = &mut 12;
43+
static mut BAR: *mut i32 = unsafe { FOO as *mut _ };
44+
45+
#[kani::proof]
46+
fn main() {
47+
unsafe {
48+
// check that we see the same initial value from all aliases
49+
assert_eq!(*FOO, 12);
50+
assert_eq!(*BAR, 12);
51+
*FOO = 13;
52+
// check that we see the same mutated value from all aliases
53+
assert_eq!(*FOO, 13);
54+
assert_eq!(*BAR, 13);
55+
}
56+
}
57+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Test that Kani can codegen statics that contain pointers to constant (i.e., immutable) allocations.
5+
// Test taken from https://github.com/rust-lang/rust/issues/79738#issuecomment-1946578159
6+
7+
// The MIR is:
8+
// alloc4 (static: BAR, size: 16, align: 8) {
9+
// ╾─────alloc3<imm>─────╼ 01 00 00 00 00 00 00 00 │ ╾──────╼........
10+
// }
11+
12+
// alloc3 (size: 4, align: 4) {
13+
// 2a 00 00 00 │ *...
14+
// }
15+
16+
// alloc1 (static: FOO, size: 16, align: 8) {
17+
// ╾─────alloc3<imm>─────╼ 01 00 00 00 00 00 00 00 │ ╾──────╼........
18+
// }
19+
pub static FOO: &[i32] = &[42];
20+
pub static BAR: &[i32] = &*FOO;
21+
22+
#[kani::proof]
23+
fn main() {
24+
assert_eq!(FOO.as_ptr(), BAR.as_ptr());
25+
}

0 commit comments

Comments
 (0)