Skip to content

fix(config): [env] relative paths definition #16957

Merged
weihanglo merged 2 commits intorust-lang:masterfrom
weihanglo:env-fix
May 5, 2026
Merged

fix(config): [env] relative paths definition #16957
weihanglo merged 2 commits intorust-lang:masterfrom
weihanglo:env-fix

Conversation

@weihanglo
Copy link
Copy Markdown
Member

@weihanglo weihanglo commented May 1, 2026

What does this PR try to resolve?

EnvConfigValue was wrapped in Value<T>,
which captured the table-level Definition for path resolution.

  1. In merge_helper,
    we merge all keys in a table but not the definition of the table itself:

    (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
    for (key, value) in mem::take(new) {
    match old.entry(key.clone()) {
    Entry::Occupied(mut entry) => {
    let new_def = value.definition().clone();
    let entry = entry.get_mut();
    parts.push(&key);
    entry.merge_helper(value, force, parts).with_context(|| {
    format!(
    "failed to merge key `{}` between \
    {} and {}",
    key,
    entry.definition(),
    new_def,
    )
    })?;
    }
    Entry::Vacant(entry) => {
    entry.insert(value);
    }
    };
    }
    }

    It is probably hard to defined the source definition of a merged struct though, perhaps people shouldn't access it to do anything meaningful.

  2. When deserializing a [env] struct with Value<T>,
    Value<T> delegate to ValueDeserializer to deserialize the value,
    which only takes the struct definition into account.
    And that turns out to be the stale table definition:

    fn deserialize_struct<V>(
    self,
    name: &'static str,
    fields: &'static [&'static str],
    visitor: V,
    ) -> Result<V::Value, Self::Error>
    where
    V: de::Visitor<'de>,
    {
    // Match on the magical struct name/field names that are passed in to
    // detect when we're deserializing `Value<T>`.
    //
    // See more comments in `value.rs` for the protocol used here.
    if name == value::NAME && fields == value::FIELDS {
    let source = ValueSource::with_deserializer(self)?;
    return visitor.visit_map(ValueDeserializer::new(source));
    }
    visitor.visit_map(ConfigMapAccess::new_struct(self, fields)?)
    }

  3. Unlike file config discovery,
    include is loaded before the including config,
    hence its definition is stale and is used.

This caused in #16954 that [env] struct got a stale old config source definition.

How to test and review this PR?

New regression tests should have covered cases in #16954, and also captured the non-canonicalized case which is a separate issue.

This fix doesn't touch the core problem that a merged Value<T> table holds a stale source definition. Holding either new or old doesn't seem correct to me, as it is a merged one. This is really a footgun.

Fixes #16954

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 1, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 1, 2026

r? @epage

rustbot has assigned @epage.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ehuss, @epage, @weihanglo
  • @ehuss, @epage, @weihanglo expanded to ehuss, epage, weihanglo
  • Random selection from ehuss, epage

@thomasw04
Copy link
Copy Markdown

https://github.com/rust-lang/cargo/pull/16957/changes#diff-b94be7179c0c88ba5f7e164aeaaaf3cf678dc65f5c4ec51a98ccadfdacd20beeR622-R636

Evaluates to foo/.cargo/../../outer, thus outer.

But now consider:

write_config_at(
        "foo/.cargo/config.toml",
        "
        include = ['../../inc.toml']
        ",
);
write_config_at(
        "inc.toml",
        "
        [env]
        MY_ENV = { value = 'outer', relative = true }
        ",
);

which will evaluate to foo/.cargo/../outer, thus foo/outer.

@rustbot rustbot added the A-configuration Area: cargo config files and env vars label May 2, 2026
@rustbot

This comment has been minimized.

Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
}
.join(&self.path);
let abs_path = paths::normalize_path(&abs_path);
Copy link
Copy Markdown
Member Author

@weihanglo weihanglo May 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thomasw04 commented in #16957 (comment):

https://github.com/rust-lang/cargo/pull/16957/changes#diff-b94be7179c0c88ba5f7e164aeaaaf3cf678dc65f5c4ec51a98ccadfdacd20beeR622-R636

Evaluates to foo/.cargo/../../outer, thus outer.

But now consider:

write_config_at(
        "foo/.cargo/config.toml",
        "
        include = ['../../inc.toml']
        ",
);
write_config_at(
        "inc.toml",
        "
        [env]
        MY_ENV = { value = 'outer', relative = true }
        ",
);

which will evaluate to foo/.cargo/../outer, thus foo/outer.

Nice catch! That is actually another bug. Fixed in 255bff3

View changes since the review

@thomasw04
Copy link
Copy Markdown

What behavior of relative paths do you assume/implement with this fix? Wouldn't it make sense to specify that in the docs as well, or is there already a specification I missed?

@weihanglo
Copy link
Copy Markdown
Member Author

