Sanitizers: your new secret weapon to kill bugs
C++ will give you a job for life:
- Buffer overflows
- Data races
- Use after delete
- Invalid Pointer References
- Undefined Behaviors
- Memory Leaks
- Use of uninitialized memory
What scares me...
- Hard to reproduce, sporadic bugs
- Bugs that disappear when you compile in Debug mode
- Silent data corruption that doesn't trigger a SEGFAULT.
Welcome sanitizers
- AddressSanitizer: detects use-after-free and buffer overflows
- ThreadSanitizer: detects data races and deadlocks
- MemorySanitizer: detects uninitialized memory reads
- UndefinedBehaviorSanitizer: detects “simple” undefined behaviors
Sanitizers, AKA dynamic code analysis
- You must run your code (it is not static code analysis)
- Aims to zero false positives
- Usually 10X faster than Valgrind
- Very detailed error report, more convenient than GDB
- Start using them in 1 minute...
AddressSanitizer: buffer-overflow
int global_array[100];
int main(int argc, char **argv)
{
int stack_array[100];
int index = 101 + argc; //index not know at compilation time
global_array[index] = 42;
stack_array[index] = 42;
std::cout << global_array[index] << std::endl;
std::cout << stack_array[index] << std::endl;
return 0;
}
AddressSanitizer: heap-overflow and use after free
int main(int argc, char **argv)
{
int *array = new int[100];
int index = 100 + argc; //argc not know at compilation time
array[index] = 42; // BOOM?
std::cout << array[index] << std::endl;
delete [] array;
array[0] = 23; // BOOM?
std::cout << array[0] << std::endl;
return 0;
}
AddressSanitizer: container-overflow
int main(int argc, char **argv)
{
std::vector<int> V(8, 42);
V.resize(5); // capacity still 8
// The memory is still there, but we are not supposed to
// access this element
std::cout << V[6] << std::endl;
return 0;
}
Note: previous examples may crash, but not this one.
AddressSanitizer: dangling reference
const std::vector<int>& Vector42( int size )
{
std::vector<int> V(size, 42);
return V;
}
int main()
{
const std::vector<int>& V = Vector42(8);
// No problem, right?
std::cout << V[6] << std::endl;
// BOOOOM
return 0;
}
AddressSanitizer: memory leaks
int main()
{
int *g = new int;
g = nullptr; // Lost the pointer.
return 0;
}
Recently I found a bug like this in Qt, where you are not supposed to call delete explicitly...
AddressSanitizer overhead
- CPU: 2X slowdown
- RAM: 1.5X-3X memory overhead
ThreadSanitizer: example
struct A
{
virtual ~A()
{
F();
}
virtual void F()
{
printf("In A");
}
};
struct B : public A
{
virtual void F()
{
printf("In B");
}
};
// guess what it prints...
I recently fixed a bug like this one... if you don't see it, it is normal
class A {
public:
A() : done_(false) {}
virtual void F() { printf("A::F\n"); }
void Done() {
std::unique_lock<std::mutex> lk(m_);
done_ = true;
cv_.notify_one();
}
virtual ~A() {
std::unique_lock<std::mutex> lk(m_);
cv_.wait(lk, [this] {return done_;});
}
private:
std::mutex m_;
std::condition_variable cv_;
bool done_;
};
class B : public A {
public:
virtual void F() { printf("B::F\n"); }
virtual ~B() {}
};
int main() {
A *a = new B;
std::thread t1([a] {a->F(); a->Done();});
std::thread t2([a] {delete a;});
t1.join(); t2.join();
}
ThreadSanitizer overhead
- CPU: 4X-10X slowdown
- RAM: 5X-8X memory overhead
If you write multi-threaded code, it does have bugs. Deal with it and sanitize it.
UndefinedBehaviorSanitizer
int main(int argc, char **argv)
{
int t = 1 << 16;
std::cout << t * t << std::endl;
return 0;
}
Bugs makes you sad? Start being awesome with Sanitizers!
Sanitizers
By Davide Faconti
Sanitizers
- 681