Skip to content

Commit 6e531ef

Browse files
committed
Merge and reframe strict_provenance_lints
There does not seem to be any good reason to have separate lints for ptr2int and int2ptr casts. The main reason to enable this lint is to help with replacing `as` casts with strict provenance APIs (if possible) or explicit provenance APIs (when needed). That applies equally to both directions. Merging the lints requires coming up with new name and lint explanation. This is a good chance to reconsider the purpose and messaging of the lints which have been essentially unchanged since the early days of the "strict provenance experiment". For reasons described below, I called the merged lint `implicit_provenance_casts`, rewrote its explanation from scratch, and made some changes to diagnostics. First, the lint is not (only) about strict provenance any more. While strict provenance is often the best fix, migrating `as` casts to exposed provenance APIs also silences the lint. The exposed provenance APIs aren't any better for Miri and CHERI, but at least provenance considerations are made *explicit*, not picked up as side effect of the `as` keyword. (Banning use of exposed provenance APIs can be done with clippy's existing `disallowed_methods` lint.) Second, provenance is now officially part of the language and prominently explained in the `std::ptr` documentation. The new lint refers to those docs and is more consistent with them. Third, while strict provenance is encouraged whenever possible, exposed provenance is also part of the language and here to stay. Indeed, if we eventually enable the lint by default and make it `cargo fix`-able to keep the ecosystem migration manageable, we may want to suggest exposed provenance APIs to err on the side of not introducing UB. Thus, I made the diagnostics more neutral and descriptive. I've kept the suggestions that introduce strict provenance APIs, but we may want to reconsider this later.
1 parent 0cf9681 commit 6e531ef

30 files changed

Lines changed: 280 additions & 291 deletions

compiler/rustc_lint/src/fuzzy_provenance_casts.rs

