19

I found a situation in which Clang produces an illegal instruction, where gcc doesn't, while experimenting with this question.

My question is: Am I doing something very wrong, or is this an actual issue with Clang?

I boiled it down to the minimal snippet necessary to reproduce the problem.

Take the file eigen.cpp:

#include <iostream>

#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>

int main() {
    Eigen::Matrix2d A;

    A << 0, 1, 2, 3;

    std::cout << A << "\n";
}

And the file eigen_matrix_addons.hpp:

friend std::ostream &operator<<(std::ostream &o, const Derived &m) {
    o << static_cast<const MatrixBase<Derived> &>(m);
}

(See here for a detailed explanation on what this file does. In short, its contents are placed directly into the class definition of template<class Derived> class MatrixBase;. So, this introduces another ostream operator on Derived that calls the Eigen implementation of the ostream operator on MatrixBase<Derived>. The motivation for this becomes apparent if you read this question.)

Compile with GCC and run:

$ g++ -std=c++11 -Wall -Wextra -pedantic -isystem/usr/include/eigen3 -I. -o eigen_gcc eigen.cpp
$ ./eigen_gcc
0 1
2 3
$ g++ --version
g++ (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

And then compile with Clang and run:

$ clang++ -std=c++11 -Wall -Wextra -pedantic -isystem/usr/include/eigen3 -I. -o eigen_clang eigen.cpp
$ ./eigen_clang
0 1
Illegal instruction
$ clang++ --version
clang version 3.4 (branches/release_34 198681)
Target: x86_64-suse-linux
Thread model: posix

As you can see, the program is interrupted after executing an illegal instruction. A back-trace in gdb reveals that the issue happens in line two of eigen_matrix_addons.hpp:

(gdb) bt
#0  0x00000000004013e1 in Eigen::operator<< (o=..., m=...)
    at ./eigen_matrix_addons.hpp:2
#1  0x00000000004010f0 in main () at eigen.cpp:15

I.e. probably the static_cast?

My Eigen version is 3.2.0-2.1.4 according to zypper.

Edit

The dis-assembly that @Mysticial asked for:

(gdb) disass
Dump of assembler code for function Eigen::operator<<(std::ostream&, Eigen::Matrix<double, 2, 2, 0, 2, 2> const&):
   0x00000000004013c0 <+0>:     push   %rbp
   0x00000000004013c1 <+1>:     mov    %rsp,%rbp
   0x00000000004013c4 <+4>:     sub    $0x20,%rsp
   0x00000000004013c8 <+8>:     mov    %rdi,-0x10(%rbp)
   0x00000000004013cc <+12>:    mov    %rsi,-0x18(%rbp)
   0x00000000004013d0 <+16>:    mov    -0x10(%rbp),%rdi
   0x00000000004013d4 <+20>:    mov    -0x18(%rbp),%rsi
   0x00000000004013d8 <+24>:    callq  0x4013f0 <Eigen::operator<< <Eigen::Matrix<double, 2, 2, 0, 2, 2> >(std::ostream&, Eigen::DenseBase<Eigen::Matrix<double, 2, 2, 0, 2, 2> > const&)>
   0x00000000004013dd <+29>:    mov    %rax,-0x20(%rbp)
=> 0x00000000004013e1 <+33>:    ud2
End of assembler dump.
Community
  • 1
  • 1
Lemming
  • 4,085
  • 3
  • 23
  • 36
  • 2
    What processor is this? And can you provide the disassembly at the point where it breaks? – Mysticial Aug 05 '14 at 20:34
  • 4
    [Flowing off the end of the value returning function is undefined behavior](http://stackoverflow.com/questions/20614282/why-does-this-c-snippet-compile-non-void-function-does-not-return-a-value). – Shafik Yaghmour Aug 05 '14 at 20:41
  • @Mysticial The processor is `Intel(R) Core(TM) i5-3340M CPU @ 2.70GHz`. I've added the dis-assembly into the question. – Lemming Aug 05 '14 at 21:35
  • `ud2` tells the CPU to raise an invalid opcode instructions. So Clang is intentionally crashing the program. Interesting, most of the compilers won't even compile if you omit the return value in a case like this. – Mysticial Aug 05 '14 at 21:39
  • @Mysticial Interesting, I didn't know that. It's odd to me, that neither gcc, nor clang gave a warning. Albeit all the warning flags... That's how I usually notice that I forgot the return in an ostream operator. – Lemming Aug 05 '14 at 21:41
  • @ShafikYaghmour Yes, it was the missing return statement, indeed. – Lemming Aug 05 '14 at 21:41

2 Answers2

32

The "Illegal instruction" error is likely because your "operator <<" is missing a return statement. This leads to undefined behaviour.

Section 6.6.3 of the Standard says:

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

You should add:

return o;

at the end of the function.

Henrik
  • 436
  • 4
  • 2
  • 4
    You can also do `return o << ...;` to keep it in one line. – Tavian Barnes Aug 05 '14 at 20:38
  • Thank you. Yes, it was the missing return statement. For some reason I always forget it in `operator<<`. It is, however, remarkable that neither gcc, nor clang give a warning in this case. – Lemming Aug 05 '14 at 21:38
  • @Lemming as I noted in my [answer here](http://stackoverflow.com/a/20614325/1708801) you can use the `-Werror=return-type` flag to generate a error for this. – Shafik Yaghmour Aug 06 '14 at 01:28
  • @ShafikYaghmour Thanks. Yes, `-Wreturn-type` is included in `-Wall`, which I'm setting. In the above example `-Werror=return-type` shows no different behaviour, and also ignores the missing return statement. The reason for this is simple, but I had completely missed it before... The Eigen library is added to the include search path via `-isystem`, which instructs the compiler to not generate any warnings from that code. (Including `-Werror`). And due to the Eigen plugin trick, my code is only parsed as part of these system headers. If I use `-I` instead, the warning is printed. – Lemming Aug 06 '14 at 07:22
  • @RichardSmith Yes, sorry, I don't see how that is different to what I said in my last comment. – Lemming Aug 06 '14 at 23:04
2

I ran into the same thing using google's unit test. I had a mocked function with a default return value set with

ON_CALL(...).WillByDefault(Return(...));

Then at an expectation I used SetArgReferee like

EXPECT_CALL(...).WillOnce(SetArgReferee<...>(...))

clang inserted an ud2 at the and of the SetArgReferee function at SOME of the identical expectations, but not at all of them. Correcting it to

EXPECT_CALL(...).WillOnce(
    DoAll(
        SetArgReferee<...>(...),
        Return(...)));

fixed it.

TmsKtel
  • 361
  • 3
  • 11