Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(forge): Forge Lint #9590

Open
wants to merge 72 commits into
base: master
Choose a base branch
from
Open

feat(forge): Forge Lint #9590

wants to merge 72 commits into from

Conversation

0xKitsune
Copy link
Contributor

@0xKitsune 0xKitsune commented Dec 24, 2024

ref #1970

This PR introduces forge lint, which implements a static analyzer built on top of solar and is capable of detecting known issues, vulnerabilities, informational warnings and gas optimizations. This is a first pass at forge lint and I am opening this draft in order to receive early feedback on the design and general direction.

The core component of this design centers around the declare_lints! macro which allows you to specify the pattern along with the severity, name and description.

declare_lints!(
    (DivideBeforeMultiply, Severity::Med, "divide-before-multiply", "Multiplication should occur before division to avoid loss of precision."),
    (VariableCamelCase, Severity::Info, "variable-camel-case", "Variables should follow `camelCase` naming conventions unless they are constants or immutables."),
    (AsmKeccak256, Severity::Gas, "asm-keccak256", "Hashing via keccak256 can be done with inline assembly to save `x` gas."),
);

Once the pattern is defined, you can implement the Visit trait to match on any instances of the pattern.

use solar_ast::{
    ast::{Expr, ExprKind},
    visit::Visit,
};

use crate::AsmKeccak256;

impl<'ast> Visit<'ast> for AsmKeccak256 {
    fn visit_expr(&mut self, expr: &'ast Expr<'ast>) {
        if let ExprKind::Call(expr, _) = &expr.kind {
            if let ExprKind::Ident(ident) = &expr.kind {
                if ident.name.as_str() == "keccak256" {
                    self.items.push(expr.span);
                }
            }
        }
        self.walk_expr(expr);
    }
}

There is still quite a bit of work to do but if this direction makes sense, I am happy to continue adding the rest of the features as well as additional patterns. I can also port over any of the patterns from solstat, sstan or start working on patterns in other static analysis tools like slither as well.

@gakonst
Copy link
Member

gakonst commented Dec 25, 2024

Fantastic. Really excited for this. Anything we can learn from slither here as well?

@0xKitsune 0xKitsune marked this pull request as ready for review January 2, 2025 02:18
@0xKitsune
Copy link
Contributor Author

0xKitsune commented Jan 2, 2025

With the linter output complete and a few other additional changes, I am marking this PR ready for initial review. I decided to make the LinterOutput display to the terminal in the same style as cargo clippy. The output is also colored according to the finding's Severity.

➜  foundry git:(master) cargo run --bin forge lint --include crates/lint/testdata/Keccak256.sol

Gas: asm-keccak256: Hashing via keccak256 can be done with inline assembly to save gas.
 -->  crates/lint/testdata/Keccak256.sol:3:9
  |
3 |         keccak256(abi.encodePacked(a, b));
  |         ^^^^^^^^^
  = help: for further information visit https://placeholder.xyz

Gas: asm-keccak256: Hashing via keccak256 can be done with inline assembly to save gas.
 -->  crates/lint/testdata/Keccak256.sol:7:9
  |
7 |         keccak256(abi.encodePacked(a, b));
  |         ^^^^^^^^^
  = help: for further information visit https://placeholder.xyz

After this initial round of review and patching any fixes, I can start adding more lints to make the linter much more comprehensive. Happy to do this either here or in a separate PR.

}
}

impl<L: Linter> fmt::Display for LinterOutput<L> {
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason of collecting results in a report and printing it manually rather than using the solar diagnostics system? It's way more flexible and it's the same structure as diagnostics in Rust

use super::DivideBeforeMultiply;

#[test]
fn test_divide_before_multiply() -> eyre::Result<()> {
Copy link
Member

Choose a reason for hiding this comment

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

If the diagnostic system is used, you can also re-use the same testing framework used in solar; this is currently not exposed but not hard to implement, publish on crates.io and re-use here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

3 participants