Skip to main content

charon_lib/transform/resugar/
reconstruct_static_accesses.rs

1//! MIR places can't name statics, so rustc accesses a static through a temporary holding its
2//! address, e.g. `_1 = const {alloc}; _0 = copy (*_1)` where the constant points to the static.
3//! We turn `*_1` back into the static itself and remove the temporary.
4//! This is needed to reliably tell what static accesses are unsafe (see `HasSafety`), as rustc
5//! will lower safe static access to otherwise unsafe raw pointer accesses.
6use rustc_hash::FxHashMap as HashMap;
7
8use crate::transform::TransformCtx;
9use crate::transform::ctx::UllbcPass;
10use crate::ullbc_ast::*;
11
12/// A local assigned the address of a static.
13struct StaticInfo {
14    global: GlobalDeclRef,
15    assign_loc: StmtLoc,
16    uses: usize,
17    derefs: usize,
18}
19
20pub struct Transform;
21impl UllbcPass for Transform {
22    fn transform_body(&self, ctx: &mut TransformCtx, body: &mut ExprBody) {
23        // Find the locals assigned the address of a static, and where they're assigned.
24        let mut statics: HashMap<LocalId, StaticInfo> = body
25            .body
26            .iter_enumerated()
27            .flat_map(|(block_id, block)| {
28                let locs = (0..).map(move |i| StmtLoc::new(block_id, i));
29                locs.zip(&block.statements)
30            })
31            .filter_map(|(loc, st)| {
32                let (dest, rvalue) = &st.kind.as_assign()?;
33                let global = match rvalue {
34                    // `Ref` for non-mut statics, `Ptr` for `static mut` and extern statics
35                    Rvalue::Use(Operand::Const(c), _)
36                        if let ConstantExprKind::Ref(pointee, _)
37                        | ConstantExprKind::Ptr(_, pointee, _) = c.kind() =>
38                    {
39                        pointee.kind().as_global()?
40                    }
41                    // Thread-local statics are accessed through `Rvalue::ThreadLocalRef` in MIR.
42                    Rvalue::Ref { place, .. } | Rvalue::RawPtr { place, .. } => {
43                        place.kind.as_global()?
44                    }
45                    _ => return None,
46                };
47                let decl = ctx.translated.global_decls.get(global.id)?;
48                if !matches!(decl.global_kind, GlobalKind::Static { .. }) {
49                    return None;
50                }
51                let info = StaticInfo {
52                    global: global.clone(),
53                    assign_loc: loc,
54                    uses: 0,
55                    derefs: 0,
56                };
57                Some((dest.as_local()?, info))
58            })
59            .collect();
60        if statics.is_empty() {
61            return;
62        }
63
64        // All desugared uses of a static go through its address, i.e. all its uses
65        // are `*_static_addr` except when it is assigned to.
66        body.body.dyn_visit_in_body(|place: &Place| {
67            if let Some(local) = place.as_local()
68                && let Some(info) = statics.get_mut(&local)
69            {
70                info.uses += 1;
71            } else if let Some((sub, ProjectionElem::Deref)) = place.as_projection()
72                && let Some(local) = sub.as_local()
73                && let Some(info) = statics.get_mut(&local)
74            {
75                info.derefs += 1;
76            }
77        });
78        statics.retain(|_, info| info.uses == info.derefs + 1);
79        if statics.is_empty() {
80            return;
81        }
82
83        // Remove the initial assignment (`_static_addr = const {alloc}`)
84        for info in statics.values() {
85            body[info.assign_loc].kind = StatementKind::Nop;
86        }
87        // ...and replace all derefs of the local with the static itself.
88        body.body.dyn_visit_in_body_mut(|place: &mut Place| {
89            if let Some((sub, ProjectionElem::Deref)) = place.as_projection()
90                && let Some(local) = sub.as_local()
91                && let Some(info) = statics.get(&local)
92            {
93                *place = Place::new_global(info.global.clone(), place.ty.clone());
94            }
95        });
96    }
97}