Skip to main content

forge_lint/sol/info/
inline_assembly.rs

1use super::InlineAssembly;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::{Stmt, StmtKind},
8    interface::BytePos,
9};
10
11declare_forge_lint!(INLINE_ASSEMBLY, Severity::Info, "inline-assembly", "inline assembly used");
12
13impl<'ast> EarlyLintPass<'ast> for InlineAssembly {
14    fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
15        let StmtKind::Assembly(asm) = &stmt.kind else { return };
16        // Keep the diagnostic highlight on the leading `assembly` keyword.
17        let kw_span = stmt.span.with_hi(stmt.span.lo() + BytePos("assembly".len() as u32));
18        let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe")
19            || has_memory_safe_natspec(ctx, stmt.span.lo());
20        let msg = if memory_safe {
21            "inline assembly declared `memory-safe`"
22        } else {
23            "inline assembly used"
24        };
25        ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg);
26    }
27}
28
29/// Returns `true` when the lines immediately preceding `stmt_lo` form a `///` NatSpec block
30/// containing `@solidity memory-safe-assembly`.
31fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool {
32    let Some(file) = ctx.source_file() else { return false };
33    let Some(before) = stmt_lo
34        .to_u32()
35        .checked_sub(file.start_pos.to_u32())
36        .and_then(|offset| file.src.get(..offset as usize))
37    else {
38        return false;
39    };
40    before
41        .lines()
42        .rev()
43        .map(str::trim_start)
44        .filter(|line| !line.is_empty())
45        .map_while(|line| line.strip_prefix("///"))
46        .any(|rest| rest.trim_start().starts_with("@solidity memory-safe-assembly"))
47}