Skip to content

miri: require (almost) all 1-ZST arguments to be actually passed#156085

Open
RalfJung wants to merge 1 commit intorust-lang:mainfrom
RalfJung:miri-ignored-args
Open

miri: require (almost) all 1-ZST arguments to be actually passed#156085
RalfJung wants to merge 1 commit intorust-lang:mainfrom
RalfJung:miri-ignored-args

Conversation

@RalfJung
Copy link
Copy Markdown
Member

@RalfJung RalfJung commented May 2, 2026

View all comments

We can't ignore all of them since the compiler itself relies on non-capturing closure arguments being ignored.

Fixes rust-lang/miri#4993

Cc @folkertdev since it also changes the checks for variadics.

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 2, 2026

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred to the CTFE machinery

cc @oli-obk, @lcnr

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 2, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 2, 2026

r? @nikomatsakis

rustbot has assigned @nikomatsakis.
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: compiler, mir
  • compiler, mir expanded to 73 candidates
  • Random selection from 21 candidates

@saethlin
Copy link
Copy Markdown
Member

saethlin commented May 2, 2026

@bors try @rust-timer queue

@rust-timer
Copy link
Copy Markdown
Collaborator

Awaiting bors try build completion.

@rustbot label: +S-waiting-on-perf

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 2, 2026

⌛ Trying commit 403e9b0 with merge 583ebe0

To cancel the try build, run the command @bors try cancel.

