Skip to main content

charon_lib/ast/bodies/
safety.rs

1//! Detect which operations and items require `unsafe`.
2use macros::EnumIsA;
3
4use crate::ast::*;
5use crate::formatter::IntoFormatter;
6use crate::pretty::FmtWithCtx;
7use crate::{llbc_ast, ullbc_ast};
8
9/// Whether an operation requires `unsafe`.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[derive(EnumIsA)]
12pub enum Safety {
13    Safe,
14    Unsafe,
15    Unknown(String),
16}
17
18impl Safety {
19    /// Combine with the safety of something else that is also evaluated: `Unsafe` takes precedence
20    /// over `Unknown`, which takes precedence over `Safe`. `other` is only computed if needed.
21    pub fn or_else(self, other: impl FnOnce() -> Safety) -> Safety {
22        match self {
23            Safety::Unsafe => Safety::Unsafe,
24            Safety::Safe => other(),
25            Safety::Unknown(reason) => match other() {
26                Safety::Unsafe => Safety::Unsafe,
27                Safety::Safe | Safety::Unknown(_) => Safety::Unknown(reason),
28            },
29        }
30    }
31}
32
33impl From<bool> for Safety {
34    fn from(is_unsafe: bool) -> Self {
35        if is_unsafe {
36            Safety::Unsafe
37        } else {
38            Safety::Safe
39        }
40    }
41}
42
43/// Combine the safety of all the elements, see [`Safety::or_else`]. We stop consuming the iterator
44/// once we know the result is `Unsafe`.
45impl FromIterator<Safety> for Safety {
46    fn from_iter<I: IntoIterator<Item = Safety>>(iter: I) -> Self {
47        let mut safety = Safety::Safe;
48        for other in iter {
49            safety = safety.or_else(|| other);
50            if safety.is_unsafe() {
51                break;
52            }
53        }
54        safety
55    }
56}
57
58/// The safety of evaluating this, following the rules outlined in
59/// <https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html> and
60/// <https://doc.rust-lang.org/reference/unsafety.html>.
61///
62/// This is computed on the translated (U)LLBC, so it can't know exactly what the user wrote. For example,
63/// macros like `println!("{x}")` expand to unsafe operations inside their own `unsafe` blocks. Some safe
64/// operations like slice indexing also expand to a bounds check followed by an unchecked access. Dead code
65/// elimination may hide an unsafe operation. So all in all this will not report exactly the same safety as
66/// rustc sees in the surface code. It is however accurate if you treat (U)LLBC as its own language: we
67/// accurately flag operations that have soundness preconditions.
68///
69/// We currently don't support unsafe fields and unsafe binders.
70pub trait HasSafety {
71    fn safety(&self, krate: &TranslatedCrate) -> Safety;
72}
73
74impl<I: ?Sized, T: HasSafety> HasSafety for I
75where
76    for<'a> &'a I: IntoIterator<Item = &'a T>,
77{
78    fn safety(&self, krate: &TranslatedCrate) -> Safety {
79        self.into_iter().map(|x| x.safety(krate)).collect()
80    }
81}
82
83impl<T: HasSafety> HasSafety for RegionBinder<T> {
84    fn safety(&self, krate: &TranslatedCrate) -> Safety {
85        self.skip_binder.safety(krate)
86    }
87}
88
89impl HasSafety for FunSig {
90    fn safety(&self, _krate: &TranslatedCrate) -> Safety {
91        self.is_unsafe.into()
92    }
93}
94
95impl Place {
96    /// The safety of reading from this place. It is unsafe if it does any of the following:
97    /// - it accesses a mutable or non-safe static (safety.unsafe-static)
98    /// - it dereferences a raw pointer (safety.unsafe-deref)
99    /// - it accesses a union field (safety.unsafe-union-access)
100    /// - it accesses an index or slice, as these are unchecked in MIR.
101    pub fn read_safety(&self, krate: &TranslatedCrate) -> Safety {
102        self.subplaces()
103            .map(|place| place.shallow_safety(krate))
104            .collect()
105    }
106
107    /// The safety of accessing this place, ignoring subplaces.
108    fn shallow_safety(&self, krate: &TranslatedCrate) -> Safety {
109        let (sub, proj) = match &self.kind {
110            PlaceKind::Global(global_ref) => {
111                return match krate.global_decls.get(global_ref.id) {
112                    Some(decl) => decl.is_unsafe_to_access(krate).into(),
113                    None => Safety::Unknown(format!(
114                        "{} wasn't translated",
115                        global_ref.with_ctx(&krate.into_fmt())
116                    )),
117                };
118            }
119            PlaceKind::Local(_) => return Safety::Safe,
120            PlaceKind::Projection(sub, proj) => (sub, proj),
121        };
122        match proj {
123            // `safety.unsafe-deref`, ignoring VTable reads (guaranteed safe)
124            ProjectionElem::Deref => (sub.ty.kind().is_raw_ptr()
125                && !matches!(sub.as_projection(), Some((_, ProjectionElem::PtrMetadata))))
126            .into(),
127            // `safety.unsafe-union-access`.
128            ProjectionElem::Field(None, _) => {
129                let Some(tref) = sub.ty.as_adt().filter(|tref| !tref.is_builtin()) else {
130                    return Safety::Safe;
131                };
132                match krate.type_decls.get(tref.id).map(|decl| &decl.kind) {
133                    Some(TypeDeclKind::Union(_)) => Safety::Unsafe,
134                    Some(
135                        TypeDeclKind::Struct(_) | TypeDeclKind::Enum(_) | TypeDeclKind::Alias(_),
136                    ) => Safety::Safe,
137                    Some(TypeDeclKind::Opaque | TypeDeclKind::Error(_)) | None => {
138                        Safety::Unknown(format!(
139                            "{} is opaque or wasn't translated",
140                            tref.with_ctx(&krate.into_fmt())
141                        ))
142                    }
143                }
144            }
145            ProjectionElem::Index { .. }
146            | ProjectionElem::Subslice { .. }
147            | ProjectionElem::Offset { .. } => Safety::Unsafe,
148            ProjectionElem::Field(Some(_), _) | ProjectionElem::PtrMetadata => Safety::Safe,
149        }
150    }
151
152    /// The safety of writing to this place. This is mostly like `read_safety`, except that writing
153    /// to a union field is safe.
154    pub fn write_safety(&self, krate: &TranslatedCrate) -> Safety {
155        match self.as_projection() {
156            // Writing to a field (e.g. `u.a.b = x`) only writes to its parent, never reads it.
157            Some((sub, ProjectionElem::Field(..))) => sub.write_safety(krate),
158            _ => self.read_safety(krate),
159        }
160    }
161}
162
163impl HasSafety for Operand {
164    fn safety(&self, krate: &TranslatedCrate) -> Safety {
165        match self {
166            Operand::Copy(place) | Operand::Move(place) => place.read_safety(krate),
167            Operand::Const(_) => Safety::Safe,
168        }
169    }
170}
171
172impl HasSafety for Rvalue {
173    fn safety(&self, krate: &TranslatedCrate) -> Safety {
174        match self {
175            Rvalue::RawPtr {
176                place,
177                ptr_metadata,
178                ..
179            } => {
180                // A raw borrow doesn't access the borrowed place itself. Like rustc, we only skip its
181                // outermost accesses: `&raw const *ptr` is ok, but `&raw const (*ptr).field` isn't.
182                let accessed = match &place.kind {
183                    PlaceKind::Global(_) => None,
184                    PlaceKind::Projection(sub, ProjectionElem::Deref) => Some(&**sub),
185                    _ => place.subplaces().find(|place| {
186                        !matches!(place.as_projection(), Some((_, ProjectionElem::Field(..))))
187                            || !place.shallow_safety(krate).is_unsafe()
188                    }),
189                };
190                accessed
191                    .map_or(Safety::Safe, |place| place.read_safety(krate))
192                    .or_else(|| ptr_metadata.safety(krate))
193            }
194            Rvalue::Ref {
195                place,
196                ptr_metadata,
197                ..
198            } => place
199                .read_safety(krate)
200                .or_else(|| ptr_metadata.safety(krate)),
201            Rvalue::Discriminant(place) | Rvalue::Len(place, ..) => place.read_safety(krate),
202            Rvalue::UnaryOp(unop, op) => unop.safety(krate).or_else(|| op.safety(krate)),
203            Rvalue::Use(op, _) | Rvalue::Repeat(op, ..) => op.safety(krate),
204            Rvalue::BinaryOp(binop, op1, op2) => binop
205                .safety(krate)
206                .or_else(|| op1.safety(krate).or_else(|| op2.safety(krate))),
207            Rvalue::Aggregate(_, ops) => ops.safety(krate),
208            Rvalue::NullaryOp(_) => Safety::Safe,
209        }
210    }
211}
212
213impl HasSafety for UnOp {
214    fn safety(&self, krate: &TranslatedCrate) -> Safety {
215        match self {
216            UnOp::Not => Safety::Safe,
217            UnOp::Neg(om) => om.safety(krate),
218            UnOp::Cast(CastKind::Concretize(..) | CastKind::Transmute(..)) => Safety::Unsafe,
219            UnOp::Cast(CastKind::PtrWithExposedProvenance(tgt)) if tgt.is_ref() => Safety::Unsafe,
220            UnOp::Cast(
221                CastKind::Scalar(..)
222                | CastKind::Subtype(..)
223                | CastKind::RawPtr(..)
224                | CastKind::PtrExposeProvenance
225                | CastKind::PtrWithExposedProvenance(..)
226                | CastKind::Unsize(..)
227                | CastKind::FnDefToFnPtr
228                | CastKind::FnPtrToUnsafe,
229            ) => Safety::Safe,
230        }
231    }
232}
233
234impl HasSafety for BinOp {
235    fn safety(&self, krate: &TranslatedCrate) -> Safety {
236        use BinOp::*;
237        match self {
238            Add(om) | Sub(om) | Mul(om) | Div(om) | Rem(om) | Shl(om) | Shr(om) => om.safety(krate),
239            BitXor | BitAnd | BitOr | Eq | Lt | Le | Ne | Ge | Gt | AddChecked | SubChecked
240            | MulChecked | Cmp => Safety::Safe,
241        }
242    }
243}
244
245impl HasSafety for OverflowMode {
246    fn safety(&self, _krate: &TranslatedCrate) -> Safety {
247        match self {
248            OverflowMode::UB => Safety::Unsafe,
249            OverflowMode::Panic | OverflowMode::Wrap => Safety::Safe,
250        }
251    }
252}
253
254impl HasSafety for SwitchData {
255    fn safety(&self, krate: &TranslatedCrate) -> Safety {
256        match &self.scrutinee {
257            SwitchScrutinee::Value(op) => op.safety(krate),
258            SwitchScrutinee::Discriminant(place) => place.read_safety(krate),
259        }
260    }
261}
262
263/// The safety of calling the function (see [`CallSafety`]) and of evaluating the function pointer,
264/// the operands and the destination.
265impl HasSafety for Call {
266    fn safety(&self, krate: &TranslatedCrate) -> Safety {
267        let callee_safety = match self.safety {
268            CallSafety::Inherit => self.func.safety(krate),
269            CallSafety::Safe => Safety::Safe,
270            CallSafety::Unsafe => Safety::Unsafe,
271        };
272        callee_safety
273            .or_else(|| match &self.func {
274                FnOperand::Dynamic(op, _) => op.safety(krate),
275                FnOperand::Regular(_) => Safety::Safe,
276            })
277            .or_else(|| self.args.safety(krate))
278            .or_else(|| self.dest.write_safety(krate))
279    }
280}
281
282impl HasSafety for FnOperand {
283    /// The safety of calling the function, based on its signature (safety.unsafe-call). This
284    /// doesn't include evaluating the function pointer itself.
285    fn safety(&self, krate: &TranslatedCrate) -> Safety {
286        match self {
287            FnOperand::Regular(fn_ptr) => match fn_ptr.kind.as_ref() {
288                FnPtrKind::Fun(fun_id) => match krate.fun_decls.get(*fun_id) {
289                    Some(decl) => decl.signature.safety(krate),
290                    None => Safety::Unknown(format!(
291                        "{} wasn't translated",
292                        fun_id.with_ctx(&krate.into_fmt())
293                    )),
294                },
295                FnPtrKind::Trait(trait_ref, method_id) => {
296                    let trait_id = trait_ref.trait_id();
297                    let method = krate
298                        .trait_decls
299                        .get(trait_id)
300                        .and_then(|decl| decl.methods.get(*method_id));
301                    match method {
302                        Some(method) => method.skip_binder.signature.safety(krate),
303                        None => Safety::Unknown(format!(
304                            "the signature of {method_id:?} of {trait_id:?} wasn't translated"
305                        )),
306                    }
307                }
308            },
309            FnOperand::Dynamic(op, _) => op.ty().kind().as_fn_ptr().unwrap().safety(krate),
310        }
311    }
312}
313
314impl HasSafety for BorrowckStatement {
315    fn safety(&self, krate: &TranslatedCrate) -> Safety {
316        match self {
317            BorrowckStatement::FakeRead(place) => place.read_safety(krate),
318            BorrowckStatement::SetOutlives(..)
319            | BorrowckStatement::PredicateHolds(..)
320            | BorrowckStatement::SetType { .. } => Safety::Safe,
321        }
322    }
323}
324
325impl HasSafety for AbortKind {
326    fn safety(&self, _krate: &TranslatedCrate) -> Safety {
327        match self {
328            AbortKind::UndefinedBehavior => Safety::Unsafe,
329            AbortKind::Panic(..) | AbortKind::UnwindTerminate => Safety::Safe,
330        }
331    }
332}
333
334/// This doesn't look into nested blocks.
335impl HasSafety for llbc_ast::Statement {
336    fn safety(&self, krate: &TranslatedCrate) -> Safety {
337        use llbc_ast::StatementKind;
338        match &self.kind {
339            StatementKind::Assign(place, rvalue) => {
340                place.write_safety(krate).or_else(|| rvalue.safety(krate))
341            }
342            StatementKind::SetDiscriminant(place, _) => place.write_safety(krate),
343            StatementKind::PlaceMention(place) | StatementKind::Drop { place, .. } => {
344                place.read_safety(krate)
345            }
346            StatementKind::Borrowck(st) => st.safety(krate),
347            StatementKind::Assert {
348                assert, on_failure, ..
349            } => assert
350                .cond
351                .safety(krate)
352                .or_else(|| on_failure.safety(krate)),
353            StatementKind::Call { call, .. } => call.safety(krate),
354            StatementKind::Switch { data, .. } => data.safety(krate),
355            StatementKind::InlineAsm { asm, .. } => asm.kind.is_asm().into(),
356            StatementKind::UndefinedBehavior => Safety::Unsafe,
357            StatementKind::StorageLive(_)
358            | StatementKind::StorageDead(_)
359            | StatementKind::Nop
360            | StatementKind::Loop(_)
361            | StatementKind::Break(_)
362            | StatementKind::Continue(_)
363            | StatementKind::Return
364            | StatementKind::Panic { .. }
365            | StatementKind::UnwindResume
366            | StatementKind::UnwindTerminate => Safety::Safe,
367        }
368    }
369}
370
371/// Note that calls are terminators.
372impl HasSafety for ullbc_ast::Statement {
373    fn safety(&self, krate: &TranslatedCrate) -> Safety {
374        use ullbc_ast::StatementKind;
375        match &self.kind {
376            StatementKind::Assign(place, rvalue) => {
377                place.write_safety(krate).or_else(|| rvalue.safety(krate))
378            }
379            StatementKind::SetDiscriminant(place, _) => place.write_safety(krate),
380            StatementKind::PlaceMention(place) => place.read_safety(krate),
381            StatementKind::Borrowck(st) => st.safety(krate),
382            StatementKind::Assert { assert, on_failure } => assert
383                .cond
384                .safety(krate)
385                .or_else(|| on_failure.safety(krate)),
386            StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Nop => {
387                Safety::Safe
388            }
389        }
390    }
391}
392
393impl HasSafety for ullbc_ast::Terminator {
394    fn safety(&self, krate: &TranslatedCrate) -> Safety {
395        use ullbc_ast::TerminatorKind;
396        match &self.kind {
397            TerminatorKind::Switch { data, .. } => data.safety(krate),
398            TerminatorKind::Call { call, .. } => call.safety(krate),
399            TerminatorKind::Drop { place, .. } => place.read_safety(krate),
400            TerminatorKind::Assert { assert, .. } => assert.cond.safety(krate),
401            TerminatorKind::InlineAsm { asm, .. } => asm.kind.is_asm().into(),
402            TerminatorKind::UndefinedBehavior => Safety::Unsafe,
403            TerminatorKind::Goto { .. }
404            | TerminatorKind::Return
405            | TerminatorKind::Panic { .. }
406            | TerminatorKind::UnwindResume
407            | TerminatorKind::UnwindTerminate => Safety::Safe,
408        }
409    }
410}