mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-10 15:14:29 +00:00
Auto merge of #8984 - xanathar:pr/suspicious_to_owned, r=llogiq
Implemented `suspicious_to_owned` lint to check if `to_owned` is called on a `Cow` changelog: Add lint ``[`suspicious_to_owned`]`` ----------------- Hi, posting this unsolicited PR as I've been burned by this issue :) Being unsolicited, feel free to reject it or reassign a different lint level etc. This lint checks whether `to_owned` is called on `Cow<'_, _>`. This is done because `to_owned` is very similarly named to `into_owned`, but the effect of calling those two methods is completely different (one makes the `Cow::Borrowed` into a `Cow::Owned`, the other just clones the `Cow`). If the cow is then passed to code for which the type is not checked (e.g. generics, closures, etc.) it might slip through and if the cow data is coming from an unsafe context there is the potential for accidentally cause undefined behavior. Even if not falling into this painful case, there's really no reason to call `to_owned` on a `Cow` other than confusing people reading the code: either `into_owned` or `clone` should be called. Note that this overlaps perfectly with `implicit_clone` as a warning, but `implicit_clone` is classified pedantic (while the consequences for `Cow` might be of a wider blast radius than just pedantry); given the overlap, I set-up the lint so that if `suspicious_to_owned` triggers `implicit_clone` will not trigger. I'm not 100% sure this is done in the correct way (I tried to copy what other lints were doing) so please provide feedback on it if it isn't. ### Checklist - \[x] Followed [lint naming conventions][lint_naming] - \[x] Added passing UI tests (including committed `.stderr` file) - \[x] `cargo test` passes locally - \[x] Executed `cargo dev update_lints` - \[x] Added lint documentation - \[x] Run `cargo dev fmt`
This commit is contained in:
commit
2d4d8e16cd
9 changed files with 202 additions and 1 deletions
|
@ -4082,6 +4082,7 @@ Released 2018-09-13
|
|||
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
|
||||
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
|
||||
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
|
||||
[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
|
||||
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
|
||||
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
|
||||
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
|
||||
|
|
|
@ -207,6 +207,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(methods::STRING_EXTEND_CHARS),
|
||||
LintId::of(methods::SUSPICIOUS_MAP),
|
||||
LintId::of(methods::SUSPICIOUS_SPLITN),
|
||||
LintId::of(methods::SUSPICIOUS_TO_OWNED),
|
||||
LintId::of(methods::UNINIT_ASSUMED_INIT),
|
||||
LintId::of(methods::UNIT_HASH),
|
||||
LintId::of(methods::UNNECESSARY_FILTER_MAP),
|
||||
|
|
|
@ -358,6 +358,7 @@ store.register_lints(&[
|
|||
methods::STRING_EXTEND_CHARS,
|
||||
methods::SUSPICIOUS_MAP,
|
||||
methods::SUSPICIOUS_SPLITN,
|
||||
methods::SUSPICIOUS_TO_OWNED,
|
||||
methods::UNINIT_ASSUMED_INIT,
|
||||
methods::UNIT_HASH,
|
||||
methods::UNNECESSARY_FILTER_MAP,
|
||||
|
|
|
@ -24,6 +24,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
|
|||
LintId::of(loops::MUT_RANGE_BOUND),
|
||||
LintId::of(methods::NO_EFFECT_REPLACE),
|
||||
LintId::of(methods::SUSPICIOUS_MAP),
|
||||
LintId::of(methods::SUSPICIOUS_TO_OWNED),
|
||||
LintId::of(multi_assignments::MULTI_ASSIGNMENTS),
|
||||
LintId::of(mut_key::MUTABLE_KEY_TYPE),
|
||||
LintId::of(octal_escapes::OCTAL_ESCAPES),
|
||||
|
|
|
@ -78,6 +78,7 @@ mod str_splitn;
|
|||
mod string_extend_chars;
|
||||
mod suspicious_map;
|
||||
mod suspicious_splitn;
|
||||
mod suspicious_to_owned;
|
||||
mod uninit_assumed_init;
|
||||
mod unit_hash;
|
||||
mod unnecessary_filter_map;
|
||||
|
@ -2053,6 +2054,55 @@ declare_clippy_lint! {
|
|||
"replace `.iter().count()` with `.len()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Calling `to_owned()` on a `Cow` creates a clone of the `Cow`
|
||||
/// itself, without taking ownership of the `Cow` contents (i.e.
|
||||
/// it's equivalent to calling `Cow::clone`).
|
||||
/// The similarly named `into_owned` method, on the other hand,
|
||||
/// clones the `Cow` contents, effectively turning any `Cow::Borrowed`
|
||||
/// into a `Cow::Owned`.
|
||||
///
|
||||
/// Given the potential ambiguity, consider replacing `to_owned`
|
||||
/// with `clone` for better readability or, if getting a `Cow::Owned`
|
||||
/// was the original intent, using `into_owned` instead.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # use std::borrow::Cow;
|
||||
/// let s = "Hello world!";
|
||||
/// let cow = Cow::Borrowed(s);
|
||||
///
|
||||
/// let data = cow.to_owned();
|
||||
/// assert!(matches!(data, Cow::Borrowed(_)))
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # use std::borrow::Cow;
|
||||
/// let s = "Hello world!";
|
||||
/// let cow = Cow::Borrowed(s);
|
||||
///
|
||||
/// let data = cow.clone();
|
||||
/// assert!(matches!(data, Cow::Borrowed(_)))
|
||||
/// ```
|
||||
/// or
|
||||
/// ```rust
|
||||
/// # use std::borrow::Cow;
|
||||
/// let s = "Hello world!";
|
||||
/// let cow = Cow::Borrowed(s);
|
||||
///
|
||||
/// let data = cow.into_owned();
|
||||
/// assert!(matches!(data, String))
|
||||
/// ```
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub SUSPICIOUS_TO_OWNED,
|
||||
suspicious,
|
||||
"calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to [`splitn`]
|
||||
|
@ -3075,6 +3125,7 @@ impl_lint_pass!(Methods => [
|
|||
FROM_ITER_INSTEAD_OF_COLLECT,
|
||||
INSPECT_FOR_EACH,
|
||||
IMPLICIT_CLONE,
|
||||
SUSPICIOUS_TO_OWNED,
|
||||
SUSPICIOUS_SPLITN,
|
||||
MANUAL_STR_REPEAT,
|
||||
EXTEND_WITH_DRAIN,
|
||||
|
@ -3553,7 +3604,12 @@ impl Methods {
|
|||
}
|
||||
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
|
||||
},
|
||||
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
|
||||
("to_owned", []) => {
|
||||
if !suspicious_to_owned::check(cx, expr, recv) {
|
||||
implicit_clone::check(cx, name, expr, recv);
|
||||
}
|
||||
},
|
||||
("to_os_string" | "to_path_buf" | "to_vec", []) => {
|
||||
implicit_clone::check(cx, name, expr, recv);
|
||||
},
|
||||
("unwrap", []) => {
|
||||
|
|
36
clippy_lints/src/methods/suspicious_to_owned.rs
Normal file
36
clippy_lints/src/methods/suspicious_to_owned.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_diag_trait_item;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::SUSPICIOUS_TO_OWNED;
|
||||
|
||||
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if is_diag_trait_item(cx, method_def_id, sym::ToOwned);
|
||||
let input_type = cx.typeck_results().expr_ty(expr);
|
||||
if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind();
|
||||
if cx.tcx.is_diagnostic_item(sym::Cow, adt.did());
|
||||
then {
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUSPICIOUS_TO_OWNED,
|
||||
expr.span,
|
||||
&format!("this `to_owned` call clones the {0} itself and does not cause the {0} contents to become owned", input_type),
|
||||
"consider using, depending on intent",
|
||||
format!("{0}.clone()` or `{0}.into_owned()", recv_snip),
|
||||
app,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
|
@ -393,6 +393,7 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
|
|||
"search_is_some.rs",
|
||||
"single_component_path_imports_nested_first.rs",
|
||||
"string_add.rs",
|
||||
"suspicious_to_owned.rs",
|
||||
"toplevel_ref_arg_non_rustfix.rs",
|
||||
"unit_arg.rs",
|
||||
"unnecessary_clone.rs",
|
||||
|
|
62
tests/ui/suspicious_to_owned.rs
Normal file
62
tests/ui/suspicious_to_owned.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
#![warn(clippy::suspicious_to_owned)]
|
||||
#![warn(clippy::implicit_clone)]
|
||||
#![allow(clippy::redundant_clone)]
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CStr;
|
||||
|
||||
fn main() {
|
||||
let moo = "Moooo";
|
||||
let c_moo = b"Moooo\0";
|
||||
let c_moo_ptr = c_moo.as_ptr() as *const i8;
|
||||
let moos = ['M', 'o', 'o'];
|
||||
let moos_vec = moos.to_vec();
|
||||
|
||||
// we expect this to be linted
|
||||
let cow = Cow::Borrowed(moo);
|
||||
let _ = cow.to_owned();
|
||||
// we expect no lints for this
|
||||
let cow = Cow::Borrowed(moo);
|
||||
let _ = cow.into_owned();
|
||||
// we expect no lints for this
|
||||
let cow = Cow::Borrowed(moo);
|
||||
let _ = cow.clone();
|
||||
|
||||
// we expect this to be linted
|
||||
let cow = Cow::Borrowed(&moos);
|
||||
let _ = cow.to_owned();
|
||||
// we expect no lints for this
|
||||
let cow = Cow::Borrowed(&moos);
|
||||
let _ = cow.into_owned();
|
||||
// we expect no lints for this
|
||||
let cow = Cow::Borrowed(&moos);
|
||||
let _ = cow.clone();
|
||||
|
||||
// we expect this to be linted
|
||||
let cow = Cow::Borrowed(&moos_vec);
|
||||
let _ = cow.to_owned();
|
||||
// we expect no lints for this
|
||||
let cow = Cow::Borrowed(&moos_vec);
|
||||
let _ = cow.into_owned();
|
||||
// we expect no lints for this
|
||||
let cow = Cow::Borrowed(&moos_vec);
|
||||
let _ = cow.clone();
|
||||
|
||||
// we expect this to be linted
|
||||
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
|
||||
let _ = cow.to_owned();
|
||||
// we expect no lints for this
|
||||
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
|
||||
let _ = cow.into_owned();
|
||||
// we expect no lints for this
|
||||
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
|
||||
let _ = cow.clone();
|
||||
|
||||
// we expect no lints for these
|
||||
let _ = moo.to_owned();
|
||||
let _ = c_moo.to_owned();
|
||||
let _ = moos.to_owned();
|
||||
|
||||
// we expect implicit_clone lints for these
|
||||
let _ = String::from(moo).to_owned();
|
||||
let _ = moos_vec.to_owned();
|
||||
}
|
42
tests/ui/suspicious_to_owned.stderr
Normal file
42
tests/ui/suspicious_to_owned.stderr
Normal file
|
@ -0,0 +1,42 @@
|
|||
error: this `to_owned` call clones the std::borrow::Cow<str> itself and does not cause the std::borrow::Cow<str> contents to become owned
|
||||
--> $DIR/suspicious_to_owned.rs:16:13
|
||||
|
|
||||
LL | let _ = cow.to_owned();
|
||||
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
|
||||
|
|
||||
= note: `-D clippy::suspicious-to-owned` implied by `-D warnings`
|
||||
|
||||
error: this `to_owned` call clones the std::borrow::Cow<[char; 3]> itself and does not cause the std::borrow::Cow<[char; 3]> contents to become owned
|
||||
--> $DIR/suspicious_to_owned.rs:26:13
|
||||
|
|
||||
LL | let _ = cow.to_owned();
|
||||
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
|
||||
|
||||
error: this `to_owned` call clones the std::borrow::Cow<std::vec::Vec<char>> itself and does not cause the std::borrow::Cow<std::vec::Vec<char>> contents to become owned
|
||||
--> $DIR/suspicious_to_owned.rs:36:13
|
||||
|
|
||||
LL | let _ = cow.to_owned();
|
||||
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
|
||||
|
||||
error: this `to_owned` call clones the std::borrow::Cow<str> itself and does not cause the std::borrow::Cow<str> contents to become owned
|
||||
--> $DIR/suspicious_to_owned.rs:46:13
|
||||
|
|
||||
LL | let _ = cow.to_owned();
|
||||
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
|
||||
|
||||
error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type
|
||||
--> $DIR/suspicious_to_owned.rs:60:13
|
||||
|
|
||||
LL | let _ = String::from(moo).to_owned();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::from(moo).clone()`
|
||||
|
|
||||
= note: `-D clippy::implicit-clone` implied by `-D warnings`
|
||||
|
||||
error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type
|
||||
--> $DIR/suspicious_to_owned.rs:61:13
|
||||
|
|
||||
LL | let _ = moos_vec.to_owned();
|
||||
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `moos_vec.clone()`
|
||||
|
||||
error: aborting due to 6 previous errors
|
||||
|
Loading…
Reference in a new issue