Skip to content

Commit 35b8439

Browse files
committed
Initial impl of raw_assign_to_drop
Fixes #4294
1 parent b3fadd5 commit 35b8439

File tree

6 files changed

+163
-0
lines changed

6 files changed

+163
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5930,6 +5930,7 @@ Released 2018-09-13
59305930
[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
59315931
[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
59325932
[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
5933+
[`raw_assign_to_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#raw_assign_to_drop
59335934
[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
59345935
[`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init
59355936
[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
599599
crate::operators::NEEDLESS_BITWISE_BOOL_INFO,
600600
crate::operators::OP_REF_INFO,
601601
crate::operators::PTR_EQ_INFO,
602+
crate::operators::RAW_ASSIGN_TO_DROP_INFO,
602603
crate::operators::REDUNDANT_COMPARISONS_INFO,
603604
crate::operators::SELF_ASSIGNMENT_INFO,
604605
crate::operators::VERBOSE_BIT_MASK_INFO,

clippy_lints/src/operators/mod.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod needless_bitwise_bool;
1818
mod numeric_arithmetic;
1919
mod op_ref;
2020
mod ptr_eq;
21+
mod raw_assign_to_drop;
2122
mod self_assignment;
2223
mod verbose_bit_mask;
2324

@@ -837,6 +838,45 @@ declare_clippy_lint! {
837838
"explicit self-assignment"
838839
}
839840

841+
declare_clippy_lint! {
842+
/// ### What it does
843+
///
844+
/// Checks for assignments via raw pointers that involve types with destructors.
845+
///
846+
/// ### Why is this bad?
847+
///
848+
/// Assignments of the form `*ptr = new_value;` assume that `*ptr` contains an initialized
849+
/// value, and unconditionally execute the `std::ops::Drop`-implementation if such
850+
/// implementation is defined on the type of `*ptr`. If the value is in fact
851+
/// uninitialized or otherwise invalid, the execution of `std::ops::Drop::drop(&mut self)`
852+
/// is always Undefined Behavior.
853+
///
854+
/// Use `std::ptr::write()` to overwrite a value without executing the destructor.
855+
///
856+
/// Use `std::ptr::drop_in_place()` to conditionally execute the destructor if you are
857+
/// sure that the place contains an initialized value.
858+
///
859+
/// ### Example
860+
/// ```no_run
861+
/// // Direct assignment always executes `String`'s destructor on `oldvalue`
862+
/// *oldvalue = "New Value".to_owned();
863+
/// ```
864+
/// Use instead:
865+
/// ```no_run
866+
/// if oldvalue_is_initialized {
867+
/// // Having established that `oldvalue` points to a valid value, selectively
868+
/// // execute the destructor to prevent a memory-leak
869+
/// oldvalue.drop_in_place();
870+
/// }
871+
/// // Overwrite the old value without running the destructor unconditionally
872+
/// oldvalue.write("New Value".to_owned());
873+
/// ```
874+
#[clippy::version = "1.85.0"]
875+
pub RAW_ASSIGN_TO_DROP,
876+
suspicious,
877+
"assignment via raw pointer that involves destructors"
878+
}
879+
840880
pub struct Operators {
841881
arithmetic_context: numeric_arithmetic::Context,
842882
verbose_bit_mask_threshold: u64,
@@ -879,6 +919,7 @@ impl_lint_pass!(Operators => [
879919
NEEDLESS_BITWISE_BOOL,
880920
PTR_EQ,
881921
SELF_ASSIGNMENT,
922+
RAW_ASSIGN_TO_DROP,
882923
]);
883924

884925
impl<'tcx> LateLintPass<'tcx> for Operators {
@@ -925,6 +966,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
925966
ExprKind::Assign(lhs, rhs, _) => {
926967
assign_op_pattern::check(cx, e, lhs, rhs);
927968
self_assignment::check(cx, e, lhs, rhs);
969+
raw_assign_to_drop::check(cx, lhs);
928970
},
929971
ExprKind::Unary(op, arg) => {
930972
if op == UnOp::Neg {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_hir::{Expr, ExprKind, UnOp};
3+
use rustc_lint::LateContext;
4+
5+
use super::RAW_ASSIGN_TO_DROP;
6+
7+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, lhs: &'tcx Expr<'_>) {
8+
if let ExprKind::Unary(UnOp::Deref, expr) = lhs.kind
9+
&& let ty = cx.typeck_results().expr_ty(expr)
10+
&& ty.is_unsafe_ptr()
11+
&& let Some(deref_ty) = ty.builtin_deref(true)
12+
&& deref_ty.needs_drop(cx.tcx, cx.typing_env())
13+
{
14+
span_lint_and_then(
15+
cx,
16+
RAW_ASSIGN_TO_DROP,
17+
expr.span,
18+
"assignment via raw pointer always executes destructor",
19+
|diag| {
20+
diag.note(format!(
21+
"the destructor defined by `{deref_ty}` is executed during assignment of the new value"
22+
));
23+
diag.span_label(
24+
expr.span,
25+
"this place may be uninitialized, causing Undefined Behavior when the destructor executes",
26+
);
27+
diag.help("use `std::ptr::write()` to overwrite a (possibly uninitialized) place");
28+
diag.help("use `std::ptr::drop_in_place()` to drop the previous value if such value exists");
29+
},
30+
);
31+
}
32+
}

tests/ui/raw_assign_to_drop.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![warn(clippy::raw_assign_to_drop)]
2+
3+
fn main() {
4+
unsafe fn foo(r: *mut String, i: *mut i32) {
5+
*r = "foo".to_owned();
6+
7+
// no lint on {integer}
8+
*i = 47;
9+
10+
(*r, *r) = ("foo".to_owned(), "bar".to_owned());
11+
12+
(*r, *i) = ("foo".to_owned(), 47);
13+
14+
let mut x: String = Default::default();
15+
*(&mut x as *mut _) = "Foo".to_owned();
16+
17+
// no lint on `u8`
18+
*x.as_mut_ptr() = b'a';
19+
20+
let mut v: Vec<String> = vec![];
21+
*v.as_mut_ptr() = Default::default();
22+
}
23+
}

tests/ui/raw_assign_to_drop.stderr

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
error: assignment via raw pointer always executes destructor
2+
--> tests/ui/raw_assign_to_drop.rs:5:10
3+
|
4+
LL | *r = "foo".to_owned();
5+
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
6+
|
7+
= 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
9+
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
10+
= note: `-D clippy::raw-assign-to-drop` implied by `-D warnings`
11+
= help: to override `-D warnings` add `#[allow(clippy::raw_assign_to_drop)]`
12+
13+
error: assignment via raw pointer always executes destructor
14+
--> tests/ui/raw_assign_to_drop.rs:10:11
15+
|
16+
LL | (*r, *r) = ("foo".to_owned(), "bar".to_owned());
17+
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
18+
|
19+
= 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
21+
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
22+
23+
error: assignment via raw pointer always executes destructor
24+
--> tests/ui/raw_assign_to_drop.rs:10:15
25+
|
26+
LL | (*r, *r) = ("foo".to_owned(), "bar".to_owned());
27+
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
28+
|
29+
= 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
31+
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
32+
33+
error: assignment via raw pointer always executes destructor
34+
--> tests/ui/raw_assign_to_drop.rs:12:11
35+
|
36+
LL | (*r, *i) = ("foo".to_owned(), 47);
37+
| ^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
38+
|
39+
= 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
41+
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
42+
43+
error: assignment via raw pointer always executes destructor
44+
--> tests/ui/raw_assign_to_drop.rs:15:10
45+
|
46+
LL | *(&mut x as *mut _) = "Foo".to_owned();
47+
| ^^^^^^^^^^^^^^^^^^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
48+
|
49+
= 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
51+
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
52+
53+
error: assignment via raw pointer always executes destructor
54+
--> tests/ui/raw_assign_to_drop.rs:21:10
55+
|
56+
LL | *v.as_mut_ptr() = Default::default();
57+
| ^^^^^^^^^^^^^^ this place may be uninitialized, causing Undefined Behavior when the destructor executes
58+
|
59+
= 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
61+
= help: use `std::ptr::drop_in_place()` to drop the previous value if such value exists
62+
63+
error: aborting due to 6 previous errors
64+

0 commit comments

Comments
 (0)