Почему boost :: any демонстрирует неопределенное поведение в boost :: program_options?

Давайте возьмем пример непосредственно из документация буста:

#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>()

Это именно то, как онлайн-документация показывает это, хотя.

Это ложный положительный результат? Есть ли ошибка в моем буст-дистрибутиве?
Как я могу избежать, чтобы дезинфицирующее средство пометило это, если оно действительно безопасно?

1

Решение

Кажется, это действительно неопределенное поведение. Этот код иллюстрирует проблему:

#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, Запуск этого кода с помощью sanitizer вызывает ошибку времени выполнения, аналогичную вашей:

/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> учебный класс. 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));

Это делает дезинфицирующее средство счастливым. Я не уверен, если это реальное решение, хотя.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]