Workflow: https://github.com/rust-lang/rust/actions/runs/25256422338

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label May 2, 2026
rust-bors Bot pushed a commit that referenced this pull request May 2, 2026
miri: require (almost) all 1-ZST arguments to be actually passed
Comment thread compiler/rustc_const_eval/src/interpret/call.rs Outdated
@RalfJung RalfJung force-pushed the miri-ignored-args branch from 403e9b0 to e7467e2 Compare May 2, 2026 16:30
Comment on lines +271 to +278
/// Determine whether this argument can be safely ignored.
/// We have to ignore closures that didn't capture anything to ensure that their
/// coercion to function pointers is sound. We don't want to ignore anything else
/// since we don't actually guarantee that anything is ever ignored.
fn is_ignored_arg(arg: &ArgAbi<'tcx, Ty<'tcx>>) -> bool {
let can_ignore = matches!(arg.layout.ty.kind(), ty::Closure (_def, closure_args) if {
closure_args.as_closure().upvar_tys().is_empty()
});
Copy link
Copy Markdown
Member

@saethlin saethlin May 2, 2026

Choose a reason for hiding this comment

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

I feel like this should mention that normally ZSTs are ignored. But maybe you disagree and think that's base knowledge for Rust argument passing, which would be fine with me.

The connection to closure coercion is turning my brain into a pretzel. Does this mean that we still ignore the argument in this?

fn func<T>(_: T) {}

func(|| {});

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have extended the comment, does that help?

The connection to closure coercion is turning my brain into a pretzel. Does this mean that we still ignore the argument in this?

Yes. That's unfortunate, ideally we'd still report UB if that argument gets ignored. However, I'm not sure it's worth trying to restrict this. It seems non-trivial to figure out whether the callee is actually a closure. But maybe you have a good idea for how to do that?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Conceptually, I think of the closure captures being implicitly passed to closure calls, not literally self. But of course the FnOnce trait takes self so I suspect trying to implement my mental model would be really fiddly, if even possible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In MIR closures are just normal functions -- closure conversion already happened at that point. So we can't really treat them as anything else.

The best we could do is check the DefId of the callee and figure out if it is a closure, and then treat the first parameter different.

@RalfJung RalfJung force-pushed the miri-ignored-args branch from e7467e2 to 3427f02 Compare May 2, 2026 16:58
@saethlin
Copy link
Copy Markdown
Member

saethlin commented May 2, 2026

The new explanation is much easier for me to follow. Thanks.

r? saethlin
@bors r+

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 2, 2026

📌 Commit 3427f02 has been approved by saethlin

It is now in the queue for this repository.

@rust-bors rust-bors Bot added the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label May 2, 2026
@rust-bors rust-bors Bot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 2, 2026
@saethlin saethlin removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label May 2, 2026
@saethlin
Copy link
Copy Markdown
Member

saethlin commented May 2, 2026

@bors try-

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 2, 2026

Unknown command "try-". Run @bors help to see available commands.

@saethlin
Copy link
Copy Markdown
Member

saethlin commented May 2, 2026

@bors try cancel

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 2, 2026

@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the miri-ignored-args branch from 3427f02 to ec802b2 Compare May 2, 2026 17:44
@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels May 2, 2026
@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 2, 2026

⚠️ A new commit ec802b2e72a59eda62cefbb9c0de81695a860de8 was pushed.

This pull request was unapproved.

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 2, 2026

📌 Commit ec802b2 has been approved by saethlin

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 2, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request May 2, 2026
…hlin

miri: require (almost) all 1-ZST arguments to be actually passed

We can't ignore *all* of them since the compiler itself relies on non-capturing closure arguments being ignored.

Fixes rust-lang/miri#4993

Cc @folkertdev since it also changes the checks for variadics.
@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels May 2, 2026
@rust-bors

This comment has been minimized.

@theemathas
Copy link
Copy Markdown
Contributor

This PR causes the following code to be UB according to Miri. I assume that this is a bug?

use std::mem::transmute;

#[repr(transparent)]
struct Wrap<T>(T);

fn foo<T>(_: T) {}

fn bar<T>(value: T) {
    let f = unsafe { transmute::<fn(T), fn(Wrap<T>)>(foo::<T>) };
    f(Wrap(value));
}

fn main() {
    bar(|| {});
}
Error output
error: Undefined Behavior: calling a function with more arguments than it expected
 --> src\main.rs:6:1
  |
6 | fn foo<T>(_: T) {}
  | ^^^^^^^^^^^^^^^ Undefined Behavior occurred here
  |
  = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
  = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
  = note: stack backtrace:
          0: foo::<{closure@src\main.rs:14:9: 14:11}>
              at src\main.rs:6:1: 6:16
          1: bar::<{closure@src\main.rs:14:9: 14:11}>
              at src\main.rs:10:5: 10:19
          2: main
              at src\main.rs:14:5: 14:15

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

@RalfJung RalfJung force-pushed the miri-ignored-args branch from ec802b2 to 9bb8594 Compare May 3, 2026 08:03
@rustbot

This comment has been minimized.

@RalfJung
Copy link
Copy Markdown
Member Author

RalfJung commented May 3, 2026

No that was deliberate. Non-capturing closures being ignored is an internal implementation detail of rustc, so I don't think repr(transparent) has to preserve it.

@theemathas
Copy link
Copy Markdown
Contributor

@RalfJung So, a repr(transparent) struct might not be ABI-compatible with the thing inside the struct? Isn't that the whole point of repr(transparent)?

@RalfJung
Copy link
Copy Markdown
Member Author

RalfJung commented May 3, 2026

Oh I think I see what you mean, it's not about relying on them being ignored...
(Putting any non-zero amount of comments or explanation next to an example helps tremendously. :)

@theemathas
Copy link
Copy Markdown
Contributor

Sorry. I thought that was self-explanatory. Apparently not. I'll add comments in my future code snippets.

@RalfJung RalfJung force-pushed the miri-ignored-args branch from 9bb8594 to 4729dc8 Compare May 3, 2026 09:25
@RalfJung
Copy link
Copy Markdown
Member Author

RalfJung commented May 3, 2026

I'm not sure how to even implement this check. :/

We can easily restrict this to when the callee is a closure, which seems to always be the case for the target of these coercions. But beyond that... we can't do it based on the types because then we have to ignore all 1-ZST due to the usual "repr(transparent) but it's not clear which field we mean". We could easily say that on the callee side we just skip the first argument, but then it becomes tricky to line up the caller's arguments with the callee's.

So what I just pushed doesn't fix the problem for closure calls.

@RalfJung RalfJung force-pushed the miri-ignored-args branch 2 times, most recently from 52f69b7 to ae6448f Compare May 3, 2026 10:13
@RalfJung
Copy link
Copy Markdown
Member Author

RalfJung commented May 3, 2026

Okay I think I found a check that works. This is a complete rewrite of the PR, so needs another review.
@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 3, 2026
@theemathas
Copy link
Copy Markdown
Contributor

I found this strange behavior. Is this intended?

#![feature(fn_traits, unboxed_closures)]

use std::mem::transmute;

#[repr(align(4))]
struct Zst;

fn foo<F: FnOnce()>(_: F) {
    // Calls the given F FnOnce, but passing an over-aligned ZST instead of the closure / function item
    let f = unsafe { transmute::<extern "rust-call" fn(F, ()), fn(Zst)>(F::call_once) };
    f(Zst)
}

fn main() {
    // This is fine
    foo(move || {
        println!("non-capturing closure");
    });
    // This is UB
    foo(function);
}

fn function() {
    println!("function");
}
Output
non-capturing closure
error: Undefined Behavior: calling a function whose parameter #1 has type fn() {function} passing argument of type Zst
   --> D:\rust\library\core\src\ops\function.rs:250:5
    |
250 |     extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
    = help: if you think this code should be accepted anyway, please report an issue with Miri
    = note: stack backtrace:
            0: <fn() {function} as std::ops::FnOnce<()>>::call_once - shim(fn() {function})
                at D:\rust\library\core\src\ops\function.rs:250:5: 250:71
            1: foo::<fn() {function}>
                at src\main.rs:11:5: 11:11
            2: main
                at src\main.rs:20:5: 20:18

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

@RalfJung
Copy link
Copy Markdown
Member Author

RalfJung commented May 3, 2026

Nice example. :)
Not quite "intended" but maybe "acceptable"?
I could further restrict this to only ever skip 1-ZST. I guess for now that would hide all such oddities since we consider all 1-ZST mutually compatible, but that's something we may have to limit in the future due to C ABI oddities and CFI.

@RalfJung RalfJung force-pushed the miri-ignored-args branch from ae6448f to 7aad1ad Compare May 3, 2026 11:56
@RalfJung RalfJung force-pushed the miri-ignored-args branch from 7aad1ad to 2763bb3 Compare May 4, 2026 05:46
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 4, 2026

This PR was rebased onto a different main 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.

@theemathas
Copy link
Copy Markdown
Contributor

theemathas commented May 4, 2026

Miri reports no UB for this code with this PR. Is this intended?

use std::mem::transmute;

fn main() {
    let f: fn(i32) = |x: i32| {
        println!("{x}");
    };
    let g: fn((), i32) = unsafe { transmute(f) };
    g((), 123);
}

I think Miri is treating the () argument as the closure argument.

@RalfJung
Copy link
Copy Markdown
Member Author

RalfJung commented May 4, 2026

It is less intended and more unavoidable.

Miri also doesn't report UB for code that incorrectly relies on repr(Rust) field order. So I would categorize that code as relying on unstable undocumented ABI guarantees, but the code will actually compile and run correctly on today's compilers so I can live with Miri not reporting an error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Function call ABI check ignores ZSTs

7 participants