From 4fa38e4d413728df9c092508f8aeef661461583c Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Wed, 7 Oct 2026 09:29:07 -0400 Subject: [PATCH 1/2] Check the bun bundled flag only for matching entries in the hosted rewrite rewrite_bun_lock ran is_bundled_entry, a full serde_json parse of each entry's meta object, before the cheap spec comparison, once per (patch, entry) pair. On the bench fixture (about 3,000 entries x 127 patches) that is roughly 380k JSON parses per scan, which doubled bun/hosted and bun/rescan after #472 (144 -> 302 ms). Decode each entry's spec once per lock, compare it first, and compute the bundled flag lazily (cached per entry) only for entries whose spec matches a patch. Bundled entries are still skipped, warned about and kept out of VEX. Refs #578, #580 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/patch/redirect/mod.rs | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index b255246a6..9a9a7cbee 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -4870,6 +4870,17 @@ fn rewrite_bun_lock( Some(content), ); + // Decode each entry's spec once per lock, not once per (patch, entry) + // pair. The bundled flag needs a full JSON parse of the entry's meta + // object, so it is computed lazily, only for entries whose spec matches + // some patch, and cached (#580: the eager per-patch parse made bun scans + // O(entries x patches) JSON parses). + let specs: Vec> = entries + .iter() + .map(|e| e.elems.first().and_then(|s| decode_json_string(s))) + .collect(); + let mut bundled: Vec> = vec![None; entries.len()]; + let mut changed = false; let mut pinned_any = false; for dep in &npm { @@ -4887,18 +4898,20 @@ fn rewrite_bun_lock( let target_spec = format!("{fname}@{}", dep.version); let url_spec = format!("{fname}@{}", dep.artifact_url); let mut matched_any = false; - for entry in &entries { - let Some(spec) = entry.elems.first().and_then(|e| decode_json_string(e)) else { + for (i, entry) in entries.iter().enumerate() { + let Some(spec) = specs[i].as_deref() else { continue; }; // Bun unpacks a bundled copy from its PARENT's tarball and never // reads the entry's spec (#469), so a rewrite here would count // as redirected (and VEX-attest the patch) while the unpatched // bundled bytes keep installing. Mirrors npm's `inBundle` guard. - if is_bundled_entry(entry) - && (spec == target_spec - || spec == url_spec - || is_prior_hosted_bun_spec(&spec, &fname, &dep.artifact_url)) + // The cheap spec match runs first; the bundled parse only for + // a match (#580). + if (spec == target_spec + || spec == url_spec + || is_prior_hosted_bun_spec(spec, &fname, &dep.artifact_url)) + && *bundled[i].get_or_insert_with(|| is_bundled_entry(entry)) { matched_any = true; result.bundled_skipped_uuids.insert(dep.patch_uuid.clone()); @@ -4942,7 +4955,7 @@ fn rewrite_bun_lock( deps_verbatim = entry.elems[1].clone(); } else if matches!(entry.elems.len(), 2 | 3) && entry.elems[1].starts_with('{') - && is_prior_hosted_bun_spec(&spec, &fname, &dep.artifact_url) + && is_prior_hosted_bun_spec(spec, &fname, &dep.artifact_url) { // A URL tuple written by an EARLIER redirect whose artifact // URL has since changed (a patch republish rotates the uuid @@ -11063,6 +11076,48 @@ mod tests { ); } + /// #580: the bundled check runs only for entries whose spec matches the + /// patch. A bundled copy of ANOTHER package (or another version) beside + /// the target never warns or keeps the patch out of VEX, while a bundled + /// copy of the target itself still does, across several patches. + #[test] + fn bun_lock_bundled_check_only_applies_to_matching_entries() { + let sha512 = format!("sha512-{}==", "A".repeat(86)); + let ovr = npm_override("is-number", "7.0.0", "http://p.test/isn.tgz", &sha512); + let regular = "\"is-number\": [\"is-number@7.0.0\", \"\", {}, \"sha512-UP==\"],"; + let other_bundled = "\"@bh/bund/kind-of\": [\"kind-of@6.0.3\", \"\", { \"bundled\": true }, \"sha512-KO==\"],"; + let other_version_bundled = "\"@bh/bund/is-number\": [\"is-number@6.0.0\", \"\", { \"bundled\": true }, \"sha512-OV==\"],"; + + let lock = format!("{regular}\n {other_bundled}\n {other_version_bundled}"); + let mut files = BTreeMap::new(); + files.insert("bun.lock".to_string(), bun_lock_file(&lock, 1)); + let mut r = RewriteResult::default(); + rewrite_bun_lock(&files, std::slice::from_ref(&ovr), &mut r); + assert_eq!(r.edits.len(), 1, "{:?}", r.edits); + assert_eq!(r.edits[0].key.as_deref(), Some("is-number")); + assert!(r.warnings.is_empty(), "{:?}", r.warnings); + assert!(r.bundled_skipped_uuids.is_empty()); + let out = r.files.get("bun.lock").expect("lock rewritten"); + assert!(out.contains(other_bundled) && out.contains(other_version_bundled)); + + // A second patch whose target IS bundled still warns, and only for + // its own uuid. + let mut kind_of = npm_override("kind-of", "6.0.3", "http://p.test/ko.tgz", &sha512); + kind_of.patch_uuid = "33333333-3333-4333-8333-333333333333".into(); + let mut r = RewriteResult::default(); + rewrite_bun_lock(&files, &[ovr.clone(), kind_of.clone()], &mut r); + assert_eq!(r.edits.len(), 1, "{:?}", r.edits); + assert_eq!( + warning_codes(&r), + vec!["redirect_bun_bundled_instance_skipped"], + "{:?}", + r.warnings + ); + assert!(r.warnings[0].detail.contains("@bh/bund/kind-of")); + assert!(r.bundled_skipped_uuids.contains(&kind_of.patch_uuid)); + assert!(!r.bundled_skipped_uuids.contains(&ovr.patch_uuid)); + } + /// REGRESSION (#367): `bun patch --commit` keys the project's own patch /// on the registry `name@version` in package.json (and bun.lock's /// mirror). Rewiring that package to a hosted URL makes Bun drop the From 32db3d1e11bfab12012d0e3442d012b702a23a87 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Wed, 7 Oct 2026 09:29:07 -0400 Subject: [PATCH 2/2] Match the bun spec before the bundled parse in vendor, with a fast path classify_rewritable and bundled_matches now run classify first and only parse the meta of entries that match the target. is_bundled_entry skips the JSON parse when the meta never spells "bundled" and holds no backslash (so an escaped key still takes the parse). Malformed meta that mentions "bundled" still fails closed. Refs #578, #580 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../socket-patch-core/src/vendor/bun_lock.rs | 10 +++---- .../src/vendor/bun_lock_text.rs | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/bun_lock.rs b/crates/socket-patch-core/src/vendor/bun_lock.rs index 4e20d5d52..713ee8142 100644 --- a/crates/socket-patch-core/src/vendor/bun_lock.rs +++ b/crates/socket-patch-core/src/vendor/bun_lock.rs @@ -1092,10 +1092,10 @@ fn classify_rewritable( name: &str, target_leaf: &str, ) -> Option { - if is_bundled_entry(entry) { - return None; - } - classify(entry, target_spec, name, target_leaf) + // The cheap spec match runs first; the bundled parse only for a match + // (#580). + let shape = classify(entry, target_spec, name, target_leaf)?; + (!is_bundled_entry(entry)).then_some(shape) } /// Keys of the bundled entries that resolve the target `name@version`. @@ -1107,7 +1107,7 @@ fn bundled_matches( ) -> Vec { entries .iter() - .filter(|e| is_bundled_entry(e) && classify(e, target_spec, name, target_leaf).is_some()) + .filter(|e| classify(e, target_spec, name, target_leaf).is_some() && is_bundled_entry(e)) .map(|e| e.key.clone()) .collect() } diff --git a/crates/socket-patch-core/src/vendor/bun_lock_text.rs b/crates/socket-patch-core/src/vendor/bun_lock_text.rs index 0b6e72220..283229e08 100644 --- a/crates/socket-patch-core/src/vendor/bun_lock_text.rs +++ b/crates/socket-patch-core/src/vendor/bun_lock_text.rs @@ -229,6 +229,12 @@ pub(crate) fn is_bundled_entry(entry: &BunEntry) -> bool { let Some(meta) = entry.elems.iter().skip(1).find(|e| e.starts_with('{')) else { return false; }; + // Fast path (#580): a meta that never spells `bundled` cannot carry the + // key. A backslash could hide it behind a JSON escape, so only a meta + // with neither skips the parse. + if !meta.contains("bundled") && !meta.contains('\\') { + return false; + } match serde_json::from_str::(meta) { Ok(value) => value.get("bundled").and_then(serde_json::Value::as_bool) == Some(true), Err(_) => meta.contains("\"bundled\""), @@ -717,6 +723,26 @@ mod tests { )); } + /// The substring fast path (#580) never changes the answer: a meta that + /// never spells `bundled` is not bundled, a malformed meta that does + /// still fails closed, and a JSON-escaped key still takes the parse. + #[test] + fn bundled_fast_path_matches_the_full_parse() { + let bundled = |line: &str| is_bundled_entry(&parse_entry_line(line).unwrap()); + assert!(!bundled( + r#" "q": ["q@1.0.0", "", { "dependencies": { "a": "1" } }, "sha512-X=="],"# + )); + assert!(!bundled( + r#" "q": ["q@1.0.0", "", { "x": , }, "sha512-X=="],"# + )); + assert!(bundled( + r#" "q": ["q@1.0.0", "", { "bundled": true, "x": , }, "sha512-X=="],"# + )); + assert!(bundled( + r#" "q": ["q@1.0.0", "", { "bundl\u0065d": true }, "sha512-X=="],"# + )); + } + #[test] fn line_grammar_parses_the_fixture_shapes() { // Registry 4-tuple with deps and trailing comma.