clang++ 内存清理程序报告 use-of-uninitialized-value
clang++ memory sanitizer reports use-of-uninitialized-value
此代码取自IncludeOS github page。我对其进行了一些修改,以便在没有其他 header 文件的情况下进行编译。 find
IncludeOS 的函数有点过于冗长,所以我想简化它。但是修改后,代码的行为与我预期的不同。
这是一个简短的解释。此代码用于解析 HTTP headers。 Header 字段是 name-value 对。它表示为 vector<pair<string, string>>
。 find
函数用于查找header中某个字段名的位置,has_field
用于检查header中是否存在特定字段名。
在main
函数中,四个元素附加到字段。 six
不应在 fields.But has_field
returns 中找到
我试图用 gdb
追踪错误。但是我迷失在输出的海洋中。我确实发现了一个有点有趣的消息。
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)
我使用了 clang
消毒剂来找出问题所在。只有内存清理器显示有趣的报告。 运行,
clang++ -std=c++17 -O1 -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer main.cc
/a.out
份报告,
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_'`.
然而,当优化级别设置为 -O3
时,什么也没有显示。
#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;
}
这可能是误报。 llvm 带有符号生成器二进制文件,它允许消毒器输出行号。我已经用这个最小的例子成功地重现了你的错误:
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 }
在这两种情况下,堆栈跟踪声称 std::endl
是罪魁祸首。要发生错误,必须发生以下神奇的事情:
- 输出
std::endl
- 有两个函数调用
如果我声明a
引用fields
,错误消失; b
则不同。所有这些让我相信这是荒谬的和误报的。作为参考,这是带行号的消毒剂输出:
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
似乎 Clang 的内存清理器还需要检测外部库 (libstrdc++),否则可能会出现误报。
https://clang.llvm.org/docs/MemorySanitizer.html#id11
“MemorySanitizer 要求检测所有程序代码。这还包括程序所依赖的任何库,甚至是 libc。”
所以 valgrind 似乎仍然是未初始化值检测最实用的方法(尽管很慢)。
此代码取自IncludeOS github page。我对其进行了一些修改,以便在没有其他 header 文件的情况下进行编译。 find
IncludeOS 的函数有点过于冗长,所以我想简化它。但是修改后,代码的行为与我预期的不同。
这是一个简短的解释。此代码用于解析 HTTP headers。 Header 字段是 name-value 对。它表示为 vector<pair<string, string>>
。 find
函数用于查找header中某个字段名的位置,has_field
用于检查header中是否存在特定字段名。
在main
函数中,四个元素附加到字段。 six
不应在 fields.But has_field
returns 中找到
我试图用 gdb
追踪错误。但是我迷失在输出的海洋中。我确实发现了一个有点有趣的消息。
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)
我使用了 clang
消毒剂来找出问题所在。只有内存清理器显示有趣的报告。 运行,
clang++ -std=c++17 -O1 -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer main.cc
/a.out
份报告,
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_'`.
然而,当优化级别设置为 -O3
时,什么也没有显示。
#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;
}
这可能是误报。 llvm 带有符号生成器二进制文件,它允许消毒器输出行号。我已经用这个最小的例子成功地重现了你的错误:
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 }
在这两种情况下,堆栈跟踪声称 std::endl
是罪魁祸首。要发生错误,必须发生以下神奇的事情:
- 输出
std::endl
- 有两个函数调用
如果我声明a
引用fields
,错误消失; b
则不同。所有这些让我相信这是荒谬的和误报的。作为参考,这是带行号的消毒剂输出:
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
似乎 Clang 的内存清理器还需要检测外部库 (libstrdc++),否则可能会出现误报。
https://clang.llvm.org/docs/MemorySanitizer.html#id11
“MemorySanitizer 要求检测所有程序代码。这还包括程序所依赖的任何库,甚至是 libc。”
所以 valgrind 似乎仍然是未初始化值检测最实用的方法(尽管很慢)。