Skip to content

Commit

Permalink
Auto merge of rust-lang#17093 - Wilfred:vfs_ignore_symlink_cycles, r=…
Browse files Browse the repository at this point in the history
…Veykril

fix: VFS should not walk circular symlinks

As of rust-lang#6246, rust-analyzer follows symlinks. This can introduce an infinite loop if symlinks point to parent directories.

Considering that rust-lang#6246 was added in 2020 without many bug reports, this is clearly a rare occurrence. However, I am observing rust-analyzer hang on projects that have symlinks of the form:

```
test/a_symlink -> ../../
```

Ignore symlinks that only point to the parent directories, as this is more robust but still allows typical symlink usage patterns.
  • Loading branch information
bors committed Apr 18, 2024
2 parents 9517bff + d37058f commit 3b8d45a
Showing 1 changed file with 27 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

#![warn(rust_2018_idioms, unused_lifetimes)]

use std::fs;
use std::{
fs,
path::{Component, Path},
};

use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
Expand Down Expand Up @@ -206,6 +209,11 @@ impl NotifyActor {
return true;
}
let path = entry.path();

if path_is_parent_symlink(path) {
return false;
}

root == path
|| dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
});
Expand Down Expand Up @@ -258,3 +266,21 @@ fn read(path: &AbsPath) -> Option<Vec<u8>> {
fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()
}

/// Is `path` a symlink to a parent directory?
///
/// Including this path is guaranteed to cause an infinite loop. This
/// heuristic is not sufficient to catch all symlink cycles (it's
/// possible to construct cycle using two or more symlinks), but it
/// catches common cases.
fn path_is_parent_symlink(path: &Path) -> bool {
let Ok(destination) = std::fs::read_link(path) else {
return false;
};

// If the symlink is of the form "../..", it's a parent symlink.
let is_relative_parent =
destination.components().all(|c| matches!(c, Component::CurDir | Component::ParentDir));

is_relative_parent || path.starts_with(destination)
}

0 comments on commit 3b8d45a

Please sign in to comment.