clang++ memory sanitizer reports use-of-uninitialized-value
Asked Answered
K

2

1

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;
}
Kozloski answered 25/11, 2017 at 13:56 Comment(2)
Don't know about the uninitialized value, but you pass the fields vector by value and thus has_field compares iterators into different copies of the vector.Inconsolable
@BoPersson You're right. Return a iterator to a container passed by value is very wrong. Here is a related stackoverflow question. #10114072Kozloski
M
1

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
Marlinmarline answered 25/11, 2017 at 14:41 Comment(1)
It's possibly false positive. I check it again with valgrind --tool=memcheck --track-origins=yes ./a.out. Nothing shows up. Bo-Persson finds the problem with my program. I was passing a vector by value, and I return a iterator to it, which is very bad.Kozloski
C
1

It seems that Clang's memory sanitizer expects external libraries (libstrdc++) to also be instrumented otherwise false positives are likely.

https://clang.llvm.org/docs/MemorySanitizer.html#handling-external-code

"MemorySanitizer requires that all program code is instrumented. This also includes any libraries that the program depends on, even libc."

So valgrind still seems the most practical for uninitialized value detection (albeit slow).

Cajuput answered 19/11, 2021 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.