Skip to content

Nested return expressions can cause drop to run on incorrect values. #15763

Closed
@luqmana

Description

@luqmana

Came across this while working on inlining enum constructors but I could reproduce on master as well:

#![allow(dead_code)]

struct Foo {
    a: int
}

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Dropping Foo: {}", self.a);
    }
}

struct Bar {
    x: Foo,
    y: int
}

fn foo() -> Bar {
    return Bar {
        x: Foo { a: 23 },
        y: return Bar {
            x: Foo { a: 32 },
            y: 0
        }
    };
}

fn main() {
    unsafe {
        std::mem::forget(foo());
    }
}

When compiled and run this will print out:

Dropping Foo: 32

This is wrong since the Foo we're actually dropping at that point (before returning in foo) is the one with it's field set to 23. The problem is that we translate return expressions to write directly into the return pointer (either a hidden outpointer or a temporary alloca which we'll return from). So with the outer return we start writing out the struct but then we encounter another return in one of the fields and overwrite the previous result so now when we pass a pointer (which is just some offset into the return pointer) to the drop glue for what should be the initial Foo we're actually calling drop on the second one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-codegenArea: Code generationA-destructorsArea: Destructors (`Drop`, …)P-mediumMedium priority

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions