Skip to content

Commit 53c4e99

Browse files
committed
Rework raw_assign_to_drop-text, allow UnsafeCell::get
1 parent 62da020 commit 53c4e99

File tree

4 files changed

+62
-30
lines changed

4 files changed

+62
-30
lines changed

clippy_lints/src/operators/mod.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -851,28 +851,38 @@ declare_clippy_lint! {
851851
/// uninitialized or otherwise invalid, the execution of `std::ops::Drop::drop(&mut self)`
852852
/// is always Undefined Behavior.
853853
///
854-
/// Use `std::ptr::write()` to overwrite a value without executing the destructor.
854+
/// If the code can guarantee that the value being replaced during assignment is safe to
855+
/// drop, it is recommended to
856+
/// * silence this lint by adding a narrowly-scoped `#[expect(raw_assign_to_drop)]`-attribute
857+
/// * make liberal use of SAFETY-comments e.g. around declaration of this unsafe pointer,
858+
/// reminding readers that this unsafe pointer *must* contain an initialized value.
855859
///
856-
/// Use `std::ptr::drop_in_place()` to conditionally execute the destructor if you are
857-
/// sure that the place contains an initialized value.
860+
/// If the code can *not* guarantee that the previous value can be safely dropped,
861+
/// use `std::ptr::write()` to overwrite the previous (possibly nonexisting) value
862+
/// without executing the destructor.
863+
///
864+
/// Use `std::ptr::drop_in_place()` to conditionally execute the destructor.
858865
///
859866
/// ### Example
860867
/// ```no_run
861868
/// unsafe fn foo(oldvalue: *mut String) {
862-
/// // Direct assignment always executes `String`'s destructor on `oldvalue`
863-
/// *oldvalue = "New Value".to_owned();
869+
/// // Direct assignment always executes `String`'s destructor on `oldvalue`.
870+
/// // However, we can't guarantee that executing the destructor is safe.
871+
/// unsafe { *oldvalue = "New Value".to_owned(); }
864872
/// }
865873
/// ```
866874
/// Use instead:
867875
/// ```no_run
868876
/// unsafe fn foo(oldvalue: *mut String, oldvalue_is_initialized: bool) {
877+
/// let newvalue = "New Value".to_owned();
869878
/// if oldvalue_is_initialized {
870879
/// // Having established that `oldvalue` points to a valid value, selectively
871880
/// // execute the destructor to prevent a memory-leak
872-
/// oldvalue.drop_in_place();
881+
/// unsafe { *oldvalue = newvalue; }
882+
/// } else {
883+
/// // Overwrite the old value without running the destructor.
884+
/// unsafe { oldvalue.write(newvalue); }
873885
/// }
874-
/// // Overwrite the old value without running the destructor unconditionally
875-
/// oldvalue.write("New Value".to_owned());
876886
/// }
877887
/// ```
878888
#[clippy::version = "1.85.0"]

clippy_lints/src/operators/raw_assign_to_drop.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, lhs: &'tcx Expr<'_>) {
1111
&& let Some(deref_ty) = ty.builtin_deref(true)
1212
&& deref_ty.needs_drop(cx.tcx, cx.typing_env())
1313
{
14+
if let ExprKind::MethodCall(path, self_arg, [], ..) = expr.kind
15+
&& let rustc_middle::ty::Adt(ty_def, ..) = cx.typeck_results().expr_ty(self_arg).kind()
16+
&& ty_def.is_unsafe_cell()
17+
&& path.ident.as_str() == "get"
18+
{
19+
// Don't lint if the raw pointer was directly retrieved from UnsafeCell::get()
20+
// We assume those to be safely managed
21+
return;
22+
}
1423
span_lint_and_then(
1524
cx,
1625
RAW_ASSIGN_TO_DROP,
@@ -22,9 +31,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, lhs: &'tcx Expr<'_>) {
2231
));
2332
diag.span_label(
2433
expr.span,
25-
"this place may be uninitialized, causing Undefined Behavior when the destructor executes",
34+
"the value may be uninitialized, causing Undefined Behavior when the destructor executes",
2635
);
27-
diag.help("use `std::ptr::write()` to overwrite a (possibly uninitialized) place");
36+
diag.help("use `std::ptr::write()` to overwrite a possibly uninitialized place");
2837
diag.help("use `std::ptr::drop_in_place()` to drop the previous value if such value exists");
2938
},
3039
);

tests/ui/raw_assign_to_drop.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#![warn(clippy::raw_assign_to_drop)]
2+
#![allow(clippy::missing_safety_doc)]
23

