Skip to content

Commit 28e1289

Browse files
committed
Teach cargo to failfast on recursive/corecursive aliases
Eg. [alias] test-1 = test-2 test-2 = test-3 test-3 = test-1 Previously it would stack overflow It pulls out non controversial bits from from #9768
1 parent 5a56cf2 commit 28e1289

File tree

2 files changed

+100
-3
lines changed

2 files changed

+100
-3
lines changed

src/bin/cargo/cli.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::anyhow;
12
use cargo::core::{features, CliUnstable};
23
use cargo::{self, drop_print, drop_println, CliResult, Config};
34
use clap::{AppSettings, Arg, ArgMatches};
@@ -123,7 +124,7 @@ Run with 'cargo -Z [FLAG] [SUBCOMMAND]'",
123124
// Global args need to be extracted before expanding aliases because the
124125
// clap code for extracting a subcommand discards global options
125126
// (appearing before the subcommand).
126-
let (expanded_args, global_args) = expand_aliases(config, args)?;
127+
let (expanded_args, global_args) = expand_aliases(config, args, vec![])?;
127128
let (cmd, subcommand_args) = match expanded_args.subcommand() {
128129
(cmd, Some(args)) => (cmd, args),
129130
_ => {
@@ -159,6 +160,7 @@ pub fn get_version_string(is_verbose: bool) -> String {
159160
fn expand_aliases(
160161
config: &mut Config,
161162
args: ArgMatches<'static>,
163+
mut already_expanded: Vec<String>,
162164
) -> Result<(ArgMatches<'static>, GlobalArgs), CliError> {
163165
if let (cmd, Some(args)) = args.subcommand() {
164166
match (
@@ -197,7 +199,21 @@ fn expand_aliases(
197199
let new_args = cli()
198200
.setting(AppSettings::NoBinaryName)
199201
.get_matches_from_safe(alias)?;
200-
let (expanded_args, _) = expand_aliases(config, new_args)?;
202+
203+
let (new_cmd, _) = new_args.subcommand();
204+
already_expanded.push(cmd.to_string());
205+
if already_expanded.contains(&new_cmd.to_string()) {
206+
// Crash if the aliases are corecursive / unresolvable
207+
return Err(anyhow!(
208+
"alias {} has unresolvable recursive definition: {} -> {}",
209+
already_expanded[0],
210+
already_expanded.join(" -> "),
211+
new_cmd,
212+
)
213+
.into());
214+
}
215+
216+
let (expanded_args, _) = expand_aliases(config, new_args, already_expanded)?;
201217
return Ok((expanded_args, global_args));
202218
}
203219
}

tests/testsuite/cargo_alias_config.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Tests for `[alias]` config command aliases.
22
3+
use std::env;
4+
5+
use cargo_test_support::tools::echo_subcommand;
36
use cargo_test_support::{basic_bin_manifest, project};
47

58
#[cargo_test]
@@ -50,7 +53,7 @@ fn alias_config() {
5053
}
5154

5255
#[cargo_test]
53-
fn recursive_alias() {
56+
fn dependent_alias() {
5457
let p = project()
5558
.file("Cargo.toml", &basic_bin_manifest("foo"))
5659
.file("src/main.rs", "fn main() {}")
@@ -73,6 +76,84 @@ fn recursive_alias() {
7376
.run();
7477
}
7578

79+
#[cargo_test]
80+
fn default_args_alias() {
81+
let echo = echo_subcommand();
82+
let p = project()
83+
.file("Cargo.toml", &basic_bin_manifest("foo"))
84+
.file("src/main.rs", "fn main() {}")
85+
.file(
86+
".cargo/config",
87+
r#"
88+
[alias]
89+
echo = "echo --flag1 --flag2"
90+
test-1 = "echo"
91+
build = "build --verbose"
92+
"#,
93+
)
94+
.build();
95+
96+
let mut paths: Vec<_> = env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect();
97+
paths.push(echo.target_debug_dir());
98+
let path = env::join_paths(paths).unwrap();
99+
100+
p.cargo("echo")
101+
.env("PATH", &path)
102+
.with_status(101)
103+
.with_stderr("error: alias echo has unresolvable recursive definition: echo -> echo")
104+
.run();
105+
106+
p.cargo("test-1")
107+
.env("PATH", &path)
108+
.with_status(101)
109+
.with_stderr(
110+
"error: alias test-1 has unresolvable recursive definition: test-1 -> echo -> echo",
111+
)
112+
.run();
113+
114+
// Builtins are not expanded by rule
115+
p.cargo("build")
116+
.with_stderr(
117+
"\
118+
[WARNING] user-defined alias `build` is ignored, because it is shadowed by a built-in command
119+
[COMPILING] foo v0.5.0 ([..])
120+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
121+
",
122+
)
123+
.run();
124+
}
125+
126+
#[cargo_test]
127+
fn corecursive_alias() {
128+
let p = project()
129+
.file("Cargo.toml", &basic_bin_manifest("foo"))
130+
.file("src/main.rs", "fn main() {}")
131+
.file(
132+
".cargo/config",
133+
r#"
134+
[alias]
135+
test-1 = "test-2 --flag1"
136+
test-2 = "test-3 --flag2"
137+
test-3 = "test-1 --flag3"
138+
"#,
139+
)
140+
.build();
141+
142+
p.cargo("test-1")
143+
.with_status(101)
144+
.with_stderr(
145+
"error: alias test-1 has unresolvable recursive definition: test-1 -> test-2 -> test-3 -> test-1",
146+
)
147+
.run();
148+
149+
p.cargo("test-2")
150+
.with_status(101)
151+
.with_stderr(
152+
"error: alias test-2 has unresolvable recursive definition: test-2 -> test-3 -> test-1 -> test-2",
153+
)
154+
.run();
155+
}
156+
76157
#[cargo_test]
77158
fn alias_list_test() {
78159
let p = project()

0 commit comments

Comments
 (0)