What behavior of relative paths do you assume/implement with this fix? Wouldn't it make sense to specify that in the docs as well, or is there already a specification I missed?

https://doc.rust-lang.org/nightly/cargo/reference/config.html#config-relative-paths

file goes to parent.parent, otherwise current working directory

@thomasw04
Copy link
Copy Markdown

Aha, okay, thanks. So I assume this is then also applicable for included config files and also distinguishes where the path came from (or took precedence):

For config files, paths are relative to the parent directory of the directory where the config files were defined, no matter those files are from either the hierarchical probing or the --config option.

Comment thread tests/testsuite/config_include.rs Outdated
Comment thread tests/testsuite/config_include.rs
Copy link
Copy Markdown
Contributor

@epage epage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to merge when rebased on top of your other PR

View changes since this review

@rustbot

This comment has been minimized.

pull Bot pushed a commit to linyihai/cargo that referenced this pull request May 5, 2026
### What does this PR try to resolve?

Extracted from <rust-lang#16957>.

Without normalizing included config paths,
`Definition::root()` will do `parent().parent()`
on non-normalized paths containing `..` segments.
And that will remove `..` and result in wrong path resolution.

### How to test and review this PR?

Two tested are added to showcase the buggy behavior, especially
`env_relative_path_included_from_upper_level` which was a wrong path
resolution.
weihanglo added 2 commits May 4, 2026 21:41
`EnvConfigValue` was wrapped in `Value<T>`,
which captured the table-level `Definition` for path resolution.

1. In merge_helper,
   we merge all keys in a table but not the definition of the table itself:
   <https://github.com/rust-lang/cargo/blob/60960cbe4148295890ccd0f2890c2fb5dd9a0914/src/cargo/util/context/config_value.rs?plain=1#L177-L199>

   It is probably hard to defined the source definition of a merged struct though, perhaps people shouldn't access it to do anything meaningful.

2. When deserializing a `[env]` struct with `Value<T>`,
   `Value<T>` delegate to `ValueDeserializer` to deserialize the value,
   which only takes the struct definition into account.
   And that turns out to be the stale table definition:
   <https://github.com/rust-lang/cargo/blob/60960cbe4148295890ccd0f2890c2fb5dd9a0914/src/cargo/util/context/de.rs?plain=1#L119-L137>

3. Unlike file config discovery,
   `include` is loaded before the including config,
   hence its definition is stale and is used.

This caused in rust-lang#16954
that `[env]` struct got a stale old config source definition.

This fix doesn't touch the core problem that
A merged `Value<T>` table holds a stale source definition.
Holding either new or old doesn't seem correct to me,
as it is a merged one. This is really a footgun.
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 5, 2026

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@weihanglo weihanglo enabled auto-merge May 5, 2026 01:51
@weihanglo weihanglo added this pull request to the merge queue May 5, 2026
Merged via the queue into rust-lang:master with commit 230e325 May 5, 2026
46 of 58 checks passed
@weihanglo weihanglo deleted the env-fix branch May 5, 2026 04:43
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 5, 2026
rust-bors Bot pushed a commit to rust-lang/rust that referenced this pull request May 9, 2026
Update cargo submodule

13 commits in 4f9b52075316e9ced380c8fa492858048d5758b6..a343accce8526b128adc517d33348573d22920a3
2026-05-01 22:36:41 +0000 to 2026-05-08 22:41:35 +0000
- docs(guide): Fix a typo (rust-lang/cargo#16980)
- chore(deps): update msrv (3 versions) to v1.93 (rust-lang/cargo#16979)
- refactor(diag): Move lints to diagnostics (rust-lang/cargo#16978)
- refactor(lints): Pull out `unknown_lints` lint logic and `missing_lints_features` diagnostic logic (rust-lang/cargo#16976)
- refactor(lints): Move things out of `lints/mod.rs` (rust-lang/cargo#16975)
- test: cover search API redirects (rust-lang/cargo#16971)
- refactor(lints): Instrument lints for logging  (rust-lang/cargo#16972)
- docs: `.cargo-checksum.json` is not a security mechanism (rust-lang/cargo#16966)
- fix(vendor): add `$comment` to `.cargo-checksum.json` (rust-lang/cargo#16967)
- test: Fixed arg order in rustdoc json test (rust-lang/cargo#16968)
- fix(config): `[env]` relative paths definition  (rust-lang/cargo#16957)
- fix(config): normalize included config paths  (rust-lang/cargo#16964)
- Fix heading level of `build.warnings` documentation. (rust-lang/cargo#16961)

r? ghost
@rustbot rustbot added this to the 1.97.0 milestone May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-configuration Area: cargo config files and env vars

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Overwritten [env] paths are not relative to .cargo/config.toml's parent, when using config-includes

4 participants