Skip to content

Commit 06f37a0

Browse files
committed
Forbid cyclic dependencies
Re-jigger some code in the resolver and refactor a little bit to get the ordering of checks right to forbid cyclic dependencies from ever reaching the backend. Closes #834
1 parent a40d3b0 commit 06f37a0

File tree

8 files changed

+112
-67
lines changed

8 files changed

+112
-67
lines changed

src/cargo/core/registry.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ pub trait Registry {
1515

1616
impl Registry for Vec<Summary> {
1717
fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
18-
debug!("querying for {:?}, summaries={:?}", dep,
19-
self.iter().map(|s| s.get_package_id()).collect::<Vec<_>>());
20-
2118
Ok(self.iter().filter(|summary| dep.matches(*summary))
2219
.map(|summary| summary.clone()).collect())
2320
}
@@ -83,8 +80,7 @@ impl<'a, 'b> PackageRegistry<'a, 'b> {
8380
}
8481

8582
pub fn get(&mut self, package_ids: &[PackageId]) -> CargoResult<Vec<Package>> {
86-
log!(5, "getting packags; sources={}; ids={:?}", self.sources.len(),
87-
package_ids);
83+
log!(5, "getting packages; sources={}", self.sources.len());
8884

8985
// TODO: Only call source with package ID if the package came from the
9086
// source

src/cargo/core/resolver/mod.rs

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -131,28 +131,44 @@ struct Context {
131131
/// Builds the list of all packages required to build the first argument.
132132
pub fn resolve(summary: &Summary, method: Method,
133133
registry: &mut Registry) -> CargoResult<Resolve> {
134-
log!(5, "resolve; summary={:?}", summary);
134+
log!(5, "resolve; summary={}", summary.get_package_id());
135+
let summary = Rc::new(summary.clone());
135136

136-
let mut cx = Box::new(Context {
137+
let cx = Box::new(Context {
137138
resolve: Resolve::new(summary.get_package_id().clone()),
138139
activations: HashMap::new(),
139140
visited: Rc::new(RefCell::new(HashSet::new())),
140141
});
141142
let _p = profile::start(format!("resolving: {:?}", summary));
142-
cx.activations.insert((summary.get_name().to_string(),
143-
summary.get_source_id().clone()),
144-
vec![Rc::new(summary.clone())]);
145-
match try!(activate(cx, registry, summary, method)) {
146-
Ok(cx) => Ok(cx.resolve),
143+
match try!(activate(cx, registry, &summary, method)) {
144+
Ok(cx) => {
145+
debug!("resolved: {:?}", cx.resolve);
146+
Ok(cx.resolve)
147+
}
147148
Err(e) => Err(e),
148149
}
149150
}
150151

151152
fn activate(mut cx: Box<Context>,
152153
registry: &mut Registry,
153-
parent: &Summary,
154+
parent: &Rc<Summary>,
154155
method: Method)
155156
-> CargoResult<CargoResult<Box<Context>>> {
157+
// Dependency graphs are required to be a DAG, so we keep a set of
158+
// packages we're visiting and bail if we hit a dupe.
159+
let id = parent.get_package_id();
160+
if !cx.visited.borrow_mut().insert(id.clone()) {
161+
return Err(human(format!("cyclic package dependency: package `{}` \
162+
depends on itself", id)))
163+
}
164+
165+
// If we're already activated, then that was easy!
166+
if flag_activated(&mut *cx, parent, &method) {
167+
cx.visited.borrow_mut().remove(id);
168+
return Ok(Ok(cx))
169+
}
170+
debug!("activating {}", parent.get_package_id());
171+
156172
// Extracting the platform request.
157173
let platform = match method {
158174
Method::Required(_, _, _, platform) => platform,
@@ -162,7 +178,7 @@ fn activate(mut cx: Box<Context>,
162178
// First, figure out our set of dependencies based on the requsted set of
163179
// features. This also calculates what features we're going to enable for
164180
// our own dependencies.
165-
let deps = try!(resolve_features(&mut *cx, parent, method));
181+
let deps = try!(resolve_features(&mut *cx, &**parent, method));
166182

167183
// Next, transform all dependencies into a list of possible candidates which
168184
// can satisfy that dependency.
@@ -185,7 +201,40 @@ fn activate(mut cx: Box<Context>,
185201
a.len().cmp(&b.len())
186202
});
187203

188-
activate_deps(cx, registry, parent, platform, deps.as_slice(), 0)
204+
Ok(match try!(activate_deps(cx, registry, &**parent, platform, &*deps, 0)) {
205+
Ok(cx) => {
206+
cx.visited.borrow_mut().remove(parent.get_package_id());
207+
Ok(cx)
208+
}
209+
Err(e) => Err(e),
210+
})
211+
}
212+
213+
// Activate this summary by inserting it into our list of known activations.
214+
//
215+
// Returns if this summary with the given method is already activated.
216+
fn flag_activated(cx: &mut Context,
217+
summary: &Rc<Summary>,
218+
method: &Method) -> bool {
219+
let id = summary.get_package_id();
220+
let key = (id.get_name().to_string(), id.get_source_id().clone());
221+
let prev = cx.activations.entry(key).get().unwrap_or_else(|e| {
222+
e.insert(Vec::new())
223+
});
224+
if !prev.iter().any(|c| c == summary) {
225+
cx.resolve.graph.add(id.clone(), &[]);
226+
prev.push(summary.clone());
227+
return false
228+
}
229+
debug!("checking if {} is already activated", summary.get_package_id());
230+
let features = match *method {
231+
Method::Required(_, features, _, _) => features,
232+
Method::Everything => return false,
233+
};
234+
match cx.resolve.features(id) {
235+
Some(prev) => features.iter().all(|f| prev.contains(f)),
236+
None => features.len() == 0,
237+
}
189238
}
190239

191240
fn activate_deps<'a>(cx: Box<Context>,
@@ -237,50 +286,20 @@ fn activate_deps<'a>(cx: Box<Context>,
237286
log!(5, "{}[{}]>{} trying {}", parent.get_name(), cur, dep.get_name(),
238287
candidate.get_version());
239288
let mut my_cx = cx.clone();
240-
let early_return = {
241-
let my_cx = &mut *my_cx;
242-
my_cx.resolve.graph.link(parent.get_package_id().clone(),
243-
candidate.get_package_id().clone());
244-
let prev = match my_cx.activations.entry(key.clone()) {
245-
Occupied(e) => e.into_mut(),
246-
Vacant(e) => e.insert(Vec::new()),
247-
};
248-
if prev.iter().any(|c| c == candidate) {
249-
match cx.resolve.features(candidate.get_package_id()) {
250-
Some(prev_features) => {
251-
features.iter().all(|f| prev_features.contains(f))
252-
}
253-
None => features.len() == 0,
254-
}
255-
} else {
256-
my_cx.resolve.graph.add(candidate.get_package_id().clone(), &[]);
257-
prev.push(candidate.clone());
258-
false
259-
}
260-
};
289+
my_cx.resolve.graph.link(parent.get_package_id().clone(),
290+
candidate.get_package_id().clone());
261291

262-
let my_cx = if early_return {
263-
my_cx
264-
} else {
265-
// Dependency graphs are required to be a DAG. Non-transitive
266-
// dependencies (dev-deps), however, can never introduce a cycle, so
267-
// we skip them.
268-
if dep.is_transitive() &&
269-
!cx.visited.borrow_mut().insert(candidate.get_package_id().clone()) {
270-
return Err(human(format!("cyclic package dependency: package `{}` \
271-
depends on itself",
272-
candidate.get_package_id())))
273-
}
274-
let my_cx = try!(activate(my_cx, registry, &**candidate, method));
275-
if dep.is_transitive() {
276-
cx.visited.borrow_mut().remove(candidate.get_package_id());
277-
}
278-
match my_cx {
279-
Ok(cx) => cx,
280-
Err(e) => { last_err = Some(e); continue }
281-
}
292+
// If we hit an intransitive dependency then clear out the visitation
293+
// list as we can't induce a cycle through transitive dependencies.
294+
if !dep.is_transitive() {
295+
my_cx.visited.borrow_mut().clear();
296+
}
297+
let my_cx = match try!(activate(my_cx, registry, candidate, method)) {
298+
Ok(cx) => cx,
299+
Err(e) => { last_err = Some(e); continue }
282300
};
283-
match try!(activate_deps(my_cx, registry, parent, platform, deps, cur + 1)) {
301+
match try!(activate_deps(my_cx, registry, parent, platform, deps,
302+
cur + 1)) {
284303
Ok(cx) => return Ok(Ok(cx)),
285304
Err(e) => { last_err = Some(e); }
286305
}

src/cargo/ops/cargo_compile.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ pub fn compile_pkg(package: &Package, options: &CompileOptions)
126126
(packages, resolved_with_overrides, registry.move_sources())
127127
};
128128

129-
debug!("packages={:?}", packages);
130-
131129
let to_build = match spec {
132130
Some(spec) => {
133131
let pkgid = try!(resolve_with_overrides.query(spec));

src/cargo/ops/cargo_read_manifest.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ pub fn read_packages(path: &Path, source_id: &SourceId, config: &Config)
6262
if all_packages.is_empty() {
6363
Err(human(format!("Could not find Cargo.toml in `{}`", path.display())))
6464
} else {
65-
log!(5, "all packages: {:?}", all_packages);
6665
Ok(all_packages.into_iter().collect())
6766
}
6867
}

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,7 @@ pub fn compile_targets<'a, 'b>(env: &str,
129129
return Ok(Compilation::new(pkg))
130130
}
131131

132-
debug!("compile_targets; targets={:?}; pkg={}; deps={:?}", targets, pkg,
133-
deps);
132+
debug!("compile_targets: {}", pkg);
134133

135134
try!(links::validate(deps));
136135

@@ -210,7 +209,7 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
210209
compiled: bool,
211210
cx: &mut Context<'a, 'b>,
212211
jobs: &mut JobQueue<'a, 'b>) -> CargoResult<()> {
213-
debug!("compile_pkg; pkg={}; targets={:?}", pkg, targets);
212+
debug!("compile_pkg; pkg={}", pkg);
214213
let _p = profile::start(format!("preparing: {}", pkg));
215214

216215
// Packages/targets which are actually getting compiled are constructed into

src/cargo/util/graph.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,15 @@ impl<N: Eq + Hash<Hasher> + Clone> Graph<N> {
7272
}
7373
}
7474

75-
impl<N: fmt::Debug + Eq + Hash<Hasher>> fmt::Debug for Graph<N> {
75+
impl<N: fmt::Display + Eq + Hash<Hasher>> fmt::Debug for Graph<N> {
7676
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
7777
try!(writeln!(fmt, "Graph {{"));
7878

7979
for (n, e) in self.nodes.iter() {
80-
try!(writeln!(fmt, " - {:?}", n));
80+
try!(writeln!(fmt, " - {}", n));
8181

8282
for n in e.iter() {
83-
try!(writeln!(fmt, " - {:?}", n));
83+
try!(writeln!(fmt, " - {}", n));
8484
}
8585
}
8686

tests/test_cargo_compile.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,10 @@ test!(self_dependency {
754754
"#)
755755
.file("src/test.rs", "fn main() {}");
756756
assert_that(p.cargo_process("build"),
757-
execs().with_status(0));
757+
execs().with_status(101)
758+
.with_stderr("\
759+
cyclic package dependency: package `test v0.0.0 ([..])` depends on itself
760+
"));
758761
});
759762

760763
test!(ignore_broken_symlinks {
@@ -1617,3 +1620,33 @@ Caused by:
16171620
[..]
16181621
"));
16191622
});
1623+
1624+
test!(cyclic_deps_rejected {
1625+
let p = project("foo")
1626+
.file("Cargo.toml", r#"
1627+
[package]
1628+
name = "foo"
1629+
version = "0.0.1"
1630+
authors = []
1631+
1632+
[dependencies.a]
1633+
path = "a"
1634+
"#)
1635+
.file("src/lib.rs", "")
1636+
.file("a/Cargo.toml", r#"
1637+
[package]
1638+
name = "a"
1639+
version = "0.0.1"
1640+
authors = []
1641+
1642+
[dependencies.foo]
1643+
path = ".."
1644+
"#)
1645+
.file("a/src/lib.rs", "");
1646+
1647+
assert_that(p.cargo_process("build").arg("-v"),
1648+
execs().with_status(101)
1649+
.with_stderr("\
1650+
cyclic package dependency: package `foo v0.0.1 ([..])` depends on itself
1651+
"));
1652+
});

tests/test_cargo_freshness.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ test!(modifying_and_moving {
2828
assert_that(p.process(cargo_dir().join("cargo")).arg("build"),
2929
execs().with_status(0).with_stdout(""));
3030
p.root().move_into_the_past().unwrap();
31+
p.root().join("target").move_into_the_past().unwrap();
3132

3233
File::create(&p.root().join("src/a.rs")).write_str("fn main() {}").unwrap();
3334
assert_that(p.process(cargo_dir().join("cargo")).arg("build"),

0 commit comments

Comments
 (0)