Skip to content

Commit 217814b

Browse files
committed
Auto merge of rust-lang#128985 - GrigorenkoPV:instantly-dangling-pointer, r=<try>
Lint against getting pointers from immediately dropped temporaries Fixes rust-lang#123613 ## Changes: 1. New lint: `dangling_pointers_from_temporaries`. Is a generalization of `temporary_cstring_as_ptr` for more types and more ways to get a temporary. 2. `temporary_cstring_as_ptr` is marked as renamed to `dangling_pointers_from_temporaries`. 3. `clippy::temporary_cstring_as_ptr` is marked as renamed to `dangling_pointers_from_temporaries`. 4. `core::cell::Cell` is now `rustc_diagnostic_item = "Cell"` ## TODO: - [x] ~Add tests for different types~ - [x] ~Add tests for different ways of getting a temporary~ - [x] ~Regroup tests for this and `temporary_cstring_as_ptr`~ - [x] ~Fix a strange ICE when the lint is `allow`ed.~ - [x] ~Check what happens when you `break` with a bound variable from `loop`/`match`/`if`/`block`.~ - [x] ~Document the lint~ - [x] ~Fix tests rust-lang#128985 (comment) - [x] ~Fix clippy~ - [x] ~Fix miri rust-lang#128985 (review) - [ ] Crater run? ## Questions: - [ ] Should we make something like `is_temporary_rvalue` (but not bogus) available in compiler? - [x] ~Should `temporary_cstring_as_ptr` be deprecated? Across an edition boundary?~ - [ ] Instead of manually checking for a list of known methods, maybe add some sort of annotation to those methods in library and check for the presence of that annotation? - [ ] Maybe even introduce some form of primitive lifetimes for pointers and check those in borrow-checker? ## Future improvements: - [ ] Fix false negatives[^fn] - [ ] Add suggestions rust-lang#128985 (comment) ## Known limitations: ### False negatives[^fn]: [^fn]: lint **should** be emitted, but **is not** - `temporary_unsafe_cell.get()` - `temporary.field.as_ptr()` - `temporary[index].as_ptr()` - Converting `&temporary` to pointer with `as` or stuff like `ptr::from_ref`. - The `&raw [mut] temporary` ### False positives[^fp]: [^fp]: lint **should not** be emitted, but **is** Both this lint and the already existing `temporary_cstring_as_ptr` will fire on code like this: ```rust foo(CString::new("hello").unwrap().as_ptr()) ``` even though using the resulting pointer inside of `foo` is completely fine (at least according to miri), probably due to argument promotion logic.
2 parents b5723af + 56036ea commit 217814b

26 files changed

+765
-114
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,6 @@ lint_crate_name_in_cfg_attr_deprecated =
209209
lint_crate_type_in_cfg_attr_deprecated =
210210
`crate_type` within an `#![cfg_attr]` attribute is deprecated
211211
212-
lint_cstring_ptr = getting the inner pointer of a temporary `CString`
213-
.as_ptr_label = this pointer will be invalid
214-
.unwrap_label = this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime
215-
.note = pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned
216-
.help = for more information, see https://doc.rust-lang.org/reference/destructors.html
217-
218212
lint_custom_inner_attribute_unstable = custom inner attributes are unstable
219213
220214
lint_default_hash_types = prefer `{$preferred}` over `{$used}`, it has better performance
@@ -411,6 +405,12 @@ lint_incomplete_include =
411405
412406
lint_inner_macro_attribute_unstable = inner macro attributes are unstable
413407
408+
lint_instantly_dangling = getting a pointer from a temporary `{$ty}` will result in a dangling pointer
409+
.label_ptr = this pointer will immediately be invalid
410+
.label_temporary = this `{$ty}` is deallocated at the end of the statement, bind it to a variable to extend its lifetime
411+
.note = pointers do not have a lifetime; when calling `{$callee}` the `{$ty}` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned
412+
.help = for more information, see https://doc.rust-lang.org/reference/destructors.html
413+
414414
lint_invalid_asm_label_binary = avoid using labels containing only the digits `0` and `1` in inline assembly
415415
.label = use a different label that doesn't start with `0` or `1`
416416
.help = start numbering with `2` instead

