Skip to content

Fix variable deallocation order in panic unwinding paths#149435

Open
sladyn98 wants to merge 6 commits intorust-lang:mainfrom
sladyn98:fix-panic-drop-ordering-rebased
Open

Fix variable deallocation order in panic unwinding paths#149435
sladyn98 wants to merge 6 commits intorust-lang:mainfrom
sladyn98:fix-panic-drop-ordering-rebased

Conversation

@sladyn98
Copy link
Contributor

@sladyn98 sladyn98 commented Nov 29, 2025

This PR fixes a soundness bug where local variables are deallocated out of order during panic unwinding, allowing destructors to access freed memory. This violates Rust's safety guarantees and has caused real-world unsoundness in crates like generatively.

This PR removes the is_generator check and unconditionally emits StorageDead statements during unwinding for ALL functions, bringing non-generator behavior in line with generators. It ensures that during unwinding, when a local variable goes out of scope, its storage is properly marked as dead via StorageDead, allowing the borrow checker to enforce the
invariant that values must outlive their references even in panic paths.

Fixes #147875

@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 Nov 29, 2025
@rustbot
Copy link
Collaborator

rustbot commented Nov 29, 2025

r? @wesleywiser

rustbot has assigned @wesleywiser.
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

@sladyn98
Copy link
Contributor Author

r? @dianne

@rustbot rustbot assigned dianne and unassigned wesleywiser Nov 29, 2025
@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@dianne dianne left a comment

Choose a reason for hiding this comment

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

It looks like there's a bug causing an assertion failure when building the standard library. I've given it a look and offered a guess at what's causing it below. There's still work to do here beyond fixing that, though.

First, could you add a ui test to demonstrate that this fixes #147875? It looks like it might not yet, since the code for scheduling unwind drops on calls panicking looks unchanged.

Second, after verifying that this results in the correct borrow-checking behavior, we need to make sure that this change doesn't negatively affect codegen. Per the old comment on needs_cleanup, at least at the time it was written, LLVM didn't handle the unnecessary cleanup blocks and StorageDeads particularly well. If you can demonstrate with codegen tests that that's not an issue anymore, and perf isn't too bad, that might be all that's needed. But my expectation is that we'll have to get rid of or ignore the StorageDeads later in compilation (sometime after they serve their purpose in borrowck). Unless there's a reason to keep the StorageDeads around longer, my gut feeling is that this cleanup would be best as a post-borrowck MIR pass (maybe as part of CleanupPostBorrowck?), since then optimization passes can be done on cleaner MIR and we can test it works with MIR tests rather than codegen tests. Could you also add a test for this not affecting later stages of compilation? If you accomplish that by removing the unwind-path StorageDeads as part of a MIR pass, that'd be a mir-opt test.

Before you push again, you'll probably want to run the codegen and mir-opt tests to make sure the former is clean and to bless the latter. Regardless of what approach we take here, if we're changing how the MIR is built, there should be differences in the MIR building test output (part of the mir-opt suite).

View changes since this review

