forge_lint/sol/info/
inline_assembly.rs1use 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 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
29fn 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}