diff --git a/Cargo.lock b/Cargo.lock index c0f34b73..976a29cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,7 +132,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -149,7 +149,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -188,26 +188,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "bincode_derive", - "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" -dependencies = [ - "virtue", -] - [[package]] name = "bindgen" version = "0.72.1" @@ -225,7 +205,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -298,7 +278,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -351,7 +331,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -384,7 +364,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -481,7 +461,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -763,6 +743,16 @@ dependencies = [ "darling_macro 0.20.11", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + [[package]] name = "darling" version = "0.23.0" @@ -784,7 +774,21 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", ] [[package]] @@ -797,7 +801,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -808,7 +812,18 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.114", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.117", ] [[package]] @@ -819,7 +834,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -856,7 +871,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.114", + "syn 2.0.117", "unicode-xid", ] @@ -1158,7 +1173,6 @@ version = "0.1.0" dependencies = [ "allocator-api2", "anyhow", - "bincode", "bstr", "bumpalo", "const_format", @@ -1188,6 +1202,7 @@ dependencies = [ "tokio-util", "which", "winapi", + "wincode", "winsafe 0.0.24", "xxhash-rust", ] @@ -1219,20 +1234,19 @@ name = "fspy_preload_unix" version = "0.0.0" dependencies = [ "anyhow", - "bincode", "bstr", "ctor", "fspy_shared", "fspy_shared_unix", "libc", "nix 0.30.1", + "wincode", ] [[package]] name = "fspy_preload_windows" version = "0.1.0" dependencies = [ - "bincode", "constcat", "fspy_detours_sys", "fspy_shared", @@ -1241,6 +1255,7 @@ dependencies = [ "tempfile", "widestring", "winapi", + "wincode", "winsafe 0.0.24", ] @@ -1249,7 +1264,6 @@ name = "fspy_seccomp_unotify" version = "0.1.0" dependencies = [ "assertables", - "bincode", "futures-util", "libc", "nix 0.30.1", @@ -1260,6 +1274,7 @@ dependencies = [ "test-log", "tokio", "tracing", + "wincode", ] [[package]] @@ -1268,7 +1283,6 @@ version = "0.0.0" dependencies = [ "allocator-api2", "assert2", - "bincode", "bitflags 2.10.0", "bstr", "bytemuck", @@ -1281,6 +1295,7 @@ dependencies = [ "tracing", "uuid", "winapi", + "wincode", ] [[package]] @@ -1289,7 +1304,6 @@ version = "0.0.0" dependencies = [ "anyhow", "base64", - "bincode", "bstr", "elf", "fspy_seccomp_unotify", @@ -1298,6 +1312,7 @@ dependencies = [ "nix 0.30.1", "phf", "stackalloc", + "wincode", ] [[package]] @@ -1363,7 +1378,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1610,7 +1625,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1941,7 +1956,7 @@ checksum = "23f5b99488110875b5904839d396c2cdfaf241ff6622638acb879cc7effad5de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2152,7 +2167,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2285,7 +2300,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2331,6 +2346,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "path-clean" version = "1.0.1" @@ -2394,7 +2415,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2460,7 +2481,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2558,7 +2579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2587,7 +2608,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "version_check", "yansi", ] @@ -2628,9 +2649,9 @@ version = "0.0.0" [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2851,7 +2872,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3037,7 +3058,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3268,7 +3289,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3276,11 +3297,11 @@ name = "subprocess_test" version = "0.0.0" dependencies = [ "base64", - "bincode", "ctor", "fspy", "portable-pty", "rustc-hash", + "wincode", ] [[package]] @@ -3315,9 +3336,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3455,7 +3476,7 @@ checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3484,7 +3505,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3495,7 +3516,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3553,7 +3574,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3650,7 +3671,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3721,7 +3742,7 @@ checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "termcolor", ] @@ -3798,12 +3819,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "utf8-chars" version = "3.0.6" @@ -3859,12 +3874,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" - [[package]] name = "vite_glob" version = "0.0.0" @@ -3889,7 +3898,6 @@ name = "vite_path" version = "0.1.0" dependencies = [ "assert2", - "bincode", "diff-struct", "path-clean", "ref-cast", @@ -3897,6 +3905,7 @@ dependencies = [ "thiserror 2.0.18", "ts-rs", "vite_str", + "wincode", ] [[package]] @@ -3914,23 +3923,23 @@ dependencies = [ name = "vite_shell" version = "0.0.0" dependencies = [ - "bincode", "brush-parser", "diff-struct", "serde", "shell-escape", "vite_str", + "wincode", ] [[package]] name = "vite_str" version = "0.1.0" dependencies = [ - "bincode", "compact_str", "diff-struct", "serde", "ts-rs", + "wincode", ] [[package]] @@ -3939,7 +3948,6 @@ version = "0.0.0" dependencies = [ "anyhow", "async-trait", - "bincode", "bstr", "clap", "ctrlc", @@ -3970,6 +3978,7 @@ dependencies = [ "vite_workspace", "wax", "winapi", + "wincode", ] [[package]] @@ -4010,7 +4019,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "bincode", "monostate", "petgraph", "pretty_assertions", @@ -4025,6 +4033,7 @@ dependencies = [ "vite_str", "vite_workspace", "wax", + "wincode", ] [[package]] @@ -4033,7 +4042,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "bincode", "clap", "copy_dir", "cow-utils", @@ -4061,6 +4069,7 @@ dependencies = [ "vite_task_graph", "vite_workspace", "which", + "wincode", ] [[package]] @@ -4198,7 +4207,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -4400,6 +4409,31 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wincode" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f42dd20febad683d07044c5f543e57f822512ebebaf2c827705c99a0ad4575" +dependencies = [ + "pastey", + "proc-macro2", + "quote", + "thiserror 2.0.18", + "wincode-derive", +] + +[[package]] +name = "wincode-derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca057fc9a13dd19cdb64ef558635d43c42667c0afa1ae7915ea1fa66993fd1a" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows" version = "0.34.0" @@ -4665,7 +4699,7 @@ dependencies = [ "heck 0.5.0", "indexmap", "prettyplease", - "syn 2.0.114", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4681,7 +4715,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4762,7 +4796,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 411e5cfc..ea50514b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = ["crates/*"] authors = ["Vite+ Authors"] edition = "2024" license = "MIT" -rust-version = "1.88.0" +rust-version = "1.89.0" [workspace.lints.rust] absolute_paths_not_starting_with_crate = "warn" @@ -44,7 +44,7 @@ assert2 = "0.3.16" assertables = "9.8.1" async-trait = "0.1.89" base64 = "0.22.1" -bincode = "2.0.1" +wincode = "0.5.2" bindgen = "0.72.1" bitflags = "2.10.0" # The newest released version (0.3.0) of brush-parser has a bug that reports incorrect locations for some ast nodes. diff --git a/crates/fspy/Cargo.toml b/crates/fspy/Cargo.toml index 7269f0f9..a9a1ffff 100644 --- a/crates/fspy/Cargo.toml +++ b/crates/fspy/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] allocator-api2 = { workspace = true, features = ["alloc"] } -bincode = { workspace = true } +wincode = { workspace = true } bstr = { workspace = true, default-features = false } bumpalo = { workspace = true } const_format = { workspace = true, features = ["fmt"] } diff --git a/crates/fspy/src/ipc.rs b/crates/fspy/src/ipc.rs index f5e7e7dd..51d49860 100644 --- a/crates/fspy/src/ipc.rs +++ b/crates/fspy/src/ipc.rs @@ -1,8 +1,7 @@ use std::io; -use bincode::borrow_decode_from_slice; use fspy_shared::ipc::{ - BINCODE_CONFIG, PathAccess, + PathAccess, channel::{Receiver, ReceiverLockGuard}, }; use tokio::task::spawn_blocking; @@ -32,11 +31,8 @@ impl OwnedReceiverLockGuard { } pub fn iter_path_accesses(&self) -> impl Iterator> { - self.borrow_lock_guard().iter_frames().map(|frame| { - let (path_access, decoded_size) = - borrow_decode_from_slice::, _>(frame, BINCODE_CONFIG).unwrap(); - assert_eq!(decoded_size, frame.len()); - path_access - }) + self.borrow_lock_guard() + .iter_frames() + .map(|frame| wincode::deserialize_exact(frame).unwrap()) } } diff --git a/crates/fspy/src/windows/mod.rs b/crates/fspy/src/windows/mod.rs index 22560f40..93bef864 100644 --- a/crates/fspy/src/windows/mod.rs +++ b/crates/fspy/src/windows/mod.rs @@ -9,7 +9,7 @@ use std::{ use const_format::formatcp; use fspy_detours_sys::{DetourCopyPayloadToProcess, DetourUpdateProcessWithDll}; use fspy_shared::{ - ipc::{BINCODE_CONFIG, PathAccess, channel::channel}, + ipc::{PathAccess, channel::channel}, windows::{PAYLOAD_ID, Payload}, }; use futures_util::FutureExt; @@ -109,7 +109,7 @@ impl SpyImpl { channel_conf: channel_conf.clone(), ansi_dll_path_with_nul: ansi_dll_path_with_nul.to_bytes(), }; - let payload_bytes = bincode::encode_to_vec(payload, BINCODE_CONFIG).unwrap(); + let payload_bytes = wincode::serialize(&payload).unwrap(); // SAFETY: process_handle is valid, PAYLOAD_ID is a static GUID, // payload_bytes is a valid buffer with correct length let success = unsafe { diff --git a/crates/fspy_preload_unix/Cargo.toml b/crates/fspy_preload_unix/Cargo.toml index 49f934d7..d76eb53d 100644 --- a/crates/fspy_preload_unix/Cargo.toml +++ b/crates/fspy_preload_unix/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib"] [target.'cfg(unix)'.dependencies] anyhow = { workspace = true } -bincode = { workspace = true } +wincode = { workspace = true } bstr = { workspace = true, default-features = false } ctor = { workspace = true } fspy_shared = { workspace = true } diff --git a/crates/fspy_preload_unix/src/client/mod.rs b/crates/fspy_preload_unix/src/client/mod.rs index 9e12a435..b9e3eeb1 100644 --- a/crates/fspy_preload_unix/src/client/mod.rs +++ b/crates/fspy_preload_unix/src/client/mod.rs @@ -6,15 +6,15 @@ use std::{ sync::OnceLock, }; -use bincode::{enc::write::SizeWriter, encode_into_slice, encode_into_writer}; use convert::{ToAbsolutePath, ToAccessMode}; -use fspy_shared::ipc::{BINCODE_CONFIG, PathAccess, channel::Sender}; +use fspy_shared::ipc::{PathAccess, channel::Sender}; use fspy_shared_unix::{ exec::ExecResolveConfig, payload::EncodedPayload, spawn::{PreExec, handle_exec}, }; use raw_exec::RawExec; +use wincode::Serialize as _; pub struct Client { encoded_payload: EncodedPayload, @@ -72,17 +72,18 @@ impl Client { return Ok(()); } let path_access = PathAccess { mode, path: path.into() }; - let mut size_writer = SizeWriter::default(); - encode_into_writer(path_access, &mut size_writer, BINCODE_CONFIG)?; + let serialized_size = usize::try_from(PathAccess::serialized_size(&path_access)?) + .expect("serialized size exceeds usize"); - let frame_size = NonZeroUsize::new(size_writer.bytes_written) + let frame_size = NonZeroUsize::new(serialized_size) .expect("fspy: encoded PathAccess should never be empty"); let mut frame = ipc_sender .claim_frame(frame_size) .expect("fspy: failed to claim frame in shared memory"); - let written_size = encode_into_slice(path_access, &mut frame, BINCODE_CONFIG)?; - assert_eq!(written_size, size_writer.bytes_written); + let mut writer: &mut [u8] = &mut frame; + PathAccess::serialize_into(&mut writer, &path_access)?; + assert_eq!(writer.len(), 0); Ok(()) } diff --git a/crates/fspy_preload_windows/Cargo.toml b/crates/fspy_preload_windows/Cargo.toml index 315248e1..d21da9c7 100644 --- a/crates/fspy_preload_windows/Cargo.toml +++ b/crates/fspy_preload_windows/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true crate-type = ["cdylib"] [target.'cfg(target_os = "windows")'.dependencies] -bincode = { workspace = true } +wincode = { workspace = true } constcat = { workspace = true } fspy_detours_sys = { workspace = true } fspy_shared = { workspace = true } diff --git a/crates/fspy_preload_windows/src/windows/client.rs b/crates/fspy_preload_windows/src/windows/client.rs index 1c0cb174..48933414 100644 --- a/crates/fspy_preload_windows/src/windows/client.rs +++ b/crates/fspy_preload_windows/src/windows/client.rs @@ -1,9 +1,8 @@ use std::{cell::SyncUnsafeCell, ffi::CStr, mem::MaybeUninit}; -use bincode::{borrow_decode_from_slice, encode_to_vec}; use fspy_detours_sys::DetourCopyPayloadToProcess; use fspy_shared::{ - ipc::{BINCODE_CONFIG, PathAccess, channel::Sender}, + ipc::{PathAccess, channel::Sender}, windows::{PAYLOAD_ID, Payload}, }; use winapi::{shared::minwindef::BOOL, um::winnt::HANDLE}; @@ -15,9 +14,7 @@ pub struct Client<'a> { impl<'a> Client<'a> { pub fn from_payload_bytes(payload_bytes: &'a [u8]) -> Self { - let (payload, decoded_len) = - borrow_decode_from_slice::<'a, Payload, _>(payload_bytes, BINCODE_CONFIG).unwrap(); - assert_eq!(decoded_len, payload_bytes.len()); + let payload: Payload<'a> = wincode::deserialize_exact(payload_bytes).unwrap(); let ipc_sender = match payload.channel_conf.sender() { Ok(sender) => Some(sender), @@ -43,11 +40,11 @@ impl<'a> Client<'a> { let Some(sender) = &self.ipc_sender else { return; }; - sender.write_encoded(&access, BINCODE_CONFIG).expect("failed to send path access"); + sender.write_encoded(&access).expect("failed to send path access"); } pub unsafe fn prepare_child_process(&self, child_handle: HANDLE) -> BOOL { - let payload_bytes = encode_to_vec(&self.payload, BINCODE_CONFIG).unwrap(); + let payload_bytes = wincode::serialize(&self.payload).unwrap(); // SAFETY: FFI call to DetourCopyPayloadToProcess with valid handle and payload buffer unsafe { DetourCopyPayloadToProcess( diff --git a/crates/fspy_seccomp_unotify/Cargo.toml b/crates/fspy_seccomp_unotify/Cargo.toml index 9a4a2a08..0870032b 100644 --- a/crates/fspy_seccomp_unotify/Cargo.toml +++ b/crates/fspy_seccomp_unotify/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" publish = false [target.'cfg(target_os = "linux")'.dependencies] -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } libc = { workspace = true } nix = { workspace = true, features = ["process", "fs", "poll", "socket", "uio"] } passfd = { workspace = true, default-features = false, optional = true } diff --git a/crates/fspy_seccomp_unotify/src/payload/filter.rs b/crates/fspy_seccomp_unotify/src/payload/filter.rs index 09831edd..5852aa7d 100644 --- a/crates/fspy_seccomp_unotify/src/payload/filter.rs +++ b/crates/fspy_seccomp_unotify/src/payload/filter.rs @@ -1,6 +1,6 @@ -use bincode::{Decode, Encode}; +use wincode::{SchemaRead, SchemaWrite}; -#[derive(Debug, Encode, Decode, Clone, Copy)] +#[derive(Debug, SchemaWrite, SchemaRead, Clone, Copy)] pub struct CodableSockFilter { code: u16, jt: u8, @@ -24,5 +24,5 @@ impl From for libc::sock_filter { } } -#[derive(Encode, Decode, Debug, Clone)] +#[derive(SchemaWrite, SchemaRead, Debug, Clone)] pub struct Filter(pub(crate) Vec); diff --git a/crates/fspy_seccomp_unotify/src/payload/mod.rs b/crates/fspy_seccomp_unotify/src/payload/mod.rs index 57bda12c..6895bc55 100644 --- a/crates/fspy_seccomp_unotify/src/payload/mod.rs +++ b/crates/fspy_seccomp_unotify/src/payload/mod.rs @@ -1,8 +1,8 @@ mod filter; -use bincode::{Decode, Encode}; pub use filter::Filter; +use wincode::{SchemaRead, SchemaWrite}; -#[derive(Debug, Encode, Decode, Clone)] +#[derive(Debug, SchemaWrite, SchemaRead, Clone)] pub struct SeccompPayload { pub(crate) ipc_path: Vec, pub(crate) filter: Filter, diff --git a/crates/fspy_shared/Cargo.toml b/crates/fspy_shared/Cargo.toml index 756e4512..8e074fc4 100644 --- a/crates/fspy_shared/Cargo.toml +++ b/crates/fspy_shared/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] allocator-api2 = { workspace = true } -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } bitflags = { workspace = true } bstr = { workspace = true } bytemuck = { workspace = true, features = ["must_cast", "derive"] } diff --git a/crates/fspy_shared/src/ipc/channel/mod.rs b/crates/fspy_shared/src/ipc/channel/mod.rs index 3e67cea8..eb073812 100644 --- a/crates/fspy_shared/src/ipc/channel/mod.rs +++ b/crates/fspy_shared/src/ipc/channel/mod.rs @@ -2,21 +2,54 @@ mod shm_io; -use std::{env::temp_dir, fs::File, io, ops::Deref, path::PathBuf, sync::Arc}; +use std::{env::temp_dir, fs::File, io, mem::MaybeUninit, ops::Deref, path::PathBuf, sync::Arc}; -use bincode::{Decode, Encode}; use shared_memory::{Shmem, ShmemConf}; pub use shm_io::FrameMut; use shm_io::{ShmReader, ShmWriter}; use tracing::debug; use uuid::Uuid; +use wincode::{ + SchemaRead, SchemaWrite, + config::Config, + error::{ReadResult, WriteResult}, + io::{Reader, Writer}, +}; use super::NativeStr; +/// wincode schema adapter for `Arc`, which is a foreign type with unsized inner. +pub(crate) struct ArcStrSchema; + +// SAFETY: Delegates to `str`'s SchemaWrite impl, preserving its size/write invariants. +unsafe impl SchemaWrite for ArcStrSchema { + type Src = Arc; + + fn size_of(src: &Self::Src) -> WriteResult { + >::size_of(src) + } + + fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + >::write(writer, src) + } +} + +// SAFETY: Delegates to `&str`'s SchemaRead impl; dst is initialized on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for ArcStrSchema { + type Dst = Arc; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let s: &str = <&str as SchemaRead<'de, C>>::get(&mut reader)?; + dst.write(Arc::from(s)); + Ok(()) + } +} + /// Serializable configuration to create channel senders. -#[derive(Encode, Decode, Clone, Debug)] +#[derive(SchemaWrite, SchemaRead, Clone, Debug)] pub struct ChannelConf { lock_file_path: Box, + #[wincode(with = "ArcStrSchema")] shm_id: Arc, shm_size: usize, } diff --git a/crates/fspy_shared/src/ipc/channel/shm_io.rs b/crates/fspy_shared/src/ipc/channel/shm_io.rs index 39a490d1..7890043d 100644 --- a/crates/fspy_shared/src/ipc/channel/shm_io.rs +++ b/crates/fspy_shared/src/ipc/channel/shm_io.rs @@ -8,11 +8,9 @@ use std::{ sync::atomic::{AtomicI32, AtomicUsize, Ordering, fence}, }; -use bincode::{ - Encode, config::Config, enc::write::SizeWriter, encode_into_slice, encode_into_writer, -}; use bytemuck::must_cast; use shared_memory::Shmem; +use wincode::{SchemaWrite, Serialize as _, config::DefaultConfig}; // `ShmWriter` writes headers using atomic operations to prevent partial writes due to crashes, // while `ShmReader` reads headers by simple pointer dereferences. @@ -109,7 +107,7 @@ impl Drop for FrameMut<'_> { #[derive(thiserror::Error, Debug)] pub enum WriteEncodedError { #[error("Failed to encode value into shared memory")] - EncodeError(#[from] bincode::error::EncodeError), + EncodeError(#[from] wincode::error::WriteError), #[error("Tried to write a frame of zero size into shared memory")] ZeroSizedFrame, #[error("Not enough space in shared memory to write the encoded frame")] @@ -226,23 +224,23 @@ impl ShmWriter { } /// Append an encoded value into the shared memory. - pub fn write_encoded( + pub fn write_encoded>( &self, value: &T, - config: C, ) -> Result<(), WriteEncodedError> { - let mut size_writer = SizeWriter::default(); - encode_into_writer(value, &mut size_writer, config)?; + let serialized_size = + usize::try_from(T::serialized_size(value)?).expect("serialized size exceeds usize"); - let Some(frame_size) = NonZeroUsize::new(size_writer.bytes_written) else { + let Some(frame_size) = NonZeroUsize::new(serialized_size) else { return Err(WriteEncodedError::ZeroSizedFrame); }; let Some(mut frame) = self.claim_frame(frame_size) else { return Err(WriteEncodedError::InsufficientSpace); }; - let written_size = encode_into_slice(value, &mut frame, config)?; - assert_eq!(written_size, size_writer.bytes_written); + let mut writer: &mut [u8] = &mut frame; + T::serialize_into(&mut writer, value)?; + assert_eq!(writer.len(), 0); Ok(()) } diff --git a/crates/fspy_shared/src/ipc/mod.rs b/crates/fspy_shared/src/ipc/mod.rs index 17df1c2d..ef63793a 100644 --- a/crates/fspy_shared/src/ipc/mod.rs +++ b/crates/fspy_shared/src/ipc/mod.rs @@ -5,14 +5,12 @@ pub(crate) mod native_str; use std::fmt::Debug; -use bincode::{BorrowDecode, Encode, config::Configuration}; use bitflags::bitflags; pub use native_path::NativePath; pub use native_str::NativeStr; +use wincode::{SchemaRead, SchemaWrite}; -pub const BINCODE_CONFIG: Configuration = bincode::config::standard(); - -#[derive(Encode, BorrowDecode, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub struct AccessMode(u8); bitflags! { @@ -35,7 +33,7 @@ impl Debug for AccessMode { } } -#[derive(Encode, BorrowDecode, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(SchemaWrite, SchemaRead, Debug, Clone, Copy, PartialEq, Eq)] pub struct PathAccess<'a> { pub mode: AccessMode, pub path: &'a NativePath, diff --git a/crates/fspy_shared/src/ipc/native_path.rs b/crates/fspy_shared/src/ipc/native_path.rs index 072ed473..6e431c28 100644 --- a/crates/fspy_shared/src/ipc/native_path.rs +++ b/crates/fspy_shared/src/ipc/native_path.rs @@ -3,12 +3,18 @@ use std::os::unix::ffi::OsStrExt as _; use std::{ ffi::OsStr, fmt::Debug, + mem::MaybeUninit, path::{Path, StripPrefixError}, }; use allocator_api2::alloc::Allocator; -use bincode::{BorrowDecode, Encode, de::BorrowDecoder, error::DecodeError}; use bytemuck::TransparentWrapper; +use wincode::{ + SchemaRead, SchemaWrite, + config::Config, + error::{ReadResult, WriteResult}, + io::{Reader, Writer}, +}; use super::native_str::NativeStr; @@ -18,12 +24,37 @@ use super::native_str::NativeStr; /// whose raw data is not meaningful for direct consumption. The only way /// to use the path is through [`strip_path_prefix`](NativePath::strip_path_prefix), /// which normalizes platform differences and extracts a workspace-relative path. -#[derive(TransparentWrapper, Encode, PartialEq, Eq)] +#[derive(TransparentWrapper, PartialEq, Eq)] #[repr(transparent)] pub struct NativePath { inner: NativeStr, } +// Manual impl: wincode derive requires Sized, but NativePath wraps unsized NativeStr. +// SAFETY: Delegates to `NativeStr`'s SchemaWrite impl, preserving its invariants. +unsafe impl SchemaWrite for NativePath { + type Src = Self; + + fn size_of(src: &Self::Src) -> WriteResult { + >::size_of(&src.inner) + } + + fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + >::write(writer, &src.inner) + } +} + +// SAFETY: Delegates to `&NativeStr`'s SchemaRead impl; dst is initialized on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for &'de NativePath { + type Dst = &'de NativePath; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let inner: &'de NativeStr = <&NativeStr as SchemaRead<'de, C>>::get(&mut reader)?; + dst.write(NativePath::wrap_ref(inner)); + Ok(()) + } +} + impl NativePath { #[cfg(windows)] #[must_use] @@ -87,15 +118,6 @@ impl Debug for NativePath { } } -impl<'a, C> BorrowDecode<'a, C> for &'a NativePath { - fn borrow_decode>( - decoder: &mut D, - ) -> Result { - let inner: &'a NativeStr = BorrowDecode::borrow_decode(decoder)?; - Ok(NativePath::wrap_ref(inner)) - } -} - #[cfg(unix)] impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a NativePath { fn from(value: &'a S) -> Self { diff --git a/crates/fspy_shared/src/ipc/native_str.rs b/crates/fspy_shared/src/ipc/native_str.rs index d2d14e3a..c4800912 100644 --- a/crates/fspy_shared/src/ipc/native_str.rs +++ b/crates/fspy_shared/src/ipc/native_str.rs @@ -6,29 +6,29 @@ use std::os::unix::ffi::OsStrExt as _; use std::os::windows::ffi::OsStrExt as _; #[cfg(windows)] use std::os::windows::ffi::OsStringExt as _; -use std::{borrow::Cow, ffi::OsStr, fmt::Debug}; +use std::{borrow::Cow, ffi::OsStr, fmt::Debug, mem::MaybeUninit}; use allocator_api2::alloc::Allocator; -use bincode::{ - BorrowDecode, Decode, Encode, - de::{BorrowDecoder, Decoder}, - error::DecodeError, - impl_borrow_decode, -}; #[cfg(windows)] use bytemuck::must_cast_slice; use bytemuck::{TransparentWrapper, TransparentWrapperAlloc}; +use wincode::{ + SchemaRead, SchemaWrite, + config::Config, + error::{ReadResult, WriteResult}, + io::{Reader, Writer}, +}; /// Similar to `OsStr`, but -/// - Can be infallibly and losslessly encoded/decoded using bincode. -/// (`Encode`/`Decoded` implementations for `OsStr` requires it to be valid UTF-8. This does not.) +/// - Can be infallibly and losslessly encoded/decoded using wincode. +/// (`SchemaWrite`/`SchemaRead` implementations for `OsStr` requires it to be valid UTF-8. This does not.) /// - Can be constructed from wide characters on Windows with zero copy. -/// - Supports zero-copy `BorrowDecode`. -#[derive(TransparentWrapper, Encode, PartialEq, Eq)] +/// - Supports zero-copy `SchemaRead`. +#[derive(TransparentWrapper, PartialEq, Eq)] #[repr(transparent)] pub struct NativeStr { // On unix, this is the raw bytes of the OsStr. - // On windows, this is safely transmuted from `&[u16]` in `NativeStr::from_wide`. We don't declare it as `&[u16]` to allow `BorrowDecode`. + // On windows, this is safely transmuted from `&[u16]` in `NativeStr::from_wide`. We don't declare it as `&[u16]` to allow zero-copy read. // Transmuting back to `&[u16]` would be unsafe because of different alignments between `u8` and `u16` (See `to_os_string`). data: [u8], } @@ -75,18 +75,60 @@ impl NativeStr { } } +// Manual impl: wincode derive requires Sized, but NativeStr wraps unsized [u8]. +// SAFETY: Delegates to `[u8]`'s SchemaWrite impl, preserving its size/write invariants. +unsafe impl SchemaWrite for NativeStr { + type Src = Self; + + fn size_of(src: &Self::Src) -> WriteResult { + <[u8] as SchemaWrite>::size_of(&src.data) + } + + fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + <[u8] as SchemaWrite>::write(writer, &src.data) + } +} + impl Debug for NativeStr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(self.to_cow_os_str().as_ref(), f) } } -impl<'a, C> BorrowDecode<'a, C> for &'a NativeStr { - fn borrow_decode>( - decoder: &mut D, - ) -> Result { - let data: &'a [u8] = BorrowDecode::borrow_decode(decoder)?; - Ok(NativeStr::wrap_ref(data)) +// SchemaRead for &NativeStr: zero-copy borrow from input bytes +// SAFETY: Delegates to `&[u8]`'s SchemaRead impl; dst is initialized on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for &'de NativeStr { + type Dst = &'de NativeStr; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let data: &'de [u8] = <&[u8] as SchemaRead<'de, C>>::get(&mut reader)?; + dst.write(NativeStr::wrap_ref(data)); + Ok(()) + } +} + +// SAFETY: Delegates to `NativeStr`'s SchemaWrite impl, preserving its invariants. +unsafe impl SchemaWrite for Box { + type Src = Self; + + fn size_of(src: &Self::Src) -> WriteResult { + >::size_of(src) + } + + fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + >::write(writer, src) + } +} + +// SchemaRead for Box: owned decode +// SAFETY: Delegates to `&[u8]`'s SchemaRead impl; dst is initialized on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for Box { + type Dst = Self; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let data: &[u8] = <&[u8] as SchemaRead<'de, C>>::get(&mut reader)?; + dst.write(NativeStr::wrap_box(data.into())); + Ok(()) } } @@ -97,14 +139,6 @@ impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a NativeStr { } } -impl Decode for Box { - fn decode>(decoder: &mut D) -> Result { - let data: Box<[u8]> = Decode::decode(decoder)?; - Ok(NativeStr::wrap_box(data)) - } -} -impl_borrow_decode!(Box); - impl Clone for Box { fn clone(&self) -> Self { NativeStr::wrap_box(self.data.into()) @@ -148,15 +182,12 @@ mod tests { fn test_from_wide() { use std::os::windows::ffi::OsStrExt; - use bincode::{borrow_decode_from_slice, config, encode_to_vec}; - let wide_str: &[u16] = &[528, 491]; let native_str = NativeStr::from_wide(wide_str); - let mut encoded = encode_to_vec(native_str, config::standard()).unwrap(); + let mut encoded = wincode::serialize(native_str).unwrap(); - let (decoded, _) = - borrow_decode_from_slice::<'_, &NativeStr, _>(&encoded, config::standard()).unwrap(); + let decoded: &NativeStr = wincode::deserialize(&encoded).unwrap(); let decoded_wide = decoded.to_os_string().encode_wide().collect::>(); assert_eq!(decoded_wide, wide_str); @@ -164,9 +195,7 @@ mod tests { encoded.push(0); encoded.copy_within(..encoded_len, 1); - let (decoded, _) = - borrow_decode_from_slice::<'_, &NativeStr, _>(&encoded[1..], config::standard()) - .unwrap(); + let decoded: &NativeStr = wincode::deserialize(&encoded[1..]).unwrap(); let decoded_wide = decoded.to_os_string().encode_wide().collect::>(); assert_eq!(decoded_wide, wide_str); } diff --git a/crates/fspy_shared/src/windows/mod.rs b/crates/fspy_shared/src/windows/mod.rs index b92bf697..cf7c536b 100644 --- a/crates/fspy_shared/src/windows/mod.rs +++ b/crates/fspy_shared/src/windows/mod.rs @@ -1,5 +1,5 @@ -use bincode::{BorrowDecode, Encode}; use winapi::DEFINE_GUID; +use wincode::{SchemaRead, SchemaWrite}; use crate::ipc::channel::ChannelConf; @@ -20,7 +20,7 @@ DEFINE_GUID!( 0x33 ); -#[derive(Encode, BorrowDecode, Debug, Clone)] +#[derive(SchemaWrite, SchemaRead, Debug, Clone)] pub struct Payload<'a> { pub channel_conf: ChannelConf, pub ansi_dll_path_with_nul: &'a [u8], diff --git a/crates/fspy_shared_unix/Cargo.toml b/crates/fspy_shared_unix/Cargo.toml index 4afedf81..fecabd52 100644 --- a/crates/fspy_shared_unix/Cargo.toml +++ b/crates/fspy_shared_unix/Cargo.toml @@ -7,7 +7,7 @@ publish = false [target.'cfg(unix)'.dependencies] anyhow = { workspace = true } base64 = { workspace = true } -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } bstr = { workspace = true } fspy_shared = { workspace = true } nix = { workspace = true, features = ["fs"] } diff --git a/crates/fspy_shared_unix/src/payload.rs b/crates/fspy_shared_unix/src/payload.rs index 11c3e1bb..5267bd42 100644 --- a/crates/fspy_shared_unix/src/payload.rs +++ b/crates/fspy_shared_unix/src/payload.rs @@ -1,14 +1,14 @@ use std::os::unix::ffi::OsStringExt; use base64::{Engine as _, prelude::BASE64_STANDARD_NO_PAD}; -use bincode::{Decode, Encode, config::standard}; use bstr::BString; #[cfg(not(target_env = "musl"))] use fspy_shared::ipc::NativeStr; #[cfg(not(target_env = "musl"))] use fspy_shared::ipc::channel::ChannelConf; +use wincode::{SchemaRead, SchemaWrite}; -#[derive(Debug, Encode, Decode)] +#[derive(Debug, SchemaWrite, SchemaRead)] pub struct Payload { #[cfg(not(target_env = "musl"))] pub ipc_channel_conf: ChannelConf, @@ -28,7 +28,7 @@ pub struct Payload { } #[cfg(target_os = "macos")] -#[derive(Debug, Encode, Decode, Clone)] +#[derive(Debug, SchemaWrite, SchemaRead, Clone)] pub struct Artifacts { pub bash_path: Box, pub coreutils_path: Box, @@ -45,11 +45,11 @@ pub struct EncodedPayload { /// /// # Panics /// -/// Panics if bincode serialization fails, which should never happen for valid `Payload` structs. +/// Panics if serialization fails, which should never happen for valid `Payload` structs. #[must_use] pub fn encode_payload(payload: Payload) -> EncodedPayload { - let bincode_bytes = bincode::encode_to_vec(&payload, standard()).unwrap(); - let encoded_string = BASE64_STANDARD_NO_PAD.encode(&bincode_bytes); + let bytes = wincode::serialize(&payload).unwrap(); + let encoded_string = BASE64_STANDARD_NO_PAD.encode(&bytes); EncodedPayload { payload, encoded_string: encoded_string.into() } } @@ -60,7 +60,7 @@ pub fn encode_payload(payload: Payload) -> EncodedPayload { /// Returns an error if: /// - The environment variable is not found /// - The base64 decoding fails -/// - The bincode deserialization fails +/// - The deserialization fails pub fn decode_payload_from_env() -> anyhow::Result { let Some(encoded_string) = std::env::var_os(PAYLOAD_ENV_NAME) else { anyhow::bail!("Environment variable '{PAYLOAD_ENV_NAME}' not found"); @@ -69,8 +69,7 @@ pub fn decode_payload_from_env() -> anyhow::Result { } fn decode_payload(encoded_string: BString) -> anyhow::Result { - let bincode_bytes = BASE64_STANDARD_NO_PAD.decode(&encoded_string)?; - let (payload, n) = bincode::decode_from_slice::(&bincode_bytes, standard())?; - assert_eq!(bincode_bytes.len(), n); + let bytes = BASE64_STANDARD_NO_PAD.decode(&encoded_string)?; + let payload: Payload = wincode::deserialize_exact(&bytes)?; Ok(EncodedPayload { payload, encoded_string }) } diff --git a/crates/subprocess_test/Cargo.toml b/crates/subprocess_test/Cargo.toml index 6d4f17fa..720099a6 100644 --- a/crates/subprocess_test/Cargo.toml +++ b/crates/subprocess_test/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true [dependencies] base64 = { workspace = true } -bincode = { workspace = true } +wincode = { workspace = true } ctor = { workspace = true } fspy = { workspace = true, optional = true } portable-pty = { workspace = true, optional = true } diff --git a/crates/subprocess_test/src/lib.rs b/crates/subprocess_test/src/lib.rs index c4d2bcd4..50fa7c83 100644 --- a/crates/subprocess_test/src/lib.rs +++ b/crates/subprocess_test/src/lib.rs @@ -1,8 +1,8 @@ use std::{env::current_exe, ffi::OsString, path::PathBuf, process::Command as StdCommand}; use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD}; -use bincode::{Decode, Encode, config}; use rustc_hash::FxHashMap; +use wincode::{SchemaReadOwned, SchemaWrite, config::DefaultConfig}; /// A command configuration that can be converted to `std::process::Command` /// or `fspy::Command` for execution. @@ -50,7 +50,7 @@ impl From for portable_pty::CommandBuilder { /// Creates a `subprocess_test::Command` that only executes the provided function. /// -/// - $arg: The argument to pass to the function, must implement `Encode` and `Decode`. +/// - $arg: The argument to pass to the function, must implement `SchemaWrite` and `SchemaReadOwned`. /// - $f: The function to run in the separate process, takes one argument of the type of $arg. #[macro_export] macro_rules! command_for_fn { @@ -119,7 +119,7 @@ fn read_proc_cmdline() -> Option> { } #[doc(hidden)] -pub fn init_impl>(expected_id: &str, f: impl FnOnce(A)) { +pub fn init_impl>(expected_id: &str, f: impl FnOnce(A)) { let args = get_args(); // let (Some(current_id), Some(arg_base64)) = (args.get(1), args.get(2)) else { @@ -129,17 +129,15 @@ pub fn init_impl>(expected_id: &str, f: impl FnOnce(A)) { return; } let arg_bytes = BASE64_STANDARD_NO_PAD.decode(arg_base64).expect("Failed to decode base64 arg"); - let arg: A = bincode::decode_from_slice(&arg_bytes, config::standard()) - .expect("Failed to decode bincode arg") - .0; + let arg: A = wincode::deserialize(&arg_bytes).expect("Failed to decode wincode arg"); f(arg); std::process::exit(0); } #[doc(hidden)] -pub fn create_command(id: &str, arg: impl Encode) -> Command { +pub fn create_command>(id: &str, arg: T) -> Command { let program = current_exe().unwrap().into_os_string(); - let arg_bytes = bincode::encode_to_vec(&arg, config::standard()).expect("Failed to encode arg"); + let arg_bytes = wincode::serialize(&arg).expect("Failed to encode arg"); let arg_base64 = BASE64_STANDARD_NO_PAD.encode(&arg_bytes); let args = vec![OsString::from(id), OsString::from(arg_base64)]; diff --git a/crates/vite_path/Cargo.toml b/crates/vite_path/Cargo.toml index 7b130463..a3736ce1 100644 --- a/crates/vite_path/Cargo.toml +++ b/crates/vite_path/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true rust-version.workspace = true [dependencies] -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } diff-struct = { workspace = true } path-clean = { workspace = true } ref-cast = { workspace = true } diff --git a/crates/vite_path/src/relative.rs b/crates/vite_path/src/relative.rs index 25b38804..82c040fa 100644 --- a/crates/vite_path/src/relative.rs +++ b/crates/vite_path/src/relative.rs @@ -6,15 +6,16 @@ use std::{ borrow::Borrow, fmt::Display, + mem::MaybeUninit, ops::Deref, path::{Component, Path}, }; -use bincode::{Decode, Encode, de::Decoder, error::DecodeError}; use diff::Diff; use ref_cast::{RefCastCustom, ref_cast_custom}; use serde::{Deserialize, Serialize}; use vite_str::Str; +use wincode::{SchemaRead, SchemaWrite, config::Config, error::ReadResult, io::Reader}; /// A relative path with additional guarantees to make it portable: /// @@ -101,14 +102,30 @@ impl RelativePath { /// A owned relative path buf with the same guarantees as `RelativePath` #[derive( - Debug, Encode, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, Default, + Debug, SchemaWrite, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, Default, )] #[expect( clippy::unsafe_derive_deserialize, - reason = "unsafe in Decode impl validates portability invariants" + reason = "unsafe in SchemaRead impl validates portability invariants" )] pub struct RelativePathBuf(Str); +// SAFETY: Delegates to `Str`'s SchemaRead impl; dst is initialized only on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for RelativePathBuf { + type Dst = Self; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let path_str = >::get(&mut reader)?; + Self::new(path_str.as_str()).map_or( + Err(wincode::error::ReadError::Custom("invalid relative path in encoded data")), + |path| { + dst.write(path); + Ok(()) + }, + ) + } +} + impl AsRef for RelativePathBuf { fn as_ref(&self) -> &Path { self.as_path() @@ -210,28 +227,12 @@ impl RelativePathBuf { #[must_use] pub fn as_relative_path(&self) -> &RelativePath { - // SAFETY: RelativePathBuf's constructors (new, Decode) validate portability + // SAFETY: RelativePathBuf's constructors (new, SchemaRead) validate portability // invariants, so the inner string is guaranteed to be a valid portable path. unsafe { RelativePath::assume_portable(&self.0) } } } -impl Decode for RelativePathBuf { - fn decode>(decoder: &mut D) -> Result { - let path_str = Str::decode(decoder)?; - Self::new(path_str.as_str()).map_err(|err| { - #[expect( - clippy::disallowed_macros, - reason = "bincode::error::DecodeError requires std format!" - )] - let msg = format!("{err}: {path_str}"); - DecodeError::OtherString(msg) - }) - } -} - -bincode::impl_borrow_decode!(RelativePathBuf); - impl TryFrom<&Path> for RelativePathBuf { type Error = FromPathError; @@ -466,10 +467,8 @@ mod tests { #[test] fn encode_decode() { let rel_path = RelativePathBuf::new("foo/bar").unwrap(); - let config = bincode::config::standard(); - let encoded = bincode::encode_to_vec(&rel_path, config).unwrap(); - let (decoded, _) = - bincode::decode_from_slice::(&encoded, config).unwrap(); + let encoded = wincode::serialize(&rel_path).unwrap(); + let decoded: RelativePathBuf = wincode::deserialize(&encoded).unwrap(); assert_eq!(rel_path, decoded); } } diff --git a/crates/vite_shell/Cargo.toml b/crates/vite_shell/Cargo.toml index 6a1a1993..243a849f 100644 --- a/crates/vite_shell/Cargo.toml +++ b/crates/vite_shell/Cargo.toml @@ -8,7 +8,7 @@ publish = false rust-version.workspace = true [dependencies] -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } brush-parser = { workspace = true } diff-struct = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/vite_shell/src/lib.rs b/crates/vite_shell/src/lib.rs index 2d768997..1bec3c9b 100644 --- a/crates/vite_shell/src/lib.rs +++ b/crates/vite_shell/src/lib.rs @@ -1,6 +1,5 @@ use std::{collections::BTreeMap, fmt::Display, ops::Range}; -use bincode::{Decode, Encode}; use brush_parser::{ Parser, ParserOptions, ast::{ @@ -13,9 +12,10 @@ use brush_parser::{ use diff::Diff; use serde::{Deserialize, Serialize}; use vite_str::Str; +use wincode::{SchemaRead, SchemaWrite}; /// "FOO=BAR program arg1 arg2" -#[derive(Encode, Decode, Serialize, Deserialize, Debug, PartialEq, Eq, Diff, Clone)] +#[derive(SchemaWrite, SchemaRead, Serialize, Deserialize, Debug, PartialEq, Eq, Diff, Clone)] #[diff(attr(#[derive(Debug)]))] pub struct TaskParsedCommand { pub envs: BTreeMap, @@ -298,8 +298,6 @@ mod tests { #[test] fn test_task_parsed_command_serialization_stability() { - use bincode::{decode_from_slice, encode_to_vec}; - // Create a command with multiple environment variables let cmd = TaskParsedCommand { envs: [ @@ -313,15 +311,14 @@ mod tests { }; // Serialize multiple times - let config = bincode::config::standard(); - let bytes1 = encode_to_vec(&cmd, config).unwrap(); - let bytes2 = encode_to_vec(&cmd, config).unwrap(); + let bytes1 = wincode::serialize(&cmd).unwrap(); + let bytes2 = wincode::serialize(&cmd).unwrap(); // Verify serialization is stable assert_eq!(bytes1, bytes2); // Verify deserialization works and maintains order - let (decoded, _): (TaskParsedCommand, _) = decode_from_slice(&bytes1, config).unwrap(); + let decoded: TaskParsedCommand = wincode::deserialize(&bytes1).unwrap(); assert_eq!(decoded, cmd); // Verify the decoded command still has stable string representation diff --git a/crates/vite_str/Cargo.toml b/crates/vite_str/Cargo.toml index 7f3909a3..9d49f89f 100644 --- a/crates/vite_str/Cargo.toml +++ b/crates/vite_str/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true rust-version.workspace = true [dependencies] -bincode = { workspace = true } +wincode = { workspace = true } compact_str = { workspace = true, features = ["serde"] } diff-struct = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/vite_str/src/lib.rs b/crates/vite_str/src/lib.rs index e54c3633..6c0f20c0 100644 --- a/crates/vite_str/src/lib.rs +++ b/crates/vite_str/src/lib.rs @@ -3,24 +3,23 @@ use std::{ borrow::Borrow, ffi::OsStr, fmt::{Debug, Display}, + mem::MaybeUninit, ops::Deref, path::Path, - str::from_utf8, sync::Arc, }; -use bincode::{ - Decode, Encode, - de::{Decoder, read::Reader}, - enc::Encoder, - error::{DecodeError, EncodeError}, - impl_borrow_decode, -}; use compact_str::CompactString; #[doc(hidden)] // for `format` macro only pub use compact_str::format_compact; use diff::Diff; use serde::{Deserialize, Serialize}; +use wincode::{ + SchemaRead, SchemaWrite, + config::Config, + error::{ReadResult, WriteResult}, + io::{Reader, Writer}, +}; #[macro_export] macro_rules! format { @@ -116,34 +115,29 @@ impl Debug for Str { } } -impl Encode for Str { - fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { - self.0.encode(encoder) +// SAFETY: Delegates to `str`'s SchemaWrite impl, preserving its size/write invariants. +unsafe impl SchemaWrite for Str { + type Src = Self; + + fn size_of(src: &Self::Src) -> WriteResult { + >::size_of(src.as_str()) } -} -// https://github.com/bincode-org/bincode/blob/48ac8d4e8057387696a7ed3af2dda198ead23246/src/de/mod.rs#L331 -fn decode_slice_len(decoder: &mut D) -> Result { - let v = u64::decode(decoder)?; - v.try_into().map_err(|_| DecodeError::OutsideUsizeRange(v)) + fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + >::write(writer, src.as_str()) + } } -impl Decode for Str { - fn decode(decoder: &mut D) -> Result { - let len = decode_slice_len(decoder)?; - decoder.claim_container_read::(len)?; - let mut compact_str = CompactString::with_capacity(len); - // SAFETY: we write exactly `len` bytes into the spare capacity, validate UTF-8, then set length - unsafe { - let buf = &mut compact_str.as_mut_bytes()[..len]; - decoder.reader().read(buf)?; - from_utf8(buf).map_err(|utf8_error| DecodeError::Utf8 { inner: utf8_error })?; - compact_str.set_len(len); - } - Ok(Self(compact_str)) +// SAFETY: Delegates to `&str`'s SchemaRead impl and wraps the result; dst is initialized on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for Str { + type Dst = Self; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let s: &str = <&str as SchemaRead<'de, C>>::get(&mut reader)?; + dst.write(Self(CompactString::from(s))); + Ok(()) } } -impl_borrow_decode!(Str); impl From<&str> for Str { fn from(value: &str) -> Self { @@ -188,7 +182,7 @@ mod ts_impl { use super::Str; - #[expect(clippy::disallowed_types, reason = "ts-rs trait requires returning String")] + #[expect(clippy::disallowed_types, reason = "ts_rs::TS trait requires returning String")] impl TS for Str { type OptionInnerType = Self; type WithoutGenerics = Self; @@ -217,17 +211,13 @@ mod ts_impl { #[cfg(test)] mod tests { - use bincode::{config::standard, decode_from_slice, encode_to_vec}; - use super::*; #[test] fn test_str_encode_decode() { - let config = standard(); let original = Str::from("Hello, World!"); - let encoded = encode_to_vec(&original, config).unwrap(); - - let decoded: Str = decode_from_slice(&encoded, config).unwrap().0; + let encoded = wincode::serialize(&original).unwrap(); + let decoded: Str = wincode::deserialize(&encoded).unwrap(); assert_eq!(original, decoded); } } diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 7f0bebb3..647f2d84 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -bincode = { workspace = true, features = ["derive"] } +wincode = { workspace = true, features = ["derive"] } bstr = { workspace = true } clap = { workspace = true, features = ["derive"] } ctrlc = { workspace = true } diff --git a/crates/vite_task/src/collections.rs b/crates/vite_task/src/collections.rs index ec01f380..eaad9511 100644 --- a/crates/vite_task/src/collections.rs +++ b/crates/vite_task/src/collections.rs @@ -1,2 +1,2 @@ -#[expect(clippy::disallowed_types, reason = "std HashMap needed for bincode/serde compatibility")] +#[expect(clippy::disallowed_types, reason = "std HashMap needed for wincode/serde compatibility")] pub type HashMap = std::collections::HashMap; diff --git a/crates/vite_task/src/maybe_str.rs b/crates/vite_task/src/maybe_str.rs index 53bca7d2..404abf1b 100644 --- a/crates/vite_task/src/maybe_str.rs +++ b/crates/vite_task/src/maybe_str.rs @@ -3,14 +3,14 @@ use std::{ ops::{Deref, DerefMut}, }; -use bincode::{Decode, Encode}; use bstr::BStr; use serde::Serialize; +use wincode::{SchemaRead, SchemaWrite}; -/// Similar to `bstr::BString`, but also implements `bincode::{Encode`, Decode}, +/// Similar to `bstr::BString`, but also implements `wincode::{SchemaWrite, SchemaRead}`, /// and serializes losslessly to utf8 for outputting debug json -#[derive(Encode, Decode)] +#[derive(SchemaWrite, SchemaRead)] #[expect(dead_code, reason = "struct fields accessed via Deref>")] pub struct MaybeString(Vec); diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs index 8a8cad44..484d7d60 100644 --- a/crates/vite_task/src/session/cache/mod.rs +++ b/crates/vite_task/src/session/cache/mod.rs @@ -4,7 +4,6 @@ pub mod display; use std::{collections::BTreeMap, fmt::Display, fs::File, io::Write, sync::Arc, time::Duration}; -use bincode::{Decode, Encode, decode_from_slice, encode_to_vec}; // Re-export display functions for convenience pub use display::format_cache_status_inline; pub use display::{ @@ -17,6 +16,12 @@ use tokio::sync::Mutex; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_task_graph::config::ResolvedInputConfig; use vite_task_plan::cache_metadata::{CacheMetadata, ExecutionCacheKey, SpawnFingerprint}; +use wincode::{ + SchemaRead, SchemaReadOwned, SchemaWrite, + config::{ConfigCore, DefaultConfig}, + error::{ReadResult, WriteResult}, + io::{Reader, Writer}, +}; use super::execute::{fingerprint::PostRunFingerprint, spawn::StdOutput}; @@ -32,7 +37,7 @@ use super::execute::{fingerprint::PostRunFingerprint, spawn::StdOutput}; /// overwrite the existing entry (e.g., input file hashes — there's no /// reason to keep the old hash around, and storing them in the value /// lets us report exactly *which file* changed). -#[derive(Debug, Encode, Decode, Serialize, PartialEq, Eq, Clone)] +#[derive(Debug, SchemaWrite, SchemaRead, Serialize, PartialEq, Eq, Clone)] pub struct CacheEntryKey { /// The spawn fingerprint (command, args, cwd, envs) pub spawn_fingerprint: SpawnFingerprint, @@ -50,14 +55,48 @@ impl CacheEntryKey { } } +/// wincode schema adapter for `Duration`. +struct DurationSchema; + +// SAFETY: Writes exactly `size_of::() + size_of::()` bytes matching size_of. +unsafe impl SchemaWrite for DurationSchema { + type Src = Duration; + + fn size_of(_src: &Self::Src) -> WriteResult { + Ok(size_of::() + size_of::()) + } + + fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + >::write(writer.by_ref(), &src.as_secs())?; + >::write(writer.by_ref(), &src.subsec_nanos())?; + Ok(()) + } +} + +// SAFETY: Reads u64 + u32, matching the write format; dst is initialized on Ok. +unsafe impl<'de, C: ConfigCore> SchemaRead<'de, C> for DurationSchema { + type Dst = Duration; + + fn read( + mut reader: impl Reader<'de>, + dst: &mut std::mem::MaybeUninit, + ) -> ReadResult<()> { + let secs = >::get(&mut reader)?; + let nanos = >::get(&mut reader)?; + dst.write(Duration::new(secs, nanos)); + Ok(()) + } +} + /// Cached execution result for a task. /// /// Contains the post-run fingerprint (from fspy), captured outputs, /// execution duration, and explicit input file hashes. -#[derive(Debug, Encode, Decode, Serialize)] +#[derive(Debug, SchemaWrite, SchemaRead, Serialize)] pub struct CacheEntryValue { pub post_run_fingerprint: PostRunFingerprint, pub std_outputs: Arc<[StdOutput]>, + #[wincode(with = "DurationSchema")] pub duration: Duration, /// Hashes of explicit input files computed from positive globs. /// Files matching negative globs are already filtered out. @@ -71,8 +110,6 @@ pub struct ExecutionCache { conn: Mutex, } -const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); - #[derive(Debug, Clone, Serialize, Deserialize)] #[expect( clippy::large_enum_variant, @@ -146,10 +183,6 @@ impl ExecutionCache { // Use file lock to prevent race conditions when multiple processes initialize the database let lock_path = path.join("db_open.lock"); let lock_file = File::create(lock_path.as_path())?; - #[expect( - clippy::incompatible_msrv, - reason = "File::lock is stable since 1.84.0, our MSRV 1.88.0 is higher; clippy false positive" - )] lock_file.lock()?; let db_path = path.join("cache.db"); @@ -168,16 +201,16 @@ impl ExecutionCache { "CREATE TABLE task_fingerprints (key BLOB PRIMARY KEY, value BLOB);", (), )?; - conn.execute("PRAGMA user_version = 10", ())?; + conn.execute("PRAGMA user_version = 11", ())?; } - 1..=9 => { + 1..=10 => { // old internal db version. reset conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, true)?; conn.execute("VACUUM", ())?; conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, false)?; } - 10 => break, // current version - 11.. => { + 11 => break, // current version + 12.. => { return Err(anyhow::anyhow!("Unrecognized database version: {user_version}")); } } @@ -327,12 +360,15 @@ impl ExecutionCache { clippy::significant_drop_tightening, reason = "lock guard cannot be dropped earlier because prepared statement borrows connection" )] - async fn get_key_by_value>( + async fn get_key_by_value< + K: SchemaWrite, + V: SchemaReadOwned, + >( &self, table: &str, key: &K, ) -> anyhow::Result> { - let key_blob = encode_to_vec(key, BINCODE_CONFIG)?; + let key_blob = wincode::serialize(key)?; let value_blob = { let conn = self.conn.lock().await; #[expect( @@ -348,7 +384,7 @@ impl ExecutionCache { let Some(value_blob) = value_blob else { return Ok(None); }; - let (value, _) = decode_from_slice::(&value_blob, BINCODE_CONFIG)?; + let value: V = wincode::deserialize(&value_blob)?; Ok(Some(value)) } @@ -370,14 +406,17 @@ impl ExecutionCache { clippy::significant_drop_tightening, reason = "lock guard must be held while executing the prepared statement" )] - async fn upsert( + async fn upsert< + K: SchemaWrite, + V: SchemaWrite, + >( &self, table: &str, key: &K, value: &V, ) -> anyhow::Result<()> { - let key_blob = encode_to_vec(key, BINCODE_CONFIG)?; - let value_blob = encode_to_vec(value, BINCODE_CONFIG)?; + let key_blob = wincode::serialize(key)?; + let value_blob = wincode::serialize(value)?; let conn = self.conn.lock().await; #[expect(clippy::disallowed_macros, reason = "SQL query string for rusqlite requires String")] let mut update_stmt = conn.prepare_cached(&format!( @@ -407,7 +446,10 @@ impl ExecutionCache { clippy::significant_drop_tightening, reason = "lock guard must be held while iterating over query rows" )] - async fn list_table + Serialize, V: Decode<()> + Serialize>( + async fn list_table< + K: SchemaReadOwned + Serialize, + V: SchemaReadOwned + Serialize, + >( &self, table: &str, out: &mut impl Write, @@ -422,8 +464,8 @@ impl ExecutionCache { while let Some(row) = rows.next()? { let key_blob: Vec = row.get(0)?; let value_blob: Vec = row.get(1)?; - let (key, _) = decode_from_slice::(&key_blob, BINCODE_CONFIG)?; - let (value, _) = decode_from_slice::(&value_blob, BINCODE_CONFIG)?; + let key: K = wincode::deserialize(&key_blob)?; + let value: V = wincode::deserialize(&value_blob)?; writeln!( out, "{} => {}", diff --git a/crates/vite_task/src/session/execute/fingerprint.rs b/crates/vite_task/src/session/execute/fingerprint.rs index 34a7c3da..dee12a89 100644 --- a/crates/vite_task/src/session/execute/fingerprint.rs +++ b/crates/vite_task/src/session/execute/fingerprint.rs @@ -10,18 +10,18 @@ use std::{ sync::Arc, }; -use bincode::{Decode, Encode}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; +use wincode::{SchemaRead, SchemaWrite}; use super::spawn::PathRead; use crate::{collections::HashMap, session::cache::InputChangeKind}; /// Post-run fingerprint capturing file state after execution. /// Used to validate whether cached outputs are still valid. -#[derive(Encode, Decode, Debug, Serialize)] +#[derive(SchemaWrite, SchemaRead, Debug, Serialize)] pub struct PostRunFingerprint { /// Paths inferred from fspy during execution with their content fingerprints. /// Only populated when `input_config.includes_auto` is true. @@ -29,7 +29,7 @@ pub struct PostRunFingerprint { } /// Fingerprint for a single path (file or directory) -#[derive(Encode, Decode, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] +#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub enum PathFingerprint { /// Path was not found when fingerprinting NotFound, @@ -43,7 +43,7 @@ pub enum PathFingerprint { } /// Kind of directory entry -#[derive(Encode, Decode, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] +#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub enum DirEntryKind { File, Dir, diff --git a/crates/vite_task/src/session/execute/spawn.rs b/crates/vite_task/src/session/execute/spawn.rs index 9c1ccb8e..92da92b1 100644 --- a/crates/vite_task/src/session/execute/spawn.rs +++ b/crates/vite_task/src/session/execute/spawn.rs @@ -7,7 +7,6 @@ use std::{ time::{Duration, Instant}, }; -use bincode::{Decode, Encode}; use fspy::AccessMode; use rustc_hash::FxHashSet; use serde::Serialize; @@ -16,6 +15,7 @@ use tokio_util::sync::CancellationToken; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_task_plan::SpawnCommand; use wax::Program as _; +use wincode::{SchemaRead, SchemaWrite}; use crate::collections::HashMap; @@ -26,14 +26,14 @@ pub struct PathRead { } /// Output kind for stdout/stderr -#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, SchemaWrite, SchemaRead, Serialize)] pub enum OutputKind { StdOut, StdErr, } /// Output chunk with stream kind -#[derive(Debug, Encode, Decode, Serialize, Clone)] +#[derive(Debug, SchemaWrite, SchemaRead, Serialize, Clone)] pub struct StdOutput { pub kind: OutputKind, pub content: Vec, diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 1316b738..151271fe 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } monostate = { workspace = true } petgraph = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index dc51fc83..5f202c31 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -2,7 +2,6 @@ pub mod user; use std::{collections::BTreeSet, sync::Arc}; -use bincode::{Decode, Encode}; use monostate::MustBe; use rustc_hash::FxHashSet; use serde::Serialize; @@ -13,6 +12,7 @@ pub use user::{ }; use vite_path::AbsolutePath; use vite_str::Str; +use wincode::{SchemaRead, SchemaWrite}; use crate::config::user::UserTaskOptions; @@ -103,7 +103,7 @@ pub struct CacheConfig { /// - `includes_auto`: Whether automatic file tracking is enabled /// - `positive_globs`: Glob patterns for files to include (without `!` prefix) /// - `negative_globs`: Glob patterns for files to exclude (without `!` prefix) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Encode, Decode)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, SchemaWrite, SchemaRead)] pub struct ResolvedInputConfig { /// Whether automatic file tracking is enabled pub includes_auto: bool, diff --git a/crates/vite_task_plan/Cargo.toml b/crates/vite_task_plan/Cargo.toml index 7ac860f9..f3a0e0ec 100644 --- a/crates/vite_task_plan/Cargo.toml +++ b/crates/vite_task_plan/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -bincode = { workspace = true } +wincode = { workspace = true, features = ["derive"] } futures-util = { workspace = true } petgraph = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/vite_task_plan/src/cache_metadata.rs b/crates/vite_task_plan/src/cache_metadata.rs index 38ed3fd0..c3435c34 100644 --- a/crates/vite_task_plan/src/cache_metadata.rs +++ b/crates/vite_task_plan/src/cache_metadata.rs @@ -1,15 +1,15 @@ use std::sync::Arc; -use bincode::{Decode, Encode}; use serde::{Deserialize, Serialize}; use vite_path::RelativePathBuf; use vite_str::{self, Str}; use vite_task_graph::config::ResolvedInputConfig; +use wincode::{SchemaRead, SchemaWrite}; use crate::envs::EnvFingerprints; /// Key to identify an execution across sessions. -#[derive(Debug, Encode, Decode, Serialize)] +#[derive(Debug, SchemaWrite, SchemaRead, Serialize)] pub enum ExecutionCacheKey { /// This execution is from a script of a user-defined task. UserTask { @@ -67,7 +67,7 @@ pub struct CacheMetadata { /// - The resolver provides envs which become part of the fingerprint /// - If resolver provides different envs between runs, cache breaks /// - Each built-in task type must have unique task name to avoid cache collision -#[derive(Encode, Decode, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(SchemaWrite, SchemaRead, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct SpawnFingerprint { pub(crate) cwd: RelativePathBuf, pub(crate) program_fingerprint: ProgramFingerprint, @@ -102,7 +102,7 @@ impl SpawnFingerprint { } /// The program fingerprint used in `SpawnFingerprint` -#[derive(Encode, Decode, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(SchemaWrite, SchemaRead, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub(crate) enum ProgramFingerprint { /// If the program is outside the workspace, fingerprint by its name only (like `node`, `npm`, etc) OutsideWorkspace { program_name: Str }, diff --git a/crates/vite_task_plan/src/envs.rs b/crates/vite_task_plan/src/envs.rs index 08aafc9b..4ea4fbf4 100644 --- a/crates/vite_task_plan/src/envs.rs +++ b/crates/vite_task_plan/src/envs.rs @@ -1,6 +1,5 @@ -use std::{collections::BTreeMap, ffi::OsStr, sync::Arc}; +use std::{collections::BTreeMap, ffi::OsStr, mem::MaybeUninit, sync::Arc}; -use bincode::{Decode, Encode}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use sha2::{Digest as _, Sha256}; @@ -8,16 +7,50 @@ use supports_color::{Stream, on}; use vite_glob::GlobPatternSet; use vite_str::Str; use vite_task_graph::config::EnvConfig; +use wincode::{ + SchemaRead, SchemaWrite, + config::Config, + error::{ReadResult, WriteResult}, + io::{Reader, Writer}, +}; + +/// wincode schema adapter for `Arc`, which is a foreign type with unsized inner. +struct ArcStrSchema; + +// SAFETY: Delegates to `str`'s SchemaWrite impl, preserving its size/write invariants. +unsafe impl SchemaWrite for ArcStrSchema { + type Src = Arc; + + fn size_of(src: &Self::Src) -> WriteResult { + >::size_of(src) + } + + fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { + >::write(writer, src) + } +} + +// SAFETY: Delegates to `&str`'s SchemaRead impl; dst is initialized on Ok. +unsafe impl<'de, C: Config> SchemaRead<'de, C> for ArcStrSchema { + type Dst = Arc; + + fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { + let s: &str = <&str as SchemaRead<'de, C>>::get(&mut reader)?; + dst.write(Arc::from(s)); + Ok(()) + } +} /// Environment variable fingerprints for a task execution. /// /// Contents of this struct are only for fingerprinting and cache key computation (some of envs may be hashed for security). /// The actual environment variables to be passed to the execution are in `LeafExecutionItem.all_envs`. -#[derive(Debug, Encode, Decode, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, SchemaWrite, SchemaRead, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct EnvFingerprints { /// Environment variables that should be fingerprinted for this execution. /// /// Use `BTreeMap` to ensure stable order. + #[wincode(with = "BTreeMap")] pub fingerprinted_envs: BTreeMap>, /// Environment variable names that should be passed through without values being fingerprinted.