Comment on lines 261 to 262
fn needs_cleanup(&self) -> bool {
self.drops.iter().any(|drop| match drop.kind {
DropKind::Value | DropKind::ForLint => true,
DropKind::Storage => false,
})
!self.drops.is_empty()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please explain this change? My understanding at least is that the ordering of StorageDeads only matters relative to actual drops, since that's when it can affect borrow-checking; reordering StorageDeads amongst each other won't do anything, but a StorageDead before a drop terminator will cause a borrow-checking failure if the destructor could reference the dead memory. As such, I'd expect we'd only need a cleanup block when there's actual drops (which is what the old version of this was checking for). Is there an edge case where we'd need a cleanup block with only StorageDeads in it? Otherwise, could you reinstate the comment about avoiding creating landing pads when there's no actual destructors?

That said, from what I can tell this method is only used for determining whether cleanup blocks are required for unwinding from panics in destructors, so could you make sure there's a MIR test checking that we don't create unnecessary cleanup blocks in other cases too, particularly for calls?

Also, if the comment about LLVM is still true and we need to get rid of the StorageDeads before codegen, we should probably keep some updated version of those comments around.

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 4, 2025
@rustbot
Copy link
Collaborator

rustbot commented Dec 4, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Dec 4, 2025
@dianne
Copy link
Contributor

dianne commented Dec 4, 2025

Also, could you change the PR description? #147875 on its own doesn't allow destructors to access freed memory, it doesn't allow for the creation of dangling references, and I'm at least not aware of a safety guarantee that it violates. You should only get unsoundness out of it if you write unsafe code on the assumption that the borrow checker will enforce the relative drop order of locals that may have destructors and those that definitely don't. Of course, per language team decision, consistent drop order is a promise Rust would like to make. But it's not quite the same as the borrow-checker failing to ensure places outlive their references.

@sladyn98
Copy link
Contributor Author

sladyn98 commented Dec 5, 2025

So what i did was write this simple rust program panic drop.rs

 //@ compile-flags: -C no-prepopulate-passes

#![crate_type = "lib"]

#[no_mangle]
pub fn function_with_drops() {

    let _a = String::from("first");
    let _b = String::from("second");
    let _c = String::from("third");
    might_panic();
}

#[inline(never)]
pub fn might_panic() {
    // This might panic
    panic!()
}

I ran the llvm to get the intermediate representaion and on looking at the IR I cannot find any llvm.lifetime.end statements suggesting to us that on master the StorageDead statements are missing, which according to my understanding means that the borrowchecker does not know when the storage becomes invalid. Let me now write the UI test to see what is up

@dianne
Copy link
Contributor

dianne commented Dec 5, 2025

StorageDead is indeed not present there on the main branch, but that doesn't create a soundness hole in the borrow-checker; those locals are simply treated as being maybe live until the end of the stack frame, so it's technically sound for destructors on the unwind path to reference them. When unwinding, the stack frame will be popped anyway, so we don't need to hint to llvm that the memory's no longer in use. The issue is that we'd like to be more strict in the borrow-checker, both for consistency and so that unsafe code can rely on drop order being enforced.

edit: adjusted wording

@sladyn98 sladyn98 force-pushed the fix-panic-drop-ordering-rebased branch from 5afe7c2 to 59a7e56 Compare January 30, 2026 09:32
@rustbot rustbot added the F-explicit_tail_calls `#![feature(explicit_tail_calls)]` label Jan 30, 2026
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@sladyn98 sladyn98 requested a review from dianne January 30, 2026 09:32
@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 Jan 30, 2026
@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@dianne dianne left a comment

Choose a reason for hiding this comment

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

This still needs CI to pass before I can review it properly. I've left a few comments on obvious things, but I don't think reviewing the code changes would be helpful at this point. Please test your changes locally. You don't have to run the whole test suite yourself, but for this change, you'll at least want make sure that the mir-opt and codegen tests all pass, that any relevant ui tests pass, and that tidy passes as well.

Could you rebase onto a more recent commit, also? I don't expect there will be conflicts in the MIR building part of this, but I'm not sure about the rest.

I don't mean to be harsh, but this is a relatively complex and nuanced change. If you're not familiar with what's being changed, why it's being changed, the consequences/needs of that, and general contribution procedure, I'd recommend gaining familiarity with easier issues instead.

View changes since this review

@rustbot rustbot 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-review Status: Awaiting review from the assignee but also interested parties. labels Jan 30, 2026
@sladyn98 sladyn98 force-pushed the fix-panic-drop-ordering-rebased branch from 59a7e56 to 44fbdb3 Compare February 1, 2026 00:14
@rustbot
Copy link
Collaborator

rustbot commented Feb 1, 2026

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@sladyn98 sladyn98 force-pushed the fix-panic-drop-ordering-rebased branch from 8df38cd to 0f688eb Compare February 1, 2026 00:25
@rust-log-analyzer

This comment has been minimized.

@sladyn98
Copy link
Contributor Author

sladyn98 commented Feb 1, 2026

I think i got a good grasp of the problem and how we want to solve it, i started by adding StorageDead to unwind paths in both diverge_cleanup_target and build_scope_drops, but only when there's a cleanup path (i.e., when there are Value or ForLint drops present). This ensures the borrow-checker treats variables as dead at the same point on all paths.

I also needed to make sure StorageDead gets removed after borrow-checking so it doesn't affect codegen, so i then went modified CleanupPostBorrowck to remove StorageDead from cleanup blocks (unwind paths) after borrow-checking. I kept StorageDead on normal paths since it's still needed for MIR validation, and it gets removed later by RemoveStorageMarkers.

I also had to figure out the right place to remove StorageDead, you suggested a post-borrowck MIR pass, so I added it to CleanupPostBorrowck, but I had to be careful to only remove it from cleanup blocks to avoid breaking MIR validation on normal paths.

Now StorageDead is emitted on unwind paths for all functions (not just coroutines), which makes the borrow-checker stricter and more consistent. The borrow-checker now treats variables as dead at the same point on all paths, which is exactly what #147875 needed. And StorageDead is properly removed from cleanup blocks after borrow-checking, so it doesn't affect codegen.

Everything else is implemented and tested. The main question is whether the comments need more precision about where StorageDead gets removed.

@sladyn98 sladyn98 marked this pull request as draft February 1, 2026 00:43
@sladyn98 sladyn98 force-pushed the fix-panic-drop-ordering-rebased branch from 1363caa to 92d28c2 Compare February 1, 2026 02:20
@sladyn98 sladyn98 marked this pull request as ready for review February 1, 2026 02:20
@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 Feb 1, 2026
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Feb 1, 2026

rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead.

cc @rust-lang/rust-analyzer

@rustbot rustbot added the T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. label Feb 1, 2026
@rust-log-analyzer

This comment has been minimized.

This commit fixes several issues related to StorageDead and ForLint drops:

1. Add StorageDead and ForLint drops to unwind_drops for all functions
   - Updated diverge_cleanup_target to include StorageDead and ForLint drops
     in the unwind_drops tree for all functions (not just coroutines), but only
     when there's a cleanup path (i.e., when there are Value or ForLint drops)
   - This ensures proper drop ordering for borrow-checking on panic paths

2. Fix break_for_tail_call to handle StorageDead and ForLint drops
   - Don't skip StorageDead drops for non-drop types
   - Adjust unwind_to pointer for StorageDead and ForLint drops, matching
     the behavior in build_scope_drops
   - Only adjust unwind_to when it's valid (not DropIdx::MAX)
   - This prevents debug assert failures when processing drops in tail calls

3. Fix index out of bounds panic when unwind_to is DropIdx::MAX
   - Added checks to ensure unwind_to != DropIdx::MAX before accessing
     unwind_drops.drop_nodes[unwind_to]
   - Only emit StorageDead on unwind paths when there's actually an unwind path
   - Only add entry points to unwind_drops when unwind_to is valid
   - This prevents panics when there's no cleanup needed

4. Add test for explicit tail calls with StorageDead drops
   - Tests that tail calls work correctly when StorageDead and ForLint drops
     are present in the unwind path
   - Verifies that unwind_to is correctly adjusted for all drop kinds

These changes make the borrow-checker stricter and more consistent by ensuring
that StorageDead statements are emitted on unwind paths for all functions when
there's a cleanup path, allowing unsafe code to rely on drop order being enforced
consistently.
- Add StorageDead to unwind paths for all functions (not just coroutines)
- Modify CleanupPostBorrowck to remove StorageDead from cleanup blocks
- Add tests for the fix and StorageDead removal
When processing drops in reverse order, unwind_to might not point to
the current drop. Only adjust unwind_to when the drop matches what
unwind_to is pointing to, rather than asserting they must match.
@sladyn98 sladyn98 force-pushed the fix-panic-drop-ordering-rebased branch from e94d041 to 67a9bdb Compare February 1, 2026 09:11
@rustbot
Copy link
Collaborator

rustbot commented Feb 1, 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.

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@dianne dianne left a comment

Choose a reason for hiding this comment

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

Not a full review, but I took a glance through the code and comment changes. I think there's some incorrect logic causing the r-a build failure and some suspicious checks papering over it in other cases. I'll take a closer look once CI passes.

View changes since this review

Comment on lines +9 to +10
//! - [`StorageDead`] statements (these are only needed for borrow-checking and are removed
//! after borrowck completes to ensure they don't affect later optimization passes or codegen)
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't true in general. Being more specific about which StorageDeads are removed may be helpful.

Comment on lines +34 to +36
// Only remove StorageDead from cleanup blocks (unwind paths).
// StorageDead on normal paths is still needed for MIR validation
// and will be removed later by RemoveStorageMarkers during optimization.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is true. RemoveStorageMarkers removes storage markers specifically when we don't want them to be emitted by codegen. It's only enabled when sess.mir_opt_level() > 0 && !sess.emit_lifetime_markers(). Storage markers can be used by codegen backends for their analyses as well. If we weren't emitting them during codegen ever, we wouldn't need to bother removing them here; we could just wait until RemoveStorageMarkers.

// Only remove StorageDead from cleanup blocks (unwind paths).
// StorageDead on normal paths is still needed for MIR validation
// and will be removed later by RemoveStorageMarkers during optimization.
let is_cleanup = basic_block.is_cleanup;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: is there a reason this needs to be assigned to a variable outside of the loop?

Comment on lines +2033 to +2034
// These StorageDead statements are removed by the `RemoveStorageMarkers` MIR
// transform pass before codegen, so they don't affect LLVM output.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// These StorageDead statements are removed by the `RemoveStorageMarkers` MIR
// transform pass before codegen, so they don't affect LLVM output.
// These StorageDead statements are removed by the `CleanupPostBorrowck` MIR
// transform pass, so they don't affect codegen.

// are Value or ForLint drops present, because:
// 1. StorageDead is only relevant for borrow-checking when there are destructors
// that might reference the dead variable
// 2. If there are no drops, there's no unwind path to emit StorageDead on
Copy link
Contributor

Choose a reason for hiding this comment

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

This may may be a bit misleading. We still unwind when there's no drops. We just don't need a cleanup block when there's no destructors to run before popping the stack frame.

debug_assert_eq!(unwind_drops.drop_nodes[unwind_to].data.local, drop_data.local);
debug_assert_eq!(unwind_drops.drop_nodes[unwind_to].data.kind, drop_data.kind);
unwind_to = unwind_drops.drop_nodes[unwind_to].next;
if unwind_to != DropIdx::MAX {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this check against DropIdx::MAX for? It doesn't seem to have been necessary previously, and if there's a DropKind::Value drop, we really should have provided a proper unwind_to. Plus if a check is needed, I really think it should be something self-evident or at least commented; checking for DropIdx::MAX feels magical and non-obvious.

}

unwind_drops.add_entry_point(block, unwind_to);
if unwind_to != DropIdx::MAX {
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, what's this check for?

Comment on lines +1995 to +2001
// Only adjust if the drop matches what unwind_to is pointing to (since we process
// drops in reverse order, unwind_to might not match the current drop).
if storage_dead_on_unwind
&& unwind_to != DropIdx::MAX
&& unwind_drops.drop_nodes[unwind_to].data.local == drop_data.local
&& unwind_drops.drop_nodes[unwind_to].data.kind == drop_data.kind
{
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels suspicious. I think this would need a lot more justification to turn those asserts into conditions, so I have a feeling something is wrong and this is papering over it.

Comment on lines +2039 to +2045
// Only adjust if the drop matches what unwind_to is pointing to (since we process
// drops in reverse order, unwind_to might not match the current drop).
if storage_dead_on_unwind
&& unwind_to != DropIdx::MAX
&& unwind_drops.drop_nodes[unwind_to].data.local == drop_data.local
&& unwind_drops.drop_nodes[unwind_to].data.kind == drop_data.kind
{
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, this feels suspicious. I think this would need a lot more justification to turn those asserts into conditions, so I have a feeling something is wrong and this is papering over it.

Comment on lines +2084 to +2088
let unwind_drop = self
.scopes
.unwind_drops
.add_drop(drop_node.data, unwind_indices[drop_node.next]);
unwind_indices.push(unwind_drop);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this should be unconditional. We only want to add StorageDeads if there's a real (or for-fcw) drop, and maybe ideally only before the last of those (if it's not too complicated). Not sure, but this might be what's causing the problem building r-a, since that involves breaking from a loop.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally there should be some way to write all of this without requiring duplicated logic all over the place. It feels really fragile to have to know in several different parts of that file exactly when unwind drops should be added.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another option to keep it simple would be to always include StorageDeads when building the MIR, then get rid of empty cleanup paths in a post-borrowck pass. I'm not sure how much worse for perf that would be, but we could try profiling it.

@rustbot rustbot 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-review Status: Awaiting review from the assignee but also interested parties. labels Feb 1, 2026
Fix lifetime issues in rust-analyzer where automaton doesn't live long
enough for op.union(). Move op declaration inside each match arm to
ensure proper lifetime scope.

This fixes compilation errors that are blocking CI, though these are
pre-existing issues unrelated to the StorageDead changes.
@rust-log-analyzer

This comment has been minimized.

When processing drops in reverse order, unwind_to might not point to the
current drop. Make the unwind_to adjustment conditional on the drop matching,
matching the behavior in build_scope_drops. This prevents assertion failures
when unwind_to points to a different drop than the one being processed.
@rust-log-analyzer
Copy link
Collaborator

The job aarch64-gnu-llvm-20-1 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
##[group]Runner Image Provisioner
Hosted Compute Agent
Version: 20260123.484
Commit: 6bd6555ca37d84114959e1c76d2c01448ff61c5d
Build Date: 2026-01-23T19:41:17Z
Worker ID: {a6e3358b-16dd-45e2-8f46-5be249c2896d}
Azure Region: southcentralus
##[endgroup]
##[group]VM Image
- OS: Linux (arm64)
- Source: Partner
- Name: Ubuntu 24.04 by Arm Limited
---
test [ui] tests/ui/asm/aarch64/may_unwind.rs ... ok
test [ui] tests/ui/asm/aarch64/type-check-2.rs ... ok
test [ui] tests/ui/asm/aarch64/type-check-3.rs ... ok
test [ui] tests/ui/asm/aarch64/type-f16.rs ... ok
test [ui] tests/ui/asm/aarch64v8r.rs#hf ... ok
test [ui] tests/ui/asm/aarch64v8r.rs#sf ... ok
test [ui] tests/ui/asm/arm-low-dreg.rs ... ok
test [ui] tests/ui/asm/asm-with-nested-closure.rs ... ok
test [ui] tests/ui/asm/binary_asm_labels.rs ... ignored, only executed when the architecture is x86_64
test [ui] tests/ui/asm/aarch64/sym.rs ... ok
test [ui] tests/ui/asm/binary_asm_labels_allowed.rs ... ok
---
test [ui] tests/ui/extern/issue-64655-extern-rust-must-allow-unwind.rs#fat3 ... ok
test [ui] tests/ui/extern/issue-80074.rs ... ok
test [ui] tests/ui/extern/issue-95829.rs ... ok
test [ui] tests/ui/extern/issue-64655-extern-rust-must-allow-unwind.rs#thin1 ... ok
test [ui] tests/ui/extern/lgamma-linkage.rs ... ok
test [ui] tests/ui/extern/not-in-block.rs ... ok
test [ui] tests/ui/extern/unsized-extern-derefmove.rs ... ok
test [ui] tests/ui/extern/no-mangle-associated-fn.rs ... ok
test [ui] tests/ui/extern/windows-tcb-trash-13259.rs ... ok
test [ui] tests/ui/feature-gates/allow-features-empty.rs ... ok
---
test [ui] tests/ui/imports/ambiguous-5.rs ... ok
test [ui] tests/ui/imports/ambiguous-4.rs ... ok
test [ui] tests/ui/imports/ambiguous-7.rs ... ok
test [ui] tests/ui/imports/ambiguous-9.rs ... ok
test [ui] tests/ui/imports/ambiguous-panic-glob-vs-multiouter.rs ... ok
test [ui] tests/ui/imports/ambiguous-panic-globvsglob.rs ... ok
test [ui] tests/ui/imports/ambiguous-8.rs ... ok
test [ui] tests/ui/imports/ambiguous-panic-no-implicit-prelude.rs ... ok
test [ui] tests/ui/imports/ambiguous-glob-vs-expanded-extern.rs ... ok
test [ui] tests/ui/imports/ambiguous-panic-non-prelude-core-glob.rs ... ok
test [ui] tests/ui/imports/ambiguous-panic-non-prelude-std-glob.rs ... ok
---
failures:

---- [ui] tests/ui/async-await/async-fn-size-moved-locals.rs stdout ----

error: test did not exit with success! code=Some(101) so test would pass with `run-fail`
status: exit status: 101
command: cd "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/async-await/async-fn-size-moved-locals" && RUSTC="/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" RUST_TEST_THREADS="4" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/async-await/async-fn-size-moved-locals/a"
stdout: none
--- stderr -------------------------------

---

---- [ui] tests/ui/async-await/async-fn-size-moved-locals.rs stdout end ----
---- [ui] tests/ui/async-await/async-fn-size.rs stdout ----

error: test did not exit with success! code=Some(101) so test would pass with `run-fail`
status: exit status: 101
command: cd "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/async-await/async-fn-size" && RUSTC="/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" RUST_TEST_THREADS="4" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/async-await/async-fn-size/a"
stdout: none
--- stderr -------------------------------

---

---- [ui] tests/ui/async-await/async-fn-size.rs stdout end ----
---- [ui] tests/ui/coroutine/overlap-locals.rs stdout ----

error: test did not exit with success! code=Some(101) so test would pass with `run-fail`
status: exit status: 101
command: cd "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/coroutine/overlap-locals" && RUSTC="/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" RUST_TEST_THREADS="4" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/coroutine/overlap-locals/a"
stdout: none
--- stderr -------------------------------

---

---- [ui] tests/ui/coroutine/overlap-locals.rs stdout end ----
---- [ui] tests/ui/coroutine/size-moved-locals.rs stdout ----

error: test did not exit with success! code=Some(101) so test would pass with `run-fail`
status: exit status: 101
command: cd "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/coroutine/size-moved-locals" && RUSTC="/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" RUST_TEST_THREADS="4" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/coroutine/size-moved-locals/a"
stdout: none
--- stderr -------------------------------

---
Saved the actual stderr to `/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/dropck/dropck_trait_cycle_checked/dropck_trait_cycle_checked.stderr`
diff of stderr:

25    |
26    = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`
27 
- error[E0597]: `o2` does not live long enough
-   --> $DIR/dropck_trait_cycle_checked.rs:113:13
-    |
- LL |     let (o1, o2, o3): (Box<dyn Obj>, Box<dyn Obj>, Box<dyn Obj>) = (O::new(), O::new(), O::new());
-    |              -- binding `o2` declared here                                    -------- coercion requires that `o2` is borrowed for `'static`
- ...
- LL |     o2.set0(&o2);
-    |             ^^^ borrowed value does not live long enough
- ...
- LL | }
-    | - `o2` dropped here while still borrowed
-    |
-    = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`
- 
- error[E0597]: `o3` does not live long enough
-   --> $DIR/dropck_trait_cycle_checked.rs:114:13
-    |
- LL |     let (o1, o2, o3): (Box<dyn Obj>, Box<dyn Obj>, Box<dyn Obj>) = (O::new(), O::new(), O::new());
-    |                  -- binding `o3` declared here                                -------- coercion requires that `o3` is borrowed for `'static`
- ...
- LL |     o2.set1(&o3);
-    |             ^^^ borrowed value does not live long enough
- ...
- LL | }
-    | - `o3` dropped here while still borrowed
-    |
-    = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`
- 
56 error[E0597]: `o1` does not live long enough
57   --> $DIR/dropck_trait_cycle_checked.rs:115:13
58    |

67    |
68    = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`
69 
- error[E0597]: `o2` does not live long enough
-   --> $DIR/dropck_trait_cycle_checked.rs:116:13
-    |
- LL |     let (o1, o2, o3): (Box<dyn Obj>, Box<dyn Obj>, Box<dyn Obj>) = (O::new(), O::new(), O::new());
-    |              -- binding `o2` declared here                                              -------- coercion requires that `o2` is borrowed for `'static`
- ...
- LL |     o3.set1(&o2);
-    |             ^^^ borrowed value does not live long enough
- LL | }
-    | - `o2` dropped here while still borrowed
-    |
-    = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`
- 
- error: aborting due to 6 previous errors
+ error: aborting due to 3 previous errors
84 
85 For more information about this error, try `rustc --explain E0597`.
---
To only update this specific test, also pass `--test-args dropck/dropck_trait_cycle_checked.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/dropck/dropck_trait_cycle_checked.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/dropck/dropck_trait_cycle_checked" "-A" "unused" "-W" "unused_attributes" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers"
stdout: none
--- stderr -------------------------------
error[E0597]: `o2` does not live long enough
##[error]  --> /checkout/tests/ui/dropck/dropck_trait_cycle_checked.rs:111:13
   |
