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, Span},
9};
10
11declare_forge_lint!(
12 INLINE_ASSEMBLY,
13 Severity::Info,
14 "inline-assembly",
15 "usage of inline assembly; assembly bypasses Solidity safety features and should be reviewed"
16);
17
18const ASSEMBLY_KW_LEN: u32 = 8;
19const NATSPEC_MEMORY_SAFE_MARKER: &str = "@solidity memory-safe-assembly";
20
21impl<'ast> EarlyLintPass<'ast> for InlineAssembly {
22 fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
23 let StmtKind::Assembly(asm) = &stmt.kind else { return };
24
25 let kw_span = assembly_keyword_span(stmt.span);
26
27 let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe")
28 || has_memory_safe_natspec(ctx, stmt.span.lo());
29
30 let msg = if memory_safe {
31 "inline assembly (declared memory-safe); review business logic and side effects"
32 } else {
33 "inline assembly used; review for memory safety and side effects"
34 };
35
36 ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg);
37 }
38}
39
40fn assembly_keyword_span(span: Span) -> Span {
42 span.with_hi(span.lo() + BytePos(ASSEMBLY_KW_LEN))
43}
44
45fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool {
48 let Some(source_file) = ctx.source_file() else { return false };
49 let src = source_file.src.as_str();
50 let start_pos = source_file.start_pos.to_u32();
51 let lo_abs = stmt_lo.to_u32();
52 if lo_abs < start_pos {
53 return false;
54 }
55 let offset = (lo_abs - start_pos) as usize;
56 if offset > src.len() {
57 return false;
58 }
59
60 for line in src[..offset].lines().rev() {
61 let trimmed = line.trim_start();
62 if trimmed.is_empty() {
63 continue;
64 }
65 let Some(rest) = trimmed.strip_prefix("///") else { return false };
66 if rest.trim_start().starts_with(NATSPEC_MEMORY_SAFE_MARKER) {
67 return true;
68 }
69 }
70 false
71}