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 { .. } | ProjectionElem::Subslice { .. } => Safety::Unsafe,
146            ProjectionElem::Field(Some(_), _) | ProjectionElem::PtrMetadata => Safety::Safe,
147        }
148    }
149
150    /// The safety of writing to this place. This is mostly like `read_safety`, except that writing
151    /// to a union field is safe.
152    pub fn write_safety(&self, krate: &TranslatedCrate) -> Safety {
153        match self.as_projection() {
154            // Writing to a field (e.g. `u.a.b = x`) only writes to its parent, never reads it.
155            Some((sub, ProjectionElem::Field(..))) => sub.write_safety(krate),
156            _ => self.read_safety(krate),
157        }
158    }
159}
160
161impl HasSafety for Operand {
162    fn safety(&self, krate: &TranslatedCrate) -> Safety {
163        match self {
164            Operand::Copy(place) | Operand::Move(place) => place.read_safety(krate),
165            Operand::Const(_) => Safety::Safe,
166        }
167    }
168}
169
170impl HasSafety for Rvalue {
171    fn safety(&self, krate: &TranslatedCrate) -> Safety {
172        match self {
173            Rvalue::RawPtr {
174                place,
175                ptr_metadata,
176                ..
177            } => {
178                // A raw borrow doesn't access the borrowed place itself. Like rustc, we only skip its
179                // outermost accesses: `&raw const *ptr` is ok, but `&raw const (*ptr).field` isn't.
180                let accessed = match &place.kind {
181                    PlaceKind::Global(_) => None,
182                    PlaceKind::Projection(sub, ProjectionElem::Deref) => Some(&**sub),
183                    _ => place.subplaces().find(|place| {
184                        !matches!(place.as_projection(), Some((_, ProjectionElem::Field(..))))
185                            || !place.shallow_safety(krate).is_unsafe()
186                    }),
187                };
188                accessed
189                    .map_or(Safety::Safe, |place| place.read_safety(krate))
190                    .or_else(|| ptr_metadata.safety(krate))
191            }
192            Rvalue::Ref {
193                place,
194                ptr_metadata,
195                ..
196            } => place
197                .read_safety(krate)
198                .or_else(|| ptr_metadata.safety(krate)),
199            Rvalue::Discriminant(place) | Rvalue::Len(place, ..) => place.read_safety(krate),
200            Rvalue::UnaryOp(unop, op) => unop.safety(krate).or_else(|| op.safety(krate)),
201            Rvalue::Use(op, _) | Rvalue::Repeat(op, ..) => op.safety(krate),
202            Rvalue::BinaryOp(binop, op1, op2) => binop
203                .safety(krate)
204                .or_else(|| op1.safety(krate).or_else(|| op2.safety(krate))),
205            Rvalue::Aggregate(_, ops) => ops.safety(krate),
206            Rvalue::NullaryOp(_) => Safety::Safe,
207        }
208    }
209}
210
211impl HasSafety for UnOp {
212    fn safety(&self, krate: &TranslatedCrate) -> Safety {
213        match self {
214            UnOp::Not => Safety::Safe,
215            UnOp::Neg(om) => om.safety(krate),
216            UnOp::Cast(CastKind::Concretize(..) | CastKind::Transmute(..)) => Safety::Unsafe,
217            UnOp::Cast(CastKind::PtrWithExposedProvenance(_, tgt)) if tgt.is_ref() => {
218                Safety::Unsafe
219            }
220            UnOp::Cast(_) => Safety::Safe,
221        }
222    }
223}
224
225impl HasSafety for BinOp {
226    fn safety(&self, krate: &TranslatedCrate) -> Safety {
227        use BinOp::*;
228        match self {
229            Add(om) | Sub(om) | Mul(om) | Div(om) | Rem(om) | Shl(om) | Shr(om) => om.safety(krate),
230            BitXor | BitAnd | BitOr | Eq | Lt | Le | Ne | Ge | Gt | AddChecked | SubChecked
231            | MulChecked | Cmp => Safety::Safe,
232            Offset => Safety::Unsafe,
233        }
234    }
235}
236
237impl HasSafety for OverflowMode {
238    fn safety(&self, _krate: &TranslatedCrate) -> Safety {
239        match self {
240            OverflowMode::UB => Safety::Unsafe,
241            OverflowMode::Panic | OverflowMode::Wrap => Safety::Safe,
242        }
243    }
244}
245
246impl HasSafety for SwitchData {
247    fn safety(&self, krate: &TranslatedCrate) -> Safety {
248        match &self.scrutinee {
249            SwitchScrutinee::Value(op) => op.safety(krate),
250            SwitchScrutinee::Discriminant(place) => place.read_safety(krate),
251        }
252    }
253}
254
255/// The safety of calling the function (see [`CallSafety`]) and of evaluating the function pointer,
256/// the operands and the destination.
257impl HasSafety for Call {
258    fn safety(&self, krate: &TranslatedCrate) -> Safety {
259        let callee_safety = match self.safety {
260            CallSafety::Inherit => self.func.safety(krate),
261            CallSafety::Safe => Safety::Safe,
262            CallSafety::Unsafe => Safety::Unsafe,
263        };
264        callee_safety
265            .or_else(|| match &self.func {
266                FnOperand::Dynamic(op) => op.safety(krate),
267                FnOperand::Regular(_) => Safety::Safe,
268            })
269            .or_else(|| self.args.safety(krate))
270            .or_else(|| self.dest.write_safety(krate))
271    }
272}
273
274impl HasSafety for FnOperand {
275    /// The safety of calling the function, based on its signature (safety.unsafe-call). This
276    /// doesn't include evaluating the function pointer itself.
277    fn safety(&self, krate: &TranslatedCrate) -> Safety {
278        match self {
279            FnOperand::Regular(fn_ptr) => match fn_ptr.kind.as_ref() {
280                FnPtrKind::Fun(fun_id) => match krate.fun_decls.get(*fun_id) {
281                    Some(decl) => decl.signature.safety(krate),
282                    None => Safety::Unknown(format!(
283                        "{} wasn't translated",
284                        fun_id.with_ctx(&krate.into_fmt())
285                    )),
286                },
287                FnPtrKind::Trait(trait_ref, method_id) => {
288                    let trait_id = trait_ref.trait_decl_ref.skip_binder.id;
289                    let method = krate
290                        .trait_decls
291                        .get(trait_id)
292                        .and_then(|decl| decl.methods.get(*method_id));
293                    match method {
294                        Some(method) => method.skip_binder.signature.safety(krate),
295                        None => Safety::Unknown(format!(
296                            "the signature of {method_id:?} of {trait_id:?} wasn't translated"
297                        )),
298                    }
299                }
300            },
301            FnOperand::Dynamic(op) => op.ty().kind().as_fn_ptr().unwrap().safety(krate),
302        }
303    }
304}
305
306impl HasSafety for BorrowckStatement {
307    fn safety(&self, krate: &TranslatedCrate) -> Safety {
308        match self {
309            BorrowckStatement::FakeRead(place) => place.read_safety(krate),
310            BorrowckStatement::SetOutlives(..)
311            | BorrowckStatement::PredicateHolds(..)
312            | BorrowckStatement::SetType { .. } => Safety::Safe,
313        }
314    }
315}
316
317impl HasSafety for AbortKind {
318    fn safety(&self, _krate: &TranslatedCrate) -> Safety {
319        match self {
320            AbortKind::UndefinedBehavior => Safety::Unsafe,
321            AbortKind::Panic(..) | AbortKind::UnwindTerminate => Safety::Safe,
322        }
323    }
324}
325
326/// This doesn't look into nested blocks.
327impl HasSafety for llbc_ast::Statement {
328    fn safety(&self, krate: &TranslatedCrate) -> Safety {
329        use llbc_ast::StatementKind;
330        match &self.kind {
331            StatementKind::Assign(place, rvalue) => {
332                place.write_safety(krate).or_else(|| rvalue.safety(krate))
333            }
334            StatementKind::SetDiscriminant(place, _) => place.write_safety(krate),
335            StatementKind::PlaceMention(place) | StatementKind::Drop { place, .. } => {
336                place.read_safety(krate)
337            }
338            StatementKind::Borrowck(st) => st.safety(krate),
339            StatementKind::Assert {
340                assert, on_failure, ..
341            } => assert
342                .cond
343                .safety(krate)
344                .or_else(|| on_failure.safety(krate)),
345            StatementKind::Call { call, .. } => call.safety(krate),
346            StatementKind::Switch { data, .. } => data.safety(krate),
347            StatementKind::InlineAsm { asm, .. } => asm.kind.is_asm().into(),
348            StatementKind::UndefinedBehavior => Safety::Unsafe,
349            StatementKind::StorageLive(_)
350            | StatementKind::StorageDead(_)
351            | StatementKind::Nop
352            | StatementKind::Loop(_)
353            | StatementKind::Break(_)
354            | StatementKind::Continue(_)
355            | StatementKind::Return
356            | StatementKind::Panic { .. }
357            | StatementKind::UnwindResume
358            | StatementKind::UnwindTerminate => Safety::Safe,
359        }
360    }
361}
362
363/// Note that calls are terminators.
364impl HasSafety for ullbc_ast::Statement {
365    fn safety(&self, krate: &TranslatedCrate) -> Safety {
366        use ullbc_ast::StatementKind;
367        match &self.kind {
368            StatementKind::Assign(place, rvalue) => {
369                place.write_safety(krate).or_else(|| rvalue.safety(krate))
370            }
371            StatementKind::SetDiscriminant(place, _) => place.write_safety(krate),
372            StatementKind::PlaceMention(place) => place.read_safety(krate),
373            StatementKind::Borrowck(st) => st.safety(krate),
374            StatementKind::Assert { assert, on_failure } => assert
375                .cond
376                .safety(krate)
377                .or_else(|| on_failure.safety(krate)),
378            StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Nop => {
379                Safety::Safe
380            }
381        }
382    }
383}
384
385impl HasSafety for ullbc_ast::Terminator {
386    fn safety(&self, krate: &TranslatedCrate) -> Safety {
387        use ullbc_ast::TerminatorKind;
388        match &self.kind {
389            TerminatorKind::Switch { data, .. } => data.safety(krate),
390            TerminatorKind::Call { call, .. } => call.safety(krate),
391            TerminatorKind::Drop { place, .. } => place.read_safety(krate),
392            TerminatorKind::Assert { assert, .. } => assert.cond.safety(krate),
393            TerminatorKind::InlineAsm { asm, .. } => asm.kind.is_asm().into(),
394            TerminatorKind::UndefinedBehavior => Safety::Unsafe,
395            TerminatorKind::Goto { .. }
396            | TerminatorKind::Return
397            | TerminatorKind::Panic { .. }
398            | TerminatorKind::UnwindResume
399            | TerminatorKind::UnwindTerminate => Safety::Safe,
400        }
401    }
402}