Skip to content

Commit dd18243

Browse files
committed
Initial impl of raw_assign_to_drop
Fixes #4294
1 parent 49ae1d4 commit dd18243

File tree

6 files changed

+215
-0
lines changed

6 files changed

+215
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6533,6 +6533,7 @@ Released 2018-09-13
65336533
[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
65346534
[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
65356535
[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
6536+
[`raw_assign_to_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#raw_assign_to_drop
65366537
[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
65376538
[`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init
65386539
[`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: &[&::declare_clippy_lint::LintInfo] = &[
599599
crate::operators::MODULO_ONE_INFO,
600600
crate::operators::NEEDLESS_BITWISE_BOOL_INFO,
601601
crate::operators::OP_REF_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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod modulo_one;
1919
mod needless_bitwise_bool;
2020
mod numeric_arithmetic;
2121
mod op_ref;
22+
mod raw_assign_to_drop;
2223
mod self_assignment;
2324
mod verbose_bit_mask;
2425

@@ -807,6 +808,67 @@ declare_clippy_lint! {
807808
"explicit self-assignment"
808809
}
809810

811+
declare_clippy_lint! {
812+
/// ### What it does
813+
///
814+
/// Checks for assignments via raw pointers that involve types with destructors.
815+
///
816+
/// ### Why restrict this?
817+
///
818+
/// Assignments of the form `*ptr = new_value;` assume that `*ptr` contains an initialized
819+
/// value, and unconditionally execute the `std::ops::Drop`-implementation if such
820+
/// implementation is defined on the type of `*ptr`. If the old value is in fact
821+
/// uninitialized or otherwise invalid, the execution of `std::ops::Drop::drop(&mut self)`
822+
/// is always Undefined Behavior.
823+
///
824+
/// The unsafe-block required to assign via the raw-pointer should include a SAFETY-comment
825+
/// that explains why it is always safe to execute the destructor.
826+
///
827+
/// If the code can not guarantee that the old value can be safely dropped, use
828+
/// `std::ptr::write()` to overwrite the (possibly nonexisting) value without executing
829+
/// the destructor; this may leak resources if the old value did in fact exist.
830+
///
831+
/// Use `std::ptr::drop_in_place()` to (selectively) execute the destructor, having
832+
/// established that it is safe to do so.
833+
///
834+
/// ### Example
835+
/// ```no_run
836+
/// unsafe fn foo(oldvalue: *mut String) {
837+
/// // Direct assignment always executes `String`'s destructor on `oldvalue`.
838+
/// // However, we can't guarantee that executing the destructor is safe.
839+
/// unsafe { *oldvalue = "New Value".to_owned(); }
840+
/// }
841+
/// ```
842+
/// Use instead:
843+
/// ```no_run
844+
/// unsafe fn foo(oldvalue: *mut String, oldvalue_is_initialized: bool) {
845+
/// let newvalue = "New Value".to_owned();
846+
///
847+
/// // In this example, `oldvalue_is_initialized` is an oracle for what
848+
/// // the programmer might be able to establish statically.
849+
/// if oldvalue_is_initialized {
850+
/// // Having established that `oldvalue` points to a valid value, it is safe to
851+
/// // execute the destructor and assign a new value.
852+
///
853+
/// // SAFETY: oldvalue is properly aligned, valid for writes, and initialized
854+
/// unsafe {
855+
/// #[allow(clippy::raw_assign_to_drop)]
856+
/// *oldvalue = newvalue;
857+
/// }
858+
/// } else {
859+
/// // Overwrite the old value without running the destructor.
860+
///
861+
/// // SAFETY: oldvalue is properly aligned and valid for writes
862+
/// unsafe { oldvalue.write(newvalue); }
863+
/// }
864+
/// }
865+
/// ```
866+
#[clippy::version = "1.91.0"]
867+
pub RAW_ASSIGN_TO_DROP,
868+
restriction,
869+
"assignment via raw pointer that involves destructors"
870+
}
871+
810872
declare_clippy_lint! {
811873
/// ### What it does
812874
/// Checks for manual implementation of `midpoint`.
@@ -904,6 +966,7 @@ impl_lint_pass!(Operators => [
904966
MODULO_ARITHMETIC,
905967
NEEDLESS_BITWISE_BOOL,
906968
SELF_ASSIGNMENT,
969+
RAW_ASSIGN_TO_DROP,
907970
MANUAL_MIDPOINT,
908971
MANUAL_IS_MULTIPLE_OF,
909972
]);
@@ -954,6 +1017,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
9541017
ExprKind::Assign(lhs, rhs, _) => {
9551018
assign_op_pattern::check(cx, e, lhs, rhs, self.msrv);
9561019
self_assignment::check(cx, e, lhs, rhs);
1020+
raw_assign_to_drop::check(cx, lhs);
9571021
},
9581022
ExprKind::Unary(op, arg) => {
9591023
if op == UnOp::Neg {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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_raw_ptr()
11+
&& let Some(deref_ty) = ty.builtin_deref(true)
12+
&& deref_ty.needs_drop(cx.tcx, cx.typing_env())
13+
{
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+
}
23+
span_lint_and_then(
24+
cx,
25+
RAW_ASSIGN_TO_DROP,
26+
expr.span,
27+
"assignment via raw pointer always executes destructor",
28+
|diag| {
29+
diag.note(format!(
30+
"the destructor defined by `{deref_ty}` is executed during assignment of the new value"
31+
));
32+
diag.span_label(
33+
expr.span,
34+
"the old value may be uninitialized, causing Undefined Behavior when the destructor executes",
35+
);
36+
diag.help("use `std::ptr::write()` to overwrite a possibly uninitialized place");
37+
diag.help(
38+
"use `std::ptr::drop_in_place()` to drop the previous value, having established such value exists",
39+
);
40+
},
41+
);
42+
}
43+
}

tests/ui/raw_assign_to_drop.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#![warn(clippy::raw_assign_to_drop)]
2+
#![allow(clippy::missing_safety_doc)]
3+
4+
use std::cell::UnsafeCell;
5+
6+
pub unsafe fn foo(r: *mut String, i: *mut i32) {
7+
unsafe {
8+
*r = "foo".to_owned();
9+
//~^ raw_assign_to_drop
10+
11+
// no lint on {integer}
12+
*i = 47;
13+
14+
(*r, *r) = ("foo".to_owned(), "bar".to_owned());
15+
//~^ raw_assign_to_drop
16+
//~^^ raw_assign_to_drop
17+
18+
(*r, *i) = ("foo".to_owned(), 47);
19+
//~^ raw_assign_to_drop
20+
21+
let mut x: String = Default::default();
22+
*(&mut x as *mut _) = "Foo".to_owned();
23+
//~^ raw_assign_to_drop
24+
25+
// no lint on `u8`
26+
*x.as_mut_ptr() = b'a';
27+
28+
let mut v: Vec<String> = vec![];
29+
*v.as_mut_ptr() = Default::default();
30+
//~^ raw_assign_to_drop
31+
}
32+
}
33+
34+
pub unsafe fn unsafecell() {
35+
// No lint
36+
let c = UnsafeCell::new(String::new());
37+
unsafe {
38+
*c.get() = String::new();
39+
}
40+
}
41+
42+
fn main() {}

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:8:10
3+
|
4+
LL | *r = "foo".to_owned();
5+
| ^ the old value 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, having established 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:14:11
15+
|
16+
LL | (*r, *r) = ("foo".to_owned(), "bar".to_owned());
17+
| ^ the old value 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, having established such value exists
22+
23+
error: assignment via raw pointer always executes destructor
24+
--> tests/ui/raw_assign_to_drop.rs:14:15
25+
|
26+
LL | (*r, *r) = ("foo".to_owned(), "bar".to_owned());
27+
| ^ the old value 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, having established such value exists
32+
33+
error: assignment via raw pointer always executes destructor
34+
--> tests/ui/raw_assign_to_drop.rs:18:11
35+
|
36+
LL | (*r, *i) = ("foo".to_owned(), 47);
37+
| ^ the old value 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, having established such value exists
42+
43+
error: assignment via raw pointer always executes destructor
44+
--> tests/ui/raw_assign_to_drop.rs:22:10
45+
|
46+
LL | *(&mut x as *mut _) = "Foo".to_owned();
47+
| ^^^^^^^^^^^^^^^^^^ the old value 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, having established such value exists
52+
53+
error: assignment via raw pointer always executes destructor
54+
--> tests/ui/raw_assign_to_drop.rs:29:10
55+
|
56+
LL | *v.as_mut_ptr() = Default::default();
57+
| ^^^^^^^^^^^^^^ the old value 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, having established such value exists
62+
63+
error: aborting due to 6 previous errors
64+

0 commit comments

Comments
 (0)