c++visual-c++-2015visual-c++-2017

MSVC interpreting move-only struct argument as a pointer


I have a simple one-member struct with deleted copy construction/assignment, and default move construction/assignment. I'm trying to pass one of these structs to a function by value and return the member - pretty straightforward.

struct NoCopy {
    explicit NoCopy(int x) : x{x} {}

    NoCopy(const NoCopy&) = delete;
    NoCopy& operator=(const NoCopy&) = delete;

    NoCopy(NoCopy&&) = default;
    NoCopy& operator=(NoCopy&&) = default;

    int x;
};

// noinline to ensure the crash is reproducible in release
// not required to reproduce the problem code
__declspec(noinline) int problem_function(NoCopy x) {
    return x.x;
}

int main() {
    return problem_function(NoCopy{ 1 });
}

The problem is that when compiled with MSVC, this function crashes.

Looking at the disassembly, it appears that when the copy constructor is deleted, MSVC attempts to interpret x as if it were a NoCopy* and the subsequent member read causes a segmentation fault.

Here's a godbolt example, with gcc and clang for reference: https://godbolt.org/z/jG7kIw

Note that both gcc and clang behave as expected. Also note that this happens in both optimised and unoptimised builds, and appears to affect both MSVC 2015 and 2017.

For reference, I'm compiling on my machine with Visual Studio Professional 2015 (14.0.25431.01 Update 3) - and I'm predominantly testing x64 builds. My platform toolset for the crash repro is set to v140.

So my question is: are there any reasonable explanations for this, or am I looking at a compiler bug.

edit: I've submitted a bug report over here

edit #2: If like me, you're stuck with a similar problem and are unable to update VS easily - it appears that defining move constructor/assignment operator manually instead of using = default causes MSVC to spit out correct code at the call site and avoids the crash. here's a new godbolt

for this reason, things like std::unique_ptr don't seem to be affected. struct size also seems to be a factor.


Solution

  • I can't see how this is anything other than an egregious compiler bug. The code is valid.

    It does perhaps seem strange that something so fundamental was broken in two MSVS versions already, but if I had to guess it'd be due to relatively new C++17 copy elision support. (Of course I am using the term "support" somewhat loosely in this instance.)

    (OP's VS bug raised online here.)