Repository: bytecodealliance/wasmtime
Pull request #7658 (2023-12-08 10:43:35)

Replace clap attributes with command and arg
While reading the code, I noticed that we were mixing clap's `clap`, `command`, `arg`, and `structopt` attributes, and it seemed quite messy, and even though `#[clap()]` still works, `#[arg()]` and `#[command()]` are now recommended since clap v4, ref https://github.com/clap-rs/clap/pull/4180.

So this patch standardizes them, eliminating the use of outdated `clap` attributes and removing some meaningless name configurations.

github-actions[bot]: #### Subscribe to Label Action

cc @cfallin, @fitzgen, @saulecabrera

<details>
This issue or pull request has been labeled: "cranelift", "isle", "winch"

Thus the following users have been cc'd because of the following labels:

* cfallin: isle
* fitzgen: isle
* saulecabrera: winch

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs
index f934e71ae..74ef67857 100644
--- a/cranelift/isle/islec/src/main.rs
+++ b/cranelift/isle/islec/src/main.rs
@@ -12,11 +12,11 @@ use std::{
 struct Opts {
     /// The output file to write the generated Rust code to. `stdout` is used if
     /// this is not given.
-    #[clap(short, long)]
+    #[arg(short, long)]
     output: Option<PathBuf>,
 
     /// The input ISLE DSL source files.
-    #[clap(required = true)]
+    #[arg(required = true)]
     inputs: Vec<PathBuf>,
 }
 
diff --git a/cranelift/serde/src/clif-json.rs b/cranelift/serde/src/clif-json.rs
index 572a9df10..cd1f7b5b0 100644
--- a/cranelift/serde/src/clif-json.rs
+++ b/cranelift/serde/src/clif-json.rs
@@ -37,12 +37,12 @@ fn call_de(file: &File) -> Result<(), String> {
 
 /// Cranelift JSON serializer/deserializer utility
 #[derive(Parser, Debug)]
-#[clap(about)]
+#[command(about)]
 enum Args {
     /// Serializes Cranelift IR into JSON
     Serialize {
         /// Generate pretty json
-        #[clap(long, short)]
+        #[arg(long, short)]
         pretty: bool,
 
         /// Input file for serialization
diff --git a/cranelift/src/bugpoint.rs b/cranelift/src/bugpoint.rs
index d7a523432..57bb5e5c8 100644
--- a/cranelift/src/bugpoint.rs
+++ b/cranelift/src/bugpoint.rs
@@ -26,14 +26,14 @@ pub struct Options {
     file: PathBuf,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the target architecture.
     target: String,
 
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 }
 
diff --git a/cranelift/src/cat.rs b/cranelift/src/cat.rs
index 167d768aa..2c48318fb 100644
--- a/cranelift/src/cat.rs
+++ b/cranelift/src/cat.rs
@@ -13,7 +13,7 @@ use std::path::{Path, PathBuf};
 #[derive(Parser)]
 pub struct Options {
     /// Specify input file(s) to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 }
 
diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs
index 79ea48668..12b740e09 100644
--- a/cranelift/src/clif-util.rs
+++ b/cranelift/src/clif-util.rs
@@ -43,15 +43,15 @@ enum Commands {
 #[derive(Parser)]
 struct TestOptions {
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 
     /// Print pass timing report for test
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     time_passes: bool,
 
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 }
 
@@ -59,11 +59,11 @@ struct TestOptions {
 #[derive(Parser)]
 struct PassOptions {
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 
     /// Print pass timing report for test
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     time_passes: bool,
 
     /// Specify an input file to be used. Use '-' for stdin.
@@ -73,7 +73,7 @@ struct PassOptions {
     target: String,
 
     /// Specify pass(es) to be run on the input file
-    #[clap(required = true)]
+    #[arg(required = true)]
     passes: Vec<String>,
 }
 
diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs
index 0df3f3fa6..782484abd 100644
--- a/cranelift/src/compile.rs
+++ b/cranelift/src/compile.rs
@@ -17,30 +17,30 @@ use std::path::PathBuf;
 #[derive(Parser)]
 pub struct Options {
     /// Print the resulting Cranelift IR
-    #[clap(short)]
+    #[arg(short)]
     print: bool,
 
     /// Print pass timing report
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     report_times: bool,
 
     /// Print machine code disassembly
-    #[clap(short = 'D', long)]
+    #[arg(short = 'D', long)]
     disasm: bool,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the Cranelift target
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 
     /// Specify an input file to be used. Use '-' for stdin.
     files: Vec<PathBuf>,
 
     /// Output object file
-    #[clap(short = 'o', long = "output")]
+    #[arg(short = 'o', long = "output")]
     output: Option<PathBuf>,
 }
 
diff --git a/cranelift/src/interpret.rs b/cranelift/src/interpret.rs
index a752d692d..cd4f50a62 100644
--- a/cranelift/src/interpret.rs
+++ b/cranelift/src/interpret.rs
@@ -14,11 +14,11 @@ use thiserror::Error;
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 }
 
diff --git a/cranelift/src/print_cfg.rs b/cranelift/src/print_cfg.rs
index fa7027c42..cadb61c76 100644
--- a/cranelift/src/print_cfg.rs
+++ b/cranelift/src/print_cfg.rs
@@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 }
 
diff --git a/cranelift/src/run.rs b/cranelift/src/run.rs
index 5564736e3..1264dfb3b 100644
--- a/cranelift/src/run.rs
+++ b/cranelift/src/run.rs
@@ -14,11 +14,11 @@ use target_lexicon::Triple;
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 }
 
diff --git a/cranelift/src/souper_harvest.rs b/cranelift/src/souper_harvest.rs
index a8490f842..f1d1c11f3 100644
--- a/cranelift/src/souper_harvest.rs
+++ b/cranelift/src/souper_harvest.rs
@@ -22,21 +22,21 @@ pub struct Options {
 
     /// Specify the directory where harvested left-hand side files should be
     /// written to.
-    #[clap(short, long)]
+    #[arg(short, long)]
     output_dir: PathBuf,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the Cranelift target
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 
     /// Add a comment from which CLIF variable and function each left-hand side
     /// was harvested from. This prevents deduplicating harvested left-hand
     /// sides.
-    #[clap(long)]
+    #[arg(long)]
     add_harvest_source: bool,
 }
 
@@ -56,7 +56,7 @@ pub fn run(options: &Options) -> Result<()> {
         ))
     };
 
-    match std::fs::create_dir_all(&options.output_dir) {
+    match fs::create_dir_all(&options.output_dir) {
         Ok(_) => {}
         Err(e)
             if e.kind() == io::ErrorKind::AlreadyExists
diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs
index 8a5e2102b..d1eb99c7b 100644
--- a/cranelift/src/wasm.rs
+++ b/cranelift/src/wasm.rs
@@ -59,46 +59,46 @@ macro_rules! vcprint {
 #[derive(Parser)]
 pub struct Options {
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 
     /// Print the resulting Cranelift IR
-    #[clap(short)]
+    #[arg(short)]
     print: bool,
 
     /// Print pass timing report
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     report_times: bool,
 
     /// Print machine code disassembly
-    #[clap(short = 'D', long)]
+    #[arg(short = 'D', long)]
     disasm: bool,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the Cranelift target
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 
     /// Specify an input file to be used. Use '-' for stdin.
     files: Vec<PathBuf>,
 
     /// Print bytecode size
-    #[clap(short = 'X')]
+    #[arg(short = 'X')]
     print_size: bool,
 
     /// Just decode Wasm into Cranelift IR, don't compile it to native code
-    #[clap(short = 't')]
+    #[arg(short = 't')]
     just_decode: bool,
 
     /// Just checks the correctness of Cranelift IR translated from Wasm
-    #[clap(short = 'c')]
+    #[arg(short = 'c')]
     check_translation: bool,
 
     /// Use colors in output? [options: auto/never/always; default: auto]
-    #[clap(long = "color", default_value("auto"))]
+    #[arg(long = "color", default_value("auto"))]
     color: ColorOpt,
 }
 
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index f474dcecf..1192017dc 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -290,39 +290,39 @@ pub struct CommonOptions {
     // now.
     /// Optimization and tuning related options for wasm performance, `-O help` to
     /// see all.
-    #[clap(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
     opts_raw: Vec<opt::CommaSeparated<Optimize>>,
 
     /// Codegen-related configuration options, `-C help` to see all.
-    #[clap(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
     codegen_raw: Vec<opt::CommaSeparated<Codegen>>,
 
     /// Debug-related configuration options, `-D help` to see all.
-    #[clap(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
     debug_raw: Vec<opt::CommaSeparated<Debug>>,
 
     /// Options for configuring semantic execution of WebAssembly, `-W help` to see
     /// all.
-    #[clap(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
     wasm_raw: Vec<opt::CommaSeparated<Wasm>>,
 
     /// Options for configuring WASI and its proposals, `-S help` to see all.
-    #[clap(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
     wasi_raw: Vec<opt::CommaSeparated<Wasi>>,
 
     // These fields are filled in by the `configure` method below via the
     // options parsed from the CLI above. This is what the CLI should use.
-    #[clap(skip)]
+    #[arg(skip)]
     configured: bool,
-    #[clap(skip)]
+    #[arg(skip)]
     pub opts: OptimizeOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub codegen: CodegenOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub debug: DebugOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub wasm: WasmOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub wasi: WasiOptions,
 }
 
diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs
index 0416b1613..d0f49b547 100644
--- a/crates/environ/examples/factc.rs
+++ b/crates/environ/examples/factc.rs
@@ -24,53 +24,53 @@ use wasmtime_environ::fact::Module;
 #[derive(Parser)]
 struct Factc {
     /// Whether or not debug code is inserted into the generated adapter.
-    #[clap(long)]
+    #[arg(long)]
     debug: bool,
 
     /// Whether or not the lifting options (the callee of the exported adapter)
     /// uses a 64-bit memory as opposed to a 32-bit memory.
-    #[clap(long)]
+    #[arg(long)]
     lift64: bool,
 
     /// Whether or not the lowering options (the caller of the exported adapter)
     /// uses a 64-bit memory as opposed to a 32-bit memory.
-    #[clap(long)]
+    #[arg(long)]
     lower64: bool,
 
     /// Whether or not a call to a `post-return` configured function is enabled
     /// or not.
-    #[clap(long)]
+    #[arg(long)]
     post_return: bool,
 
     /// Whether or not to skip validation of the generated adapter module.
-    #[clap(long)]
+    #[arg(long)]
     skip_validate: bool,
 
     /// Where to place the generated adapter module. Standard output is used if
     /// this is not specified.
-    #[clap(short, long)]
+    #[arg(short, long)]
     output: Option<PathBuf>,
 
     /// Output the text format for WebAssembly instead of the binary format.
-    #[clap(short, long)]
+    #[arg(short, long)]
     text: bool,
 
-    #[clap(long, value_parser = parse_string_encoding, default_value = "utf8")]
+    #[arg(long, value_parser = parse_string_encoding, default_value = "utf8")]
     lift_str: StringEncoding,
 
-    #[clap(long, value_parser = parse_string_encoding, default_value = "utf8")]
+    #[arg(long, value_parser = parse_string_encoding, default_value = "utf8")]
     lower_str: StringEncoding,
 
     /// TODO
     input: PathBuf,
 }
 
-fn parse_string_encoding(name: &str) -> anyhow::Result<StringEncoding> {
+fn parse_string_encoding(name: &str) -> Result<StringEncoding> {
     Ok(match name {
         "utf8" => StringEncoding::Utf8,
         "utf16" => StringEncoding::Utf16,
         "compact-utf16" => StringEncoding::CompactUtf16,
-        other => anyhow::bail!("invalid string encoding: `{other}`"),
+        other => bail!("invalid string encoding: `{other}`"),
     })
 }
 
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index 5d527b241..d3d52408d 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -12,6 +12,7 @@ use cap_std::net::Pool;
 use cap_std::{ambient_authority, AmbientAuthority};
 use std::mem;
 use std::net::{Ipv4Addr, Ipv6Addr};
+use std::sync::Arc;
 use wasmtime::component::ResourceTable;
 
 pub struct WasiCtxBuilder {
@@ -303,7 +304,7 @@ impl WasiCtxBuilder {
             env,
             args,
             preopens,
-            pool,
+            pool: Arc::new(pool),
             random,
             insecure_random,
             insecure_random_seed,
@@ -333,7 +334,7 @@ pub struct WasiCtx {
     pub(crate) stdin: Box<dyn StdinStream>,
     pub(crate) stdout: Box<dyn StdoutStream>,
     pub(crate) stderr: Box<dyn StdoutStream>,
-    pub(crate) pool: Pool,
+    pub(crate) pool: Arc<Pool>,
     pub(crate) allowed_network_uses: AllowedNetworkUses,
 }
 
diff --git a/crates/wasi/src/preview2/host/udp.rs b/crates/wasi/src/preview2/host/udp.rs
index 9b5955511..cd6dd62c3 100644
--- a/crates/wasi/src/preview2/host/udp.rs
+++ b/crates/wasi/src/preview2/host/udp.rs
@@ -35,20 +35,24 @@ impl<T: WasiView> udp::HostUdpSocket for T {
     ) -> SocketResult<()> {
         self.ctx().allowed_network_uses.check_allowed_udp()?;
         let table = self.table_mut();
-        let socket = table.get(&this)?;
-        let network = table.get(&network)?;
-        let local_address: SocketAddr = local_address.into();
 
-        match socket.udp_state {
+        match table.get(&this)?.udp_state {
             UdpState::Default => {}
             UdpState::BindStarted => return Err(ErrorCode::ConcurrencyConflict.into()),
             UdpState::Bound | UdpState::Connected => return Err(ErrorCode::InvalidState.into()),
         }
 
+        // Set the pool on the socket so later functions have access to it through the socket handle
+        let pool = table.get(&network)?.pool.clone();
+        table.get_mut(&this)?.pool.replace(pool.clone());
+
+        let socket = table.get(&this)?;
+        let local_address: SocketAddr = local_address.into();
+
         util::validate_address_family(&local_address, &socket.family)?;
 
         {
-            let binder = network.pool.udp_binder(local_address)?;
+            let binder = pool.udp_binder(local_address)?;
             let udp_socket = &*socket
                 .udp_socket()
                 .as_socketlike_view::<cap_std::net::UdpSocket>();
@@ -128,8 +132,13 @@ impl<T: WasiView> udp::HostUdpSocket for T {
 
         // Step #2: (Re)connect
         if let Some(connect_addr) = remote_address {
+            let Some(pool) = socket.pool.as_ref() else {
+                return Err(ErrorCode::InvalidState.into());
+            };
             util::validate_remote_address(&connect_addr)?;
             util::validate_address_family(&connect_addr, &socket.family)?;
+            // We don't actually use the connecter, we just use it to verify that `connect_addr` is allowed
+            let _ = pool.udp_connecter(connect_addr)?;
 
             rustix::net::connect(socket.udp_socket(), &connect_addr).map_err(
                 |error| match error {
@@ -153,6 +162,7 @@ impl<T: WasiView> udp::HostUdpSocket for T {
             remote_address,
             family: socket.family,
             send_state: SendState::Idle,
+            pool: socket.pool.clone(),
         };
 
         Ok((
@@ -345,7 +355,6 @@ impl<T: WasiView> udp::HostIncomingDatagramStream for T {
                 _ => {}
             }
 
-            // FIXME: check permission to receive from `received_addr`.
             Ok(Some(udp::IncomingDatagram {
                 data: buf[..size].into(),
                 remote_address: received_addr.into(),
@@ -448,7 +457,16 @@ impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
 
             let provided_addr = datagram.remote_address.map(SocketAddr::from);
             let addr = match (stream.remote_address, provided_addr) {
-                (None, Some(addr)) => addr,
+                (None, Some(addr)) => {
+                    let Some(pool) = stream.pool.as_ref() else {
+                        return Err(ErrorCode::InvalidState.into());
+                    };
+                    // We don't actually use the connecter, we just use it to verify that `addr`
+                    // is allowed. We only need to check the provided addr as the stream's remote
+                    // address was checked when the stream was created.
+                    let _ = pool.udp_connecter(addr)?;
+                    addr
+                }
                 (Some(addr), None) => addr,
                 (Some(connected_addr), Some(provided_addr)) if connected_addr == provided_addr => {
                     connected_addr
@@ -459,7 +477,6 @@ impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
             util::validate_remote_address(&addr)?;
             util::validate_address_family(&addr, &stream.family)?;
 
-            // FIXME: check permission to send to `addr`.
             if stream.remote_address == Some(addr) {
                 stream.inner.try_send(&datagram.data)?;
             } else {
diff --git a/crates/wasi/src/preview2/network.rs b/crates/wasi/src/preview2/network.rs
index 13575202b..6384b1803 100644
--- a/crates/wasi/src/preview2/network.rs
+++ b/crates/wasi/src/preview2/network.rs
@@ -2,9 +2,10 @@ use crate::preview2::bindings::sockets::network::{Ipv4Address, Ipv6Address};
 use crate::preview2::bindings::wasi::sockets::network::ErrorCode;
 use crate::preview2::TrappableError;
 use cap_std::net::Pool;
+use std::sync::Arc;
 
 pub struct Network {
-    pub pool: Pool,
+    pub pool: Arc<Pool>,
     pub allow_ip_name_lookup: bool,
 }
 
diff --git a/crates/wasi/src/preview2/udp.rs b/crates/wasi/src/preview2/udp.rs
index 16335d601..13194c1f5 100644
--- a/crates/wasi/src/preview2/udp.rs
+++ b/crates/wasi/src/preview2/udp.rs
@@ -2,6 +2,7 @@ use crate::preview2::poll::Subscribe;
 use crate::preview2::with_ambient_tokio_runtime;
 use async_trait::async_trait;
 use cap_net_ext::{AddressFamily, Blocking, UdpSocketExt};
+use cap_std::net::Pool;
 use io_lifetimes::raw::{FromRawSocketlike, IntoRawSocketlike};
 use std::io;
 use std::net::SocketAddr;
@@ -42,6 +43,9 @@ pub struct UdpSocket {
 
     /// Socket address family.
     pub(crate) family: SocketAddressFamily,
+
+    /// The pool of allowed addresses
+    pub(crate) pool: Option<Arc<Pool>>,
 }
 
 #[async_trait]
@@ -67,6 +71,7 @@ impl UdpSocket {
             inner: Arc::new(socket),
             udp_state: UdpState::Default,
             family: socket_address_family,
+            pool: None,
         })
     }
 
@@ -104,6 +109,9 @@ pub struct OutgoingDatagramStream {
     pub(crate) family: SocketAddressFamily,
 
     pub(crate) send_state: SendState,
+
+    /// The pool of allowed addresses
+    pub(crate) pool: Option<Arc<Pool>>,
 }
 
 pub(crate) enum SendState {
diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs
index 8324ce329..6abcbf819 100644
--- a/src/bin/wasmtime.rs
+++ b/src/bin/wasmtime.rs
@@ -8,7 +8,7 @@ use clap::Parser;
 
 /// Wasmtime WebAssembly Runtime
 #[derive(Parser, PartialEq)]
-#[clap(
+#[command(
     version = version(),
     after_help = "If a subcommand is not provided, the `run` subcommand will be used.\n\
                   \n\
@@ -34,9 +34,9 @@ use clap::Parser;
     args_conflicts_with_subcommands = true
 )]
 struct Wasmtime {
-    #[clap(subcommand)]
+    #[command(subcommand)]
     subcommand: Option<Subcommand>,
-    #[clap(flatten)]
+    #[command(flatten)]
     run: wasmtime_cli::commands::RunCommand,
 }
 
diff --git a/src/commands/compile.rs b/src/commands/compile.rs
index d5f7ef0d3..4a399238f 100644
--- a/src/commands/compile.rs
+++ b/src/commands/compile.rs
@@ -30,30 +30,29 @@ static AFTER_HELP: Lazy<String> = Lazy::new(|| {
 
 /// Compiles a WebAssembly module.
 #[derive(Parser, PartialEq)]
-#[structopt(
-    name = "compile",
+#[command(
     version,
     after_help = AFTER_HELP.as_str()
 )]
 pub struct CompileCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     #[allow(missing_docs)]
     pub common: CommonOptions,
 
     /// The target triple; default is the host triple
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     pub target: Option<String>,
 
     /// The path of the output compiled module; defaults to <MODULE>.cwasm
-    #[clap(short = 'o', long, value_name = "OUTPUT")]
+    #[arg(short = 'o', long, value_name = "OUTPUT")]
     pub output: Option<PathBuf>,
 
     /// The directory path to write clif files into, one clif file per wasm function.
-    #[clap(long = "emit-clif", value_name = "PATH")]
+    #[arg(long = "emit-clif", value_name = "PATH")]
     pub emit_clif: Option<PathBuf>,
 
     /// The path of the WebAssembly to compile
-    #[clap(index = 1, value_name = "MODULE")]
+    #[arg(index = 1, value_name = "MODULE")]
     pub module: PathBuf,
 }
 
diff --git a/src/commands/config.rs b/src/commands/config.rs
index b18e90ea8..d53b8f9a1 100644
--- a/src/commands/config.rs
+++ b/src/commands/config.rs
@@ -1,23 +1,22 @@
 //! The module that implements the `wasmtime config` command.
 
 use anyhow::Result;
-use clap::Parser;
+use clap::{Parser, Subcommand};
 
 const CONFIG_NEW_AFTER_HELP: &str =
     "If no file path is specified, the system configuration file path will be used.";
 
 /// Controls Wasmtime configuration settings
 #[derive(Parser, PartialEq)]
-#[clap(name = "config")]
 pub struct ConfigCommand {
-    #[clap(subcommand)]
+    #[command(subcommand)]
     subcommand: ConfigSubcommand,
 }
 
-#[derive(clap::Subcommand, PartialEq)]
+#[derive(Subcommand, PartialEq)]
 enum ConfigSubcommand {
     /// Creates a new Wasmtime configuration file
-    #[clap(after_help = CONFIG_NEW_AFTER_HELP)]
+    #[command(after_help = CONFIG_NEW_AFTER_HELP)]
     New(ConfigNewCommand),
 }
 
@@ -32,10 +31,9 @@ impl ConfigCommand {
 
 /// Creates a new Wasmtime configuration file
 #[derive(Parser, PartialEq)]
-#[clap(name = "new", after_help = CONFIG_NEW_AFTER_HELP)]
 pub struct ConfigNewCommand {
     /// The path of the new configuration file
-    #[clap(index = 1, value_name = "FILE_PATH")]
+    #[arg(index = 1, value_name = "FILE_PATH")]
     path: Option<String>,
 }
 
diff --git a/src/commands/explore.rs b/src/commands/explore.rs
index 6b008253f..465a1d342 100644
--- a/src/commands/explore.rs
+++ b/src/commands/explore.rs
@@ -7,22 +7,21 @@ use wasmtime_cli_flags::CommonOptions;
 
 /// Explore the compilation of a WebAssembly module to native code.
 #[derive(Parser, PartialEq)]
-#[clap(name = "explore")]
 pub struct ExploreCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     common: CommonOptions,
 
     /// The target triple; default is the host triple
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     target: Option<String>,
 
     /// The path of the WebAssembly module to compile
-    #[clap(required = true, value_name = "MODULE")]
+    #[arg(required = true, value_name = "MODULE")]
     module: PathBuf,
 
     /// The path of the explorer output (derived from the MODULE name if none
     /// provided)
-    #[clap(short, long)]
+    #[arg(short, long)]
     output: Option<PathBuf>,
 }
 
diff --git a/src/commands/run.rs b/src/commands/run.rs
index b5b465535..964a50bde 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -55,9 +55,8 @@ fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
 
 /// Runs a WebAssembly module
 #[derive(Parser, PartialEq)]
-#[structopt(name = "run")]
 pub struct RunCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     #[allow(missing_docs)]
     pub run: RunCommon,
 
@@ -67,7 +66,7 @@ pub struct RunCommand {
     /// host is made available within the guest. If specified as `HOST::GUEST`
     /// then the `HOST` directory is opened and made available as the name
     /// `GUEST` in the guest.
-    #[clap(long = "dir", value_name = "HOST_DIR[::GUEST_DIR]", value_parser = parse_dirs)]
+    #[arg(long = "dir", value_name = "HOST_DIR[::GUEST_DIR]", value_parser = parse_dirs)]
     pub dirs: Vec<(String, String)>,
 
     /// Pass an environment variable to the program.
@@ -77,15 +76,15 @@ pub struct RunCommand {
     /// form will set the environment variable named `FOO` to the same value it
     /// has in the calling process for the guest, or in other words it will
     /// cause the environment variable `FOO` to be inherited.
-    #[clap(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
+    #[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
     pub vars: Vec<(String, Option<String>)>,
 
     /// The name of the function to run
-    #[clap(long, value_name = "FUNCTION")]
+    #[arg(long, value_name = "FUNCTION")]
     pub invoke: Option<String>,
 
     /// Load the given WebAssembly module before the main module
-    #[clap(
+    #[arg(
         long = "preload",
         number_of_values = 1,
         value_name = "NAME=MODULE_PATH",
@@ -98,7 +97,7 @@ pub struct RunCommand {
     /// Arguments passed to the wasm module will be configured as WASI CLI
     /// arguments unless the `--invoke` CLI argument is passed in which case
     /// arguments will be interpreted as arguments to the function specified.
-    #[clap(value_name = "WASM", trailing_var_arg = true, required = true)]
+    #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
     pub module_and_args: Vec<OsString>,
 }
 
@@ -782,7 +781,7 @@ impl RunCommand {
             builder.allow_tcp(enable);
         }
         if let Some(enable) = self.run.common.wasi.udp {
-            builder.allow_tcp(enable);
+            builder.allow_udp(enable);
         }
 
         store.data_mut().preview2_ctx = Some(Arc::new(builder.build()));
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 5601b3ddd..9cfc16e30 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -67,17 +67,16 @@ const DEFAULT_ADDR: std::net::SocketAddr = std::net::SocketAddr::new(
 
 /// Runs a WebAssembly module
 #[derive(Parser, PartialEq)]
-#[structopt(name = "serve")]
 pub struct ServeCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     run: RunCommon,
 
     /// Socket address for the web server to bind to.
-    #[clap(long = "addr", value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR )]
+    #[arg(long = "addr", value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR )]
     addr: std::net::SocketAddr,
 
     /// The WebAssembly component to run.
-    #[clap(value_name = "WASM", required = true)]
+    #[arg(value_name = "WASM", required = true)]
     component: PathBuf,
 }
 
diff --git a/src/commands/settings.rs b/src/commands/settings.rs
index 600a23454..9053b2cd6 100644
--- a/src/commands/settings.rs
+++ b/src/commands/settings.rs
@@ -9,14 +9,13 @@ use wasmtime_environ::{CompilerBuilder, FlagValue, Setting, SettingKind};
 
 /// Displays available Cranelift settings for a target.
 #[derive(Parser, PartialEq)]
-#[clap(name = "run")]
 pub struct SettingsCommand {
     /// The target triple to get the settings for; defaults to the host triple.
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     target: Option<String>,
 
     /// Switch output format to JSON
-    #[clap(long)]
+    #[arg(long)]
     json: bool,
 }
 
diff --git a/src/commands/wast.rs b/src/commands/wast.rs
index 669b817a5..1edeeb4dc 100644
--- a/src/commands/wast.rs
+++ b/src/commands/wast.rs
@@ -9,13 +9,12 @@ use wasmtime_wast::WastContext;
 
 /// Runs a WebAssembly test script file
 #[derive(Parser, PartialEq)]
-#[clap(name = "wast", version)]
 pub struct WastCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     common: CommonOptions,
 
     /// The path of the WebAssembly test script to run
-    #[clap(required = true, value_name = "SCRIPT_FILE")]
+    #[arg(required = true, value_name = "SCRIPT_FILE")]
     scripts: Vec<PathBuf>,
 }
 
diff --git a/src/common.rs b/src/common.rs
index 2d5f68c6f..70782c0b3 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -37,7 +37,7 @@ impl RunTarget {
 /// Common command line arguments for run commands.
 #[derive(Parser, PartialEq)]
 pub struct RunCommon {
-    #[clap(flatten)]
+    #[command(flatten)]
     pub common: CommonOptions,
 
     /// Allow executing precompiled WebAssembly modules as `*.cwasm` files.
@@ -46,7 +46,7 @@ pub struct RunCommon {
     /// is arbitrary user input. Only `wasmtime`-precompiled modules generated
     /// via the `wasmtime compile` command or equivalent should be passed as an
     /// argument with this option specified.
-    #[clap(long = "allow-precompiled")]
+    #[arg(long = "allow-precompiled")]
     pub allow_precompiled: bool,
 
     /// Profiling strategy (valid options are: perfmap, jitdump, vtune, guest)
@@ -64,7 +64,7 @@ pub struct RunCommon {
     /// where `path` is where to write the profile and `interval` is the
     /// duration between samples. When used with `--wasm-timeout` the timeout
     /// will be rounded up to the nearest multiple of this interval.
-    #[clap(
+    #[arg(
         long,
         value_name = "STRATEGY",
         value_parser = Profile::parse,
diff --git a/src/old_cli.rs b/src/old_cli.rs
index ec73aab63..f6bbe5ff4 100644
--- a/src/old_cli.rs
+++ b/src/old_cli.rs
@@ -10,7 +10,7 @@ use std::time::Duration;
 
 /// Wasmtime WebAssembly Runtime
 #[derive(Parser)]
-#[clap(
+#[command(
     version,
     after_help = "If a subcommand is not provided, the `run` subcommand will be used.\n\
                   \n\
@@ -50,23 +50,23 @@ pub enum Subcommand {
 }
 
 #[derive(Parser)]
-#[structopt(name = "run", trailing_var_arg = true)]
+#[command(trailing_var_arg = true)]
 pub struct RunCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     common: CommonOptions,
 
     /// Allow unknown exports when running commands.
-    #[clap(long = "allow-unknown-exports")]
+    #[arg(long = "allow-unknown-exports")]
     allow_unknown_exports: bool,
 
     /// Allow the main module to import unknown functions, using an
     /// implementation that immediately traps, when running commands.
-    #[clap(long = "trap-unknown-imports")]
+    #[arg(long = "trap-unknown-imports")]
     trap_unknown_imports: bool,
 
     /// Allow the main module to import unknown functions, using an
     /// implementation that returns default values, when running commands.
-    #[clap(long = "default-values-unknown-imports")]
+    #[arg(long = "default-values-unknown-imports")]
     default_values_unknown_imports: bool,
 
     /// Allow executing precompiled WebAssembly modules as `*.cwasm` files.
@@ -75,16 +75,16 @@ pub struct RunCommand {
     /// is arbitrary user input. Only `wasmtime`-precompiled modules generated
     /// via the `wasmtime compile` command or equivalent should be passed as an
     /// argument with this option specified.
-    #[clap(long = "allow-precompiled")]
+    #[arg(long = "allow-precompiled")]
     allow_precompiled: bool,
 
     /// Inherit environment variables and file descriptors following the
     /// systemd listen fd specification (UNIX only)
-    #[clap(long = "listenfd")]
+    #[arg(long = "listenfd")]
     listenfd: bool,
 
     /// Grant access to the given TCP listen socket
-    #[clap(
+    #[arg(
         long = "tcplisten",
         number_of_values = 1,
         value_name = "SOCKET ADDRESS"
@@ -92,7 +92,7 @@ pub struct RunCommand {
     tcplisten: Vec<String>,
 
     /// Grant access to the given host directory
-    #[clap(long = "dir", number_of_values = 1, value_name = "DIRECTORY")]
+    #[arg(long = "dir", number_of_values = 1, value_name = "DIRECTORY")]
     dirs: Vec<String>,
 
     /// Pass an environment variable to the program.
@@ -102,15 +102,15 @@ pub struct RunCommand {
     /// form will set the environment variable named `FOO` to the same value it
     /// has in the calling process for the guest, or in other words it will
     /// cause the environment variable `FOO` to be inherited.
-    #[clap(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
+    #[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
     vars: Vec<(String, Option<String>)>,
 
     /// The name of the function to run
-    #[clap(long, value_name = "FUNCTION")]
+    #[arg(long, value_name = "FUNCTION")]
     invoke: Option<String>,
 
     /// Grant access to a guest directory mapped as a host directory
-    #[clap(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_map_dirs)]
+    #[arg(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_map_dirs)]
     map_dirs: Vec<(String, String)>,
 
     /// Pre-load machine learning graphs (i.e., models) for use by wasi-nn.
@@ -121,11 +121,11 @@ pub struct RunCommand {
     /// an OpenVINO model named `bar`. Note that which model encodings are
     /// available is dependent on the backends implemented in the
     /// `wasmtime_wasi_nn` crate.
-    #[clap(long = "wasi-nn-graph", value_name = "FORMAT::HOST_DIR", value_parser = parse_graphs)]
+    #[arg(long = "wasi-nn-graph", value_name = "FORMAT::HOST_DIR", value_parser = parse_graphs)]
     graphs: Vec<(String, String)>,
 
     /// The path of the WebAssembly module to run
-    #[clap(
+    #[arg(
 		required = true,
         value_name = "MODULE",
         value_parser = OsStringValueParser::new().try_map(parse_module),
@@ -133,7 +133,7 @@ pub struct RunCommand {
     module: PathBuf,
 
     /// Load the given WebAssembly module before the main module
-    #[clap(
+    #[arg(
         long = "preload",
         number_of_values = 1,
         value_name = "NAME=MODULE_PATH",
@@ -142,7 +142,7 @@ pub struct RunCommand {
     preloads: Vec<(String, PathBuf)>,
 
     /// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
-    #[clap(
+    #[arg(
         long = "wasm-timeout",
         value_name = "TIME",
         value_parser = parse_dur,
@@ -164,7 +164,7 @@ pub struct RunCommand {
     /// where `path` is where to write the profile and `interval` is the
     /// duration between samples. When used with `--wasm-timeout` the timeout
     /// will be rounded up to the nearest multiple of this interval.
-    #[clap(
+    #[arg(
         long,
         value_name = "STRATEGY",
         value_parser = parse_profile,
@@ -172,35 +172,35 @@ pub struct RunCommand {
     profile: Option<Profile>,
 
     /// Enable coredump generation after a WebAssembly trap.
-    #[clap(long = "coredump-on-trap", value_name = "PATH")]
+    #[arg(long = "coredump-on-trap", value_name = "PATH")]
     coredump_on_trap: Option<String>,
 
     // NOTE: this must come last for trailing varargs
     /// The arguments to pass to the module
-    #[clap(value_name = "ARGS")]
+    #[arg(value_name = "ARGS")]
     module_args: Vec<String>,
 
     /// Maximum size, in bytes, that a linear memory is allowed to reach.
     ///
     /// Growth beyond this limit will cause `memory.grow` instructions in
     /// WebAssembly modules to return -1 and fail.
-    #[clap(long, value_name = "BYTES")]
+    #[arg(long, value_name = "BYTES")]
     max_memory_size: Option<usize>,
 
     /// Maximum size, in table elements, that a table is allowed to reach.
-    #[clap(long)]
+    #[arg(long)]
     max_table_elements: Option<u32>,
 
     /// Maximum number of WebAssembly instances allowed to be created.
-    #[clap(long)]
+    #[arg(long)]
     max_instances: Option<usize>,
 
     /// Maximum number of WebAssembly tables allowed to be created.
-    #[clap(long)]
+    #[arg(long)]
     max_tables: Option<usize>,
 
     /// Maximum number of WebAssembly linear memories allowed to be created.
-    #[clap(long)]
+    #[arg(long)]
     max_memories: Option<usize>,
 
     /// Force a trap to be raised on `memory.grow` and `table.grow` failure
@@ -209,7 +209,7 @@ pub struct RunCommand {
     /// This is not necessarily a spec-compliant option to enable but can be
     /// useful for tracking down a backtrace of what is requesting so much
     /// memory, for example.
-    #[clap(long)]
+    #[arg(long)]
     trap_on_grow_failure: bool,
 
     /// Indicates that the implementation of WASI preview1 should be backed by
@@ -217,18 +217,18 @@ pub struct RunCommand {
     ///
     /// This will become the default in the future and this option will be
     /// removed. For now this is primarily here for testing.
-    #[clap(long)]
+    #[arg(long)]
     preview2: bool,
 
     /// Enables memory error checking.
     ///
     /// See wmemcheck.md for documentation on how to use.
-    #[clap(long)]
+    #[arg(long)]
     wmemcheck: bool,
 
     /// Flag for WASI preview2 to inherit the host's network within the guest so
     /// it has full access to all addresses/ports/etc.
-    #[clap(long)]
+    #[arg(long)]
     inherit_network: bool,
 }
 fn parse_module(s: OsString) -> anyhow::Result<PathBuf> {
@@ -315,23 +315,23 @@ fn parse_profile(s: &str) -> Result<Profile> {
 /// Compiles a WebAssembly module.
 #[derive(Parser)]
 pub struct CompileCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     pub common: CommonOptions,
 
     /// The target triple; default is the host triple
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     pub target: Option<String>,
 
     /// The path of the output compiled module; defaults to <MODULE>.cwasm
-    #[clap(short = 'o', long, value_name = "OUTPUT")]
+    #[arg(short = 'o', long, value_name = "OUTPUT")]
     pub output: Option<PathBuf>,
 
     /// The directory path to write clif files into, one clif file per wasm function.
-    #[clap(long = "emit-clif", value_name = "PATH")]
+    #[arg(long = "emit-clif", value_name = "PATH")]
     pub emit_clif: Option<PathBuf>,
 
     /// The path of the WebAssembly to compile
-    #[clap(index = 1, value_name = "MODULE")]
+    #[arg(index = 1, value_name = "MODULE")]
     pub module: PathBuf,
 }
 
@@ -340,45 +340,45 @@ pub struct CompileCommand {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 pub struct CommonOptions {
     /// Use specified configuration file
-    #[clap(long, value_name = "CONFIG_PATH")]
+    #[arg(long, value_name = "CONFIG_PATH")]
     pub config: Option<PathBuf>,
 
     /// Disable logging
-    #[clap(long, conflicts_with = "log_to_files")]
+    #[arg(long, conflicts_with = "log_to_files")]
     pub disable_logging: bool,
 
     /// Log to per-thread log files instead of stderr
-    #[clap(long)]
+    #[arg(long)]
     pub log_to_files: bool,
 
     /// Generate debug information
-    #[clap(short = 'g')]
+    #[arg(short = 'g')]
     pub debug_info: bool,
 
     /// Disable cache system
-    #[clap(long)]
+    #[arg(long)]
     pub disable_cache: bool,
 
     /// Disable parallel compilation
-    #[clap(long)]
+    #[arg(long)]
     pub disable_parallel_compilation: bool,
 
     /// Enable or disable WebAssembly features
-    #[clap(long, value_name = "FEATURE,FEATURE,...", value_parser = parse_wasm_features)]
+    #[arg(long, value_name = "FEATURE,FEATURE,...", value_parser = parse_wasm_features)]
     pub wasm_features: Option<WasmFeatures>,
 
     /// Enable or disable WASI modules
-    #[clap(long, value_name = "MODULE,MODULE,...", value_parser = parse_wasi_modules)]
+    #[arg(long, value_name = "MODULE,MODULE,...", value_parser = parse_wasi_modules)]
     pub wasi_modules: Option<WasiModules>,
 
     /// Generate jitdump file (supported on --features=profiling build)
     /// Run optimization passes on translated functions, on by default
-    #[clap(short = 'O', long)]
+    #[arg(short = 'O', long)]
     pub optimize: bool,
 
     /// Optimization level for generated functions
     /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
-    #[clap(
+    #[arg(
         long,
         value_name = "LEVEL",
         value_parser = parse_opt_level,
@@ -388,7 +388,7 @@ pub struct CommonOptions {
 
     /// Set a Cranelift setting to a given value.
     /// Use `wasmtime settings` to list Cranelift settings for a target.
-    #[clap(
+    #[arg(
         long = "cranelift-set",
         value_name = "NAME=VALUE",
         number_of_values = 1,
@@ -399,7 +399,7 @@ pub struct CommonOptions {
 
     /// Enable a Cranelift boolean setting or preset.
     /// Use `wasmtime settings` to list Cranelift settings for a target.
-    #[clap(
+    #[arg(
         long,
         value_name = "SETTING",
         number_of_values = 1,
@@ -409,32 +409,32 @@ pub struct CommonOptions {
 
     /// Maximum size in bytes of wasm memory before it becomes dynamically
     /// relocatable instead of up-front-reserved.
-    #[clap(long, value_name = "MAXIMUM")]
+    #[arg(long, value_name = "MAXIMUM")]
     pub static_memory_maximum_size: Option<u64>,
 
     /// Force using a "static" style for all wasm memories
-    #[clap(long)]
+    #[arg(long)]
     pub static_memory_forced: bool,
 
     /// Byte size of the guard region after static memories are allocated
-    #[clap(long, value_name = "SIZE")]
+    #[arg(long, value_name = "SIZE")]
     pub static_memory_guard_size: Option<u64>,
 
     /// Byte size of the guard region after dynamic memories are allocated
-    #[clap(long, value_name = "SIZE")]
+    #[arg(long, value_name = "SIZE")]
     pub dynamic_memory_guard_size: Option<u64>,
 
     /// Bytes to reserve at the end of linear memory for growth for dynamic
     /// memories.
-    #[clap(long, value_name = "SIZE")]
+    #[arg(long, value_name = "SIZE")]
     pub dynamic_memory_reserved_for_growth: Option<u64>,
 
     /// Enable Cranelift's internal debug verifier (expensive)
-    #[clap(long)]
+    #[arg(long)]
     pub enable_cranelift_debug_verifier: bool,
 
     /// Enable Cranelift's internal NaN canonicalization
-    #[clap(long)]
+    #[arg(long)]
     pub enable_cranelift_nan_canonicalization: bool,
 
     /// Enable execution fuel with N units fuel, where execution will trap after
@@ -444,33 +444,33 @@ pub struct CommonOptions {
     /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any
     /// execution cost associated with them involves other instructions which do
     /// consume fuel.
-    #[clap(long, value_name = "N")]
+    #[arg(long, value_name = "N")]
     pub fuel: Option<u64>,
 
     /// Executing wasm code will yield when a global epoch counter
     /// changes, allowing for async operation without blocking the
     /// executor.
-    #[clap(long)]
+    #[arg(long)]
     pub epoch_interruption: bool,
 
     /// Disable the on-by-default address map from native code to wasm code
-    #[clap(long)]
+    #[arg(long)]
     pub disable_address_map: bool,
 
     /// Disable the default of attempting to initialize linear memory via a
     /// copy-on-write mapping
-    #[clap(long)]
+    #[arg(long)]
     pub disable_memory_init_cow: bool,
 
     /// Enable the pooling allocator, in place of the on-demand
     /// allocator.
     #[cfg(feature = "pooling-allocator")]
-    #[clap(long)]
+    #[arg(long)]
     pub pooling_allocator: bool,
 
     /// Maximum stack size, in bytes, that wasm is allowed to consume before a
     /// stack overflow is reported.
-    #[clap(long)]
+    #[arg(long)]
     pub max_wasm_stack: Option<usize>,
 
     /// Whether or not to force deterministic and host-independent behavior of
@@ -481,14 +481,14 @@ pub struct CommonOptions {
     /// of these instructions to match the deterministic behavior classified in
     /// the specification. Note that enabling this option may come at a
     /// performance cost.
-    #[clap(long)]
+    #[arg(long)]
     pub relaxed_simd_deterministic: bool,
 
     /// Explicitly specify the name of the compiler to use for WebAssembly.
     ///
     /// Currently only `cranelift` and `winch` are supported, but not all builds
     /// of Wasmtime have both built in.
-    #[clap(long)]
+    #[arg(long)]
     pub compiler: Option<String>,
 }
 
diff --git a/winch/src/compile.rs b/winch/src/compile.rs
index e6c3040e3..e6d8d7173 100644
--- a/winch/src/compile.rs
+++ b/winch/src/compile.rs
@@ -17,7 +17,7 @@ pub struct Options {
     input: PathBuf,
 
     /// The target architecture.
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 }
 


Test patch:
diff --git a/crates/test-programs/src/bin/cli_no_udp.rs b/crates/test-programs/src/bin/cli_no_udp.rs
new file mode 100644
index 000000000..1dd024061
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_no_udp.rs
@@ -0,0 +1,16 @@
+//! This test assumes that it will be run without udp support enabled
+use test_programs::wasi::sockets::{
+    network::IpAddress,
+    udp::{ErrorCode, IpAddressFamily, IpSocketAddress, Network, UdpSocket},
+};
+
+fn main() {
+    let net = Network::default();
+    let family = IpAddressFamily::Ipv4;
+    let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321);
+    let sock = UdpSocket::new(family).unwrap();
+
+    let bind = sock.blocking_bind(&net, remote1);
+    eprintln!("Result of binding: {bind:?}");
+    assert!(matches!(bind, Err(ErrorCode::AccessDenied)));
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 7fb807eb1..527572cba 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1514,4 +1514,20 @@ mod test_programs {
         assert!(output.status.success());
         Ok(())
     }
+
+    #[test]
+    fn cli_no_udp() -> Result<()> {
+        let output = super::run_wasmtime_for_output(
+            &[
+                "-Wcomponent-model",
+                // Turn on network but turn off UDP
+                "-Sinherit-network,udp=no",
+                CLI_NO_UDP_COMPONENT,
+            ],
+            None,
+        )?;
+        println!("{}", String::from_utf8_lossy(&output.stderr));
+        assert!(output.status.success());
+        Ok(())
+    }
 }
diff --git a/winch/src/filetests.rs b/winch/src/filetests.rs
index 6917736c9..423f8d836 100644
--- a/winch/src/filetests.rs
+++ b/winch/src/filetests.rs
@@ -7,7 +7,7 @@ use clap::Parser;
 pub struct Options {
     /// Passes extra arguments to `cargo test --package winch-filetests`. For example, to run a single
     /// test, use `-- --test-threads 1 --test single_test_name`.
-    #[clap(last = true, value_parser)]
+    #[arg(last = true, value_parser)]
     cargo_test_args: Vec<String>,
 }