Skip to content

Commit f1646ac

Browse files
committed
[hermes] Harden against weird symlink layout
gherrit-pr-id: Gtu5rhuk5rjkrhhqpfwbmniejgf57ogzn
1 parent 0556c73 commit f1646ac

File tree

25 files changed

+113
-9
lines changed

25 files changed

+113
-9
lines changed

tools/hermes/src/parse.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ where
118118
F: FnMut(&str, Result<ParsedLeanItem, HermesError>),
119119
{
120120
log::trace!("read_file_and_scan_compilation_unit({:?})", path);
121-
let source = fs::read_to_string(path).expect("Failed to read file");
121+
let source = fs::read_to_string(path)?;
122122
let unloaded_modules = scan_compilation_unit(&source, f);
123123
Ok((source, unloaded_modules))
124124
}

tools/hermes/src/resolve.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
env,
2+
env, fs,
33
hash::{Hash as _, Hasher as _},
44
path::PathBuf,
55
};
@@ -341,7 +341,9 @@ fn resolve_targets<'a>(
341341
/// is found.
342342
pub fn check_for_external_deps(metadata: &Metadata) -> Result<()> {
343343
log::trace!("check_for_external_deps");
344-
let workspace_root = metadata.workspace_root.as_std_path();
344+
// Canonicalize workspace root to handle symlinks correctly
345+
let workspace_root = fs::canonicalize(&metadata.workspace_root)
346+
.context("Failed to canonicalize workspace root")?;
345347

346348
for pkg in &metadata.packages {
347349
// We only care about packages that are "local" (source is None).
@@ -350,8 +352,12 @@ pub fn check_for_external_deps(metadata: &Metadata) -> Result<()> {
350352
if pkg.source.is_none() {
351353
let pkg_path = pkg.manifest_path.as_std_path();
352354

355+
// Canonicalize the package path for comparison
356+
let canonical_pkg_path = fs::canonicalize(pkg_path)
357+
.with_context(|| format!("Failed to canonicalize path for package {}", pkg.name))?;
358+
353359
// Check if the package lives outside the workspace tree
354-
if !pkg_path.starts_with(workspace_root) {
360+
if !canonical_pkg_path.starts_with(&workspace_root) {
355361
anyhow::bail!(
356362
"Unsupported external dependency: '{}' at {:?}.\n\
357363
Hermes currently only supports verifying workspaces where all local \

tools/hermes/src/scanner.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
collections::HashMap,
3+
ffi::OsStr,
34
hash::{Hash, Hasher as _},
45
path::{Path, PathBuf},
56
sync::mpsc::{self, Sender},
@@ -141,14 +142,28 @@ fn process_file_recursive<'a>(
141142
name: HermesTargetName,
142143
) {
143144
log::trace!("process_file_recursive(src_path: {:?})", src_path);
144-
if !visited.insert(src_path.to_path_buf()) {
145+
146+
// Canonicalize the path to ensure we don't process the same file multiple
147+
// times (e.g. via symlinks or different relative paths).
148+
let src_path = match std::fs::canonicalize(src_path) {
149+
Ok(p) => p,
150+
Err(e) => {
151+
// It is valid for a module to be declared but not exist (e.g., if
152+
// it is cfg-gated for another platform). In strict Rust, we would
153+
// check the cfg attributes, but since we are just scanning, it is
154+
// safe to warn and return.
155+
log::debug!("Skipping unreachable or missing file {:?}: {}", src_path, e);
156+
return;
157+
}
158+
};
159+
160+
if !visited.insert(src_path.clone()) {
145161
return;
146162
}
147163

148-
// Walking the AST is enough to collect new modules.
149164
// Walk the AST, collecting new modules to process.
150165
let (_source_code, unloaded_modules) =
151-
match parse::read_file_and_scan_compilation_unit(src_path, |_src, item_result| {
166+
match parse::read_file_and_scan_compilation_unit(&src_path, |_src, item_result| {
152167
match item_result {
153168
Ok(parsed_item) => {
154169
if let Some(item_name) = parsed_item.item.name() {
@@ -177,11 +192,23 @@ fn process_file_recursive<'a>(
177192
}
178193
};
179194

195+
// Determine the directory to search for child modules in.
196+
// - For `mod.rs`, `lib.rs`, `main.rs`, children are in the parent directory.
197+
// e.g. `src/lib.rs` -> `mod foo` -> `src/foo.rs`
198+
// - For `my_mod.rs`, children are in a sibling directory of the same name.
199+
// e.g. `src/my_mod.rs` -> `mod sub` -> `src/my_mod/sub.rs`
200+
let file_stem = src_path.file_stem().and_then(OsStr::to_str).unwrap_or("");
201+
let base_dir = if matches!(file_stem, "mod" | "lib" | "main") {
202+
src_path.parent().unwrap_or(&src_path).to_path_buf()
203+
} else {
204+
// e.g. src/foo.rs -> src/foo/
205+
src_path.with_extension("")
206+
};
207+
180208
// Resolve and queue child modules for processing.
181-
let base_dir = src_path.parent().unwrap();
182209
for module in unloaded_modules {
183210
if let Some(mod_path) =
184-
resolve_module_path(base_dir, &module.name, module.path_attr.as_deref())
211+
resolve_module_path(&base_dir, &module.name, module.path_attr.as_deref())
185212
{
186213
// Spawn new tasks for discovered modules.
187214
let (err_tx, path_tx) = (err_tx.clone(), path_tx.clone());
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
success

tools/hermes/tests/fixtures/canonicalized_workspace/hermes.toml

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[workspace]
2+
members = ["crates/app", "crates/lib"]
3+
resolver = "2"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "app"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
# Intentionally messy relative path that traverses up and down
8+
# ../../crates/lib is inside the workspace, but we structure it to ensure
9+
# canonicalization is required to match it correctly.
10+
my_lib = { path = "../../crates/lib" }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/// ```lean
2+
/// theorem main_proof : True := trivial
3+
/// ```
4+
pub fn main_proof() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "my_lib"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub fn shared() {}

0 commit comments

Comments
 (0)