LL |     let (o1, o2, o3): (Box<dyn Obj>, Box<dyn Obj>, Box<dyn Obj>) = (O::new(), O::new(), O::new());
   |              -- binding `o2` declared here                          -------- coercion requires that `o2` is borrowed for `'static`
LL |     o1.set0(&o2); //~ ERROR `o2` does not live long enough
   |             ^^^ borrowed value does not live long enough
...
LL | }
   | - `o2` dropped here while still borrowed
   |
   = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`

error[E0597]: `o3` does not live long enough
##[error]  --> /checkout/tests/ui/dropck/dropck_trait_cycle_checked.rs:112:13
   |
LL |     let (o1, o2, o3): (Box<dyn Obj>, Box<dyn Obj>, Box<dyn Obj>) = (O::new(), O::new(), O::new());
   |                  -- binding `o3` declared here                      -------- coercion requires that `o3` is borrowed for `'static`
LL |     o1.set0(&o2); //~ ERROR `o2` does not live long enough
LL |     o1.set1(&o3); //~ ERROR `o3` does not live long enough
   |             ^^^ borrowed value does not live long enough
...
LL | }
   | - `o3` dropped here while still borrowed
   |
   = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`

error[E0597]: `o1` does not live long enough
##[error]  --> /checkout/tests/ui/dropck/dropck_trait_cycle_checked.rs:115:13
   |
LL |     let (o1, o2, o3): (Box<dyn Obj>, Box<dyn Obj>, Box<dyn Obj>) = (O::new(), O::new(), O::new());
   |          -- binding `o1` declared here                                                  -------- coercion requires that `o1` is borrowed for `'static`