Lines changed: 0 additions & 79 deletions
This file was deleted.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use rustc_ast::util::parser::ExprPrecedence;
2+
use rustc_hir as hir;
3+
use rustc_middle::ty::Ty;
4+
use rustc_session::{declare_lint, declare_lint_pass};
5+
6+
use crate::lints::{
7+
ImplicitProvenanceCastsInt2Ptr, ImplicitProvenanceCastsPtr2Int, Int2PtrSuggestion,
8+
Ptr2IntSuggestion,
9+
};
10+
use crate::{LateContext, LateLintPass};
11+
12+
declare_lint! {
13+
/// The `implicit_provenance_casts` lint detects integer-to-pointer and pointer-to-integer casts
14+
/// that rely on [*Exposed Provenance*][exposed-provenance].
15+
///
16+
/// ### Example
17+
///
18+
/// ```rust
19+
/// #![feature(strict_provenance_lints)]
20+
/// #![warn(implicit_provenance_casts)]
21+
///
22+
/// fn main() {
23+
/// let x: u8 = 37;
24+
/// let addr: usize = &x as *const u8 as usize;
25+
/// let _ptr = addr as *const u8;
26+
/// }
27+
/// ```
28+
///
29+
/// {{produces}}
30+
///
31+
/// ### Explanation
32+
///
33+
/// This lint exists to help migrate code to [*Strict Provenance* APIs][strict-provenance] where
34+
/// possible, and make remaining uses of [*Exposed Provenance*][exposed-provenance] explicit.
35+
/// For more information on pointer provenance, see the [`std::ptr` documentation][provenance].
36+
///
37+
/// Earlier versions of Rust did not have a clear answer how integer-to-pointer and
38+
/// pointer-to-integer casts interact with provenance. Such casts are now defined to use the
39+
/// exposed provenance model, but in many cases the code can be updated to strict provenance
40+
/// APIs, which is preferable as it enables more precise reasoning about unsafe code, both by
41+
/// humans and by tools like [Miri].
42+
///
43+
/// However, there are situations where exposed provenance is required or following the strict
44+
/// provenance model requires major refactorings. In those cases, it's still useful to replace
45+
/// the `as` casts with explicit use of exposed provenance APIs and a comment explaining why
46+
/// they are needed.
47+
///
48+
/// [provenance]: https://doc.rust-lang.org/core/ptr/index.html#provenance
49+
/// [strict-provenance]: https://doc.rust-lang.org/core/ptr/index.html#strict-provenance
50+
/// [exposed-provenance]: https://doc.rust-lang.org/core/ptr/index.html#exposed-provenance
51+
/// [Miri]: https://github.com/rust-lang/miri
52+
pub IMPLICIT_PROVENANCE_CASTS,
53+
Allow,
54+
"an `as` cast relying on exposed provenance is used",
55+
@feature_gate = strict_provenance_lints;
56+
}
57+
58+
declare_lint_pass!(
59+
/// Lint for int2ptr and ptr2int `as` casts.
60+
ImplicitProvenanceCasts => [IMPLICIT_PROVENANCE_CASTS]
61+
);
62+
63+
impl<'tcx> LateLintPass<'tcx> for ImplicitProvenanceCasts {
64+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
65+
let hir::ExprKind::Cast(cast_from_expr, cast_to_hir) = expr.kind else { return };
66+
67+
let typeck_results = cx.typeck_results();
68+
let cast_from_ty = typeck_results.expr_ty(cast_from_expr);
69+
if cast_from_ty.is_raw_ptr() {
70+
let cast_to_ty = typeck_results.expr_ty(expr);
71+
if cast_to_ty.is_integral() {
72+
lint_ptr2int(cx, expr, cast_from_expr, cast_from_ty, cast_to_hir, cast_to_ty)
73+
}
74+
} else if cast_from_ty.is_integral() {
75+
let cast_to_ty = typeck_results.expr_ty(expr);
76+
if cast_to_ty.is_raw_ptr() {
77+
lint_int2ptr(cx, expr, cast_from_expr, cast_from_ty, cast_to_hir, cast_to_ty)
78+
}
79+
}
80+
}
81+
}
82+
83+
fn lint_ptr2int<'tcx>(
84+
cx: &LateContext<'tcx>,
85+
expr: &'tcx hir::Expr<'tcx>,
86+
cast_from_expr: &'tcx hir::Expr<'tcx>,
87+
cast_from_ty: Ty<'tcx>,
88+
cast_to_hir: &'tcx hir::Ty<'tcx>,
89+
cast_to_ty: Ty<'tcx>,
90+
) {
91+
let sugg = expr.span.can_be_used_for_suggestions().then(|| {
92+
let needs_parens = cx.precedence(cast_from_expr) < ExprPrecedence::Unambiguous;
93+
let needs_cast = !cast_to_ty.is_usize();
94+
let cast_span = cast_from_expr.span.shrink_to_hi().to(cast_to_hir.span);
95+
let expr_span = cast_from_expr.span.shrink_to_lo();
96+
match (needs_parens, needs_cast) {
97+
(true, true) => Ptr2IntSuggestion::NeedsParensCast { expr_span, cast_span, cast_to_ty },
98+
(true, false) => Ptr2IntSuggestion::NeedsParens { expr_span, cast_span },
99+
(false, true) => Ptr2IntSuggestion::NeedsCast { cast_span, cast_to_ty },
100+
(false, false) => Ptr2IntSuggestion::Other { cast_span },
101+
}
102+
});
103+
104+
let lint = ImplicitProvenanceCastsPtr2Int { cast_from_ty, cast_to_ty, sugg };
105+
cx.tcx.emit_node_span_lint(IMPLICIT_PROVENANCE_CASTS, expr.hir_id, expr.span, lint);
106+
}
107+
108+
fn lint_int2ptr<'tcx>(
109+
cx: &LateContext<'tcx>,
110+
expr: &'tcx hir::Expr<'tcx>,
111+
cast_from_expr: &'tcx hir::Expr<'tcx>,
112+
cast_from_ty: Ty<'tcx>,
113+
cast_to_hir: &'tcx hir::Ty<'tcx>,
114+
cast_to_ty: Ty<'tcx>,
115+
) {
116+
let sugg = expr.span.can_be_used_for_suggestions().then(|| Int2PtrSuggestion {
117+
lo: cast_from_expr.span.shrink_to_lo(),
118+
hi: cast_from_expr.span.shrink_to_hi().to(cast_to_hir.span),
119+
});
120+
let lint = ImplicitProvenanceCastsInt2Ptr { expr_ty: cast_from_ty, cast_ty: cast_to_ty, sugg };
121+
cx.tcx.emit_node_span_lint(IMPLICIT_PROVENANCE_CASTS, expr.hir_id, expr.span, lint)
122+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ mod expect;
4545
mod for_loops_over_fallibles;
4646
mod foreign_modules;
4747
mod function_cast_as_integer;
48-
mod fuzzy_provenance_casts;
4948
mod gpukernel_abi;
5049
mod if_let_rescope;
5150
mod impl_trait_overcaptures;
51+
mod implicit_provenance_casts;
5252
mod interior_mutable_consts;
5353
mod internal;
5454
mod invalid_from_utf8;
@@ -57,7 +57,6 @@ mod let_underscore;
5757
mod levels;
5858
pub mod lifetime_syntax;
5959
mod lints;
60-
mod lossy_provenance_casts;
6160
mod macro_expr_fragment_specifier_2024_migration;
6261
mod map_unit_fn;
6362
mod multiple_supertrait_upcastable;
@@ -94,16 +93,15 @@ use drop_forget_useless::*;
9493
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
9594
use for_loops_over_fallibles::*;
9695
use function_cast_as_integer::*;
97-
use fuzzy_provenance_casts::FuzzyProvenanceCasts;
9896
use gpukernel_abi::*;
9997
use if_let_rescope::IfLetRescope;
10098
use impl_trait_overcaptures::ImplTraitOvercaptures;
99+
use implicit_provenance_casts::ImplicitProvenanceCasts;
101100
use interior_mutable_consts::*;
102101
use internal::*;
103102
use invalid_from_utf8::*;
104103
use let_underscore::*;
105104
use lifetime_syntax::*;
106-
use lossy_provenance_casts::LossyProvenanceCasts;
107105
use macro_expr_fragment_specifier_2024_migration::*;
108106
use map_unit_fn::*;
109107
use multiple_supertrait_upcastable::*;
@@ -254,8 +252,7 @@ late_lint_methods!(
254252
CheckTransmutes: CheckTransmutes,
255253
LifetimeSyntax: LifetimeSyntax,
256254
InternalEqTraitMethodImpls: InternalEqTraitMethodImpls,
257-
FuzzyProvenanceCasts: FuzzyProvenanceCasts,
258-
LossyProvenanceCasts: LossyProvenanceCasts,
255+
ImplicitProvenanceCasts: ImplicitProvenanceCasts,
259256
]
260257
]
261258
);
@@ -380,6 +377,8 @@ fn register_builtins(store: &mut LintStore) {
380377
"repr_transparent_external_private_fields",
381378
"repr_transparent_non_zst_fields",
382379
);
380+
store.register_renamed("fuzzy_provenance_casts", "implicit_provenance_casts");
381+
store.register_renamed("lossy_provenance_casts", "implicit_provenance_casts");
383382