3-
fn main() {
4-
unsafe fn foo(r: *mut String, i: *mut i32) {
4+
use std::cell::UnsafeCell;
5+
6+
pub unsafe fn foo(r: *mut String, i: *mut i32) {
7+
unsafe {
58
*r = "foo".to_owned();
69

710
// no lint on {integer}
@@ -21,3 +24,13 @@ fn main() {
2124
*v.as_mut_ptr() = Default::default();
2225
}
2326
}
27+
28+
pub unsafe fn unsafecell() {
29+
// No lint
30+
let c = UnsafeCell::new(String::new());
31+
unsafe {
32+
*c.get() = String::new();
33+
}
34+
}
35+
36+
fn main() {}

tests/ui/raw_assign_to_drop.stderr

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,63 @@
11
error: assignment via raw pointer always executes destructor
2-
--> tests/ui/raw_assign_to_drop.rs:5:10
2+
--> tests/ui/raw_assign_to_drop.rs:8:10
33
|
44
LL | *r = "foo".to_owned();
5-
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
5+
| ^ the value may be uninitialized, causing Undefined Behavior when the destructor executes
66
|
77
= note: the destructor defined by `std::string::String` is executed during assignment of the new value
8-
= help: use `std::ptr::write()` to overwrite a (possibly uninitialized) place
8+
= help: use `std::ptr::write()` to overwrite a possibly uninitialized place
99
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
1010
= note: `-D clippy::raw-assign-to-drop` implied by `-D warnings`
1111
= help: to override `-D warnings` add `#[allow(clippy::raw_assign_to_drop)]`
1212

1313
error: assignment via raw pointer always executes destructor
14-
--> tests/ui/raw_assign_to_drop.rs:10:11
14+
--> tests/ui/raw_assign_to_drop.rs:13:11
1515
|
1616
LL | (*r, *r) = ("foo".to_owned(), "bar".to_owned());
17-
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
17+
| ^ the value may be uninitialized, causing Undefined Behavior when the destructor executes
1818
|
1919
= note: the destructor defined by `std::string::String` is executed during assignment of the new value
20-
= help: use `std::ptr::write()` to overwrite a (possibly uninitialized) place
20+
= help: use `std::ptr::write()` to overwrite a possibly uninitialized place
2121
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
2222

2323
error: assignment via raw pointer always executes destructor
24-
--> tests/ui/raw_assign_to_drop.rs:10:15
24+
--> tests/ui/raw_assign_to_drop.rs:13:15
2525
|
2626
LL | (*r, *r) = ("foo".to_owned(), "bar".to_owned());
27-
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
27+
| ^ the value may be uninitialized, causing Undefined Behavior when the destructor executes
2828
|
2929
= note: the destructor defined by `std::string::String` is executed during assignment of the new value
30-
= help: use `std::ptr::write()` to overwrite a (possibly uninitialized) place
30+
= help: use `std::ptr::write()` to overwrite a possibly uninitialized place
3131
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
3232

3333
error: assignment via raw pointer always executes destructor
34-
--> tests/ui/raw_assign_to_drop.rs:12:11
34+
--> tests/ui/raw_assign_to_drop.rs:15:11
3535
|
3636
LL | (*r, *i) = ("foo".to_owned(), 47);
37-
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
37+
| ^ the value may be uninitialized, causing Undefined Behavior when the destructor executes
3838
|
3939
= note: the destructor defined by `std::string::String` is executed during assignment of the new value
40-
= help: use `std::ptr::write()` to overwrite a (possibly uninitialized) place
40+
= help: use `std::ptr::write()` to overwrite a possibly uninitialized place
4141
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
4242

4343
error: assignment via raw pointer always executes destructor
44-
--> tests/ui/raw_assign_to_drop.rs:15:10
44+
--> tests/ui/raw_assign_to_drop.rs:18:10
4545
|
4646
LL | *(&mut x as *mut _) = "Foo".to_owned();
47-
| ^^^^^^^^^^^^^^^^^^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
47+
| ^^^^^^^^^^^^^^^^^^ the value may be uninitialized, causing Undefined Behavior when the destructor executes
4848
|
4949
= note: the destructor defined by `std::string::String` is executed during assignment of the new value
50-
= help: use `std::ptr::write()` to overwrite a (possibly uninitialized) place
50+
= help: use `std::ptr::write()` to overwrite a possibly uninitialized place
5151
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
5252

5353
error: assignment via raw pointer always executes destructor
54-
--> tests/ui/raw_assign_to_drop.rs:21:10
54+
--> tests/ui/raw_assign_to_drop.rs:24:10
5555
|
5656
LL | *v.as_mut_ptr() = Default::default();
57-
| ^^^^^^^^^^^^^^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
57+
| ^^^^^^^^^^^^^^ the value may be uninitialized, causing Undefined Behavior when the destructor executes
5858
|
5959
= note: the destructor defined by `std::string::String` is executed during assignment of the new value
60-
= help: use `std::ptr::write()` to overwrite a (possibly uninitialized) place
60+
= help: use `std::ptr::write()` to overwrite a possibly uninitialized place
6161
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
6262

6363
error: aborting due to 6 previous errors

0 commit comments

Comments
 (0)