Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented suspicious_to_owned lint to check if to_owned is called on a Cow #8984

Merged
merged 1 commit into from Aug 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_all.rs
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_suspicious.rs
Expand Up @@ -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),
Expand Down
58 changes: 57 additions & 1 deletion clippy_lints/src/methods/mod.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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`]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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", []) => {
Expand Down
36 changes: 36 additions & 0 deletions clippy_lints/src/methods/suspicious_to_owned.rs
@@ -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
}
1 change: 1 addition & 0 deletions tests/compile-test.rs
Expand Up @@ -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",
Expand Down
62 changes: 62 additions & 0 deletions tests/ui/suspicious_to_owned.rs
@@ -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 changes: 42 additions & 0 deletions tests/ui/suspicious_to_owned.stderr
@@ -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