Skip to content

new lint: unnecessary_path_exists#17331

Open
davidh167 wants to merge 3 commits into
rust-lang:masterfrom
davidh167:feat/linter-Path-metadata-after-Path-exists
Open

new lint: unnecessary_path_exists#17331
davidh167 wants to merge 3 commits into
rust-lang:masterfrom
davidh167:feat/linter-Path-metadata-after-Path-exists

Conversation

@davidh167

@davidh167 davidh167 commented Jul 1, 2026

Copy link
Copy Markdown

Fixes #17158

Adds unnecessary_path_exists (nursery) which detects calls to Path::exists
used as an if condition immediately before a filesystem operation on the same
path, creating a TOCTOU race condition. The subsequent operation's return value
already indicates whether the path exists, making the exists() check redundant.

Detected patterns:

  • Direct: if path.exists() { path.metadata() }
  • With else branch: if path.exists() { ... } else { ... }
  • Compound condition: if path.exists() && other { ... }
  • Stored bool: let b = path.exists(); if b { ... }

Detected fs operations: metadata, symlink_metadata, canonicalize,
read_link, read_dir, is_file, is_dir, is_symlink.

changelog: new lint: [unnecessary_path_exists]

@rustbot rustbot added needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jul 1, 2026
@rustbot

rustbot commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome! The Rust Project is excited to review your changes, and you should hear from @samueltardieu (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue
Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: 8 candidates
  • 8 candidates expanded to 8 candidates
  • Random selection from Jarcho, dswij, llogiq, samueltardieu

@samueltardieu samueltardieu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the PR title.

View changes since this review

Comment on lines +151 to +152
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if matches!(ty.opt_diag_name(cx), Some(sym::Path | sym::PathBuf)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should rather check whether the method that is actually called is Path::exists, it would work with any type dereferencing to Path (including PathBuf).

}

let stmts = block.stmts;
for (idx, stmt) in stmts.iter().enumerate() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also zip block.stmts.iter() with block.smts.iter().skip(1).

) -> Option<Span> {
match expr.kind {
ExprKind::MethodCall(method_seg, recv, _, _) => {
if FS_METHODS.contains(&method_seg.ident.name.as_str()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compare symbols, not strings.

ExprKind::MethodCall(method_seg, recv, _, _) => {
if FS_METHODS.contains(&method_seg.ident.name.as_str()) {
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
if matches!(recv_ty.opt_diag_name(cx), Some(sym::Path | sym::PathBuf))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above: check if this is a method belonging to Path.

@@ -0,0 +1,169 @@
#![warn(clippy::unnecessary_path_exists)]
#![allow(unused)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#![allow(unused)]

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Jul 1, 2026
@rustbot

rustbot commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label Jul 1, 2026
@davidh167 davidh167 changed the title new lint: \unnecessary_path_exists`` new lint: unnecessary_path_exists Jul 1, 2026
@davidh167

Copy link
Copy Markdown
Author

Thanks for your valuable feedback! I've made the requested changes in the latest commit

@davidh167

Copy link
Copy Markdown
Author

@rustbot review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jul 2, 2026

@samueltardieu samueltardieu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add tests involving macros, as well as tests involving a user-defined type implementing Deref<Target = Path>?

View changes since this review

/// if b { ... }`), only detects when the `if` immediately follows the `let`.
#[clippy::version = "1.98.0"]
pub UNNECESSARY_PATH_EXISTS,
nursery,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to choose a category, nursery is where lints go to die nowadays.

return;
}

for (stmt, next_stmt) in block.stmts.iter().zip(block.stmts.iter().skip(1)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than checking each and every block, it probably would be more efficient to first check for a call to the .exists() method and then analyze the surrounding block. This lint should probably be moved into the methods directory.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TOCTOU: call Path::metadata after Path::exists

3 participants