...
LL |     o3.set0(&o1); //~ ERROR `o1` does not live long enough
   |             ^^^ borrowed value does not live long enough
LL |     o3.set1(&o2); //~ ERROR `o2` does not live long enough
LL | }
   | - `o1` dropped here while still borrowed
   |
   = note: due to object lifetime defaults, `Box<dyn Obj<'_>>` actually means `Box<(dyn Obj<'_> + 'static)>`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0597`.
------------------------------------------
---
To only update this specific test, also pass `--test-args explicit-tail-calls/ctfe-arg-bad-borrow.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/explicit-tail-calls/ctfe-arg-bad-borrow" "-A" "unused" "-W" "unused_attributes" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers"
stdout: none
--- stderr -------------------------------
error[E0597]: `local` does not live long enough
##[error]  --> /checkout/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs:8:25
   |
---
---- [ui] tests/ui/print_type_sizes/coroutine_discr_placement.rs stdout ----
Saved the actual stdout to `/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/print_type_sizes/coroutine_discr_placement/coroutine_discr_placement.stdout`
diff of stdout:

- print-type-size type: `{coroutine@$DIR/coroutine_discr_placement.rs:13:5: 13:7}`: 8 bytes, alignment: 4 bytes
+ print-type-size type: `{coroutine@$DIR/coroutine_discr_placement.rs:13:5: 13:7}`: 12 bytes, alignment: 4 bytes
2 print-type-size     discriminant: 1 bytes
3 print-type-size     variant `Unresumed`: 0 bytes
- print-type-size     variant `Suspend0`: 7 bytes
+ print-type-size     variant `Suspend0`: 4 bytes
+ print-type-size         local `.w`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
+ print-type-size     variant `Suspend1`: 8 bytes
5 print-type-size         padding: 3 bytes
- print-type-size         local `.w`: 4 bytes, alignment: 4 bytes
- print-type-size     variant `Suspend1`: 7 bytes
- print-type-size         padding: 3 bytes
9 print-type-size         local `.z`: 4 bytes, alignment: 4 bytes
10 print-type-size     variant `Returned`: 0 bytes
11 print-type-size     variant `Panicked`: 0 bytes

