Skip to content

Commit 8b7d5df

Browse files
authored
refactor(forge): rewrite geiger with Solar (#9382)
1 parent 37cc284 commit 8b7d5df

File tree

13 files changed

+266
-635
lines changed

13 files changed

+266
-635
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ foundry-block-explorers = { version = "0.9.0", default-features = false }
172172
foundry-compilers = { version = "0.12.3", default-features = false }
173173
foundry-fork-db = "0.7.0"
174174
solang-parser = "=0.3.3"
175+
solar-ast = { version = "=0.1.0", default-features = false }
175176
solar-parse = { version = "=0.1.0", default-features = false }
176177

177178
## revm

crates/forge/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ semver.workspace = true
8989
serde_json.workspace = true
9090
similar = { version = "2", features = ["inline"] }
9191
solang-parser.workspace = true
92+
solar-ast.workspace = true
93+
solar-parse.workspace = true
9294
strum = { workspace = true, features = ["derive"] }
9395
thiserror.workspace = true
9496
tokio = { workspace = true, features = ["time"] }

crates/forge/bin/cmd/geiger.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use clap::{Parser, ValueHint};
2+
use eyre::{Result, WrapErr};
3+
use foundry_cli::utils::LoadConfig;
4+
use foundry_compilers::{resolver::parse::SolData, Graph};
5+
use foundry_config::{impl_figment_convert_basic, Config};
6+
use itertools::Itertools;
7+
use solar_ast::visit::Visit;
8+
use solar_parse::{ast, interface::Session};
9+
use std::path::{Path, PathBuf};
10+
11+
/// CLI arguments for `forge geiger`.
12+
#[derive(Clone, Debug, Parser)]
13+
pub struct GeigerArgs {
14+
/// Paths to files or directories to detect.
15+
#[arg(
16+
conflicts_with = "root",
17+
value_hint = ValueHint::FilePath,
18+
value_name = "PATH",
19+
num_args(1..),
20+
)]
21+
paths: Vec<PathBuf>,
22+
23+
/// The project's root path.
24+
///
25+
/// By default root of the Git repository, if in one,
26+
/// or the current working directory.
27+
#[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
28+
root: Option<PathBuf>,
29+
30+
/// Globs to ignore.
31+
#[arg(
32+
long,
33+
value_hint = ValueHint::FilePath,
34+
value_name = "PATH",
35+
num_args(1..),
36+
)]
37+
ignore: Vec<PathBuf>,
38+
39+
#[arg(long, hide = true)]
40+
check: bool,
41+
#[arg(long, hide = true)]
42+
full: bool,
43+
}
44+
45+
impl_figment_convert_basic!(GeigerArgs);
46+
47+
impl GeigerArgs {
48+
pub fn sources(&self, config: &Config) -> Result<Vec<PathBuf>> {
49+
let cwd = std::env::current_dir()?;
50+
51+
let mut sources: Vec<PathBuf> = {
52+
if self.paths.is_empty() {
53+
let paths = config.project_paths();
54+
Graph::<SolData>::resolve(&paths)?
55+
.files()
56+
.keys()
57+
.filter(|f| !paths.libraries.iter().any(|lib| f.starts_with(lib)))
58+
.cloned()
59+
.collect()
60+
} else {
61+
self.paths
62+
.iter()
63+
.flat_map(|path| foundry_common::fs::files_with_ext(path, "sol"))
64+
.unique()
65+
.collect()
66+
}
67+
};
68+
69+
sources.retain_mut(|path| {
70+
let abs_path = if path.is_absolute() { path.clone() } else { cwd.join(&path) };
71+
*path = abs_path.strip_prefix(&cwd).unwrap_or(&abs_path).to_path_buf();
72+
!self.ignore.iter().any(|ignore| {
73+
if ignore.is_absolute() {
74+
abs_path.starts_with(ignore)
75+
} else {
76+
abs_path.starts_with(cwd.join(ignore))
77+
}
78+
})
79+
});
80+
81+
Ok(sources)
82+
}
83+
84+
pub fn run(self) -> Result<usize> {
85+
if self.check {
86+
sh_warn!("`--check` is deprecated as it's now the default behavior\n")?;
87+
}
88+
if self.full {
89+
sh_warn!("`--full` is deprecated as reports are not generated anymore\n")?;
90+
}
91+
92+
let config = self.try_load_config_emit_warnings()?;
93+
let sources = self.sources(&config).wrap_err("Failed to resolve files")?;
94+
95+
if config.ffi {
96+
sh_warn!("FFI enabled\n")?;
97+
}
98+
99+
let mut sess = Session::builder().with_stderr_emitter().build();
100+
sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false);
101+
let unsafe_cheatcodes = &[
102+
"ffi".to_string(),
103+
"readFile".to_string(),
104+
"readLine".to_string(),
105+
"writeFile".to_string(),
106+
"writeLine".to_string(),
107+
"removeFile".to_string(),
108+
"closeFile".to_string(),
109+
"setEnv".to_string(),
110+
"deriveKey".to_string(),
111+
];
112+
Ok(sess
113+
.enter(|| sources.iter().map(|file| lint_file(&sess, unsafe_cheatcodes, file)).sum()))
114+
}
115+
}
116+
117+
fn lint_file(sess: &Session, unsafe_cheatcodes: &[String], path: &Path) -> usize {
118+
try_lint_file(sess, unsafe_cheatcodes, path).unwrap_or(0)
119+
}
120+
121+
fn try_lint_file(
122+
sess: &Session,
123+
unsafe_cheatcodes: &[String],
124+
path: &Path,
125+
) -> solar_parse::interface::Result<usize> {
126+
let arena = solar_parse::ast::Arena::new();
127+
let mut parser = solar_parse::Parser::from_file(sess, &arena, path)?;
128+
let ast = parser.parse_file().map_err(|e| e.emit())?;
129+
let mut visitor = Visitor::new(sess, unsafe_cheatcodes);
130+
visitor.visit_source_unit(&ast);
131+
Ok(visitor.count)
132+
}
133+
134+
struct Visitor<'a> {
135+
sess: &'a Session,
136+
count: usize,
137+
unsafe_cheatcodes: &'a [String],
138+
}
139+
140+
impl<'a> Visitor<'a> {
141+
fn new(sess: &'a Session, unsafe_cheatcodes: &'a [String]) -> Self {
142+
Self { sess, count: 0, unsafe_cheatcodes }
143+
}
144+
}
145+
146+
impl<'ast> Visit<'ast> for Visitor<'_> {
147+
fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) {
148+
if let ast::ExprKind::Call(lhs, _args) = &expr.kind {
149+
if let ast::ExprKind::Member(_lhs, member) = &lhs.kind {
150+
if self.unsafe_cheatcodes.iter().any(|c| c.as_str() == member.as_str()) {
151+
let msg = format!("usage of unsafe cheatcode `vm.{member}`");
152+
self.sess.dcx.err(msg).span(member.span).emit();
153+
self.count += 1;
154+
}
155+
}
156+
}
157+
self.walk_expr(expr);
158+
}
159+
}

crates/forge/bin/cmd/geiger/error.rs

Lines changed: 0 additions & 11 deletions
This file was deleted.

crates/forge/bin/cmd/geiger/find.rs

Lines changed: 0 additions & 165 deletions
This file was deleted.

0 commit comments

Comments
 (0)