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!(
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 = stmt.span.with_hi(stmt.span.lo() + BytePos(ASSEMBLY_KW_LEN));
27
28 let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe")
29 || has_memory_safe_natspec(ctx, stmt.span.lo());
30
31 let msg = if memory_safe {
32 "inline assembly (declared memory-safe); review business logic and side effects"
33 } else {
34 "inline assembly used; review for memory safety and side effects"
35 };
36
37 ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg);
38 }
39}
40
41fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool {
44 let Some(source_file) = ctx.source_file() else { return false };
45 let src = source_file.src.as_str();
46 let start_pos = source_file.start_pos.to_u32();
47 let lo_abs = stmt_lo.to_u32();
48 if lo_abs < start_pos {
49 return false;
50 }
51 let offset = (lo_abs - start_pos) as usize;
52 if offset > src.len() {
53 return false;
54 }
55
56 for line in src[..offset].lines().rev() {
57 let trimmed = line.trim_start();
58 if trimmed.is_empty() {
59 continue;
60 }
61 let Some(rest) = trimmed.strip_prefix("///") else { return false };
62 if rest.trim_start().starts_with(NATSPEC_MEMORY_SAFE_MARKER) {
63 return true;
64 }
65 }
66 false
67}