Skip to content

Commit 2fb4ff4

Browse files
ofeegxFrednet
authored andcommitted
New lint clippy::join_absolute_paths
1 parent b9906ac commit 2fb4ff4

File tree

6 files changed

+122
-0
lines changed

6 files changed

+122
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5037,6 +5037,7 @@ Released 2018-09-13
50375037
[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero
50385038
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
50395039
[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
5040+
[`join_absolute_paths`]: https://rust-lang.github.io/rust-clippy/master/index.html#join_absolute_paths
50405041
[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
50415042
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
50425043
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
370370
crate::methods::ITER_SKIP_NEXT_INFO,
371371
crate::methods::ITER_SKIP_ZERO_INFO,
372372
crate::methods::ITER_WITH_DRAIN_INFO,
373+
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
373374
crate::methods::MANUAL_FILTER_MAP_INFO,
374375
crate::methods::MANUAL_FIND_MAP_INFO,
375376
crate::methods::MANUAL_NEXT_BACK_INFO,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::ty::is_type_diagnostic_item;
3+
use rustc_ast::ast::LitKind;
4+
use rustc_hir::{Expr, ExprKind};
5+
use rustc_lint::LateContext;
6+
use rustc_span::symbol::sym::Path;
7+
8+
use super::JOIN_ABSOLUTE_PATHS;
9+
10+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>) {
11+
let ty = cx.typeck_results().expr_ty(expr).peel_refs();
12+
if is_type_diagnostic_item(cx, ty, Path)
13+
&& let ExprKind::Lit(spanned) = &join_arg.kind
14+
&& let LitKind::Str(symbol, _) = spanned.node
15+
&& (symbol.as_str().starts_with('/') || symbol.as_str().starts_with('\\'))
16+
{
17+
span_lint_and_then(
18+
cx,
19+
JOIN_ABSOLUTE_PATHS,
20+
join_arg.span,
21+
"argument to `Path::join` starts with a path separator",
22+
|diag| {
23+
diag
24+
.note("joining a path starting with separator will replace the path instead")
25+
.help("if this is unintentional, try removing the starting separator")
26+
.help("if this is intentional, try creating a new Path instead");
27+
},
28+
);
29+
}
30+
}

clippy_lints/src/methods/mod.rs

Lines changed: 41 additions & 0 deletions
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;
@@ -3563,6 +3564,43 @@ declare_clippy_lint! {
35633564
"calls to `.take()` or `.skip()` that are out of bounds"
35643565
}
35653566

3567+
declare_clippy_lint! {
3568+
/// ### What it does
3569+
/// Checks for calls to `Path::join` that start with a path separator, like `\\` or `/`..
3570+
///
3571+
/// ### Why is this bad?
3572+
/// `.join()` arguments starting with a separator (`/` or `\\`) can replace the entire path.
3573+
/// If this is intentional, prefer using `Path::new()` instead.
3574+
///
3575+
/// See [`Path::join()`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join)
3576+
///
3577+
/// ### Example
3578+
/// ```rust
3579+
/// # use std::path::{Path, PathBuf};
3580+
/// let path = Path::new("/bin");
3581+
/// let joined_path = path.join("/sh");
3582+
/// assert_eq!(joined_path, PathBuf::from("/sh"));
3583+
/// ```
3584+
///
3585+
/// Use instead;
3586+
/// ```rust
3587+
/// # use std::path::{Path, PathBuf};
3588+
/// let path = Path::new("/bin");
3589+
///
3590+
/// // If this was unintentional, remove the leading separator
3591+
/// let joined_path = path.join("sh");
3592+
/// assert_eq!(joined_path, PathBuf::from("/bin/sh"));
3593+
///
3594+
/// // If this was intentional, create a new path instead
3595+
/// let new = Path::new("/sh");
3596+
/// assert_eq!(new, PathBuf::from("/sh"));
3597+
/// ```
3598+
#[clippy::version = "1.74.0"]
3599+
pub JOIN_ABSOLUTE_PATHS,
3600+
correctness,
3601+
"arg to .join called on a `Path` contains leading separator"
3602+
}
3603+
35663604
pub struct Methods {
35673605
avoid_breaking_exported_api: bool,
35683606
msrv: Msrv,
@@ -3703,6 +3741,7 @@ impl_lint_pass!(Methods => [
37033741
FILTER_MAP_BOOL_THEN,
37043742
READONLY_WRITE_LOCK,
37053743
ITER_OUT_OF_BOUNDS,
3744+
JOIN_ABSOLUTE_PATHS,
37063745
]);
37073746

37083747
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4062,6 +4101,8 @@ impl Methods {
40624101
("join", [join_arg]) => {
40634102
if let Some(("collect", _, _, span, _)) = method_call(recv) {
40644103
unnecessary_join::check(cx, expr, recv, join_arg, span);
4104+
} else {
4105+
join_absolute_paths::check(cx, recv, join_arg);
40654106
}
40664107
},
40674108
("last", []) => {

tests/ui/join_absolute_paths.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![allow(unused)]
2+
#![warn(clippy::join_absolute_paths)]
3+
4+
use std::path::{Path, PathBuf};
5+
6+
fn main() {
7+
// should be linted
8+
let path = Path::new("/bin");
9+
path.join("/sh");
10+
11+
// should be linted
12+
let path = Path::new("C:\\Users");
13+
path.join("\\user");
14+
15+
// should not be linted
16+
let path: &[&str] = &["/bin"];
17+
path.join("/sh");
18+
19+
// should not be linted
20+
let path = Path::new("/bin");
21+
path.join("sh");
22+
23+
// should be linted
24+
let path = PathBuf::from("/bin");
25+
path.join("/sh");
26+
}

tests/ui/join_absolute_paths.stderr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: argument to `Path::join` starts with a path separator
2+
--> $DIR/join_absolute_paths.rs:9:15
3+
|
4+
LL | path.join("/sh");
5+
| ^^^^^
6+
|
7+
= note: joining a path starting with separator will replace the path instead
8+
= help: if this is unintentional, try removing the starting separator
9+
= help: if this is intentional, try creating a new Path instead
10+
= note: `-D clippy::join-absolute-paths` implied by `-D warnings`
11+
12+
error: argument to `Path::join` starts with a path separator
13+
--> $DIR/join_absolute_paths.rs:13:15
14+
|
15+
LL | path.join("\\user");
16+
| ^^^^^^^^
17+
|
18+
= note: joining a path starting with separator will replace the path instead
19+
= help: if this is unintentional, try removing the starting separator
20+
= help: if this is intentional, try creating a new Path instead
21+
22+
error: aborting due to 2 previous errors
23+

0 commit comments

Comments
 (0)