feat(solc): improve error on case mismatch (#1998)

This commit is contained in:
Matthias Seitz 2023-01-03 14:14:01 +01:00 committed by GitHub
parent 6ac3e75c6a
commit d1df3417f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 86 additions and 11 deletions

View File

@ -27,18 +27,19 @@ pub enum SolcError {
/// Filesystem IO error
#[error(transparent)]
Io(#[from] SolcIoError),
#[error("File could not be resolved due to broken symlink: {0}.")]
ResolveBadSymlink(SolcIoError),
/// Failed to resolve a file
#[error("Failed to resolve file: {0}.\n Check configured remappings.")]
Resolve(SolcIoError),
#[error("File could not be resolved due to broken symlink: {0}.")]
ResolveBadSymlink(SolcIoError),
#[error("File cannot be resolved due to mismatch of file name case: {error}.\n Found existing file: {existing_file:?}\n Please check the case of the import.")]
ResolveCaseSensitiveFileName { error: SolcIoError, existing_file: PathBuf },
#[error(
r#"Failed to resolve file: {0}.
r#"{0}.
--> {1:?}
{2:?}
Check configured remappings."#
{2:?}"#
)]
FailedResolveImport(SolcIoError, PathBuf, PathBuf),
FailedResolveImport(Box<SolcError>, PathBuf, PathBuf),
#[cfg(feature = "svm-solc")]
#[error(transparent)]
SvmError(#[from] svm::SolcVmError),

View File

@ -59,6 +59,7 @@ use std::{
mod parse;
mod tree;
use crate::utils::find_case_sensitive_existing_file;
pub use parse::SolImportAlias;
pub use tree::{print, Charset, TreeOptions};
@ -371,10 +372,12 @@ impl Graph {
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)
.map_err(|err| {
match err {
SolcError::Resolve(err) => {
// make the error more verbose
err @ SolcError::ResolveCaseSensitiveFileName { .. } |
err @ SolcError::Resolve(_) => {
// make the error more helpful by providing additional
// context
SolcError::FailedResolveImport(
err,
Box::new(err),
node.path.clone(),
import_path.clone(),
)
@ -832,11 +835,22 @@ impl Node {
pub fn read(file: impl AsRef<Path>) -> Result<Self> {
let file = file.as_ref();
let source = Source::read(file).map_err(|err| {
if !err.path().exists() && err.path().is_symlink() {
let exists = err.path().exists();
if !exists && err.path().is_symlink() {
SolcError::ResolveBadSymlink(err)
} else {
// This is an additional check useful on OS that have case-sensitive paths, See also <https://docs.soliditylang.org/en/v0.8.17/path-resolution.html#import-callback>
if !exists {
// check if there exists a file with different case
if let Some(existing_file) = find_case_sensitive_existing_file(file) {
SolcError::ResolveCaseSensitiveFileName { error: err, existing_file }
} else {
SolcError::Resolve(err)
}
} else {
SolcError::Resolve(err)
}
}
})?;
let data = SolData::parse(source.as_ref(), file);
Ok(Self { path: file.to_path_buf(), source, data })

View File

@ -385,6 +385,26 @@ pub(crate) fn find_fave_or_alt_path(root: impl AsRef<Path>, fave: &str, alt: &st
p
}
/// Attempts to find a file with different case that exists next to the `non_existing` file
pub(crate) fn find_case_sensitive_existing_file(non_existing: &Path) -> Option<PathBuf> {
let non_existing_file_name = non_existing.file_name()?;
let parent = non_existing.parent()?;
WalkDir::new(parent)
.max_depth(1)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_file())
.find_map(|e| {
let existing_file_name = e.path().file_name()?;
if existing_file_name.eq_ignore_ascii_case(non_existing_file_name) &&
existing_file_name != non_existing_file_name
{
return Some(e.path().to_path_buf())
}
None
})
}
#[cfg(not(target_arch = "wasm32"))]
use tokio::runtime::{Handle, Runtime};
@ -453,6 +473,7 @@ pub fn create_parent_dir_all(file: impl AsRef<Path>) -> Result<(), SolcError> {
#[cfg(test)]
mod tests {
use super::*;
use crate::resolver::Node;
use solang_parser::pt::SourceUnitPart;
use std::{
collections::HashSet,
@ -460,6 +481,45 @@ mod tests {
};
use tempdir;
#[test]
fn can_find_different_case() {
let tmp_dir = tempdir("out").unwrap();
let path = tmp_dir.path().join("forge-std");
create_dir_all(&path).unwrap();
let existing = path.join("Test.sol");
let non_existing = path.join("test.sol");
std::fs::write(&existing, b"").unwrap();
#[cfg(target_os = "linux")]
assert!(!non_existing.exists());
let found = find_case_sensitive_existing_file(&non_existing).unwrap();
assert_eq!(found, existing);
}
#[cfg(target_os = "linux")]
#[test]
fn can_read_different_case() {
let tmp_dir = tempdir("out").unwrap();
let path = tmp_dir.path().join("forge-std");
create_dir_all(&path).unwrap();
let existing = path.join("Test.sol");
let non_existing = path.join("test.sol");
std::fs::write(
&existing,
r#"
pragma solidity ^0.8.10;
contract A {}
"#,
)
.unwrap();
assert!(!non_existing.exists());
let found = Node::read(&non_existing).unwrap_err();
matches!(found, SolcError::ResolveCaseSensitiveFileName { .. });
}
#[test]
fn can_create_parent_dirs_with_ext() {
let tmp_dir = tempdir("out").unwrap();