4

I've been running clang 3.3's static analyser on various projects of mine. Except some issues that were my own fault (which was to be expected, I would have been both very sad and very smug otherwise), all went pretty smoothly except the following problem concerning std::function's move constructor which is a false positive.

Before discussing it further, here's a simple test case:

int main() {
  std::function<void ()> f1;
  std::function<void ()> f2 = std::move(f1);
}

Run it through clang++ -std=c++11 --analyze -Xanalyzer -analyzer-output=text foo.cpp (which uses GCC's libstdc++ -- namely the 4.8.1 version -- not clang's libc++) and you get the following trace:

In file included from foo.cpp:1:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/iostream:39:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/ostream:38:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/ios:40:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/char_traits.h:39:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/stl_algobase.h:64:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/stl_pair.h:59:
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/move.h:175:7: warning: Assigned value is garbage or undefined
      _Tp __tmp = _GLIBCXX_MOVE(__a);
      ^~~~~~~~~   ~~~~~~~~~~~~~~~~~~
foo.cpp:30:31: note: Calling move constructor for 'function'
  std::function<void ()> f2 = std::move(f1);
                              ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2232:2: note: Calling 'function::swap'
        __x.swap(*this);
        ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2359:2: note: Calling 'swap'
        std::swap(_M_invoker, __x._M_invoker);
        ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/move.h:175:19: note: Calling 'move'
      _Tp __tmp = _GLIBCXX_MOVE(__a);
                  ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/move.h:142:30: note: expanded from macro '_GLIBCXX_MOVE'
#define _GLIBCXX_MOVE(__val) std::move(__val)
                             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/move.h:175:19: note: Returning from 'move'
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/move.h:142:30: note: expanded from macro '_GLIBCXX_MOVE'
#define _GLIBCXX_MOVE(__val) std::move(__val)
                             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/move.h:175:7: note: Assigned value is garbage or undefined
      _Tp __tmp = _GLIBCXX_MOVE(__a);
      ^
1 warning generated.

As you can see, the move constructor std::function(std::function&&) is implemented in terms of swap. The steps of the whole operation are (if we are to believe clang):

  • f1 is correctly constructed
  • f2 is not yet constructed and thus contains garbage
  • f1 is moved into f2 but actually it's really a swap
  • f2 now contains the old f1, but f1 contains the old f2 ie. garbage
  • at some point, f1 containing garbage is destructed... what happens then?

The theory says, this is very bad. In practice, looking at the implementation, it appears that std::function privately inherits from _Function_base which takes care to initialize all that matters (ie. _M_manager) to null, so clang's warning about _M_invoker is pointless.

Just in case someone is doubting, moving an object is supposed to leave it in an indeterminate state, where you can only either assign to it or destruct it. GCC's function implementation does exactly that: only _M_manager is important as far as resource management is involved, the rest (including _M_invoker) are just "convenience" pointers.

I dug far enough in GCC's function implementation to have absolutely no doubt as to the status of false positive of clang's diagnostic. But since I have literally hundreds of places where this happens in my code, this makes going through the static analyser's results quite a pain to say the least.

How can I instruct clang not to report this very issue?

Again, in case you missed it, I'm using clang 3.3 along with GCC's libstdc++ 4.8.1.


Note: If you are running a clang 3.4 build and if it doesn't trigger this false positive, please let me know. As far as I tried, I couldn't get 3.4 to run on my system yet (Debian Jessie) but if it solves this problem I'll just try harder.

syam
  • 14,701
  • 3
  • 41
  • 65
  • Clang's website implies that false positives in Clang's diagnostics should be considered bugs to be reported. One action you could take is therefore to report the false positive to Clang developers (if you manage to obtain a recent version and the problem is still there). http://clang-analyzer.llvm.org – Pascal Cuoq Oct 19 '13 at 22:49
  • @PascalCuoq Yeah I was considering a bug report anyway, but I hoped there was an end-user mechanism to get rid of false positives. You know, in the meantime. :) Especially since the nature of this problem is extremely dependent on what actually happens in the destructor or assignment, so it might not be very easy to fix. – syam Oct 19 '13 at 22:53
  • 1
    clang++3.4 (trunk 193040) does not to issue any diagnostic message for your example. Using libstdc++4.8.1 – dyp Oct 20 '13 at 16:11
  • @DyP Thanks for the info, I'm going to give it a (harder) try then. – syam Oct 20 '13 at 16:38

1 Answers1

0

This might be interesting for you: Future directions for the analyzer

Basically you can currently only disable certain checkers per TU or use

#ifndef __clang_analyzer__
...
#endif

if you really need to. But of course actual false positives should be reported.

Trass3r
  • 5,858
  • 2
  • 30
  • 45