Skip to content

Commit 876ea2c

Browse files
authored
fix(resolver): Report invalid index entries (#14927)
### What does this PR try to resolve? While #14897 reported packages with an unsupported index schema version, that only worked if the changes in the schema version did not cause errors in deserializing `IndexPackage` or in generating a `Summary`. This extends that change by recoverying on error with a more lax, incomplete parse of `IndexPackage` which should always generate a valid `Summary`. To help with a buggy Index, we also will report as many as we can. This does not provide a way to report to users or log on cache reads if the index entry is not at least `{"name": "<string>", "vers": "<semver>"}`. Fixes #10623 Fixes #14894 ### How should we test and review this PR? My biggest paranoia is some bad interaction with the index cache including more "invalid" index entries. That should be ok as we will ignore the invalid entry in the cache when loading it. Ignoring of invalid entries dates back to #6880 when the index cache was introduced. ### Additional information
2 parents 63ecc8d + 1d54a2b commit 876ea2c

File tree

8 files changed

+250
-57
lines changed

8 files changed

+250
-57
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ cargo-credential-macos-keychain = { version = "0.4.7", path = "credential/cargo-
3333
cargo-credential-wincred = { version = "0.4.7", path = "credential/cargo-credential-wincred" }
3434
cargo-platform = { path = "crates/cargo-platform", version = "0.2.0" }
3535
cargo-test-macro = { version = "0.4.0", path = "crates/cargo-test-macro" }
36-
cargo-test-support = { version = "0.6.0", path = "crates/cargo-test-support" }
36+
cargo-test-support = { version = "0.7.0", path = "crates/cargo-test-support" }
3737
cargo-util = { version = "0.2.14", path = "crates/cargo-util" }
3838
cargo-util-schemas = { version = "0.7.0", path = "crates/cargo-util-schemas" }
3939
cargo_metadata = "0.19.0"

crates/cargo-test-support/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-test-support"
3-
version = "0.6.1"
3+
version = "0.7.0"
44
edition.workspace = true
55
rust-version = "1.83" # MSRV:1
66
license.workspace = true

crates/cargo-test-support/src/registry.rs

+32-18
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,8 @@ pub struct Package {
570570
features: FeatureMap,
571571
local: bool,
572572
alternative: bool,
573-
invalid_json: bool,
573+
invalid_index_line: bool,
574+
index_line: Option<String>,
574575
edition: Option<String>,
575576
resolver: Option<String>,
576577
proc_macro: bool,
@@ -1251,7 +1252,8 @@ impl Package {
12511252
features: BTreeMap::new(),
12521253
local: false,
12531254
alternative: false,
1254-
invalid_json: false,
1255+
invalid_index_line: false,
1256+
index_line: None,
12551257
edition: None,
12561258
resolver: None,
12571259
proc_macro: false,
@@ -1422,8 +1424,16 @@ impl Package {
14221424

14231425
/// Causes the JSON line emitted in the index to be invalid, presumably
14241426
/// causing Cargo to skip over this version.
1425-
pub fn invalid_json(&mut self, invalid: bool) -> &mut Package {
1426-
self.invalid_json = invalid;
1427+
pub fn invalid_index_line(&mut self, invalid: bool) -> &mut Package {
1428+
self.invalid_index_line = invalid;
1429+
self
1430+
}
1431+
1432+
/// Override the auto-generated index line
1433+
///
1434+
/// This can give more control over error cases than [`Package::invalid_index_line`]
1435+
pub fn index_line(&mut self, line: &str) -> &mut Package {
1436+
self.index_line = Some(line.to_owned());
14271437
self
14281438
}
14291439

@@ -1496,22 +1506,26 @@ impl Package {
14961506
let c = t!(fs::read(&self.archive_dst()));
14971507
cksum(&c)
14981508
};
1499-
let name = if self.invalid_json {
1500-
serde_json::json!(1)
1509+
let line = if let Some(line) = self.index_line.clone() {
1510+
line
15011511
} else {
1502-
serde_json::json!(self.name)
1512+
let name = if self.invalid_index_line {
1513+
serde_json::json!(1)
1514+
} else {
1515+
serde_json::json!(self.name)
1516+
};
1517+
create_index_line(
1518+
name,
1519+
&self.vers,
1520+
deps,
1521+
&cksum,
1522+
self.features.clone(),
1523+
self.yanked,
1524+
self.links.clone(),
1525+
self.rust_version.as_deref(),
1526+
self.v,
1527+
)
15031528
};
1504-
let line = create_index_line(
1505-
name,
1506-
&self.vers,
1507-
deps,
1508-
&cksum,
1509-
self.features.clone(),
1510-
self.yanked,
1511-
self.links.clone(),
1512-
self.rust_version.as_deref(),
1513-
self.v,
1514-
);
15151529

15161530
let registry_path = if self.alternative {
15171531
alt_registry_path()

src/cargo/core/resolver/errors.rs

+7
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ pub(super) fn activation_error(
271271
);
272272
}
273273
}
274+
IndexSummary::Invalid(summary) => {
275+
let _ = writeln!(
276+
&mut msg,
277+
" version {}'s index entry is invalid",
278+
summary.version()
279+
);
280+
}
274281
}
275282
}
276283
} else if let Some(candidates) = alt_versions(registry, dep) {

src/cargo/sources/registry/index/mod.rs

+94-35
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ pub enum IndexSummary {
135135
Offline(Summary),
136136
/// From a newer schema version and is likely incomplete or inaccurate
137137
Unsupported(Summary, u32),
138+
/// An error was encountered despite being a supported schema version
139+
Invalid(Summary),
138140
}
139141

140142
impl IndexSummary {
@@ -144,7 +146,8 @@ impl IndexSummary {
144146
IndexSummary::Candidate(sum)
145147
| IndexSummary::Yanked(sum)
146148
| IndexSummary::Offline(sum)
147-
| IndexSummary::Unsupported(sum, _) => sum,
149+
| IndexSummary::Unsupported(sum, _)
150+
| IndexSummary::Invalid(sum) => sum,
148151
}
149152
}
150153

@@ -154,7 +157,8 @@ impl IndexSummary {
154157
IndexSummary::Candidate(sum)
155158
| IndexSummary::Yanked(sum)
156159
| IndexSummary::Offline(sum)
157-
| IndexSummary::Unsupported(sum, _) => sum,
160+
| IndexSummary::Unsupported(sum, _)
161+
| IndexSummary::Invalid(sum) => sum,
158162
}
159163
}
160164

@@ -164,17 +168,13 @@ impl IndexSummary {
164168
IndexSummary::Yanked(s) => IndexSummary::Yanked(f(s)),
165169
IndexSummary::Offline(s) => IndexSummary::Offline(f(s)),
166170
IndexSummary::Unsupported(s, v) => IndexSummary::Unsupported(f(s), v.clone()),
171+
IndexSummary::Invalid(s) => IndexSummary::Invalid(f(s)),
167172
}
168173
}
169174

170175
/// Extract the package id from any variant
171176
pub fn package_id(&self) -> PackageId {
172-
match self {
173-
IndexSummary::Candidate(sum)
174-
| IndexSummary::Yanked(sum)
175-
| IndexSummary::Offline(sum)
176-
| IndexSummary::Unsupported(sum, _) => sum.package_id(),
177-
}
177+
self.as_summary().package_id()
178178
}
179179

180180
/// Returns `true` if the index summary is [`Yanked`].
@@ -259,8 +259,52 @@ pub struct IndexPackage<'a> {
259259
pub v: Option<u32>,
260260
}
261261

262-
/// A dependency as encoded in the [`IndexPackage`] index JSON.
262+
impl IndexPackage<'_> {
263+
fn to_summary(&self, source_id: SourceId) -> CargoResult<Summary> {
264+
// ****CAUTION**** Please be extremely careful with returning errors, see
265+
// `IndexSummary::parse` for details
266+
let pkgid = PackageId::new(self.name.into(), self.vers.clone(), source_id);
267+
let deps = self
268+
.deps
269+
.iter()
270+
.map(|dep| dep.clone().into_dep(source_id))
271+
.collect::<CargoResult<Vec<_>>>()?;
272+
let mut features = self.features.clone();
273+
if let Some(features2) = &self.features2 {
274+
for (name, values) in features2 {
275+
features.entry(*name).or_default().extend(values);
276+
}
277+
}
278+
let mut summary = Summary::new(
279+
pkgid,
280+
deps,
281+
&features,
282+
self.links,
283+
self.rust_version.clone(),
284+
)?;
285+
summary.set_checksum(self.cksum.clone());
286+
Ok(summary)
287+
}
288+
}
289+
263290
#[derive(Deserialize, Serialize)]
291+
struct IndexPackageMinimum {
292+
name: InternedString,
293+
vers: Version,
294+
}
295+
296+
#[derive(Deserialize, Serialize, Default)]
297+
struct IndexPackageRustVersion {
298+
rust_version: Option<RustVersion>,
299+
}
300+
301+
#[derive(Deserialize, Serialize, Default)]
302+
struct IndexPackageV {
303+
v: Option<u32>,
304+
}
305+
306+
/// A dependency as encoded in the [`IndexPackage`] index JSON.
307+
#[derive(Deserialize, Serialize, Clone)]
264308
pub struct RegistryDependency<'a> {
265309
/// Name of the dependency. If the dependency is renamed, the original
266310
/// would be stored in [`RegistryDependency::package`].
@@ -706,32 +750,45 @@ impl IndexSummary {
706750
// between different versions that understand the index differently.
707751
// Make sure to consider the INDEX_V_MAX and CURRENT_CACHE_VERSION
708752
// values carefully when making changes here.
709-
let IndexPackage {
710-
name,
711-
vers,
712-
cksum,
713-
deps,
714-
mut features,
715-
features2,
716-
yanked,
717-
links,
718-
rust_version,
719-
v,
720-
} = serde_json::from_slice(line)?;
721-
let v = v.unwrap_or(1);
722-
tracing::trace!("json parsed registry {}/{}", name, vers);
723-
let pkgid = PackageId::new(name.into(), vers.clone(), source_id);
724-
let deps = deps
725-
.into_iter()
726-
.map(|dep| dep.into_dep(source_id))
727-
.collect::<CargoResult<Vec<_>>>()?;
728-
if let Some(features2) = features2 {
729-
for (name, values) in features2 {
730-
features.entry(name).or_default().extend(values);
753+
let index_summary = (|| {
754+
let index = serde_json::from_slice::<IndexPackage<'_>>(line)?;
755+
let summary = index.to_summary(source_id)?;
756+
Ok((index, summary))
757+
})();
758+
let (index, summary, valid) = match index_summary {
759+
Ok((index, summary)) => (index, summary, true),
760+
Err(err) => {
761+
let Ok(IndexPackageMinimum { name, vers }) =
762+
serde_json::from_slice::<IndexPackageMinimum>(line)
763+
else {
764+
// If we can't recover, prefer the original error
765+
return Err(err);
766+
};
767+
tracing::info!(
768+
"recoverying from failed parse of registry package {name}@{vers}: {err}"
769+
);
770+
let IndexPackageRustVersion { rust_version } =
771+
serde_json::from_slice::<IndexPackageRustVersion>(line).unwrap_or_default();
772+
let IndexPackageV { v } =
773+
serde_json::from_slice::<IndexPackageV>(line).unwrap_or_default();
774+
let index = IndexPackage {
775+
name,
776+
vers,
777+
rust_version,
778+
v,
779+
deps: Default::default(),
780+
features: Default::default(),
781+
features2: Default::default(),
782+
cksum: Default::default(),
783+
yanked: Default::default(),
784+
links: Default::default(),
785+
};
786+
let summary = index.to_summary(source_id)?;
787+
(index, summary, false)
731788
}
732-
}
733-
let mut summary = Summary::new(pkgid, deps, &features, links, rust_version)?;
734-
summary.set_checksum(cksum);
789+
};
790+
let v = index.v.unwrap_or(1);
791+
tracing::trace!("json parsed registry {}/{}", index.name, index.vers);
735792

736793
let v_max = if bindeps {
737794
INDEX_V_MAX + 1
@@ -741,7 +798,9 @@ impl IndexSummary {
741798

742799
if v_max < v {
743800
Ok(IndexSummary::Unsupported(summary, v))
744-
} else if yanked.unwrap_or(false) {
801+
} else if !valid {
802+
Ok(IndexSummary::Invalid(summary))
803+
} else if index.yanked.unwrap_or(false) {
745804
Ok(IndexSummary::Yanked(summary))
746805
} else {
747806
Ok(IndexSummary::Candidate(summary))

src/cargo/sources/registry/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,9 @@ impl<'gctx> Source for RegistrySource<'gctx> {
834834
summary.version()
835835
);
836836
}
837+
IndexSummary::Invalid(summary) => {
838+
tracing::debug!("invalid ({} {})", summary.name(), summary.version());
839+
}
837840
IndexSummary::Offline(summary) => {
838841
tracing::debug!("offline ({} {})", summary.name(), summary.version());
839842
}

0 commit comments

Comments
 (0)