Search code examples
c++gcc10

std::vector null pointer dereference from g++ -fanalyzer


I was experimenting with -fanalyzer on gcc 10, and managed to get a null-pointer dereference reported in std::vector. But I'm not sure if my code has some mistake?

#include <vector>

class Bar
{
public:
    explicit Bar()
    {
    }
    int m_val;
};

int main()
{
    std::vector<Bar> a;
    std::vector<Bar> b(a);
    static_cast<void>(b);
    return 0;
}

It only fails with -O2 (or -O -O1 -O3)

g++-10 -fanalyzer TestVector.cpp -O2
In copy constructor ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’:                                                                     
cc1plus: warning: dereference of NULL ‘__cur’ [CWE-690] [-Wanalyzer-null-dereference]                                                                                                                       
  ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’: events 1-2                                                                            
    |                                                                                                                                                                                                       
    |/usr/include/c++/10/bits/stl_vector.h:553:7:                                                                                                                                                           
    |  305 |       { _M_create_storage(__n); }                                                                                                                                                              
    |      |         ~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                                 
    |      |                          |                                                                                                                                                                     
    |      |                          (2) calling ‘std::_Vector_base<Bar, std::allocator<Bar> >::_M_create_storage’ from ‘std::vector<Bar>::vector’
    |......
    |  553 |       vector(const vector& __x)
    |      |       ^~~~~~
    |      |       |
    |      |       (1) entry to ‘std::vector<Bar>::vector’
    |
    +--> ‘void std::_Vector_base<_Tp, _Alloc>::_M_create_storage(std::size_t) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’: events 3-5
           |
           |  346 |  return __n != 0 ? _Tr::allocate(_M_impl, __n) : pointer();
           |      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           |      |                  |
           |      |                  (4) following ‘false’ branch (when ‘__n == 0’)...
           |......
           |  359 |       _M_create_storage(size_t __n)
           |      |       ^~~~~~~~~~~~~~~~~
           |      |       |
           |      |       (3) entry to ‘std::_Vector_base<Bar, std::allocator<Bar> >::_M_create_storage’
           |  360 |       {
           |  361 |  this->_M_impl._M_start = this->_M_allocate(__n);
           |      |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           |      |                         |
           |      |                         (5) ...to here
           |
    <------+
    |
  ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’: events 6-7
    |
    |  305 |       { _M_create_storage(__n); }
    |      |         ~~~~~~~~~~~~~~~~~^~~~~
    |      |                          |
    |      |                          (6) returning to ‘std::vector<Bar>::vector’ from ‘std::_Vector_base<Bar, std::allocator<Bar> >::_M_create_storage’
    |......
    |  558 |    std::__uninitialized_copy_a(__x.begin(), __x.end(),
    |      |    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    |      |                               |
    |      |                               (7) ‘<unknown>’ is NULL
    |  559 |           this->_M_impl._M_start,
    |      |           ~~~~~~~~~~~~~~~~~~~~~~~
    |  560 |           _M_get_Tp_allocator());
    |      |           ~~~~~~~~~~~~~~~~~~~~~~
    |
  ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’: event 8
    |
    |/usr/include/c++/10/bits/stl_uninitialized.h:90:23:
    |   90 |        for (; __first != __last; ++__first, (void)++__cur)
    |      |               ~~~~~~~~^~~~~~~~~
    |      |                       |
    |      |                       (8) following ‘true’ branch...
    |
  ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’: event 9
    |
    |/usr/include/c++/10/bits/stl_iterator.h:980:2:
    |  980 |  ++_M_current;
    |      |  ^~
    |      |  |
    |      |  (9) ...to here
    |
  ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = Bar; _Alloc = std::allocator<Bar>]’: event 10
    |
    |cc1plus:
    | (10): dereference of NULL ‘__cur’
    |

g++-10 -fanalyzer TestVector.cpp -O0 compiles fine.

Removing m_val or the constructor from Bar also compile fine.

$ g++-10 --version
g++-10 (Ubuntu 10.1.0-2ubuntu1~18.04) 10.1.0

Live demo


Solution

  • Your code is not responsible. GCC is.

    Someone reported a similar problem on the gcchelp mailing list, and the response from Mr Wakely was:

    It's a known limitation that the analyzer doesn't support C++ yet.

    It looks to me like it followed the "true" branch by mistake, though that's interesting because the conditional operator isn't unique to C++.

    Unfortunately, this fact of the analyzer doesn't appear to be documented, at least not where the switch is described.