Skip to content

Commit a4ec470

Browse files
committed
Add an exercise on UBSan.
1 parent c047ae2 commit a4ec470

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

exercises/ubsan/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Undefined Behaviour Sanitizer
2+
3+
This directory contains a program that's full of evil bugs. Many of those would cause compiler warnings,
4+
but the problems can easily be obscured by making the code slightly more complicated, e.g. by passing
5+
pointers or indices through functions or similar.
6+
7+
UBSan is a run-time tool, so it can catch these bugs even if the compilers don't emit warnings.
8+
This should illustrate why every larger project should have a UBSan and a ASan build as part of
9+
it Continuous Integration.
10+
11+
## Instructions
12+
13+
- Compile and run this program using a compiler invocation like
14+
`<compiler> -g -std=c++17 -o undefinedBehaviour undefinedBehaviour.cpp`
15+
16+
- You might see warnings depending on the compiler, but on most platforms the program runs without observable issues.
17+
18+
- Recompile with "-fsanitize=undefined", and observe that almost every second line contains a serious bug.
19+
NOTE: If this fails, your compiler does not support UBSan (yet). In this case, you can try to read the
20+
program and catch the bugs, or continue with the next exercise.
21+
22+
- Try to understand what's wrong. The solution contains a few comments what kind of bugs are hidden in the program.
23+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <iostream>
2+
#include <limits>
3+
4+
// You don't need to change any code in these structs:
5+
struct Base {
6+
virtual void print() = 0;
7+
virtual ~Base() {}
8+
};
9+
struct Derived1 : public Base {
10+
void print() override {
11+
std::cout << "Derived1::print()\n";
12+
}
13+
};
14+
struct Derived2 : public Base {
15+
void print() override {
16+
std::cout << "Derived2::print()\n";
17+
}
18+
};
19+
20+
21+
/**
22+
* ***************
23+
* Instructions:
24+
* ***************
25+
*
26+
* Compile and run this program using a compiler invocation like
27+
* <compiler> -g -std=c++17 -o undefinedBehaviour undefinedBehaviour.cpp
28+
*
29+
* You might see warnings depending on the compiler, but on most platforms the program runs without observable issues.
30+
* Smart compilers will warn with most of these bugs, but the compilers can easily be deceived by hiding a nullptr in
31+
* a variable, or passing it as a function argument or similar.
32+
*
33+
* Since UBSan does runtime checks, it will catch these errors even if they are obscured.
34+
* Recompile with "-fsanitize=undefined", and observe that almost every second line contains a serious bug.
35+
* Try to understand what's wrong.
36+
*/
37+
38+
int runTests() {
39+
int arr[] = {1, 2, 3, 4};
40+
std::cout << "arr[4]=" << arr[4] << "\n"; // Array overrun
41+
42+
unsigned short s = 1;
43+
std::cout << "s << 33=" << (s << 33) << "\n"; // We cannot shift a type with 32 bits by 33 bits.
44+
45+
int i = std::numeric_limits<int>::max();
46+
std::cout << "i + 1 =" << i + 1 << "\n"; // Adding 1 to max int overflows to min int
47+
48+
Derived1 d1;
49+
Base & base = d1;
50+
auto & d2 = static_cast<Derived2&>(base); // Casting an original d1 to d2 is wrong
51+
d2.print(); // Calling a function on a wrongly-cast object is wrong
52+
53+
Derived2 * d2ptr = nullptr;
54+
Derived2 & nullref = static_cast<Derived2&>(*d2ptr); // Cannot bind references to a wrong address
55+
} // Forgot to return a value
56+
57+
int main() {
58+
const auto result = runTests();
59+
return result;
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <iostream>
2+
#include <limits>
3+
4+
// You don't need to change any code in these structs:
5+
struct Base {
6+
virtual void print() = 0;
7+
virtual ~Base() {}
8+
};
9+
struct Derived1 : public Base {
10+
void print() override {
11+
std::cout << "Derived1::print()\n";
12+
}
13+
};
14+
struct Derived2 : public Base {
15+
void print() override {
16+
std::cout << "Derived2::print()\n";
17+
}
18+
};
19+
20+
21+
/**
22+
* ***************
23+
* Instructions:
24+
* ***************
25+
*
26+
* Compile and run this program using a compiler invocation like
27+
* <compiler> -g -std=c++17 -o undefinedBehaviour undefinedBehaviour.cpp
28+
*
29+
* You might see warnings depending on the compiler, but on most platforms the program runs without observable issues.
30+
* Smart compilers will warn with most of these bugs, but the compilers can easily be deceived by hiding a nullptr in
31+
* a variable, or passing it as a function argument or similar.
32+
*
33+
* Since UBSan does runtime checks, it will catch these errors even if they are obscured.
34+
* Recompile with "-fsanitize=undefined", and observe that almost every second line contains a serious bug.
35+
* Try to understand what's wrong.
36+
*/
37+
38+
int runTests() {
39+
int arr[] = {1, 2, 3, 4};
40+
std::cout << "arr[4]=" << arr[4] << "\n";
41+
42+
unsigned short s = 1;
43+
std::cout << "s << 33=" << (s << 33) << "\n";
44+
45+
int i = std::numeric_limits<int>::max();
46+
std::cout << "i + 1 =" << i + 1 << "\n";
47+
48+
Derived1 d1;
49+
Base & base = d1;
50+
auto & d2 = static_cast<Derived2&>(base);
51+
d2.print();
52+
53+
Derived2 * d2ptr = nullptr;
54+
Derived2 & nullref = static_cast<Derived2&>(*d2ptr);
55+
}
56+
57+
int main() {
58+
const auto result = runTests();
59+
return result;
60+
}

0 commit comments

Comments
 (0)