Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:33:58.264Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

blocking_io_on_foreground.rs

247 lines · 8.3 KB · rust
1use clippy_utils::diagnostics::span_lint;
2use rustc_hir::def::Res;
3use rustc_hir::{Expr, ExprKind, HirId, Node};
4use rustc_lint::{LateContext, LateLintPass};
5use rustc_middle::ty::Ty;
6
7use crate::render_helpers::is_directly_in_render_method;
8
9rustc_session::declare_lint! {
10    /// ### What it does
11    ///
12    /// Flags calls to known blocking IO functions from the standard library
13    /// (`std::fs`, `std::thread::sleep`, `std::process::Command`, `std::net`)
14    /// when they appear inside a function that receives a synchronous GPUI
15    /// context parameter (`&App`, `&mut App`, `&Context<T>`,
16    /// `&mut Context<T>`, `&mut Window`) or directly inside a
17    /// `Render::render` / `RenderOnce::render` method.
18    ///
19    /// ### Why is this bad?
20    ///
21    /// In GPUI, code that receives a synchronous context type runs on the
22    /// foreground (UI) thread. A blocking IO call on this thread freezes the
23    /// application until the syscall returns.
24    pub BLOCKING_IO_ON_FOREGROUND,
25    Warn,
26    "blocking IO call on the GPUI foreground thread"
27}
28
29pub(crate) struct BlockingIoOnForeground;
30
31rustc_session::impl_lint_pass!(BlockingIoOnForeground => [BLOCKING_IO_ON_FOREGROUND]);
32
33const BLOCKING_FN_PATHS: &[&str] = &[
34    // std::fs free functions
35    "std::fs::read",
36    "std::fs::read_to_string",
37    "std::fs::write",
38    "std::fs::read_dir",
39    "std::fs::read_link",
40    "std::fs::metadata",
41    "std::fs::symlink_metadata",
42    "std::fs::set_permissions",
43    "std::fs::canonicalize",
44    "std::fs::create_dir",
45    "std::fs::create_dir_all",
46    "std::fs::remove_file",
47    "std::fs::remove_dir",
48    "std::fs::remove_dir_all",
49    "std::fs::copy",
50    "std::fs::rename",
51    "std::fs::hard_link",
52    // std::fs::File associated functions
53    "std::fs::File::open",
54    "std::fs::File::create",
55    "std::fs::File::create_new",
56    // std::thread
57    "std::thread::sleep",
58    // std::path::Path methods (resolved via method call def_id)
59    "std::path::Path::metadata",
60    "std::path::Path::symlink_metadata",
61    "std::path::Path::read_link",
62    "std::path::Path::read_dir",
63    "std::path::Path::exists",
64    "std::path::Path::try_exists",
65    "std::path::Path::is_file",
66    "std::path::Path::is_dir",
67    "std::path::Path::is_symlink",
68    "std::path::Path::canonicalize",
69    // std::net associated functions
70    "std::net::TcpStream::connect",
71    "std::net::TcpStream::connect_timeout",
72    "std::net::TcpListener::bind",
73    "std::net::UdpSocket::bind",
74];
75
76const BLOCKING_METHODS: &[(&str, &str)] = &[
77    // std::process
78    ("Command", "output"),
79    ("Command", "status"),
80    ("Command", "spawn"),
81    ("Child", "wait"),
82    ("Child", "wait_with_output"),
83    // std::fs::File instance methods
84    ("File", "sync_all"),
85    ("File", "sync_data"),
86    ("File", "set_len"),
87    ("File", "metadata"),
88    ("File", "try_clone"),
89    ("File", "set_permissions"),
90    // std::net — TCP
91    ("TcpStream", "connect"),
92    ("TcpStream", "peek"),
93    ("TcpListener", "bind"),
94    ("TcpListener", "accept"),
95    ("TcpListener", "incoming"),
96    // std::net — UDP
97    ("UdpSocket", "send"),
98    ("UdpSocket", "send_to"),
99    ("UdpSocket", "recv"),
100    ("UdpSocket", "recv_from"),
101    ("UdpSocket", "peek"),
102    ("UdpSocket", "peek_from"),
103    // std::sync
104    ("Mutex", "lock"),
105    ("RwLock", "read"),
106    ("RwLock", "write"),
107    ("Condvar", "wait"),
108    ("Condvar", "wait_timeout"),
109    ("Condvar", "wait_while"),
110    ("Barrier", "wait"),
111    // std::sync::mpsc
112    ("Receiver", "recv"),
113    ("Receiver", "recv_timeout"),
114    ("SyncSender", "send"),
115];
116
117fn is_blocking_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
118    match &expr.kind {
119        ExprKind::Call(callee, _) => {
120            if let ExprKind::Path(qpath) = &callee.kind {
121                if let Res::Def(_, def_id) = cx.qpath_res(qpath, callee.hir_id) {
122                    let path = cx.tcx.def_path_str(def_id);
123                    return BLOCKING_FN_PATHS.iter().any(|blocked| path == *blocked);
124                }
125            }
126            false
127        }
128        ExprKind::MethodCall(segment, receiver, _args, _span) => {
129            if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
130                let path = cx.tcx.def_path_str(def_id);
131                if BLOCKING_FN_PATHS.iter().any(|blocked| path == *blocked) {
132                    return true;
133                }
134            }
135            let method_name = segment.ident.name.as_str();
136            if !BLOCKING_METHODS
137                .iter()
138                .any(|(_, name)| *name == method_name)
139            {
140                return false;
141            }
142            let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
143            if let Some(adt) = receiver_ty.ty_adt_def() {
144                let type_name = cx.tcx.item_name(adt.did());
145                return BLOCKING_METHODS
146                    .iter()
147                    .any(|(ty, name)| *name == method_name && type_name.as_str() == *ty);
148            }
149            false
150        }
151        _ => false,
152    }
153}
154
155/// Returns `true` if `ty` (after peeling references) is a synchronous GPUI
156/// foreground type: `App`, `Context`, or `Window`.
157fn is_gpui_foreground_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
158    let peeled = ty.peel_refs();
159    let Some(adt) = peeled.ty_adt_def() else {
160        return false;
161    };
162    let did = adt.did();
163    let crate_name = cx.tcx.crate_name(did.krate);
164    if crate_name.as_str() != "gpui" {
165        return false;
166    }
167    let name = cx.tcx.item_name(did);
168    matches!(name.as_str(), "App" | "Context" | "Window")
169}
170
171/// Walks up the HIR parent chain from `hir_id` to find the enclosing
172/// function. Returns `true` if that function has a parameter whose type is a
173/// synchronous GPUI context type. Returns `false` if a closure boundary is
174/// crossed first (the closure might run on a background thread).
175fn is_in_foreground_fn(cx: &LateContext<'_>, hir_id: HirId) -> bool {
176    for (_parent_id, node) in cx.tcx.hir_parent_iter(hir_id) {
177        match node {
178            Node::Expr(expr) if matches!(expr.kind, ExprKind::Closure(_)) => {
179                return false;
180            }
181            Node::Item(item) => {
182                if let rustc_hir::ItemKind::Fn { .. } = &item.kind {
183                    let owner_id = item.owner_id.def_id;
184                    return owner_has_foreground_param(cx, owner_id);
185                }
186                return false;
187            }
188            Node::ImplItem(impl_item) => {
189                if let rustc_hir::ImplItemKind::Fn(_, _) = &impl_item.kind {
190                    let owner_id = impl_item.owner_id.def_id;
191                    return owner_has_foreground_param(cx, owner_id);
192                }
193                return false;
194            }
195            Node::TraitItem(trait_item) => {
196                if let rustc_hir::TraitItemKind::Fn(_, _) = &trait_item.kind {
197                    let owner_id = trait_item.owner_id.def_id;
198                    return owner_has_foreground_param(cx, owner_id);
199                }
200                return false;
201            }
202            _ => {}
203        }
204    }
205    false
206}
207
208/// Checks whether the function identified by `local_def_id` has any parameter
209/// whose type is a synchronous GPUI foreground type.
210fn owner_has_foreground_param(
211    cx: &LateContext<'_>,
212    local_def_id: rustc_hir::def_id::LocalDefId,
213) -> bool {
214    let def_id = local_def_id.to_def_id();
215    let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
216    sig.inputs()
217        .skip_binder()
218        .iter()
219        .any(|ty| is_gpui_foreground_type(cx, *ty))
220}
221
222impl<'tcx> LateLintPass<'tcx> for BlockingIoOnForeground {
223    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
224        if expr.span.from_expansion() {
225            return;
226        }
227
228        if !is_blocking_call(cx, expr) {
229            return;
230        }
231
232        let in_render = is_directly_in_render_method(cx, expr.hir_id);
233        let in_foreground_fn = is_in_foreground_fn(cx, expr.hir_id);
234
235        if !in_render && !in_foreground_fn {
236            return;
237        }
238
239        span_lint(
240            cx,
241            BLOCKING_IO_ON_FOREGROUND,
242            expr.span,
243            "blocking IO call on the GPUI foreground thread",
244        );
245    }
246}
247
Served at tenant.openagents/omega Member data and write actions are omitted.