Skip to main content

rustc_lint/
lossy_provenance_casts.rs

1use rustc_ast::util::parser::ExprPrecedence;
2use rustc_hir as hir;
3use rustc_session::{declare_lint, declare_lint_pass};
4
5use crate::lints::{LossyProvenancePtr2Int, LossyProvenancePtr2IntSuggestion};
6use crate::{LateContext, LateLintPass};
7
8#[doc =
r" The `lossy_provenance_casts` lint detects an `as` cast between a pointer"]
#[doc = r" and an integer."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" #![feature(strict_provenance_lints)]"]
#[doc = r" #![warn(lossy_provenance_casts)]"]
#[doc = r""]
#[doc = r" fn main() {"]
#[doc = r"     let x: u8 = 37;"]
#[doc = r"     let _addr: usize = &x as *const u8 as usize;"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" This lint is part of the strict provenance effort, see [issue #95228]."]
#[doc =
r" Casting a pointer to an integer is a lossy operation, because beyond"]
#[doc = r" just an *address* a pointer may be associated with a particular"]
#[doc =
r" *provenance*. This information is used by the optimiser and for dynamic"]
#[doc =
r" analysis/dynamic program verification (e.g. Miri or CHERI platforms)."]
#[doc = r""]
#[doc = r" Since this cast is lossy, it is considered good style to use the"]
#[doc =
r" [`ptr::addr`] method instead, which has a similar effect, but doesn't"]
#[doc =
r#" "expose" the pointer provenance. This improves optimisation potential."#]
#[doc =
r" See the docs of [`ptr::addr`] and [`ptr::expose_provenance`] for more information"]
#[doc = r" about exposing pointer provenance."]
#[doc = r""]
#[doc =
r" If your code can't comply with strict provenance and needs to expose"]
#[doc =
r" the provenance, then there is [`ptr::expose_provenance`] as an escape hatch,"]
#[doc =
r" which preserves the behaviour of `as usize` casts while being explicit"]
#[doc = r" about the semantics."]
#[doc = r""]
#[doc = r" [issue #95228]: https://github.com/rust-lang/rust/issues/95228"]
#[doc =
r" [`ptr::addr`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.addr"]
#[doc =
r" [`ptr::expose_provenance`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.expose_provenance"]
pub static LOSSY_PROVENANCE_CASTS: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "LOSSY_PROVENANCE_CASTS",
            default_level: ::rustc_lint_defs::Allow,
            desc: "a lossy pointer to integer cast is used",
            is_externally_loaded: false,
            feature_gate: Some(rustc_span::sym::strict_provenance_lints),
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
9    /// The `lossy_provenance_casts` lint detects an `as` cast between a pointer
10    /// and an integer.
11    ///
12    /// ### Example
13    ///
14    /// ```rust
15    /// #![feature(strict_provenance_lints)]
16    /// #![warn(lossy_provenance_casts)]
17    ///
18    /// fn main() {
19    ///     let x: u8 = 37;
20    ///     let _addr: usize = &x as *const u8 as usize;
21    /// }
22    /// ```
23    ///
24    /// {{produces}}
25    ///
26    /// ### Explanation
27    ///
28    /// This lint is part of the strict provenance effort, see [issue #95228].
29    /// Casting a pointer to an integer is a lossy operation, because beyond
30    /// just an *address* a pointer may be associated with a particular
31    /// *provenance*. This information is used by the optimiser and for dynamic
32    /// analysis/dynamic program verification (e.g. Miri or CHERI platforms).
33    ///
34    /// Since this cast is lossy, it is considered good style to use the
35    /// [`ptr::addr`] method instead, which has a similar effect, but doesn't
36    /// "expose" the pointer provenance. This improves optimisation potential.
37    /// See the docs of [`ptr::addr`] and [`ptr::expose_provenance`] for more information
38    /// about exposing pointer provenance.
39    ///
40    /// If your code can't comply with strict provenance and needs to expose
41    /// the provenance, then there is [`ptr::expose_provenance`] as an escape hatch,
42    /// which preserves the behaviour of `as usize` casts while being explicit
43    /// about the semantics.
44    ///
45    /// [issue #95228]: https://github.com/rust-lang/rust/issues/95228
46    /// [`ptr::addr`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.addr
47    /// [`ptr::expose_provenance`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.expose_provenance
48    pub LOSSY_PROVENANCE_CASTS,
49    Allow,
50    "a lossy pointer to integer cast is used",
51    @feature_gate = strict_provenance_lints;
52}
53
54#[doc = r" Lint for `as` casts between a pointer and an integer."]
pub struct LossyProvenanceCasts;
#[automatically_derived]
impl ::core::marker::Copy for LossyProvenanceCasts { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LossyProvenanceCasts { }
#[automatically_derived]
impl ::core::clone::Clone for LossyProvenanceCasts {
    #[inline]
    fn clone(&self) -> LossyProvenanceCasts { *self }
}
impl ::rustc_lint_defs::LintPass for LossyProvenanceCasts {
    fn name(&self) -> &'static str { "LossyProvenanceCasts" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [LOSSY_PROVENANCE_CASTS]))
    }
}
impl LossyProvenanceCasts {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [LOSSY_PROVENANCE_CASTS]))
    }
}declare_lint_pass!(
55    /// Lint for `as` casts between a pointer and an integer.
56    LossyProvenanceCasts => [LOSSY_PROVENANCE_CASTS]
57);
58
59impl<'tcx> LateLintPass<'tcx> for LossyProvenanceCasts {
60    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
61        let hir::ExprKind::Cast(cast_from_expr, cast_to_hir) = expr.kind else { return };
62
63        let typeck_results = cx.typeck_results();
64        // Only lint casts from pointer to integer
65        let cast_from_ty = typeck_results.expr_ty(cast_from_expr);
66        if !cast_from_ty.is_raw_ptr() {
67            return;
68        }
69        let cast_to_ty = typeck_results.expr_ty(expr);
70        if !cast_to_ty.is_integral() {
71            return;
72        }
73
74        let sugg = expr.span.can_be_used_for_suggestions().then(|| {
75            let needs_parens = cx.precedence(cast_from_expr) < ExprPrecedence::Unambiguous;
76            let needs_cast = !cast_to_ty.is_usize();
77            let cast_span = cast_from_expr.span.shrink_to_hi().to(cast_to_hir.span);
78            let expr_span = cast_from_expr.span.shrink_to_lo();
79            match (needs_parens, needs_cast) {
80                (true, true) => LossyProvenancePtr2IntSuggestion::NeedsParensCast {
81                    expr_span,
82                    cast_span,
83                    cast_to_ty,
84                },
85                (true, false) => {
86                    LossyProvenancePtr2IntSuggestion::NeedsParens { expr_span, cast_span }
87                }
88                (false, true) => {
89                    LossyProvenancePtr2IntSuggestion::NeedsCast { cast_span, cast_to_ty }
90                }
91                (false, false) => LossyProvenancePtr2IntSuggestion::Other { cast_span },
92            }
93        });
94
95        let lint = LossyProvenancePtr2Int { cast_from_ty, cast_to_ty, sugg };
96        cx.tcx.emit_node_span_lint(LOSSY_PROVENANCE_CASTS, expr.hir_id, expr.span, lint);
97    }
98}