Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange Clang behaviour

Have a look at this piece of code:

#include <iostream>
#include <string>

void foo(int(*f)()) {
    std::cout << f() << std::endl;
}

void foo(std::string(*f)()) {
    std::string s = f();
    std::cout << s << std::endl;
}

int main() {
    auto bar = [] () -> std::string {
        return std::string("bla");
    };

    foo(bar);

    return 0;
}

Compiling it with

g++ -o test test.cpp -std=c++11

leads to:

bla

like it should do. Compiling it with

clang++ -o test test.cpp -std=c++11 -stdlib=libc++

leads to:

zsh: illegal hardware instruction  ./test

And Compiling it with

clang++ -o test test.cpp -std=c++11 -stdlib=stdlibc++

leads also to:

zsh: illegal hardware instruction  ./test

Clang/GCC Versions:

clang version 3.2 (tags/RELEASE_32/final)
Target: x86_64-pc-linux-gnu
Thread model: posix

gcc version 4.7.2 (Gentoo 4.7.2-r1 p1.5, pie-0.5.5) 

Anyone any suggestions what is going wrong?

Thanks in advance!

like image 325
rralf Avatar asked Apr 03 '13 21:04

rralf


1 Answers

Yes, it is a bug in Clang++. I can reproduce it with CLang 3.2 in i386-pc-linux-gnu.

And now some random analysis...

I've found that the bug is in the conversion from labmda to pointer-to-function: the compiler creates a kind of thunk with the appropriate signature that calls the lambda, but it has the instruction ud2 instead of ret.

The instruction ud2, as you all probably know, is an instruction that explicitly raises the "Invalid Opcode" exception. That is, an instruction intentionally left undefined.

Take a look at the disassemble: this is the thunk function:

main::$_0::__invoke():
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        movl    %ecx, 4(%esp)
        calll   main::$_0::operator()() const ; this calls to the real lambda
        subl    $4, %esp
        ud2   ; <<<-- What the...!!!

So a minimal example of the bug will be simply:

int main() {
    std::string(*f)() = [] () -> std::string {
        return "bla";
    };
    f();
    return 0;
}

Curiously enough, the bug doesn't happen if the return type is a simple type, such as int. Then the generated thunk is:

main::$_0::__invoke():
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    %eax, (%esp)
        calll   main::$_0::operator()() const
        addl    $8, %esp
        popl    %ebp
        ret

I suspect that the problem is in the forwarding of the return value. If it fits in a register, such as eax all goes well. But if it is a big struct, such as std::string it is returned in the stack, the compiler is confused and emits the ud2 in desperation.

like image 110
rodrigo Avatar answered Sep 17 '22 19:09

rodrigo