Skip to content

Commit d7ab4a6

Browse files
committed
cargo tree: Fix stack overflow on cyclic features.
1 parent 5907635 commit d7ab4a6

File tree

2 files changed

+142
-26
lines changed

2 files changed

+142
-26
lines changed

src/cargo/ops/tree/graph.rs

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -427,28 +427,32 @@ fn add_pkg(
427427
/// ```text
428428
/// from -Edge-> featname -Edge::Feature-> to
429429
/// ```
430+
///
431+
/// Returns a tuple `(missing, index)`.
432+
/// `missing` is true if this feature edge was already added.
433+
/// `index` is the index of the index in the graph of the `Feature` node.
430434
fn add_feature(
431435
graph: &mut Graph<'_>,
432436
name: InternedString,
433437
from: Option<usize>,
434438
to: usize,
435439
kind: EdgeKind,
436-
) -> usize {
440+
) -> (bool, usize) {
437441
// `to` *must* point to a package node.
438442
assert!(matches! {graph.nodes[to], Node::Package{..}});
439443
let node = Node::Feature {
440444
node_index: to,
441445
name,
442446
};
443-
let node_index = match graph.index.get(&node) {
444-
Some(idx) => *idx,
445-
None => graph.add_node(node),
447+
let (missing, node_index) = match graph.index.get(&node) {
448+
Some(idx) => (false, *idx),
449+
None => (true, graph.add_node(node)),
446450
};
447451
if let Some(from) = from {
448452
graph.edges[from].add_edge(kind, node_index);
449453
}
450454
graph.edges[node_index].add_edge(EdgeKind::Feature, to);
451-
node_index
455+
(missing, node_index)
452456
}
453457

454458
/// Adds nodes for features requested on the command-line for the given member.
@@ -480,7 +484,7 @@ fn add_cli_features(
480484
for fv in to_add {
481485
match fv {
482486
FeatureValue::Feature(feature) => {
483-
let index = add_feature(graph, feature, None, package_index, EdgeKind::Feature);
487+
let index = add_feature(graph, feature, None, package_index, EdgeKind::Feature).1;
484488
graph.cli_features.insert(index);
485489
}
486490
// This is enforced by CliFeatures.
@@ -511,10 +515,11 @@ fn add_cli_features(
511515
if is_optional {
512516
// Activate the optional dep on self.
513517
let index =
514-
add_feature(graph, dep_name, None, package_index, EdgeKind::Feature);
518+
add_feature(graph, dep_name, None, package_index, EdgeKind::Feature).1;
515519
graph.cli_features.insert(index);
516520
}
517-
let index = add_feature(graph, dep_feature, None, dep_index, EdgeKind::Feature);
521+
let index =
522+
add_feature(graph, dep_feature, None, dep_index, EdgeKind::Feature).1;
518523
graph.cli_features.insert(index);
519524
}
520525
}
@@ -571,21 +576,24 @@ fn add_feature_rec(
571576
for fv in fvs {
572577
match fv {
573578
FeatureValue::Feature(dep_name) => {
574-
let feat_index = add_feature(
579+
let (missing, feat_index) = add_feature(
575580
graph,
576581
*dep_name,
577582
Some(from),
578583
package_index,
579584
EdgeKind::Feature,
580585
);
581-
add_feature_rec(
582-
graph,
583-
resolve,
584-
*dep_name,
585-
package_id,
586-
feat_index,
587-
package_index,
588-
);
586+
// Don't recursive if the edge already exists to deal with cycles.
587+
if missing {
588+
add_feature_rec(
589+
graph,
590+
resolve,
591+
*dep_name,
592+
package_id,
593+
feat_index,
594+
package_index,
595+
);
596+
}
589597
}
590598
// Dependencies are already shown in the graph as dep edges. I'm
591599
// uncertain whether or not this might be confusing in some cases
@@ -628,21 +636,23 @@ fn add_feature_rec(
628636
EdgeKind::Feature,
629637
);
630638
}
631-
let feat_index = add_feature(
639+
let (missing, feat_index) = add_feature(
632640
graph,
633641
*dep_feature,
634642
Some(from),
635643
dep_index,
636644
EdgeKind::Feature,
637645
);
638-
add_feature_rec(
639-
graph,
640-
resolve,
641-
*dep_feature,
642-
dep_pkg_id,
643-
feat_index,
644-
dep_index,
645-
);
646+
if missing {
647+
add_feature_rec(
648+
graph,
649+
resolve,
650+
*dep_feature,
651+
dep_pkg_id,
652+
feat_index,
653+
dep_index,
654+
);
655+
}
646656
}
647657
}
648658
}

tests/testsuite/tree.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,3 +1831,109 @@ foo v0.1.0 ([..]/foo)
18311831
.with_status(101)
18321832
.run();
18331833
}
1834+
1835+
#[cargo_test]
1836+
fn cyclic_features() {
1837+
// Check for stack overflow with cyclic features (oops!).
1838+
let p = project()
1839+
.file(
1840+
"Cargo.toml",
1841+
r#"
1842+
[package]
1843+
name = "foo"
1844+
version = "1.0.0"
1845+
1846+
[features]
1847+
a = ["b"]
1848+
b = ["a"]
1849+
default = ["a"]
1850+
"#,
1851+
)
1852+
.file("src/lib.rs", "")
1853+
.build();
1854+
1855+
p.cargo("tree -e features")
1856+
.with_stdout("foo v1.0.0 ([ROOT]/foo)")
1857+
.run();
1858+
1859+
p.cargo("tree -e features -i foo")
1860+
.with_stdout(
1861+
"\
1862+
foo v1.0.0 ([ROOT]/foo)
1863+
├── foo feature \"a\"
1864+
│ ├── foo feature \"b\"
1865+
│ │ └── foo feature \"a\" (*)
1866+
│ └── foo feature \"default\" (command-line)
1867+
├── foo feature \"b\" (*)
1868+
└── foo feature \"default\" (command-line)
1869+
",
1870+
)
1871+
.run();
1872+
}
1873+
1874+
#[cargo_test]
1875+
fn dev_dep_cycle_with_feature() {
1876+
// Cycle with features and a dev-dependency.
1877+
let p = project()
1878+
.file(
1879+
"Cargo.toml",
1880+
r#"
1881+
[package]
1882+
name = "foo"
1883+
version = "1.0.0"
1884+
1885+
[dev-dependencies]
1886+
bar = { path = "bar" }
1887+
1888+
[features]
1889+
a = ["bar/feat1"]
1890+
"#,
1891+
)
1892+
.file("src/lib.rs", "")
1893+
.file(
1894+
"bar/Cargo.toml",
1895+
r#"
1896+
[package]
1897+
name = "bar"
1898+
version = "1.0.0"
1899+
1900+
[dependencies]
1901+
foo = { path = ".." }
1902+
1903+
[features]
1904+
feat1 = ["foo/a"]
1905+
"#,
1906+
)
1907+
.file("bar/src/lib.rs", "")
1908+
.build();
1909+
1910+
p.cargo("tree -e features --features a")
1911+
.with_stdout(
1912+
"\
1913+
foo v1.0.0 ([ROOT]/foo)
1914+
[dev-dependencies]
1915+
└── bar feature \"default\"
1916+
└── bar v1.0.0 ([ROOT]/foo/bar)
1917+
└── foo feature \"default\" (command-line)
1918+
└── foo v1.0.0 ([ROOT]/foo) (*)
1919+
",
1920+
)
1921+
.run();
1922+
1923+
p.cargo("tree -e features --features a -i foo")
1924+
.with_stdout(
1925+
"\
1926+
foo v1.0.0 ([ROOT]/foo)
1927+
├── foo feature \"a\" (command-line)
1928+
│ └── bar feature \"feat1\"
1929+
│ └── foo feature \"a\" (command-line) (*)
1930+
└── foo feature \"default\" (command-line)
1931+
└── bar v1.0.0 ([ROOT]/foo/bar)
1932+
├── bar feature \"default\"
1933+
│ [dev-dependencies]
1934+
│ └── foo v1.0.0 ([ROOT]/foo) (*)
1935+
└── bar feature \"feat1\" (*)
1936+
",
1937+
)
1938+
.run();
1939+
}

0 commit comments

Comments
 (0)