False-positive `clang-analyzer-core.uninitialized.*` or hidden threats of using `std::valarray`?

  c++, clang, clang-tidy

I use clang-tidy static code analyzer

$ clang-tidy --version
LLVM (http://llvm.org/):
  LLVM version 11.0.1
  
  Optimized build.
  Default target: x86_64-pc-linux-gnu
  Host CPU: skylake

Given a code

#include <valarray>

std::valarray<int> f()
{
  const std::valarray<int> r(0, 4UL);
  return r;
}

int main() {
  std::valarray<int> a{f()};
  return a[1UL];
}

I’ve got an error Undefined or garbage value returned to caller [clang-analyzer-core.uninitialized.UndefReturn] because clang-tidy thinks that a[1UL] may be uninitialized when I modify it.

main.cpp:11:3: warning: Undefined or garbage value returned to caller [clang-analyzer-core.uninitialized.UndefReturn]
  return a[1UL];
  ^
main.cpp:10:24: note: Calling 'f'
  std::valarray<int> a{f()};
                       ^
main.cpp:6:10: note: Calling copy constructor for 'valarray<int>'
  return r;
         ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:647:37: note: Calling '__valarray_get_storage<int>'
    : _M_size(__v._M_size), _M_data(__valarray_get_storage<_Tp>(__v._M_size))
                                    ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:58:32: note: Storing uninitialized value
    { return static_cast<_Tp*>(operator new(__n * sizeof(_Tp))); }
                               ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:647:37: note: Returning from '__valarray_get_storage<int>'
    : _M_size(__v._M_size), _M_data(__valarray_get_storage<_Tp>(__v._M_size))
                                    ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:648:7: note: Calling '__valarray_copy_construct<int>'
    { std::__valarray_copy_construct(__v._M_data, __v._M_data + _M_size,
      ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:163:7: note: Calling '_Array_copy_ctor::_S_do_it'
      _Array_copy_ctor<_Tp, __is_trivial(_Tp)>::_S_do_it(__b, __e, __o);
      ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:153:6: note: Assuming '__b' is null
        if (__b)
            ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:153:2: note: Taking false branch
        if (__b)
        ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:163:7: note: Returning from '_Array_copy_ctor::_S_do_it'
      _Array_copy_ctor<_Tp, __is_trivial(_Tp)>::_S_do_it(__b, __e, __o);
      ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:648:7: note: Returning from '__valarray_copy_construct<int>'
    { std::__valarray_copy_construct(__v._M_data, __v._M_data + _M_size,
      ^
main.cpp:6:10: note: Returning from copy constructor for 'valarray<int>'
  return r;
         ^
main.cpp:10:24: note: Returning from 'f'
  std::valarray<int> a{f()};
                       ^
main.cpp:11:12: note: Passing the value 1 via 1st parameter '__i'
  return a[1UL];
           ^
main.cpp:11:3: note: Undefined or garbage value returned to caller
  return a[1UL];
  ^

though, if I remove const from the r initialization, the code works fine.

Another way of "fixing" this is to set a size less than 4UL — for example, 2UL or 3UL. Even 1UL fits, though it’s false-negative and I don’t hope that static analyzer finds everything in the world.

The complicated copy constructor could be a problem but consider the following example

#include <cassert>
#include <iostream>
#include <valarray>

struct A {
  std::valarray<int> value;
  explicit A(const std::size_t half_size) : value(0, 2UL * half_size) {
    assert(half_size == 2UL);
  }
};

int main() {
  std::size_t size;
  std::cin >> size;
  A a{size};
  ++a.value[1UL];
  return 0;
}

with a very similar problem

main.cpp:16:3: warning: The expression is an uninitialized value. The computed value will also be garbage [clang-analyzer-core.uninitialized.Assign]
  ++a.value[1UL];
  ^
main.cpp:15:5: note: Calling constructor for 'A'
  A a{size};
    ^
main.cpp:7:45: note: Calling constructor for 'valarray<int>'
  explicit A(const std::size_t half_size) : value(0, 2UL * half_size) {
                                            ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:632:29: note: Calling '__valarray_get_storage<int>'
    : _M_size(__n), _M_data(__valarray_get_storage<_Tp>(__n))
                            ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:58:32: note: Storing uninitialized value
    { return static_cast<_Tp*>(operator new(__n * sizeof(_Tp))); }
                               ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:632:29: note: Returning from '__valarray_get_storage<int>'
    : _M_size(__n), _M_data(__valarray_get_storage<_Tp>(__n))
                            ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:633:7: note: Calling '__valarray_fill_construct<int>'
    { std::__valarray_fill_construct(_M_data, _M_data + __n, __t); }
      ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:127:7: note: Calling '_Array_init_ctor::_S_do_it'
      _Array_init_ctor<_Tp, __is_trivial(_Tp)>::_S_do_it(__b, __e, __t);
      ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:118:9: note: Assuming '__b' is equal to '__e'
        while (__b != __e)
               ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:118:2: note: Loop condition is false. Execution continues on line 118
        while (__b != __e)
        ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/valarray_array.h:127:7: note: Returning from '_Array_init_ctor::_S_do_it'
      _Array_init_ctor<_Tp, __is_trivial(_Tp)>::_S_do_it(__b, __e, __t);
      ^
/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/valarray:633:7: note: Returning from '__valarray_fill_construct<int>'
    { std::__valarray_fill_construct(_M_data, _M_data + __n, __t); }
      ^
main.cpp:7:45: note: Returning from constructor for 'valarray<int>'
  explicit A(const std::size_t half_size) : value(0, 2UL * half_size) {
                                            ^
main.cpp:8:12: note: Assuming 'half_size' is equal to 2
    assert(half_size == 2UL);
           ^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
     (static_cast <bool> (expr)                                         
                          ^
main.cpp:8:12: note: 'half_size' is equal to 2
    assert(half_size == 2UL);
           ^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
     (static_cast <bool> (expr)                                         
                          ^
main.cpp:8:5: note: '?' condition is true
    assert(half_size == 2UL);
    ^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
     (static_cast <bool> (expr)                                         
      ^
main.cpp:15:5: note: Returning from constructor for 'A'
  A a{size};
    ^
main.cpp:16:13: note: Passing the value 1 via 1st parameter '__i'
  ++a.value[1UL];
            ^
main.cpp:16:3: note: The expression is an uninitialized value. The computed value will also be garbage
  ++a.value[1UL];
  ^

I’ve tried reading LLVM source code but don’t know which file to monitor, so I didn’t succeed in finding the false-positive cause (if it’s a false positive).

Changing valarray to vector fixes both examples.

Source: Windows Questions C++

LEAVE A COMMENT