mercredi 19 juillet 2017

Possible bug in Visual C++ 2013 compiler when temporary initializer_list is used in for-loop

Recently I've encountered a problem related to the usage of rvalue std::initializer_list object in the range-based for-loop, specifically on Microsoft Visual C++ 2013 Update 4 in Release x64.

Luckily, it was solved easily by simplifying the code (I cannot express how much I'd love every problem to be solved like that), namely creating a local variable to store std::initializer_list object.

Anyway, I am still curious about the root cause and hopefully you can help me finding it. So if you could explain what goes wrong in the story below or at least give me some hint, you'll make me a happier man ;-)

The symptom

Let's assume that we work with a tree of objects, all of them inherit from the Base class. So Derived1 and Derived2 below both derive from Base.

Derived1* p1, p2;
Derived2* p3, p4, p5, p6;

// code that initializes p1..p6 and ensures they are not nullptr

const std::unique_ptr<Base> pObject = /*some factory function call*/;
for (auto* ptr : std::initializer_list<Base*>{p1, p2, p3, p4, p5, p6})
  ptr->setParent(pObject.get()); // crash occurs here

The problem ceases away as I create a local initializer_list variable. All the surrounding code remains the same, only a single line changes.

Derived1* p1, p2;
Derived2* p3, p4, p5, p6;

// code that initializes p1..p6 and ensures they are not nullptr

const std::unique_ptr<Base> pObject = /*some factory function call*/;
const std::initializer_list<Base*> il{p1, p2, p3, p4, p5, p6};

for (auto* ptr : il)
  ptr->setParent(pObject.get()); // everything goes fine

Note: I can observe such problem only with temporary std::initializer_list object. If I replace it with e.g. temporary std::vector<Base*> object, the problem ceases. Also, I wasn't able to reproduce the crash on a small toy example. It occurs only in the presence of the surrounding code, in Release x64 with /O2 flag.

Since such a change in a single line produces a difference in behavior, I made a conclusion that this is a problem with Visual C++ compiler.

Assembly output

Of course, I tried to check the generated assembly code. But as I'm not a big expert in that, I was unable to see anything suspicious. Maybe it will give a clue on what was going wrong to those who read this.

I 'anonymized' the accompanying function and variable names, just as in the C++ samples above, but the assembly itself is left intact.

Here is an assembly output from the first program which results in access violation:

    const std::unique_ptr<Base> pObject = /*some factory function call*/;
00007FFE25AC21C5  mov         esi,6  
00007FFE25AC21CA  mov         rbx,qword ptr [rbp-80h]  
00007FFE25AC21CE  xchg        ax,ax  
    for (auto* thing : std::initializer_list<Base*>{ p1, p2,
00007FFE25AC21D0  mov         rcx,qword ptr [rdi]  
         p3, p4, p5, p6 })
      thing->setParent(pObject.get());
00007FFE25AC21D3  mov         rax,qword ptr [rcx]  
00007FFE25AC21D6  xor         r8d,r8d  
00007FFE25AC21D9  mov         rdx,rbx  
00007FFE25AC21DC  call        qword ptr [rax+40h]  
00007FFE25AC21DF  lea         rdi,[rdi+8]  
    for (auto* thing : std::initializer_list<Base*>{ p1, p2,
00007FFE25AC21E3  dec         rsi  
00007FFE25AC21E6  jne         `anonymous namespace'::someUnrelatedFunctionCall+4B0h (07FFE25AC21D0h)

And here's one from the second version which works fine:

            const std::unique_ptr<Base> pObject = /*some factory function call*/;
    00007FFE25D021C1  lea         rcx,[pObject]  
    00007FFE25D021C6  call        Scene_NS::buildSmartPtr<Base> (07FFE25CF55F0h)  
    00007FFE25D021CB  nop  
        std::initializer_list < Base* > il{ p1, p2, p3, p4, p5, p6 };
    00007FFE25D021CC  mov         qword ptr [rbp+50h],r15  
    00007FFE25D021D0  mov         qword ptr [rbp+58h],r14  
    00007FFE25D021D4  mov         qword ptr [rbp+60h],rdi  
    00007FFE25D021D8  mov         qword ptr [rbp+68h],r12  
    00007FFE25D021DC  mov         qword ptr [rbp+70h],r13  
    00007FFE25D021E0  mov         qword ptr [rbp+78h],rbx  
        for (auto* thing : il)
    00007FFE25D021E4  lea         rdi,[rbp+50h]  
    const std::unique_ptr<Base> pObject = /*some factory function call*/;
00007FFE25D021E8  mov         esi,6  
00007FFE25D021ED  mov         rbx,qword ptr [pObject]  
00007FFE25D021F2  nop         dword ptr [rax]  
00007FFE25D021F6  nop         word ptr [rax+rax]  
    for (auto* thing : il)
00007FFE25D02200  mov         rcx,qword ptr [rdi]  
      thing->setParent(pObject.get());
00007FFE25D02203  mov         rax,qword ptr [rcx]  
00007FFE25D02206  xor         r8d,r8d  
00007FFE25D02209  mov         rdx,rbx  
00007FFE25D0220C  call        qword ptr [rax+40h]  
00007FFE25D0220F  lea         rdi,[rdi+8]  
    for (auto* thing : il)
00007FFE25D02213  dec         rsi  
00007FFE25D02216  jne         `anonymous namespace'::someUnrelatedFunctionCall+4E0h (07FFE25

Aucun commentaire:

Enregistrer un commentaire