+ print-type-size     end padding: 3 bytes
12 print-type-size type: `std::mem::ManuallyDrop<i32>`: 4 bytes, alignment: 4 bytes
13 print-type-size     field `.value`: 4 bytes
14 print-type-size type: `std::mem::MaybeDangling<i32>`: 4 bytes, alignment: 4 bytes

Note: some mismatched output was normalized before being compared
- print-type-size type: `{coroutine@/checkout/tests/ui/print_type_sizes/coroutine_discr_placement.rs:13:5: 13:7}`: 12 bytes, alignment: 4 bytes
+ print-type-size type: `{coroutine@$DIR/coroutine_discr_placement.rs:13:5: 13:7}`: 12 bytes, alignment: 4 bytes
+ print-type-size     variant `Suspend0`: 4 bytes
+ print-type-size         local `.w`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
+ print-type-size     variant `Suspend1`: 8 bytes
+ print-type-size     end padding: 3 bytes


The actual stdout differed from the expected stdout
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args print_type_sizes/coroutine_discr_placement.rs`

error: 1 errors occurred comparing output.
status: exit status: 0
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/print_type_sizes/coroutine_discr_placement.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/print_type_sizes/coroutine_discr_placement" "-A" "unused" "-W" "unused_attributes" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers" "-Z" "print-type-sizes" "--crate-type" "lib"
--- stdout -------------------------------
print-type-size type: `{coroutine@/checkout/tests/ui/print_type_sizes/coroutine_discr_placement.rs:13:5: 13:7}`: 12 bytes, alignment: 4 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `Unresumed`: 0 bytes
print-type-size     variant `Suspend0`: 4 bytes
print-type-size         local `.w`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
print-type-size     variant `Suspend1`: 8 bytes
print-type-size         padding: 3 bytes
print-type-size         local `.z`: 4 bytes, alignment: 4 bytes
print-type-size     variant `Returned`: 0 bytes
print-type-size     variant `Panicked`: 0 bytes
print-type-size     end padding: 3 bytes
print-type-size type: `std::mem::ManuallyDrop<i32>`: 4 bytes, alignment: 4 bytes
print-type-size     field `.value`: 4 bytes
print-type-size type: `std::mem::MaybeDangling<i32>`: 4 bytes, alignment: 4 bytes
print-type-size     field `.0`: 4 bytes
print-type-size type: `std::mem::MaybeUninit<i32>`: 4 bytes, alignment: 4 bytes
print-type-size     variant `MaybeUninit`: 4 bytes
print-type-size         field `.uninit`: 0 bytes
print-type-size         field `.value`: 4 bytes
------------------------------------------
stderr: none

---- [ui] tests/ui/print_type_sizes/coroutine_discr_placement.rs stdout end ----

Copy link
Member

Choose a reason for hiding this comment

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

From your commit message:

This fixes compilation errors that are blocking CI, though these are
pre-existing issues unrelated to the StorageDead changes.

I guess this breakage comes from this PR rather than pre-existing, as this isn't failing in other PRs?

@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 3, 2026

☔ The latest upstream changes (presumably #152035) made this pull request unmergeable. Please resolve the merge conflicts.

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

Labels

F-explicit_tail_calls `#![feature(explicit_tail_calls)]` S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Local variable deallocated out of order in the panic path?

6 participants