How to catch glaring undefined behavior statically?

Viewed 191

I'm using clion and I'm running some code that has UB. My goal is to catch it statically:

#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;

}

This is UB, and I'm trying to catch it.

I have added the following to my cmake project:

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

I still get no warnings.

what do I have to enable so that I can catch such UB instances?

2 Answers

Some compilers can detect some UB statically. No compiler is required to detect any UB except in some cases specified in the standard (in constant expressions).

what do I have to enable so that I can catch such UB instances?

Best you can do is to enable all warnings. If the compiler doesn't detect it, then modify the compiler to do so. This may be either very difficult, or very expensive in compile time, or both.

Sanitisers can help only at runtime.

@eerorika Seemed to answer the question, but I wanted to give some insight on why this code still works.

For this specific example, I'd say this isn't so much a case of C++ UB, but rather a case of misusing an API (the API being std::vector). Depending on how std::vector is implemented, the snippet of code may not lead to any C++ UB.

Under the hood, a vector may just be implemented as a malloc'd pointer at a certain capacity:

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() doesn't change the capacity of the vector. So it could just be that for a clear() implementation, neither the capacity nor buffer change, but size_ is just set to 0. In this case, sanitizers wouldn't report a dangling reference with auto &first = v[0]; since (internally to the vector) it's actually pointing to valid malloc'd memory still.

This would perhaps lead to some UB if more stuff was done on the internal buffer. For example:

#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;
}

The shrink_to_fit can (but isn't guaranteed to) reallocate the internal vector buffer and cause first to be a dangling reference. Compiling with clang++ v8 and ASan can show that (if a relocation occured), we'd be accessing previously free'd memory on the heap:

$ 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

Edit: To tie this back to the original question, sanitizers and warnings for checking UB are good for checking "Am I using C++ correctly?", but when it comes to questions regarding classes or APIs, this is more a question of "Am I using this API correctly?". Checking wether or not you are using the API correctly is up to the API itself (for example, indexing using operator[] doesn't actually do bounds checking, but at() does and would throw an exception.

Related