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 local = dest
34                    .as_local()
35                    .filter(|l| !body.locals.is_return_or_arg(*l))?;
36                let global = match rvalue {
37                    // `Ref` for non-mut statics, `Ptr` for `static mut` and extern statics
38                    Rvalue::Use(Operand::Const(c), _)
39                        if let ConstantExprKind::Ref(pointee, _)
40                        | ConstantExprKind::Ptr(_, pointee, _) = c.kind()
41                            && let GlobalPlaceKind::Global(GlobalProvenance::Global(global)) =
42                                &pointee.kind =>
43                    {
44                        global
45                    }
46                    // Thread-local statics are accessed through `Rvalue::ThreadLocalRef` in MIR.
47                    Rvalue::Ref { place, .. } | Rvalue::RawPtr { place, .. } => {
48                        place.kind.as_global()?
49                    }
50                    _ => return None,
51                };
52                let decl = ctx.translated.global_decls.get(global.id)?;
53                if !matches!(decl.global_kind, GlobalKind::Static { .. }) {
54                    return None;
55                }
56                let info = StaticInfo {
57                    global: global.clone(),
58                    assign_loc: loc,
59                    uses: 0,
60                    derefs: 0,
61                };
62                Some((local, info))
63            })
64            .collect();
65        if statics.is_empty() {
66            return;
67        }
68
69        // All desugared uses of a static go through its address, i.e. all its uses
70        // are `*_static_addr` except when it is assigned to.
71        body.body.dyn_visit_in_body(|place: &Place| {
72            if let Some(local) = place.as_local()
73                && let Some(info) = statics.get_mut(&local)
74            {
75                info.uses += 1;
76            } else if let Some((sub, ProjectionElem::Deref)) = place.as_projection()
77                && let Some(local) = sub.as_local()
78                && let Some(info) = statics.get_mut(&local)
79            {
80                info.derefs += 1;
81            }
82        });
83        statics.retain(|_, info| info.uses == info.derefs + 1);
84        if statics.is_empty() {
85            return;
86        }
87
88        // Remove the initial assignment (`_static_addr = const {alloc}`)
89        for info in statics.values() {
90            body[info.assign_loc].kind = StatementKind::Nop;
91        }
92        // ...and replace all derefs of the local with the static itself.
93        body.body.dyn_visit_in_body_mut(|place: &mut Place| {
94            if let Some((sub, ProjectionElem::Deref)) = place.as_projection()
95                && let Some(local) = sub.as_local()
96                && let Some(info) = statics.get(&local)
97            {
98                *place = Place::new_global(info.global.clone(), place.ty.clone());
99            }
100        });
101    }
102}