Search code examples
c++initializationclang++memory-sanitizer

clang++ memory sanitizer reports use-of-uninitialized-value


This code is taken from IncludeOS github page. I modified it a bit so that it compiles without other header files. find function from IncludeOS is a bit too verbose, so I want to simplify it. But after modification, the code behaves differently from what I expected.

Here is a short explanation. This code is used to parse HTTP headers. Header fields are name-value pairs. It's represented as vector<pair<string, string>>. find function is used to find the location of a field name in the header, and has_field is used to check whether a specific field name exists in the header.

In main function, four elements are appended to fields. six shouldn't be found in fields.But has_field returns true.

I tried to track the error with gdb. But I was lost in the sea of outputs. I did find a somewhat interesting message.

std::__uninitialized_copy<false>::__uninit_copy<__gnu_cxx::__normal_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const*, std::vector<std::pair<std::__cxx11::basic_string<char, std::char_traits<char<, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char<, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >>>>, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*> (__first={first = "one", second = "1"}, __last=

{first = <error reading variable: Cannot create a lazy string with address 0x0, and a non-zero length.>, second = ""}, __result=0x61bf00)

I used clang sanitizer to find out what's wrong. Only memory sanitizer shows interesting reports. Running,

clang++ -std=c++17 -O1 -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer main.cc

/a.out reports,

Uninitialized value was created by an allocation of 'ref.tmp' in the stack frame of function '_ZNSt4pairINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_EC2IRA6_KcRA2_S8_Lb1EEEOT_OT0_'`.

When optimization level is set to -O3, however, nothing shows up.

#include <algorithm>
#include <iostream>
#include <vector>
#include <experimental/string_view>

using Headers = std::vector<std::pair<std::string, std::string>>;
using string_view = std::experimental::string_view;

Headers::const_iterator find(Headers fields, const string_view field) {
  if (field.empty()) return fields.cend();
  //-----------------------------------
  return
    std::find_if(fields.cbegin(), fields.cend(), [field](const auto _) {
      return std::equal(_.first.cbegin(), _.first.cend(), field.cbegin(), field.cend(), 
        [](const auto a, const auto b) { return std::tolower(a) == std::tolower(b); }); 
    }); 
}

bool has_field(Headers fields, const string_view field)
{
  return find(fields, field) != fields.cend();
}

int main()
{
  Headers fields;
  fields.emplace_back("one", "1");
  fields.emplace_back("two", "2");
  fields.emplace_back("three", "3");
  fields.emplace_back("four", "4");

  std::string s = "six";
  if (has_field(fields, s)) 
    std::cout << s << " is in " << "fields" << std::endl;

  return 0;
}

Solution

  • It's likely a false positive. llvm comes with the symbolizer binary, which allows the sanitizer to output line numbers. I've managed to reproduce your error with this minimal example:

      1 #include <iostream>
      2 #include <vector>
      3 
      4 using Headers = std::vector<int>;
      5 
      6 bool a(Headers fields) {
      7     return true;
      8 }   
      9 
     10 bool b(Headers fields)
     11 {
     12   return a(fields);
     13 }
     14 
     15 int main()
     16 { 
     17   Headers fields;
     18   
     19   if (b(fields)) {
     20     std::cout << std::endl;
     21   }
     22 
     23   return 0;
     24 }
    

    In both cases, the stack trace claims std::endl is the culprit. For the error to occur the following magical things have to occur:

    • Output std::endl
    • Have two function calls

    If I declare a to take fields by reference, the error disappears; the same cannot be said for b. All of this leads me to believe it's nonsensical and a false positive. For reference, here's the sanitizer output with line numbers:

    Uninitialized bytes in __interceptor_memcmp at offset 192 inside [0x7fff18347610, 256)
    ==5724==WARNING: MemorySanitizer: use-of-uninitialized-value
        #0 0x7f8f663d94ab in std::ctype<char>::_M_widen_init() const (/lib64/libstdc++.so.6+0xb74ab)
        #1 0x7f8f66435d17 in std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&) (/lib64/libstdc++.so.6+0x113d17)
        #2 0x4912ff in main test.cpp:20:15
        #3 0x7f8f65415889 in __libc_start_main (/lib64/libc.so.6+0x20889)
        #4 0x41a9b9 in _start (a.out+0x41a9b9)
    
      Uninitialized value was created by an allocation of 'ref.tmp' in the stack frame of function '_ZNSt6vectorIiSaIiEEC2ERKS1_'
        #0 0x491360 in std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const&) /usr/bin/../lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/bits/stl_vector.h:329
    
    SUMMARY: MemorySanitizer: use-of-uninitialized-value (/lib64/libstdc++.so.6+0xb74ab) in std::ctype<char>::_M_widen_init() const
    Exiting