Skip to content

Commit e517864

Browse files
committed
Implemented suspicious_to_owned lint to check if to_owned is called on a 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. This creates confusion (stemming from the ambiguity of the 'owned' term in the context of `Cow`s) and might not be what the writer intended.
1 parent 919cf50 commit e517864

File tree

9 files changed

+200
-1
lines changed

9 files changed

+200
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3756,6 +3756,7 @@ Released 2018-09-13
37563756
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
37573757
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
37583758
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
3759+
[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
37593760
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
37603761
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
37613762
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
209209
LintId::of(methods::STRING_EXTEND_CHARS),
210210
LintId::of(methods::SUSPICIOUS_MAP),
211211
LintId::of(methods::SUSPICIOUS_SPLITN),
212+
LintId::of(methods::SUSPICIOUS_TO_OWNED),
212213
LintId::of(methods::UNINIT_ASSUMED_INIT),
213214
LintId::of(methods::UNNECESSARY_FILTER_MAP),
214215
LintId::of(methods::UNNECESSARY_FIND_MAP),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ store.register_lints(&[
351351
methods::STRING_EXTEND_CHARS,
352352
methods::SUSPICIOUS_MAP,
353353
methods::SUSPICIOUS_SPLITN,
354+
methods::SUSPICIOUS_TO_OWNED,
354355
methods::UNINIT_ASSUMED_INIT,
355356
methods::UNNECESSARY_FILTER_MAP,
356357
methods::UNNECESSARY_FIND_MAP,

clippy_lints/src/lib.register_suspicious.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
2727
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
2828
LintId::of(methods::NO_EFFECT_REPLACE),
2929
LintId::of(methods::SUSPICIOUS_MAP),
30+
LintId::of(methods::SUSPICIOUS_TO_OWNED),
3031
LintId::of(mut_key::MUTABLE_KEY_TYPE),
3132
LintId::of(octal_escapes::OCTAL_ESCAPES),
3233
LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),

clippy_lints/src/methods/mod.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ mod str_splitn;
6262
mod string_extend_chars;
6363
mod suspicious_map;
6464
mod suspicious_splitn;
65+
mod suspicious_to_owned;
6566
mod uninit_assumed_init;
6667
mod unnecessary_filter_map;
6768
mod unnecessary_fold;
@@ -1961,6 +1962,53 @@ declare_clippy_lint! {
19611962
"replace `.iter().count()` with `.len()`"
19621963
}
19631964

1965+
declare_clippy_lint! {
1966+
/// ### What it does
1967+
/// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`.
1968+
///
1969+
/// ### Why is this bad?
1970+
/// `Cow`s can contain either `Borrowed` data or `Owned` data.
1971+
/// The `into_owned()` method on a `Cow` can be used to clone the
1972+
/// underlying data and become a `Cow::Owned`. Calling `to_owned()`,
1973+
/// on the other hand, clones the `Cow` itself, rather than the
1974+
/// contained data.
1975+
/// Given the similar naming, the use of `to_owned()` is suspicious
1976+
/// and can potentially lead to undefined behavior if it's
1977+
/// inadvertently used on data coming from unsafe contexts.
1978+
/// Consider replacing `to_owned` with `into_owned` if the desired
1979+
/// result is getting a `Cow::Owned`, or explitly calling `clone()`
1980+
/// if the desired result is cloning the `Cow`.
1981+
///
1982+
/// ### Example
1983+
/// ```rust
1984+
/// let s = "Hello world!";
1985+
/// let cow = Cow::Borrowed(s);
1986+
///
1987+
/// let data = cow.to_owned();
1988+
/// assert!(matches!(data, Cow::Borrowed(_)))
1989+
/// ```
1990+
/// Use instead:
1991+
/// ```rust
1992+
/// let s = "Hello world!";
1993+
/// let cow = Cow::Borrowed(s);
1994+
///
1995+
/// let data = cow.into_owned();
1996+
/// assert!(matches!(data, Cow::Owned(_)))
1997+
/// ```
1998+
/// or
1999+
/// ```rust
2000+
/// let s = "Hello world!";
2001+
/// let cow = Cow::Borrowed(s);
2002+
///
2003+
/// let data = cow.clone();
2004+
/// assert!(matches!(data, Cow::Borrowed(_)))
2005+
/// ```
2006+
#[clippy::version = "1.63.0"]
2007+
pub SUSPICIOUS_TO_OWNED,
2008+
suspicious,
2009+
"calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected"
2010+
}
2011+
19642012
declare_clippy_lint! {
19652013
/// ### What it does
19662014
/// Checks for calls to [`splitn`]
@@ -2317,6 +2365,7 @@ impl_lint_pass!(Methods => [
23172365
FROM_ITER_INSTEAD_OF_COLLECT,
23182366
INSPECT_FOR_EACH,
23192367
IMPLICIT_CLONE,
2368+
SUSPICIOUS_TO_OWNED,
23202369
SUSPICIOUS_SPLITN,
23212370
MANUAL_STR_REPEAT,
23222371
EXTEND_WITH_DRAIN,
@@ -2706,7 +2755,12 @@ impl Methods {
27062755
}
27072756
},
27082757
("take", []) => needless_option_take::check(cx, expr, recv),
2709-
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
2758+
("to_owned", []) => {
2759+
if !suspicious_to_owned::check(cx, expr, recv) {
2760+
implicit_clone::check(cx, name, expr, recv);
2761+
}
2762+
},
2763+
("to_os_string" | "to_path_buf" | "to_vec", []) => {
27102764
implicit_clone::check(cx, name, expr, recv);
27112765
},
27122766
("unwrap", []) => {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_diag_trait_item;
3+
use clippy_utils::source::snippet_with_context;
4+
use if_chain::if_chain;
5+
use rustc_errors::Applicability;
6+
use rustc_hir as hir;
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty;
9+
use rustc_span::sym;
10+
11+
use super::SUSPICIOUS_TO_OWNED;
12+
13+
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool {
14+
if_chain! {
15+
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
16+
if is_diag_trait_item(cx, method_def_id, sym::ToOwned);
17+
let input_type = cx.typeck_results().expr_ty(expr);
18+
if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind();
19+
if cx.tcx.is_diagnostic_item(sym::Cow, adt.did());
20+
then {
21+
let mut app = Applicability::MaybeIncorrect;
22+
let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
23+
span_lint_and_sugg(
24+
cx,
25+
SUSPICIOUS_TO_OWNED,
26+
expr.span,
27+
&format!("this call to `to_owned` does not cause the {} contents to become owned", input_type),
28+
"consider using",
29+
format!("{}.into_owned()", recv_snip),
30+
app,
31+
);
32+
return true;
33+
}
34+
}
35+
false
36+
}

tests/compile-test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
375375
"search_is_some.rs",
376376
"single_component_path_imports_nested_first.rs",
377377
"string_add.rs",
378+
"suspicious_to_owned.rs",
378379
"toplevel_ref_arg_non_rustfix.rs",
379380
"unit_arg.rs",
380381
"unnecessary_clone.rs",

tests/ui/suspicious_to_owned.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#![warn(clippy::suspicious_to_owned)]
2+
#![warn(clippy::implicit_clone)]
3+
#![allow(clippy::redundant_clone)]
4+
use std::borrow::Cow;
5+
use std::ffi::CStr;
6+
7+
fn main() {
8+
let moo = "Moooo";
9+
let c_moo = b"Moooo\0";
10+
let c_moo_ptr = c_moo.as_ptr() as *const i8;
11+
let moos = ['M', 'o', 'o'];
12+
let moos_vec = moos.to_vec();
13+
14+
// we expect this to be linted
15+
let cow = Cow::Borrowed(moo);
16+
let _ = cow.to_owned();
17+
// we expect no lints for this
18+
let cow = Cow::Borrowed(moo);
19+
let _ = cow.into_owned();
20+
// we expect no lints for this
21+
let cow = Cow::Borrowed(moo);
22+
let _ = cow.clone();
23+
24+
// we expect this to be linted
25+
let cow = Cow::Borrowed(&moos);
26+
let _ = cow.to_owned();
27+
// we expect no lints for this
28+
let cow = Cow::Borrowed(&moos);
29+
let _ = cow.into_owned();
30+
// we expect no lints for this
31+
let cow = Cow::Borrowed(&moos);
32+
let _ = cow.clone();
33+
34+
// we expect this to be linted
35+
let cow = Cow::Borrowed(&moos_vec);
36+
let _ = cow.to_owned();
37+
// we expect no lints for this
38+
let cow = Cow::Borrowed(&moos_vec);
39+
let _ = cow.into_owned();
40+
// we expect no lints for this
41+
let cow = Cow::Borrowed(&moos_vec);
42+
let _ = cow.clone();
43+
44+
// we expect this to be linted
45+
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
46+
let _ = cow.to_owned();
47+
// we expect no lints for this
48+
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
49+
let _ = cow.into_owned();
50+
// we expect no lints for this
51+
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
52+
let _ = cow.clone();
53+
54+
// we expect no lints for these
55+
let _ = moo.to_owned();
56+
let _ = c_moo.to_owned();
57+
let _ = moos.to_owned();
58+
59+
// we expect implicit_clone lints for these
60+
let _ = String::from(moo).to_owned();
61+
let _ = moos_vec.to_owned();
62+
}

tests/ui/suspicious_to_owned.stderr

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error: this call to `to_owned` does not cause the std::borrow::Cow<str> contents to become owned
2+
--> $DIR/suspicious_to_owned.rs:16:13
3+
|
4+
LL | let _ = cow.to_owned();
5+
| ^^^^^^^^^^^^^^ help: consider using: `cow.into_owned()`
6+
|
7+
= note: `-D clippy::suspicious-to-owned` implied by `-D warnings`
8+
9+
error: this call to `to_owned` does not cause the std::borrow::Cow<[char; 3]> contents to become owned
10+
--> $DIR/suspicious_to_owned.rs:26:13
11+
|
12+
LL | let _ = cow.to_owned();
13+
| ^^^^^^^^^^^^^^ help: consider using: `cow.into_owned()`
14+
15+
error: this call to `to_owned` does not cause the std::borrow::Cow<std::vec::Vec<char>> contents to become owned
16+
--> $DIR/suspicious_to_owned.rs:36:13
17+
|
18+
LL | let _ = cow.to_owned();
19+
| ^^^^^^^^^^^^^^ help: consider using: `cow.into_owned()`
20+
21+
error: this call to `to_owned` does not cause the std::borrow::Cow<str> contents to become owned
22+
--> $DIR/suspicious_to_owned.rs:46:13
23+
|
24+
LL | let _ = cow.to_owned();
25+
| ^^^^^^^^^^^^^^ help: consider using: `cow.into_owned()`
26+
27+
error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type
28+
--> $DIR/suspicious_to_owned.rs:60:13
29+
|
30+
LL | let _ = String::from(moo).to_owned();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::from(moo).clone()`
32+
|
33+
= note: `-D clippy::implicit-clone` implied by `-D warnings`
34+
35+
error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type
36+
--> $DIR/suspicious_to_owned.rs:61:13
37+
|
38+
LL | let _ = moos_vec.to_owned();
39+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `moos_vec.clone()`
40+
41+
error: aborting due to 6 previous errors
42+

0 commit comments

Comments
 (0)