Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible MSVC compiler bug

Given shared_ptr variable declared in condition clause of for loop and for loop body contains if/continue statement, microsoft compiler (as of version 2015) generates extra destructor call (two total) per loop iteration. This causes destruction of Item objects that should be out of reach of Holder interface users. See example code below

namespace
{
    class Item
    {
    public:
        Item(size_t v)
            : value_(v)
        {
            std::cout << "Item(" << value_ << ")" << std::endl;
        }

        ~Item()
        {
            std::cout << "~Item(" << value_ << ")" << std::endl;
        }

        void print() const
        {
            std::cout << "Item::print(" << value_ << ")" << std::endl;
        }

    private:
        size_t value_;

    };

    typedef std::shared_ptr<const Item> ItemCPtr;

    class Holder
    {
    public:
        Holder(size_t n)
        {
            for (size_t i = 0; i < n; ++i)
                items_.emplace_back(new Item(i));
        }

        ItemCPtr getItem(size_t i) const
        {
            if (i < items_.size())
                return items_[i];
            return ItemCPtr();
        }

    private:
        std::vector<ItemCPtr> items_;
    };
}

TEST(Test, Test)
{
    Holder _holder(5);

    std::cout << "before loop" << std::endl;

    for (size_t i = 0; auto _item = _holder.getItem(i); ++i)
    {
        if (!!(i % 2))
            continue;

        _item->print();
    }

    std::cout << "after loop" << std::endl;

    _holder.getItem(1)->print();
    _holder.getItem(3)->print();
}

This yield output below

||   [ RUN      ] Test.Test
||   Item(0)
||   Item(1)
||   Item(2)
||   Item(3)
||   Item(4)
||   before loop
||   Item::print(0)
||   ~Item(1)
||   Item::print(2)
||   ~Item(3)
||   Item::print(4)
||   after loop
||   Item::print(3722304989)
||   Item::print(3722304989)
||   ~Item(0)
||   ~Item(2)
||   ~Item(4)
||   [       OK ] Test.Test (0 ms)

If I move _item declaration out of for loop this way

    auto _item = ItemCPtr();
    for (size_t i = 0; _item = _holder.getItem(i); ++i)

then I get expected output like this

||   [ RUN      ] Test.Test
||   Item(0)
||   Item(1)
||   Item(2)
||   Item(3)
||   Item(4)
||   before loop
||   Item::print(0)
||   Item::print(2)
||   Item::print(4)
||   after loop
||   Item::print(1)
||   Item::print(3)
||   ~Item(0)
||   ~Item(1)
||   ~Item(2)
||   ~Item(3)
||   ~Item(4)
||   [       OK ] Test.Test (0 ms)

As far as I can understand, getItem should yield a copy of ItemCPtr and no Items could be modified through Holder interface. Yet user can destroy two out of five items inside loop, see ~Item(1) and ~Item(3) destructor output between before loop/after loop marks.

This is trivial example to expose problem. In real world this would cause hard to track memory corruption problem.

Compiler identity:

cmake -G "Visual Studio 14 2015" ..\
-- Selecting Windows SDK version 10.0.14393.0 to target Windows 10.0.19041.
-- The C compiler identification is MSVC 19.0.24210.0
-- The CXX compiler identification is MSVC 19.0.24210.0

OS is 64bit Windows 10

Bug manifests itself even when optimization is disabled /Od, default options.

like image 466
Dmitry Teslenko Avatar asked Feb 26 '21 08:02

Dmitry Teslenko


People also ask

Is MSVC a good compiler?

Microsoft Visual Studio is a good compiler for developing Windows applications. Although Visual Studio presents a ton of choices to the user when first starting out (for instance, there are a lot of different project types), the amount of choice gives a good idea of the overall scope of this tool.

What compiler does MSVC use?

Microsoft C++ Compiler (MSVC) This is the default compiler for most Visual Studio C++ projects and is recommended if you are targeting Windows.

Is MSVC better than MinGW?

MSVC is doing the compilation job significantly faster than MinGW-w64. The DLL sizes are comparable, if optimization is set to "-O2" for MinGW-w64, with "-O3" the DLLs from MinGW-w64 are larger. Binary files compiled with MinGW-w64 are performing significantly better than those compiled with MSVC.

Does MSVC support C ++ 17?

The /std:c++17 option enables C++17 standard-specific features and behavior. It enables the full set of C++17 features implemented by the MSVC compiler. This option disables compiler and standard library support for features that are new or changed after C++17.

Why can't I run MSVC code in Clang?

However, when Clang and GCC accept your code without a diagnostic, and MSVC doesn't, you've probably found a bug in our compiler. (Other possibilities include differences in Unix and Windows behavior, or different levels of C++ standards implementation, and so on.)

What is the worst kind of bug a compiler can have?

However, in theory this bug—and more generally, bugs just like it—could lead to an improper optimization in your code. That is among the worst sort of bug a compiler can have: silent-bad-codegen. I’m very pleased to have squashed this one.

How do I report a problem with a C++ compiler?

If you find problems in the Microsoft C++ compiler (MSVC), the linker, or other tools and libraries, we want to know about them. When the issue is in our documentation, we want to know about that, too. The best way to let us know about a problem is to send us a report that includes a description of the problem you've discovered.

Why does my C++ code get rejected by all compilers?

(Other possibilities include differences in Unix and Windows behavior, or different levels of C++ standards implementation, and so on.) When all the compilers reject your code, then it's likely that your code is incorrect. Seeing different error messages may help you diagnose the issue yourself.


1 Answers

As far as I can understand, getItem should yield a copy of ItemCPtr and no Items could be modified through Holder interface.

Your understanding is 100% accurate. That is exactly the behavior described for the C++ standard's abstract machine.

Even under the as-if rule, which allows compilers to optimize, the observable behavior (on account of printing to the standard output stream) should be as though there was at least one shared pointer (for each item) that exists until the end of the test's scope. That is clearly not what you see though.

My educated guess would be that MSVC optimizes the copies out entirely. And instead refers to pointers inside the vector directly in the loop body. That on its own is okay.

The bug would likely be that it mishandles the code which would have destroyed the local variable in case of a continue statment. And errounosly applies it to the object in the vector. This is a bug.

like image 103
StoryTeller - Unslander Monica Avatar answered Oct 29 '22 04:10

StoryTeller - Unslander Monica