compiler/rustc_lint/src/dangling.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use rustc_hir::{Expr, ExprKind, LangItem};
2+
use rustc_middle::ty::{Ty, TyCtxt};
3+
use rustc_session::{declare_lint, declare_lint_pass};
4+
use rustc_span::symbol::sym;
5+
6+
use crate::lints::InstantlyDangling;
7+
use crate::{LateContext, LateLintPass, LintContext};
8+
9+
// FIXME: does not catch UnsafeCell::get
10+
// FIXME: does not catch getting a ref to a temporary and then converting it to a ptr
11+
declare_lint! {
12+
/// The `dangling_pointers_from_temporaries` lint detects getting a pointer to data
13+
/// of a temporary that will immediately get dropped.
14+
///
15+
/// ### Example
16+
///
17+
/// ```rust
18+
/// # #![allow(unused)]
19+
/// # unsafe fn use_data(ptr: *const u8) {
20+
/// # dbg!(unsafe { ptr.read() });
21+
/// # }
22+
/// fn gather_and_use(bytes: impl Iterator<Item = u8>) {
23+
/// let x: *const u8 = bytes.collect::<Vec<u8>>().as_ptr();
24+
/// unsafe { use_data(x) }
25+
/// }
26+
/// ```
27+
///
28+
/// {{produces}}
29+
///
30+
/// ### Explanation
31+
///
32+
/// Getting a pointer from a temporary value will not prolong its lifetime,
33+
/// which means that the value can be dropped and the allocation freed
34+
/// while the pointer still exists, making the pointer dangling.
35+
/// This is not an error (as far as the type system is concerned)
36+
/// but probably is not what the user intended either.
37+
///
38+
/// If you need stronger guarantees, consider using references instead,
39+
/// as they are statically verified by the borrow-checker to never dangle.
40+
pub DANGLING_POINTERS_FROM_TEMPORARIES,
41+
Warn,
42+
"detects getting a pointer from a temporary"
43+
}
44+
45+
declare_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES]);
46+
47+
impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
48+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
49+
if let ExprKind::MethodCall(method, receiver, _args, _span) = expr.kind
50+
&& matches!(method.ident.name, sym::as_ptr | sym::as_mut_ptr)
51+
&& is_temporary_rvalue(receiver)
52+
&& let ty = cx.typeck_results().expr_ty(receiver)
53+
&& is_interesting(cx.tcx, ty)
54+
{
55+
cx.emit_span_lint(
56+
DANGLING_POINTERS_FROM_TEMPORARIES,
57+
method.ident.span,
58+
InstantlyDangling {
59+
callee: method.ident.name,
60+
ty,
61+
ptr_span: method.ident.span,
62+
temporary_span: receiver.span,
63+
},
64+
)
65+
}
66+
}
67+
}
68+
69+
fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
70+
match expr.kind {
71+
// Const is not temporary.
72+
ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
73+
74+
// This is literally lvalue.
75+
ExprKind::Path(..) => false,
76+
77+
// Calls return rvalues.
78+
ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) => true,
79+
80+
// Inner blocks are rvalues.
81+
ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
82+
83+
// FIXME: these should probably recurse and typecheck along the way.
84+
// Some false negatives are possible for now.
85+
ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
86+
87+
ExprKind::Struct(..) => true,
88+
89+
// FIXME: this has false negatives, but I do not want to deal with 'static/const promotion just yet.
90+
ExprKind::Array(..) => false,
91+
92+
// These typecheck to `!`
93+
ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
94+
false
95+
}
96+
97+
// These typecheck to `()`
98+
ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
99+
100+
// Compiler-magic macros
101+
ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
102+
103+
// We are not interested in these
104+
ExprKind::Cast(..)
105+
| ExprKind::Closure(..)
106+
| ExprKind::Tup(..)
107+
| ExprKind::DropTemps(..)
108+
| ExprKind::Let(..) => false,
109+
110+
// Not applicable
111+
ExprKind::Type(..) | ExprKind::Err(..) => false,
112+
}
113+
}
114+
115+
// Array, Vec, String, CString, MaybeUninit, Cell, Box<[_]>, Box<str>, Box<CStr>,
116+
// or any of the above in arbitrary many nested Box'es.
117+
fn is_interesting(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
118+
if ty.is_array() {
119+
true
120+
} else if ty.is_box() {
121+
let inner = ty.boxed_ty();
122+
inner.is_slice()
123+
|| inner.is_str()
124+
|| inner.ty_adt_def().is_some_and(|def| tcx.is_lang_item(def.did(), LangItem::CStr))
125+
|| is_interesting(tcx, inner)
126+
} else if let Some(def) = ty.ty_adt_def() {
127+
for lang_item in [LangItem::String, LangItem::MaybeUninit] {
128+
if tcx.is_lang_item(def.did(), lang_item) {
129+
return true;
130+
}
131+
}
132+
tcx.get_diagnostic_name(def.did())
133+
.is_some_and(|name| matches!(name, sym::cstring_type | sym::Vec | sym::Cell))
134+
} else {
135+
false
136+
}
137+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ mod async_closures;
4545
mod async_fn_in_trait;
4646
pub mod builtin;
4747
mod context;
48+
mod dangling;
4849
mod deref_into_dyn_supertrait;
4950
mod drop_forget_useless;
5051
mod early;
@@ -63,7 +64,6 @@ mod levels;
6364
mod lints;
6465
mod macro_expr_fragment_specifier_2024_migration;
6566
mod map_unit_fn;
66-
mod methods;
6767
mod multiple_supertrait_upcastable;
6868
mod non_ascii_idents;
6969
mod non_fmt_panic;
@@ -87,6 +87,7 @@ mod unused;
8787
use async_closures::AsyncClosureUsage;
8888
use async_fn_in_trait::AsyncFnInTrait;
8989
use builtin::*;
90+
use dangling::*;
9091
use deref_into_dyn_supertrait::*;
9192
use drop_forget_useless::*;
9293
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
@@ -98,7 +99,6 @@ use invalid_from_utf8::*;
9899
use let_underscore::*;
99100
use macro_expr_fragment_specifier_2024_migration::*;
100101
use map_unit_fn::*;
101-
use methods::*;
102102
use multiple_supertrait_upcastable::*;
103103
use non_ascii_idents::*;
104104
use non_fmt_panic::NonPanicFmt;
@@ -225,7 +225,7 @@ late_lint_methods!(
225225
UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller,
226226
ShadowedIntoIter: ShadowedIntoIter,
227227
DropTraitConstraints: DropTraitConstraints,
228-
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
228+
DanglingPointers: DanglingPointers,
229229
NonPanicFmt: NonPanicFmt,
230230
NoopMethodCall: NoopMethodCall,
231231
EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
@@ -346,6 +346,7 @@ fn register_builtins(store: &mut LintStore) {
346346
store.register_renamed("non_fmt_panic", "non_fmt_panics");
347347
store.register_renamed("unused_tuple_struct_fields", "dead_code");
348348
store.register_renamed("static_mut_ref", "static_mut_refs");
349+
store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries");
349350

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

compiler/rustc_lint/src/lints.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,16 +1124,19 @@ pub struct IgnoredUnlessCrateSpecified<'a> {
11241124
pub name: Symbol,
11251125
}
11261126

1127-
// methods.rs
1127+
// dangling.rs
11281128
#[derive(LintDiagnostic)]
1129-
#[diag(lint_cstring_ptr)]
1129+
#[diag(lint_instantly_dangling)]
11301130
#[note]
11311131
#[help]
1132-
pub struct CStringPtr {
1133-
#[label(lint_as_ptr_label)]
1134-
pub as_ptr: Span,
1135-
#[label(lint_unwrap_label)]
1136-
pub unwrap: Span,
1132+
// FIXME: use #[primary_span]
1133+
pub struct InstantlyDangling<'tcx> {
1134+
pub callee: Symbol,
1135+
pub ty: Ty<'tcx>,
1136+
#[label(lint_label_ptr)]
1137+
pub ptr_span: Span,
1138+
#[label(lint_label_temporary)]
1139+
pub temporary_span: Span,
11371140
}
11381141

11391142
// multiple_supertrait_upcastable.rs

compiler/rustc_lint/src/methods.rs

Lines changed: 0 additions & 70 deletions
This file was deleted.

compiler/rustc_span/src/symbol.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ symbols! {
170170
CallOnceFuture,
171171
CallRefFuture,
172172
Capture,
173+
Cell,
173174
Center,
174175
Cleanup,
175176
Clone,
@@ -411,6 +412,7 @@ symbols! {
411412
arm,
412413
arm_target_feature,
413414
array,
415+
as_mut_ptr,
414416
as_ptr,
415417
as_ref,
416418
as_str,

library/alloc/tests/boxed.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use core::mem::MaybeUninit;
44
use core::ptr::NonNull;
55

66
#[test]
7+
#[cfg_attr(not(bootstrap), expect(dangling_pointers_from_temporaries))]
78
fn uninitialized_zero_size_box() {
89
assert_eq!(
910
&*Box::<()>::new_uninit() as *const _,

library/core/src/cell.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ pub use once::OnceCell;
304304
/// ```
305305
///
306306
/// See the [module-level documentation](self) for more.
307+
#[cfg_attr(not(test), rustc_diagnostic_item = "Cell")]
307308
#[stable(feature = "rust1", since = "1.0.0")]
308309
#[repr(transparent)]
309310
pub struct Cell<T: ?Sized> {

library/core/src/ffi/c_str.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,9 @@ impl CStr {
463463
/// behavior when `ptr` is used inside the `unsafe` block:
464464
///
465465
/// ```no_run
466-
/// # #![allow(unused_must_use)] #![allow(temporary_cstring_as_ptr)]
466+
/// # #![allow(unused_must_use)]
467+
/// # #![cfg_attr(bootstrap, expect(temporary_cstring_as_ptr))]
468+
/// # #![cfg_attr(not(bootstrap), expect(dangling_pointers_from_temporaries))]
467469
/// use std::ffi::CString;
468470
///
469471
/// // Do not do this:

src/tools/clippy/clippy_lints/src/deprecated_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
166166
#[clippy::version = ""]
167167
("clippy::positional_named_format_parameters", "named_arguments_used_positionally"),
168168
#[clippy::version = ""]
169-
("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"),
169+
("clippy::temporary_cstring_as_ptr", "dangling_pointers_from_temporaries"),
170170
#[clippy::version = ""]
171171
("clippy::undropped_manually_drops", "undropped_manually_drops"),
172172
#[clippy::version = ""]

src/tools/clippy/tests/ui/rename.fixed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
#![allow(enum_intrinsics_non_enums)]
5555
#![allow(non_fmt_panics)]
5656
#![allow(named_arguments_used_positionally)]
57-
#![allow(temporary_cstring_as_ptr)]
57+
#![allow(dangling_pointers_from_temporaries)]
5858
#![allow(undropped_manually_drops)]
5959
#![allow(unknown_lints)]
6060
#![allow(unused_labels)]
@@ -120,7 +120,7 @@
120120
#![warn(unexpected_cfgs)] //~ ERROR: lint `clippy::mismatched_target_os`
121121
#![warn(non_fmt_panics)] //~ ERROR: lint `clippy::panic_params`
122122
#![warn(named_arguments_used_positionally)] //~ ERROR: lint `clippy::positional_named_format_parameters`
123-
#![warn(temporary_cstring_as_ptr)] //~ ERROR: lint `clippy::temporary_cstring_as_ptr`
123+
#![warn(dangling_pointers_from_temporaries)] //~ ERROR: lint `clippy::temporary_cstring_as_ptr`
124124
#![warn(undropped_manually_drops)] //~ ERROR: lint `clippy::undropped_manually_drops`
125125
#![warn(unknown_lints)] //~ ERROR: lint `clippy::unknown_clippy_lints`
126126
#![warn(unused_labels)] //~ ERROR: lint `clippy::unused_label`

0 commit comments

Comments
 (0)