Skip to content

Commit 38c5819

Browse files
committed
add a harder test.
1 parent 3e0a07f commit 38c5819

File tree

2 files changed

+121
-39
lines changed

2 files changed

+121
-39
lines changed

src/cargo/core/resolver/mod.rs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -601,30 +601,36 @@ fn activate(
601601
.link(candidate.summary.package_id(), parent.package_id()),
602602
)
603603
.push(dep.clone());
604-
let mut stack = vec![(parent.package_id(), dep.is_public())];
605-
while let Some((p, public)) = stack.pop() {
606-
match cx
607-
.public_dependency
608-
.entry(p)
609-
.or_default()
610-
.entry(candidate.summary.name())
611-
{
612-
im_rc::hashmap::Entry::Occupied(mut o) => {
613-
assert_eq!(o.get().0, candidate.summary.package_id());
614-
if o.get().1 {
615-
continue;
604+
let cs: Vec<PackageId> = cx
605+
.public_dependency
606+
.get(&candidate.summary.package_id())
607+
.iter()
608+
.flat_map(|x| x.values())
609+
.filter_map(|x| if x.1 { Some(&x.0) } else { None })
610+
.chain(Some(candidate.summary.package_id()).iter())
611+
.cloned()
612+
.collect();
613+
for c in cs {
614+
let mut stack = vec![(parent.package_id(), dep.is_public())];
615+
while let Some((p, public)) = stack.pop() {
616+
match cx.public_dependency.entry(p).or_default().entry(c.name()) {
617+
im_rc::hashmap::Entry::Occupied(mut o) => {
618+
assert_eq!(o.get().0, c);
619+
if o.get().1 {
620+
continue;
621+
}
622+
if public {
623+
o.insert((c, public));
624+
}
616625
}
617-
if public {
618-
o.insert((candidate.summary.package_id(), public));
626+
im_rc::hashmap::Entry::Vacant(v) => {
627+
v.insert((c, public));
619628
}
620629
}
621-
im_rc::hashmap::Entry::Vacant(v) => {
622-
v.insert((candidate.summary.package_id(), public));
623-
}
624-
}
625-
if public {
626-
for &(grand, ref d) in cx.parents.edges(&p) {
627-
stack.push((grand, d.iter().any(|x| x.is_public())));
630+
if public {
631+
for &(grand, ref d) in cx.parents.edges(&p) {
632+
stack.push((grand, d.iter().any(|x| x.is_public())));
633+
}
628634
}
629635
}
630636
}
@@ -763,23 +769,27 @@ impl RemainingCandidates {
763769
continue;
764770
}
765771
}
766-
767-
let mut stack = vec![(parent, dep.is_public())];
768-
while let Some((p, public)) = stack.pop() {
769-
// TODO: dont look at the same thing more then once
770-
if let Some(o) = cx
771-
.public_dependency
772-
.get(&p)
773-
.and_then(|x| x.get(&b.summary.name()))
774-
{
775-
if o.0 != b.summary.package_id() {
776-
// TODO: conflicting_prev_active
777-
continue 'main;
772+
for &t in cx
773+
.public_dependency
774+
.get(&b.summary.package_id())
775+
.iter()
776+
.flat_map(|x| x.values())
777+
.filter_map(|x| if x.1 { Some(&x.0) } else { None })
778+
.chain(Some(b.summary.package_id()).iter())
779+
{
780+
let mut stack = vec![(parent, dep.is_public())];
781+
while let Some((p, public)) = stack.pop() {
782+
// TODO: dont look at the same thing more then once
783+
if let Some(o) = cx.public_dependency.get(&p).and_then(|x| x.get(&t.name())) {
784+
if o.0 != t {
785+
// TODO: conflicting_prev_active
786+
continue 'main;
787+
}
778788
}
779-
}
780-
if public {
781-
for &(grand, ref d) in cx.parents.edges(&p) {
782-
stack.push((grand, d.iter().any(|x| x.is_public())));
789+
if public {
790+
for &(grand, ref d) in cx.parents.edges(&p) {
791+
stack.push((grand, d.iter().any(|x| x.is_public())));
792+
}
783793
}
784794
}
785795
}

tests/testsuite/resolve.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,12 @@ proptest! {
236236
}
237237

238238
#[test]
239-
fn public_dependency() {
239+
fn basic_public_dependency() {
240240
let reg = registry(vec![
241241
pkg!(("A", "0.1.0")),
242242
pkg!(("A", "0.2.0")),
243243
pkg!("B" => [dep_req_kind("A", "0.1", Kind::Normal, true)]),
244-
pkg!("C" => [dep_req("A", "*"), dep_req("B", "*")]),
244+
pkg!("C" => [dep("A"), dep("B")]),
245245
]);
246246

247247
let res = resolve_and_validated(&pkg_id("root"), vec![dep("C")], &reg).unwrap();
@@ -256,6 +256,78 @@ fn public_dependency() {
256256
);
257257
}
258258

259+
#[test]
260+
fn public_dependency_filling_in() {
261+
// The resolver has an optimization where if a candidate to resolve a dependency
262+
// has already bean activated then we skip looking at the candidates dependencies.
263+
// However, we have to be careful as the new path may make pub dependencies invalid.
264+
265+
// Triggering this case requires dependencies to be resolved in a specific order.
266+
// Fuzzing found this unintuitive case, that triggers this unfortunate order of operations:
267+
// 1. `d`'s dep on `c` is resolved
268+
// 2. `d`'s dep on `a` is resolved with `0.1.1`
269+
// 3. `c`'s dep on `b` is resolved with `0.0.2`
270+
// 4. `b`'s dep on `a` is resolved with `0.0.6` no pub dev conflict as `b` is private to `c`
271+
// 5. `d`'s dep on `b` is resolved with `0.0.2` triggering the optimization.
272+
// Do we notice that `d` has a pub dep conflict on `a`? Lets try it and see.
273+
let reg = registry(vec![
274+
pkg!(("a", "0.0.6")),
275+
pkg!(("a", "0.1.1")),
276+
pkg!(("b", "0.0.0") => [dep("bad")]),
277+
pkg!(("b", "0.0.1") => [dep("bad")]),
278+
pkg!(("b", "0.0.2") => [dep_req_kind("a", "=0.0.6", Kind::Normal, true)]),
279+
pkg!("c" => [dep_req("b", ">=0.0.1")]),
280+
pkg!("d" => [dep("c"), dep("a"), dep("b")]),
281+
]);
282+
283+
let res = resolve_and_validated(&pkg_id("root"), vec![dep("d")], &reg).unwrap();
284+
assert_same(
285+
&res,
286+
&names(&[
287+
("root", "1.0.0"),
288+
("d", "1.0.0"),
289+
("c", "1.0.0"),
290+
("b", "0.0.2"),
291+
("a", "0.0.6"),
292+
]),
293+
);
294+
}
295+
296+
#[test]
297+
fn public_dependency_filling_in_and_update() {
298+
// The resolver has an optimization where if a candidate to resolve a dependency
299+
// has already bean activated then we skip looking at the candidates dependencies.
300+
// However, we have to be careful as the new path may make pub dependencies invalid.
301+
302+
// Triggering this case requires dependencies to be resolved in a specific order.
303+
// Fuzzing found this unintuitive case, that triggers this unfortunate order of operations:
304+
// 1. `D`'s dep on `B` is resolved
305+
// 2. `D`'s dep on `C` is resolved
306+
// 3. `B`'s dep on `A` is resolved with `0.0.0`
307+
// 4. `C`'s dep on `B` triggering the optimization.
308+
// So did we add `A 0.0.0` to the deps `C` can see?
309+
// Or are we going to resolve `C`'s dep on `A` with `0.0.2`?
310+
// Lets try it and see.
311+
let reg = registry(vec![
312+
pkg!(("A", "0.0.0")),
313+
pkg!(("A", "0.0.2")),
314+
pkg!("B" => [dep_req_kind("A", "=0.0.0", Kind::Normal, true),]),
315+
pkg!("C" => [dep("A"),dep("B")]),
316+
pkg!("D" => [dep("B"),dep("C")]),
317+
]);
318+
let res = resolve_and_validated(&pkg_id("root"), vec![dep("D")], &reg).unwrap();
319+
assert_same(
320+
&res,
321+
&names(&[
322+
("root", "1.0.0"),
323+
("D", "1.0.0"),
324+
("C", "1.0.0"),
325+
("B", "1.0.0"),
326+
("A", "0.0.0"),
327+
]),
328+
);
329+
}
330+
259331
#[test]
260332
#[should_panic(expected = "assertion failed: !name.is_empty()")]
261333
fn test_dependency_with_empty_name() {

0 commit comments

Comments
 (0)