Как отловить явно неопределенное поведение статически? - PullRequest
4 голосов
/ 23 марта 2020

Я использую clion и запускаю некоторый код с UB. Моя цель - поймать статически:

#include <iostream>
#include <vector>



int main() {
    auto v = std::vector<int>();
    v.push_back(20);
    auto &first = v[0];
    auto vector_ref = &v;
    vector_ref->clear();
    std::cout << first;

}

Это UB, и я пытаюсь поймать его.

Я добавил в свой проект cmake следующее:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -O1 -fno-omit-frame-pointer -g")

Я все еще не получаю предупреждений.

что мне нужно включить, чтобы я мог перехватывать такие экземпляры UB?

Ответы [ 2 ]

6 голосов
/ 23 марта 2020

Некоторые компиляторы могут определять некоторые UB статически. Не требуется компилятор для обнаружения каких-либо UB, за исключением некоторых случаев, указанных в стандарте (в константных выражениях).

Что мне нужно включить, чтобы я мог перехватывать такие экземпляры UB?

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

Дезинфицирующие средства могут помочь только во время выполнения.

1 голос
/ 29 апреля 2020

@ eerorika Казалось бы, ответ на вопрос, но я хотел бы дать некоторое представление о том, почему этот код все еще работает.

Для этого конкретного примера c, я бы сказал, что это не так много случай C ++ UB, а скорее случай неправильного использования API (API std::vector). В зависимости от того, как реализован std::vector, фрагмент кода может не привести к какой-либо C ++ UB.

Под капотом вектор может быть просто реализован как указатель mallo c d с определенной емкостью :

template <typename T>
class vector {
 public:
   // Methods ...
 private:
  T *buffer_;  // internal buffer
  size_t size_;  // # of elements
  size_t capacity_;  // Actual size of the buffer
};

std::vector::clear() не меняет емкость вектора . Таким образом, может быть так, что для реализации clear() не изменяется ни емкость, ни буфер, но size_ просто устанавливается на 0. В этом случае дезинфицирующие средства не будут сообщать об зависшей ссылке с auto &first = v[0];, поскольку (внутренне для вектор) он фактически указывает на действительную память mallo c d.

Это, возможно, приведет к некоторой UB, если во внутреннем буфере будет сделано больше вещей. Например:

#include <iostream>
#include <vector>

int main() {
    auto v = std::vector<int>();
    v.push_back(20);
    auto &first = v[0];
    auto vector_ref = &v;
    vector_ref->clear();
    vector_ref->shrink_to_fit();  // Suggest to vector that we want to take back some memory
    std::cout << first;
}

Может shrink_to_fit (но не гарантирует ) перераспределить внутренний векторный буфер и заставить first быть висячей ссылкой. Компиляция с clang++ v8 и ASan может показать, что (если произойдет перемещение), мы будем обращаться к ранее свободной памяти в куче:

$ clang++ /tmp/test.cpp -std=c++17 -fsanitize=address
$ ./a.out 
=================================================================
==228989==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x0000004fc531 bp 0x7fff8bfcfb70 sp 0x7fff8bfcfb68
... Rest of the error

Редактировать: в t ie Вернемся к первоначальному вопросу: дезинфицирующие средства и предупреждения для проверки UB полезны для проверки «Правильно ли я использую C ++?», но когда дело доходит до вопросов, касающихся классов или API, это больше вопрос «Правильно ли я использую этот API?» ?». Проверять, правильно ли вы используете API, зависит от самого API (например, индексирование с использованием operator[] на самом деле не выполняет проверку границ , но at() делает и бросил бы исключение.

...