Skip to content

Commit 531958b

Browse files
committed
Auto merge of #9791 - nipunn1313:cargo_crash_alias_loop, r=alexcrichton
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 Pulled out non controversial bits from from #9768
2 parents e8a78cc + 28e1289 commit 531958b

File tree

4 files changed

+128
-25
lines changed

4 files changed

+128
-25
lines changed

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Common executables that can be reused by various tests.
22
3-
use crate::{basic_manifest, paths, project};
3+
use crate::{basic_manifest, paths, project, Project};
44
use lazy_static::lazy_static;
55
use std::path::PathBuf;
66
use std::sync::Mutex;
@@ -38,3 +38,22 @@ pub fn echo_wrapper() -> PathBuf {
3838
*lock = Some(path.clone());
3939
path
4040
}
41+
42+
/// Returns a project which builds a cargo-echo simple subcommand
43+
pub fn echo_subcommand() -> Project {
44+
let p = project()
45+
.at("cargo-echo")
46+
.file("Cargo.toml", &basic_manifest("cargo-echo", "0.0.1"))
47+
.file(
48+
"src/main.rs",
49+
r#"
50+
fn main() {
51+
let args: Vec<_> = ::std::env::args().skip(1).collect();
52+
println!("{}", args.join(" "));
53+
}
54+
"#,
55+
)
56+
.build();
57+
p.cargo("build").run();
58+
p
59+
}

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()

tests/testsuite/cargo_command.rs

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ use std::path::{Path, PathBuf};
77
use std::process::Stdio;
88
use std::str;
99

10-
use cargo_test_support::cargo_process;
11-
use cargo_test_support::paths;
1210
use cargo_test_support::registry::Package;
13-
use cargo_test_support::{basic_bin_manifest, basic_manifest, cargo_exe, project, project_in_home};
11+
use cargo_test_support::tools::echo_subcommand;
12+
use cargo_test_support::{
13+
basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home,
14+
};
1415

1516
fn path() -> Vec<PathBuf> {
1617
env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
@@ -283,31 +284,17 @@ fn cargo_subcommand_env() {
283284

284285
#[cargo_test]
285286
fn cargo_subcommand_args() {
286-
let p = project()
287-
.at("cargo-foo")
288-
.file("Cargo.toml", &basic_manifest("cargo-foo", "0.0.1"))
289-
.file(
290-
"src/main.rs",
291-
r#"
292-
fn main() {
293-
let args: Vec<_> = ::std::env::args().collect();
294-
println!("{}", args.join(" "));
295-
}
296-
"#,
297-
)
298-
.build();
299-
300-
p.cargo("build").run();
301-
let cargo_foo_bin = p.bin("cargo-foo");
287+
let p = echo_subcommand();
288+
let cargo_foo_bin = p.bin("cargo-echo");
302289
assert!(cargo_foo_bin.is_file());
303290

304291
let mut path = path();
305292
path.push(p.target_debug_dir());
306293
let path = env::join_paths(path.iter()).unwrap();
307294

308-
cargo_process("foo bar -v --help")
295+
cargo_process("echo bar -v --help")
309296
.env("PATH", &path)
310-
.with_stdout("[CWD]/cargo-foo/target/debug/cargo-foo[EXE] foo bar -v --help")
297+
.with_stdout("echo bar -v --help")
311298
.run();
312299
}
313300

0 commit comments

Comments
 (0)