From 75a738585e645cebcec54f1e9a5aaaacb069fdda Mon Sep 17 00:00:00 2001 From: ychampion Date: Sat, 11 Jul 2026 18:29:51 +0000 Subject: [PATCH] Reject unknown toolchain file keys before they mask configuration Constraint: Older rustup versions cannot safely interpret future toolchain-file semantics.\nRejected: Warn and continue | Maintainer direction requires a hard error.\nConfidence: high\nScope-risk: narrow\nDirective: Keep the documented key-version table current when extending the schema.\nTested: Focused parser and CLI tests; strict all-target Clippy with test feature; rustfmt; diff check.\nNot-tested: All-feature Clippy exceeded local disk while compiling vendored TLS backends. --- doc/user-guide/src/overrides.md | 8 ++++++++ src/config.rs | 28 ++++++++++++++++++++++++++++ tests/suite/cli_rustup.rs | 25 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/doc/user-guide/src/overrides.md b/doc/user-guide/src/overrides.md index 849ed515d2..b68351cdc5 100644 --- a/doc/user-guide/src/overrides.md +++ b/doc/user-guide/src/overrides.md @@ -114,6 +114,14 @@ issues with dependencies. ### Toolchain file settings +| Setting | Supported since | +|---------|-----------------| +| `channel` | rustup 1.23.0 | +| `components` | rustup 1.23.0 | +| `targets` | rustup 1.23.0 | +| `path` | rustup 1.24.0 | +| `profile` | rustup 1.24.0 | + #### channel The `channel` setting specifies which [toolchain] to use. The value is a diff --git a/src/config.rs b/src/config.rs index 84e267ea42..2371eccaa4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -40,6 +40,7 @@ enum OverrideFileConfigError { } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] struct OverrideFile { toolchain: ToolchainSection, } @@ -51,6 +52,7 @@ impl OverrideFile { } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] struct ToolchainSection { channel: Option, path: Option, @@ -1413,4 +1415,30 @@ channel = nightly Ok(OverrideFileConfigError::Parsing) )); } + + #[test] + fn parse_toml_unknown_toolchain_key() { + let contents = r#"[toolchain] +channel = "nightly" +channnel = "stable" +"#; + + let result = Cfg::parse_override_file(contents, ParseMode::Both); + let error = format!("{:#}", result.unwrap_err()); + assert!(error.contains("unknown field `channnel`"), "{error}"); + } + + #[test] + fn parse_toml_unknown_top_level_key() { + let contents = r#"[toolchain] +channel = "nightly" + +[future] +key = "value" +"#; + + let result = Cfg::parse_override_file(contents, ParseMode::Both); + let error = format!("{:#}", result.unwrap_err()); + assert!(error.contains("unknown field `future`"), "{error}"); + } } diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 1001c0a253..bd3dcc752f 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -3968,6 +3968,31 @@ error: rustup could not choose a version of rustc to run, because one wasn't spe .is_ok(); } +#[tokio::test] +async fn rust_toolchain_toml_rejects_unknown_keys() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + let cwd = cx.config.current_dir(); + let toolchain_file = cwd.join("rust-toolchain.toml"); + raw::write_file( + &toolchain_file, + "[toolchain]\nchannel = \"nightly\"\nchannnel = \"stable\"", + ) + .unwrap(); + + cx.config + .expect(["rustup", "show", "active-toolchain"]) + .await + .extend_redactions([("[CWD]", &cwd)]) + .with_stderr(snapbox::str![[r#" +... +error: could not parse override file: '[CWD]/rust-toolchain.toml'[..] +... +unknown field `channnel`, expected one of `channel`, `path`, `components`, `targets`, `profile`[..] +... +"#]]) + .is_err(); +} + /// Ensures that `rust-toolchain.toml` files (with `.toml` extension) only allow TOML contents #[tokio::test] async fn only_toml_in_rust_toolchain_toml() {