384383
// These were moved to tool lints, but rustc still sees them when compiling normally, before
385384
// tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use

compiler/rustc_lint/src/lints.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,45 +2935,43 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion {
29352935
pub(crate) struct EqInternalMethodImplemented;
29362936

29372937
#[derive(Diagnostic)]
2938-
#[diag("strict provenance disallows casting integer `{$expr_ty}` to pointer `{$cast_ty}`")]
2938+
#[diag("cast from `{$expr_ty}` to `{$cast_ty}` implicitly relies on exposed provenance")]
29392939
#[help(
2940-
"if you can't comply with strict provenance and don't have a pointer with the correct provenance you can use `std::ptr::with_exposed_provenance()` instead"
2940+
"if conforming to strict provenance is not possible, use `std::ptr::with_exposed_provenance()`"
29412941
)]
2942-
pub(crate) struct LossyProvenanceInt2Ptr<'tcx> {
2942+
#[note("for more information, visit <https://doc.rust-lang.org/std/ptr/index.html#provenance>")]
2943+
pub(crate) struct ImplicitProvenanceCastsInt2Ptr<'tcx> {
29432944
pub expr_ty: Ty<'tcx>,
29442945
pub cast_ty: Ty<'tcx>,
29452946
#[subdiagnostic]
2946-
pub sugg: Option<LossyProvenanceInt2PtrSuggestion>,
2947+
pub sugg: Option<Int2PtrSuggestion>,
29472948
}
29482949

29492950
#[derive(Subdiagnostic)]
29502951
#[multipart_suggestion(
2951-
"use `.with_addr()` to adjust a valid pointer in the same allocation, to this address",
2952+
"use `.with_addr()` to adjust the address of a valid pointer in the same allocation",
29522953
applicability = "has-placeholders"
29532954
)]
2954-
pub(crate) struct LossyProvenanceInt2PtrSuggestion {
2955+
pub(crate) struct Int2PtrSuggestion {
29552956
#[suggestion_part(code = "(...).with_addr(")]
29562957
pub lo: Span,
29572958
#[suggestion_part(code = ")")]
29582959
pub hi: Span,
29592960
}
29602961

29612962
#[derive(Diagnostic)]
2962-
#[diag(
2963-
"under strict provenance it is considered bad style to cast pointer `{$cast_from_ty}` to integer `{$cast_to_ty}`"
2964-
)]
2965-
#[help(
2966-
"if you can't comply with strict provenance and need to expose the pointer provenance you can use `.expose_provenance()` instead"
2967-
)]
2968-
pub(crate) struct LossyProvenancePtr2Int<'tcx> {
2963+
#[diag("cast from `{$cast_from_ty}` to `{$cast_to_ty}` implicitly exposes pointer provenance")]
2964+
#[help("if conforming to strict provenance is not possible, use `.expose_provenance()`")]
2965+
#[note("for more information, visit <https://doc.rust-lang.org/std/ptr/index.html#provenance>")]
2966+
pub(crate) struct ImplicitProvenanceCastsPtr2Int<'tcx> {
29692967
pub cast_from_ty: Ty<'tcx>,
29702968
pub cast_to_ty: Ty<'tcx>,
29712969
#[subdiagnostic]
2972-
pub sugg: Option<LossyProvenancePtr2IntSuggestion<'tcx>>,
2970+
pub sugg: Option<Ptr2IntSuggestion<'tcx>>,
29732971
}
29742972

29752973
#[derive(Subdiagnostic)]
2976-
pub(crate) enum LossyProvenancePtr2IntSuggestion<'tcx> {
2974+
pub(crate) enum Ptr2IntSuggestion<'tcx> {
29772975
#[multipart_suggestion(
29782976
"use `.addr()` to obtain the address of a pointer",
29792977
applicability = "maybe-incorrect"

0 commit comments

Comments
 (0)