1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// Copyright 2026 Umberto Gotti <umberto.gotti@umbertogotti.dev>
// Licensed under the MIT License
// SPDX-License-Identifier: MIT
use syn::visit::Visit;
const IO_METHOD_NAMES: &[&str] = &[
"connect",
"send_to",
"recv_from",
"write_all",
"read_to_string",
"flush",
"open",
"create",
"bind",
"accept",
];
fn is_io_method(name: &str) -> bool {
IO_METHOD_NAMES.contains(&name)
}
pub(crate) struct IoCallFinder {
pub(crate) found: bool,
}
impl IoCallFinder {
pub(crate) fn new() -> Self {
Self { found: false }
}
}
impl<'ast> Visit<'ast> for IoCallFinder {
fn visit_expr(&mut self, expr: &'ast syn::Expr) {
match expr {
syn::Expr::Call(expr_call) => {
if let syn::Expr::Path(expr_path) = &*expr_call.func {
let segments: Vec<_> = expr_path
.path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect();
let flagged = if segments.len() >= 2 {
matches!(
segments[0].as_str(),
"fs" | "net"
| "io"
| "TcpStream"
| "UdpSocket"
| "File"
| "OpenOptions"
)
} else {
false
};
if flagged || segments.last().is_some_and(|n| is_io_method(n)) {
self.found = true;
return;
}
}
}
syn::Expr::MethodCall(expr_method) => {
if is_io_method(&expr_method.method.to_string()) {
self.found = true;
return;
}
}
syn::Expr::Macro(expr_macro) => {
if let Some(name) = expr_macro.mac.path.get_ident() {
let n = name.to_string();
if n == "writeln" || n == "write" {
self.found = true;
return;
}
}
}
_ => {}
}
syn::visit::visit_expr(self, expr);
}
}