Skip to content

Commit c69912c

Browse files
authored
Forbid std::process::Command spawning, replace with smol where appropriate (#38894)
std commands can block for an arbitrary duration and so runs risk of blocking tasks for too long. This replaces all such uses where sensible with async processes. Release Notes: - N/A *or* Added/Fixed/Improved ...
1 parent 7f14ab2 commit c69912c

File tree

31 files changed

+220
-141
lines changed

31 files changed

+220
-141
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,7 @@ todo = "deny"
862862
declare_interior_mutable_const = "deny"
863863

864864
redundant_clone = "deny"
865+
disallowed_methods = "deny"
865866

866867
# We currently do not restrict any style rules
867868
# as it slows down shipping code to Zed.

clippy.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@ ignore-interior-mutability = [
55
# and Hash impls do not use fields with interior mutability.
66
"agent::context::AgentContextKey"
77
]
8+
disallowed-methods = [
9+
{ path = "std::process::Command::spawn", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::spawn" },
10+
{ path = "std::process::Command::output", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::output" },
11+
{ path = "std::process::Command::status", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::status" },
12+
]
13+
disallowed-types = [
14+
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },
15+
# { path = "std::collections::HashSet", replacement = "collections::HashSet" },
16+
# { path = "indexmap::IndexSet", replacement = "collections::IndexSet" },
17+
# { path = "indexmap::IndexMap", replacement = "collections::IndexMap" },
18+
]

crates/auto_update/src/auto_update.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,10 @@ impl AutoUpdater {
310310
// the app after an update, we use `set_restart_path` to run the auto
311311
// update helper instead of the app, so that it can overwrite the app
312312
// and then spawn the new binary.
313-
let quit_subscription = Some(cx.on_app_quit(|_, _| async move {
314-
#[cfg(target_os = "windows")]
315-
finalize_auto_update_on_quit();
316-
}));
313+
#[cfg(target_os = "windows")]
314+
let quit_subscription = Some(cx.on_app_quit(|_, _| finalize_auto_update_on_quit()));
315+
#[cfg(not(target_os = "windows"))]
316+
let quit_subscription = None;
317317

318318
cx.on_app_restart(|this, _| {
319319
this.quit_subscription.take();
@@ -942,11 +942,12 @@ async fn install_release_windows(downloaded_installer: PathBuf) -> Result<Option
942942
let helper_path = std::env::current_exe()?
943943
.parent()
944944
.context("No parent dir for Zed.exe")?
945-
.join("tools\\auto_update_helper.exe");
945+
.join("tools")
946+
.join("auto_update_helper.exe");
946947
Ok(Some(helper_path))
947948
}
948949

949-
pub fn finalize_auto_update_on_quit() {
950+
pub async fn finalize_auto_update_on_quit() {
950951
let Some(installer_path) = std::env::current_exe()
951952
.ok()
952953
.and_then(|p| p.parent().map(|p| p.join("updates")))
@@ -959,12 +960,14 @@ pub fn finalize_auto_update_on_quit() {
959960
if flag_file.exists()
960961
&& let Some(helper) = installer_path
961962
.parent()
962-
.map(|p| p.join("tools\\auto_update_helper.exe"))
963+
.map(|p| p.join("tools").join("auto_update_helper.exe"))
963964
{
964-
let mut command = std::process::Command::new(helper);
965+
let mut command = smol::process::Command::new(helper);
965966
command.arg("--launch");
966967
command.arg("false");
967-
let _ = command.spawn();
968+
if let Ok(mut cmd) = command.spawn() {
969+
_ = cmd.status().await;
970+
}
968971
}
969972
}
970973

crates/auto_update_helper/src/updater.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool)
160160
}
161161
}
162162
if launch {
163+
#[allow(clippy::disallowed_methods, reason = "doesn't run in the main binary")]
163164
let _ = std::process::Command::new(app_dir.join("Zed.exe"))
164165
.creation_flags(CREATE_NEW_PROCESS_GROUP.0)
165166
.spawn();

crates/cli/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
12
use std::process::Command;
23

34
fn main() {

crates/cli/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#![allow(
2+
clippy::disallowed_methods,
3+
reason = "We are not in an async environment, so std::process::Command is fine"
4+
)]
15
#![cfg_attr(
26
any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
37
allow(dead_code)

crates/collections/src/collections.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,8 @@
1-
#[cfg(feature = "test-support")]
21
pub type HashMap<K, V> = FxHashMap<K, V>;
3-
4-
#[cfg(feature = "test-support")]
52
pub type HashSet<T> = FxHashSet<T>;
6-
7-
#[cfg(feature = "test-support")]
83
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, rustc_hash::FxBuildHasher>;
9-
10-
#[cfg(feature = "test-support")]
114
pub type IndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>;
125

13-
#[cfg(not(feature = "test-support"))]
14-
pub type HashMap<K, V> = std::collections::HashMap<K, V>;
15-
16-
#[cfg(not(feature = "test-support"))]
17-
pub type HashSet<T> = std::collections::HashSet<T>;
18-
19-
#[cfg(not(feature = "test-support"))]
20-
pub type IndexMap<K, V> = indexmap::IndexMap<K, V>;
21-
22-
#[cfg(not(feature = "test-support"))]
23-
pub type IndexSet<T> = indexmap::IndexSet<T>;
24-
256
pub use indexmap::Equivalent;
267
pub use rustc_hash::FxHasher;
278
pub use rustc_hash::{FxHashMap, FxHashSet};

crates/crashes/src/crashes.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use log::info;
33
use minidumper::{Client, LoopAction, MinidumpBinary};
44
use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
55
use serde::{Deserialize, Serialize};
6+
use smol::process::Command;
67

78
#[cfg(target_os = "macos")]
89
use std::sync::atomic::AtomicU32;
@@ -12,7 +13,7 @@ use std::{
1213
io,
1314
panic::{self, PanicHookInfo},
1415
path::{Path, PathBuf},
15-
process::{self, Command},
16+
process::{self},
1617
sync::{
1718
Arc, OnceLock,
1819
atomic::{AtomicBool, Ordering},
@@ -53,13 +54,13 @@ pub async fn init(crash_init: InitCrashHandler) {
5354
// used by the crash handler isn't destroyed correctly which causes it to stay on the file
5455
// system and block further attempts to initialize crash handlers with that socket path.
5556
let socket_name = paths::temp_dir().join(format!("zed-crash-handler-{zed_pid}"));
56-
#[allow(unused)]
57-
let server_pid = Command::new(exe)
57+
let _crash_handler = Command::new(exe)
5858
.arg("--crash-handler")
5959
.arg(&socket_name)
6060
.spawn()
61-
.expect("unable to spawn server process")
62-
.id();
61+
.expect("unable to spawn server process");
62+
#[cfg(target_os = "linux")]
63+
let server_pid = _crash_handler.id();
6364
info!("spawning crash handler process");
6465

6566
let mut elapsed = Duration::ZERO;

crates/explorer_command_injector/src/explorer_command_injector.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ impl IExplorerCommand_Impl for ExplorerCommandInjector_Impl {
7777
for idx in 0..count {
7878
let item = unsafe { items.GetItemAt(idx)? };
7979
let item_path = unsafe { item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
80+
#[allow(clippy::disallowed_methods, reason = "no async context in sight..")]
8081
std::process::Command::new(&zed_exe)
8182
.arg(&item_path)
8283
.spawn()

crates/extension/src/extension_builder.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ impl ExtensionBuilder {
142142
manifest: &mut ExtensionManifest,
143143
options: CompileExtensionOptions,
144144
) -> anyhow::Result<()> {
145-
self.install_rust_wasm_target_if_needed()?;
145+
self.install_rust_wasm_target_if_needed().await?;
146146

147147
let cargo_toml_content = fs::read_to_string(extension_dir.join("Cargo.toml"))?;
148148
let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content)?;
@@ -151,7 +151,7 @@ impl ExtensionBuilder {
151151
"compiling Rust crate for extension {}",
152152
extension_dir.display()
153153
);
154-
let output = util::command::new_std_command("cargo")
154+
let output = util::command::new_smol_command("cargo")
155155
.args(["build", "--target", RUST_TARGET])
156156
.args(options.release.then_some("--release"))
157157
.arg("--target-dir")
@@ -160,6 +160,7 @@ impl ExtensionBuilder {
160160
.env("RUSTC_WRAPPER", "")
161161
.current_dir(extension_dir)
162162
.output()
163+
.await
163164
.context("failed to run `cargo`")?;
164165
if !output.status.success() {
165166
bail!(
@@ -235,7 +236,8 @@ impl ExtensionBuilder {
235236
&grammar_repo_dir,
236237
&grammar_metadata.repository,
237238
&grammar_metadata.rev,
238-
)?;
239+
)
240+
.await?;
239241

240242
let base_grammar_path = grammar_metadata
241243
.path
@@ -248,7 +250,7 @@ impl ExtensionBuilder {
248250
let scanner_path = src_path.join("scanner.c");
249251

250252
log::info!("compiling {grammar_name} parser");
251-
let clang_output = util::command::new_std_command(&clang_path)
253+
let clang_output = util::command::new_smol_command(&clang_path)
252254
.args(["-fPIC", "-shared", "-Os"])
253255
.arg(format!("-Wl,--export=tree_sitter_{grammar_name}"))
254256
.arg("-o")
@@ -258,6 +260,7 @@ impl ExtensionBuilder {
258260
.arg(&parser_path)
259261
.args(scanner_path.exists().then_some(scanner_path))
260262
.output()
263+
.await
261264
.context("failed to run clang")?;
262265

263266
if !clang_output.status.success() {
@@ -271,15 +274,16 @@ impl ExtensionBuilder {
271274
Ok(())
272275
}
273276

274-
fn checkout_repo(&self, directory: &Path, url: &str, rev: &str) -> Result<()> {
277+
async fn checkout_repo(&self, directory: &Path, url: &str, rev: &str) -> Result<()> {
275278
let git_dir = directory.join(".git");
276279

277280
if directory.exists() {
278-
let remotes_output = util::command::new_std_command("git")
281+
let remotes_output = util::command::new_smol_command("git")
279282
.arg("--git-dir")
280283
.arg(&git_dir)
281284
.args(["remote", "-v"])
282-
.output()?;
285+
.output()
286+
.await?;
283287
let has_remote = remotes_output.status.success()
284288
&& String::from_utf8_lossy(&remotes_output.stdout)
285289
.lines()
@@ -298,22 +302,24 @@ impl ExtensionBuilder {
298302
fs::create_dir_all(directory).with_context(|| {
299303
format!("failed to create grammar directory {}", directory.display(),)
300304
})?;
301-
let init_output = util::command::new_std_command("git")
305+
let init_output = util::command::new_smol_command("git")
302306
.arg("init")
303307
.current_dir(directory)
304-
.output()?;
308+
.output()
309+
.await?;
305310
if !init_output.status.success() {
306311
bail!(
307312
"failed to run `git init` in directory '{}'",
308313
directory.display()
309314
);
310315
}
311316

312-
let remote_add_output = util::command::new_std_command("git")
317+
let remote_add_output = util::command::new_smol_command("git")
313318
.arg("--git-dir")
314319
.arg(&git_dir)
315320
.args(["remote", "add", "origin", url])
316321
.output()
322+
.await
317323
.context("failed to execute `git remote add`")?;
318324
if !remote_add_output.status.success() {
319325
bail!(
@@ -323,19 +329,21 @@ impl ExtensionBuilder {
323329
}
324330
}
325331

326-
let fetch_output = util::command::new_std_command("git")
332+
let fetch_output = util::command::new_smol_command("git")
327333
.arg("--git-dir")
328334
.arg(&git_dir)
329335
.args(["fetch", "--depth", "1", "origin", rev])
330336
.output()
337+
.await
331338
.context("failed to execute `git fetch`")?;
332339

333-
let checkout_output = util::command::new_std_command("git")
340+
let checkout_output = util::command::new_smol_command("git")
334341
.arg("--git-dir")
335342
.arg(&git_dir)
336343
.args(["checkout", rev])
337344
.current_dir(directory)
338345
.output()
346+
.await
339347
.context("failed to execute `git checkout`")?;
340348
if !checkout_output.status.success() {
341349
if !fetch_output.status.success() {
@@ -356,11 +364,12 @@ impl ExtensionBuilder {
356364
Ok(())
357365
}
358366

359-
fn install_rust_wasm_target_if_needed(&self) -> Result<()> {
360-
let rustc_output = util::command::new_std_command("rustc")
367+
async fn install_rust_wasm_target_if_needed(&self) -> Result<()> {
368+
let rustc_output = util::command::new_smol_command("rustc")
361369
.arg("--print")
362370
.arg("sysroot")
363371
.output()
372+
.await
364373
.context("failed to run rustc")?;
365374
if !rustc_output.status.success() {
366375
bail!(
@@ -374,11 +383,12 @@ impl ExtensionBuilder {
374383
return Ok(());
375384
}
376385

377-
let output = util::command::new_std_command("rustup")
386+
let output = util::command::new_smol_command("rustup")
378387
.args(["target", "add", RUST_TARGET])
379388
.stderr(Stdio::piped())
380389
.stdout(Stdio::inherit())
381390
.output()
391+
.await
382392
.context("failed to run `rustup target add`")?;
383393
if !output.status.success() {
384394
bail!(

0 commit comments

Comments
 (0)