Skip to content

Commit 11a2eb0

Browse files
committed
Auto merge of #11453 - xFrednet:11208-path-join-correct, r=blyxyas
New lint `clippy::join_absolute_paths` Hey `@ofeeg,` this PR is a copy of all the changes you did in 47171d3 from PR #11208. I wasn't sure how to fix the git history. Hope you're okay with me figuring this out in this separate PR --- changelog: New lint [`join_absolute_paths`] [#11453](#11453) Closes: #10655 r? `@Centri3` Since you also gave feedback on the other PR. I hope that I copied everything correctly, but a third pair of eyes would be appreciated :D
2 parents 41140e3 + 34d9e88 commit 11a2eb0

File tree

6 files changed

+196
-0
lines changed

6 files changed

+196
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5190,6 +5190,7 @@ Released 2018-09-13
51905190
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
51915191
[`iter_without_into_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_without_into_iter
51925192
[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
5193+
[`join_absolute_paths`]: https://rust-lang.github.io/rust-clippy/master/index.html#join_absolute_paths
51935194
[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
51945195
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
51955196
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
377377
crate::methods::ITER_SKIP_NEXT_INFO,
378378
crate::methods::ITER_SKIP_ZERO_INFO,
379379
crate::methods::ITER_WITH_DRAIN_INFO,
380+
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
380381
crate::methods::MANUAL_FILTER_MAP_INFO,
381382
crate::methods::MANUAL_FIND_MAP_INFO,
382383
crate::methods::MANUAL_NEXT_BACK_INFO,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::expr_or_init;
3+
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::ty::is_type_diagnostic_item;
5+
use rustc_ast::ast::LitKind;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Expr, ExprKind};
8+
use rustc_lint::LateContext;
9+
use rustc_span::symbol::sym;
10+
use rustc_span::Span;
11+
12+
use super::JOIN_ABSOLUTE_PATHS;
13+
14+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) {
15+
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
16+
if (is_type_diagnostic_item(cx, ty, sym::Path) || is_type_diagnostic_item(cx, ty, sym::PathBuf))
17+
&& let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind
18+
&& let LitKind::Str(symbol, _) = spanned.node
19+
&& let sym_str = symbol.as_str()
20+
&& sym_str.starts_with(['/', '\\'])
21+
{
22+
span_lint_and_then(
23+
cx,
24+
JOIN_ABSOLUTE_PATHS,
25+
join_arg.span,
26+
"argument to `Path::join` starts with a path separator",
27+
|diag| {
28+
let arg_str = snippet_opt(cx, spanned.span).unwrap_or_else(|| "..".to_string());
29+
30+
let no_separator = if sym_str.starts_with('/') {
31+
arg_str.replacen('/', "", 1)
32+
} else {
33+
arg_str.replacen('\\', "", 1)
34+
};
35+
36+
diag.note("joining a path starting with separator will replace the path instead")
37+
.span_suggestion(
38+
spanned.span,
39+
"if this is unintentional, try removing the starting separator",
40+
no_separator,
41+
Applicability::Unspecified,
42+
)
43+
.span_suggestion(
44+
expr_span,
45+
"if this is intentional, try using `Path::new` instead",
46+
format!("PathBuf::from({arg_str})"),
47+
Applicability::Unspecified,
48+
);
49+
},
50+
);
51+
}
52+
}

clippy_lints/src/methods/mod.rs

+44
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ mod iter_skip_next;
4949
mod iter_skip_zero;
5050
mod iter_with_drain;
5151
mod iterator_step_by_zero;
52+
mod join_absolute_paths;
5253
mod manual_next_back;
5354
mod manual_ok_or;
5455
mod manual_saturating_arithmetic;
@@ -3684,6 +3685,46 @@ declare_clippy_lint! {
36843685
"calling the `try_from` and `try_into` trait methods when `From`/`Into` is implemented"
36853686
}
36863687

3688+
declare_clippy_lint! {
3689+
/// ### What it does
3690+
/// Checks for calls to `Path::join` that start with a path separator (`\\` or `/`).
3691+
///
3692+
/// ### Why is this bad?
3693+
/// If the argument to `Path::join` starts with a separator, it will overwrite
3694+
/// the original path. If this is intentional, prefer using `Path::new` instead.
3695+
///
3696+
/// Note the behavior is platform dependent. A leading `\\` will be accepted
3697+
/// on unix systems as part of the file name
3698+
///
3699+
/// See [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join)
3700+
///
3701+
/// ### Example
3702+
/// ```rust
3703+
/// # use std::path::{Path, PathBuf};
3704+
/// let path = Path::new("/bin");
3705+
/// let joined_path = path.join("/sh");
3706+
/// assert_eq!(joined_path, PathBuf::from("/sh"));
3707+
/// ```
3708+
///
3709+
/// Use instead;
3710+
/// ```rust
3711+
/// # use std::path::{Path, PathBuf};
3712+
/// let path = Path::new("/bin");
3713+
///
3714+
/// // If this was unintentional, remove the leading separator
3715+
/// let joined_path = path.join("sh");
3716+
/// assert_eq!(joined_path, PathBuf::from("/bin/sh"));
3717+
///
3718+
/// // If this was intentional, create a new path instead
3719+
/// let new = Path::new("/sh");
3720+
/// assert_eq!(new, PathBuf::from("/sh"));
3721+
/// ```
3722+
#[clippy::version = "1.76.0"]
3723+
pub JOIN_ABSOLUTE_PATHS,
3724+
suspicious,
3725+
"calls to `Path::join` which will overwrite the original path"
3726+
}
3727+
36873728
pub struct Methods {
36883729
avoid_breaking_exported_api: bool,
36893730
msrv: Msrv,
@@ -3833,6 +3874,7 @@ impl_lint_pass!(Methods => [
38333874
REDUNDANT_AS_STR,
38343875
WAKER_CLONE_WAKE,
38353876
UNNECESSARY_FALLIBLE_CONVERSIONS,
3877+
JOIN_ABSOLUTE_PATHS,
38363878
]);
38373879

38383880
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4235,6 +4277,8 @@ impl Methods {
42354277
("join", [join_arg]) => {
42364278
if let Some(("collect", _, _, span, _)) = method_call(recv) {
42374279
unnecessary_join::check(cx, expr, recv, join_arg, span);
4280+
} else {
4281+
join_absolute_paths::check(cx, recv, join_arg, expr.span);
42384282
}
42394283
},
42404284
("last", []) => {

tests/ui/join_absolute_paths.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//@no-rustfix
2+
3+
#![allow(clippy::needless_raw_string_hashes)]
4+
#![warn(clippy::join_absolute_paths)]
5+
6+
use std::path::{Path, PathBuf};
7+
8+
fn main() {
9+
let path = Path::new("/bin");
10+
path.join("/sh");
11+
//~^ ERROR: argument to `Path::join` starts with a path separator
12+
13+
let path = Path::new("C:\\Users");
14+
path.join("\\user");
15+
//~^ ERROR: argument to `Path::join` starts with a path separator
16+
17+
let path = PathBuf::from("/bin");
18+
path.join("/sh");
19+
//~^ ERROR: argument to `Path::join` starts with a path separator
20+
21+
let path = PathBuf::from("/bin");
22+
path.join(r#"/sh"#);
23+
//~^ ERROR: argument to `Path::join` starts with a path separator
24+
25+
let path: &[&str] = &["/bin"];
26+
path.join("/sh");
27+
28+
let path = Path::new("/bin");
29+
path.join("sh");
30+
}

tests/ui/join_absolute_paths.stderr

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
error: argument to `Path::join` starts with a path separator
2+
--> $DIR/join_absolute_paths.rs:10:15
3+
|
4+
LL | path.join("/sh");
5+
| ^^^^^
6+
|
7+
= note: joining a path starting with separator will replace the path instead
8+
= note: `-D clippy::join-absolute-paths` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::join_absolute_paths)]`
10+
help: if this is unintentional, try removing the starting separator
11+
|
12+
LL | path.join("sh");
13+
| ~~~~
14+
help: if this is intentional, try using `Path::new` instead
15+
|
16+
LL | PathBuf::from("/sh");
17+
| ~~~~~~~~~~~~~~~~~~~~
18+
19+
error: argument to `Path::join` starts with a path separator
20+
--> $DIR/join_absolute_paths.rs:14:15
21+
|
22+
LL | path.join("\\user");
23+
| ^^^^^^^^
24+
|
25+
= note: joining a path starting with separator will replace the path instead
26+
help: if this is unintentional, try removing the starting separator
27+
|
28+
LL | path.join("\user");
29+
| ~~~~~~~
30+
help: if this is intentional, try using `Path::new` instead
31+
|
32+
LL | PathBuf::from("\\user");
33+
| ~~~~~~~~~~~~~~~~~~~~~~~
34+
35+
error: argument to `Path::join` starts with a path separator
36+
--> $DIR/join_absolute_paths.rs:18:15
37+
|
38+
LL | path.join("/sh");
39+
| ^^^^^
40+
|
41+
= note: joining a path starting with separator will replace the path instead
42+
help: if this is unintentional, try removing the starting separator
43+
|
44+
LL | path.join("sh");
45+
| ~~~~
46+
help: if this is intentional, try using `Path::new` instead
47+
|
48+
LL | PathBuf::from("/sh");
49+
| ~~~~~~~~~~~~~~~~~~~~
50+
51+
error: argument to `Path::join` starts with a path separator
52+
--> $DIR/join_absolute_paths.rs:22:15
53+
|
54+
LL | path.join(r#"/sh"#);
55+
| ^^^^^^^^
56+
|
57+
= note: joining a path starting with separator will replace the path instead
58+
help: if this is unintentional, try removing the starting separator
59+
|
60+
LL | path.join(r#"sh"#);
61+
| ~~~~~~~
62+
help: if this is intentional, try using `Path::new` instead
63+
|
64+
LL | PathBuf::from(r#"/sh"#);
65+
| ~~~~~~~~~~~~~~~~~~~~~~~
66+
67+
error: aborting due to 4 previous errors
68+

0 commit comments

Comments
 (0)