为什么 boost::any 在 boost::program_options 中表现出未定义的行为?
Why does boost::any exhibit undefined behaviour in boost::program_options?
直接从boost's documentation举个例子:
#include <iostream>
#include <string>
#include <boost/program_options.hpp>
int main(int const ac, char** const av){
// Declare the supported options.
namespace po = boost::program_options;
using namespace std;
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("compression", po::value<int>(), "set compression level")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
if (vm.count("help")) {
cout << desc << "\n";
return 1;
}
if (vm.count("compression")) {
cout << "Compression level was set to "
<< vm["compression"].as<int>() << ".\n";
} else {
cout << "Compression level was not set.\n";
}
}
程序运行正常。
但是,当使用 gcc 的消毒剂(或 clang 的)编译时:
g++ -std=c++1z -o main main.cpp -fsanitize=undefined -lboost_program_options
它产生以下运行时错误:
./main --compression="1" 134
/usr/include/boost/any.hpp:243:16: runtime error: downcast of address 0x000001153fb0 which does not point to an object of type 'holder'
0x000001153fb0: note: object is of type 'boost::any::holder<int>'
00 00 00 00 20 bc 42 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 31 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<int>'
Compression level was set to 1.
我已将问题提炼为更小的问题:
#include <iostream>
#include <string>
#include <boost/program_options.hpp>
int main(int const argc, char** const argv){
using namespace boost::program_options;
//create description
options_description desc("");
//add entry
desc.add_options()
("foo",value<std::string>(),"desc");
//create variable map
variables_map vm;
//store variables in map
positional_options_description pod;
store(command_line_parser(argc, argv).options(desc).positional(pod).run(), vm);
notify(vm);
//get variable out of map
std::string foo;
if (vm.count("foo")){
foo = vm["foo"].as<std::string>(); //UNDEFINED BEHAVIOUR
}
}
编译为:
g++ -std=c++1z -o main main.cpp -fsanitize=undefined -lboost_program_options
执行时:
./main --foo="hello"
/usr/include/boost/any.hpp:243:16: runtime error: downcast of address 0x000000d85fd0 which does not point to an object of type 'holder'
0x000000d85fd0: note: object is of type 'boost::any::holder<std::string>'
00 00 00 00 b0 c5 5e 90 f8 7f 00 00 98 5f d8 00 00 00 00 00 00 00 00 00 00 00 00 00 31 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<std::string>'
显然是变量映射的强制转换导致了 UB:
vm["foo"].as<std::string>()
这正是联机文档显示的方式。
这是误报吗?我的 boost 分发中有错误吗?
如果它确实安全,我怎样才能避免消毒剂标记它?
这似乎真的是一个未定义的行为。此代码说明了问题:
#include <boost/any.hpp>
int main()
{
int value = 0;
int const& const_ref = value;
boost::any any_var {const_ref};
boost::any_cast<int&>(any_var); // ubsan error
}
这里 any_var
是用 const 值构造的,并作为非常量 int
访问。 运行 此带有消毒剂的代码会引发类似于您的运行时错误:
/usr/local/include/boost/any.hpp:259:16: runtime error: downcast of address 0x60200000eff0 which does not point to an object of type 'any::holder<int>'
0x60200000eff0: note: object is of type 'boost::any::holder<int const>'
01 00 00 0c b0 ee 49 00 00 00 00 00 00 00 00 00 be be be be 00 00 00 00 00 00 00 00 00 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<int const>'
SUMMARY: AddressSanitizer: undefined-behavior /usr/local/include/boost/any.hpp:259:16 in
/usr/local/include/boost/any.hpp:259:73: runtime error: member access within address 0x60200000eff0 which does not point to an object of type 'any::holder<int>'
0x60200000eff0: note: object is of type 'boost::any::holder<int const>'
01 00 00 0c b0 ee 49 00 00 00 00 00 00 00 00 00 be be be be 00 00 00 00 00 00 00 00 00 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<int const>'
SUMMARY: AddressSanitizer: undefined-behavior /usr/local/include/boost/any.hpp:259:73 in
问题在于代码中的 any_cast<int&>
试图通过将类型擦除指针向下转换为 any::holder<int>
来访问存储的值,但实际类型是 any::holder<int const>
。因此未定义的行为。
boost::program_options
中的未定义行为
在 boost::program_options 中,类型 T 的值作为 any
对象存储在 typed_value<T>
class 中。 any
对象构造如下:
// In class typed_value
typed_value* implicit_value(const T &v)
{
m_implicit_value = boost::any(v);
m_implicit_value_as_text =
boost::lexical_cast<std::string>(v);
return this;
}
请注意,值 v
被声明为常量引用。但是,typed_value<T>::notify()
(在您的代码中从 po::notify()
调用)访问存储值时不使用 const:
template<class T, class charT>
void
typed_value<T, charT>::notify(const boost::any& value_store) const
{
const T* value = boost::any_cast<T>(&value_store);
...
}
这会导致未定义的行为。
解决方法
在 boost/program_options/value_semantic.hpp 中,更改 implicit_value()
函数的以下行
m_implicit_value = boost::any(v);
至
m_implicit_value = boost::any(T(v));
这让消毒剂很开心。不过,我不确定这是否是真正的修复。
直接从boost's documentation举个例子:
#include <iostream>
#include <string>
#include <boost/program_options.hpp>
int main(int const ac, char** const av){
// Declare the supported options.
namespace po = boost::program_options;
using namespace std;
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("compression", po::value<int>(), "set compression level")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
if (vm.count("help")) {
cout << desc << "\n";
return 1;
}
if (vm.count("compression")) {
cout << "Compression level was set to "
<< vm["compression"].as<int>() << ".\n";
} else {
cout << "Compression level was not set.\n";
}
}
程序运行正常。
但是,当使用 gcc 的消毒剂(或 clang 的)编译时:
g++ -std=c++1z -o main main.cpp -fsanitize=undefined -lboost_program_options
它产生以下运行时错误:
./main --compression="1" 134
/usr/include/boost/any.hpp:243:16: runtime error: downcast of address 0x000001153fb0 which does not point to an object of type 'holder'
0x000001153fb0: note: object is of type 'boost::any::holder<int>'
00 00 00 00 20 bc 42 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 31 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<int>'
Compression level was set to 1.
我已将问题提炼为更小的问题:
#include <iostream>
#include <string>
#include <boost/program_options.hpp>
int main(int const argc, char** const argv){
using namespace boost::program_options;
//create description
options_description desc("");
//add entry
desc.add_options()
("foo",value<std::string>(),"desc");
//create variable map
variables_map vm;
//store variables in map
positional_options_description pod;
store(command_line_parser(argc, argv).options(desc).positional(pod).run(), vm);
notify(vm);
//get variable out of map
std::string foo;
if (vm.count("foo")){
foo = vm["foo"].as<std::string>(); //UNDEFINED BEHAVIOUR
}
}
编译为:
g++ -std=c++1z -o main main.cpp -fsanitize=undefined -lboost_program_options
执行时:
./main --foo="hello"
/usr/include/boost/any.hpp:243:16: runtime error: downcast of address 0x000000d85fd0 which does not point to an object of type 'holder'
0x000000d85fd0: note: object is of type 'boost::any::holder<std::string>'
00 00 00 00 b0 c5 5e 90 f8 7f 00 00 98 5f d8 00 00 00 00 00 00 00 00 00 00 00 00 00 31 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<std::string>'
显然是变量映射的强制转换导致了 UB:
vm["foo"].as<std::string>()
这正是联机文档显示的方式。
这是误报吗?我的 boost 分发中有错误吗?
如果它确实安全,我怎样才能避免消毒剂标记它?
这似乎真的是一个未定义的行为。此代码说明了问题:
#include <boost/any.hpp>
int main()
{
int value = 0;
int const& const_ref = value;
boost::any any_var {const_ref};
boost::any_cast<int&>(any_var); // ubsan error
}
这里 any_var
是用 const 值构造的,并作为非常量 int
访问。 运行 此带有消毒剂的代码会引发类似于您的运行时错误:
/usr/local/include/boost/any.hpp:259:16: runtime error: downcast of address 0x60200000eff0 which does not point to an object of type 'any::holder<int>'
0x60200000eff0: note: object is of type 'boost::any::holder<int const>'
01 00 00 0c b0 ee 49 00 00 00 00 00 00 00 00 00 be be be be 00 00 00 00 00 00 00 00 00 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<int const>'
SUMMARY: AddressSanitizer: undefined-behavior /usr/local/include/boost/any.hpp:259:16 in
/usr/local/include/boost/any.hpp:259:73: runtime error: member access within address 0x60200000eff0 which does not point to an object of type 'any::holder<int>'
0x60200000eff0: note: object is of type 'boost::any::holder<int const>'
01 00 00 0c b0 ee 49 00 00 00 00 00 00 00 00 00 be be be be 00 00 00 00 00 00 00 00 00 00 00 00
^~~~~~~~~~~~~~~~~~~~~~~
vptr for 'boost::any::holder<int const>'
SUMMARY: AddressSanitizer: undefined-behavior /usr/local/include/boost/any.hpp:259:73 in
问题在于代码中的 any_cast<int&>
试图通过将类型擦除指针向下转换为 any::holder<int>
来访问存储的值,但实际类型是 any::holder<int const>
。因此未定义的行为。
boost::program_options
中的未定义行为在 boost::program_options 中,类型 T 的值作为 any
对象存储在 typed_value<T>
class 中。 any
对象构造如下:
// In class typed_value
typed_value* implicit_value(const T &v)
{
m_implicit_value = boost::any(v);
m_implicit_value_as_text =
boost::lexical_cast<std::string>(v);
return this;
}
请注意,值 v
被声明为常量引用。但是,typed_value<T>::notify()
(在您的代码中从 po::notify()
调用)访问存储值时不使用 const:
template<class T, class charT>
void
typed_value<T, charT>::notify(const boost::any& value_store) const
{
const T* value = boost::any_cast<T>(&value_store);
...
}
这会导致未定义的行为。
解决方法
在 boost/program_options/value_semantic.hpp 中,更改 implicit_value()
函数的以下行
m_implicit_value = boost::any(v);
至
m_implicit_value = boost::any(T(v));
这让消毒剂很开心。不过,我不确定这是否是真正的修复。