Hello,

I bumped into a strange bug today: the program hang at a point, inspecting the disassembly revealed that it was a single jmp instruction which jumped onto itself. Most of the code from the C++ function was missing. The bug occurs at -O2 or -O3, -O1 generates correct code. The function itself didn't change recently, but some referenced classes did; earlier the code was correct even at -O3.

It turned out that during maintenance, an infinite recursion creeped in. At -O1, the program crashed with SIGSEGV as expected due to exhausting the stack.

Details:

The original structure looked like this:

struct J;

struct S
{
    J& m_root;
    S(J& j) : m_root(j) {}
    void Dump(Printer&);
};

struct J
{
    S m_root;
    J() : m_root(*this) {}
    S& GetRoot() { return m_root; }
    void Dump(Printer& p) { m_root.Dump(p); }
};

An S is embedded in J, and J::Dump() is forwarded to it. Then, some minor changes occured: instead of embedding S, inheritance was used:

struct J : S
{
    // m_root removed
    J() : S(*this);
    // GetRoot() removed
    void Dump(Printer& p) { m_root.Dump(p); }
};

Accidentally, J::Dump() was not removed, and by the incident that both S and J had an m_root member in the first version, m_root.Dump(p) called itself in an infinite recursion.

Here is the test case:
----8<---8<----8<---8<----8<---8<----8<---8<---
struct J;

struct S
{
    J&      j;

            S(J& j_) : j(j_) { }
    void    F() { }
};

struct J : S
{
            J() : S(*this) { }
    void    F() { j.F(); }
};

void foo()
{
    J j;
    j.F();
}

int main()
{
    foo();
}
----8<---8<----8<---8<----8<---8<----8<---8<---

The generated code at -O3 is:

(gdb) disassemble foo
Dump of assembler code for function foo():
   0x0000000000400470 <+0>:    jmp    0x400470 <foo()>
End of assembler dump.

-O1 is interesting:

(gdb) disassemble foo
Dump of assembler code for function foo():
   0x000000000040046c <+0>:    sub    rsp,0x18
   0x0000000000400470 <+4>:    lea    rax,[rsp]
   0x0000000000400474 <+8>:    mov    QWORD PTR [rsp],rax
   0x0000000000400478 <+12>:    mov    rdi,rsp
   0x000000000040047b <+15>:    call   0x400498 <J::F()>
   0x0000000000400480 <+20>:    add    rsp,0x18
   0x0000000000400484 <+24>:    ret
End of assembler dump.

So far so good, but J::F() is strange:

Dump of assembler code for function J::F():
   0x0000000000400498 <+0>:    sub    rsp,0x8
   0x000000000040049c <+4>:    mov    rax,QWORD PTR [rdi]
   0x000000000040049f <+7>:    mov    rax,QWORD PTR [rax]
   0x00000000004004a2 <+10>:    mov    rax,QWORD PTR [rax]
   0x00000000004004a5 <+13>:    mov    rax,QWORD PTR [rax]
   0x00000000004004a8 <+16>:    mov    rax,QWORD PTR [rax]
   0x00000000004004ab <+19>:    mov    rax,QWORD PTR [rax]
   0x00000000004004ae <+22>:    mov    rax,QWORD PTR [rax]
   0x00000000004004b1 <+25>:    mov    rax,QWORD PTR [rax]
   0x00000000004004b4 <+28>:    mov    rdi,QWORD PTR [rax]
   0x00000000004004b7 <+31>:    call   0x400498 <J::F()>
   0x00000000004004bc <+36>:    add    rsp,0x8
   0x00000000004004c0 <+40>:    ret
End of assembler dump.

What are those mov rax,QWORD PTR [rax]'s ?

My question is: wouldn't it be possible to print a warning when a jmp to itself or trivial infinite recursion is generated? The code compiled fine w/ -Wall -Wextra -Werror w/ 4.6 and 4.7.

Cheers, Peter

Reply via email to