C++ code examples with bugs that should seemingly be easily detected with static analysis tools, yet they are not.
The list of static analysis tools tested to detect bugs in each of the examples:
- cppcheck
- PVS‑Studio
- clang-tidy (while searching the list of diagnostics for words related to the specific bug)
- clang diagnostics (e.g. -Wunsequenced for unspecified_evaluation_order.cpp)
mkdir build && cd build
cmake ..
make
As of September 2023, no static analysis tools are able to detect this:
int main(int argc, char *argv[]) {
std::unordered_map<int, int> my_map = {{1, 10}, {2, 20}, {3, 30}};
for (const auto& p : my_map) {
if (p.first == 2) {
my_map.erase(p.first);
}
}
std::cout << my_map.size() << std::endl;
return 0;
}
No clang-tidy checks provided for this issue. Cppcheck also show no warnings, and PVS‑Studio team mailed me that their V789 diagnostic needs a fix to detect this.
Quite obvious and is detected with some tools, including -Wunsequenced
clang diagnostics:
void foo(int a, int b) {
std::cout << a << ", " << b << std::endl;
}
int main(int argc, char *argv[]) {
int i = 0;
foo(++i, --i);
}
Quite obvious and only detected with clang -std=c++14 -Wunsequenced
:
int main(int argc, char *argv[]) {
std::map<int, int> m;
int i = 0;
m[++i] = --i;
return 0;
}
Isn't so obvious and no detections:
#include <unordered_map>
#include <cassert>
#include <memory>
#include <iostream>
struct Object {
};
struct Wrapper {
std::unique_ptr<Object> object;
std::string description;
};
class ObjectRegistry {
public:
void add_object(Wrapper& w) {
objects_[w.object.get()] = Wrapper{std::move(w.object), "added from " + w.description};
// this assertion may or may not fail depending on the order of evaluation in the line above, which is unspecified
assert(objects_.begin()->first != nullptr);
}
private:
// In our application, we need to quickly access the wrapper by raw pointer of its object
std::unordered_map<Object*, Wrapper> objects_;
};
int main(int argc, char *argv[]) {
ObjectRegistry registry;
Wrapper w{std::make_unique<Object>(), "first object"};
registry.add_object(w);
return 0;
}
This is self-explanatory:
#include <functional>
int main(int argc, char *argv[]) {
std::function<int()> f;
return f();
}
If you managed to find a static analysis tool that detects a problem here, try with the example where the initialization logic of f
is non-trivial:
void maybe_initialize(std::function<int()>& f) {
if (rand()%2)
f = [](){return 42;};
}
int main(int argc, char *argv[]) {
std::function<int()> f;
maybe_initialize(f);
return f();
}
This may print "hello world" or just crash. PVS-Studio gives V1047 (Lifetime of the lambda is greater than lifetime of the local variable 'hello_world' captured by reference)
int main(int argc, char *argv[]) {
std::function<void()> print_hello_world;
{
std::string hello_world = "hello world!\n";
print_hello_world = [&](){std::cout << hello_world;};
}
print_hello_world();
return 0;
}
But here's an example that more closely resembles the real bug found in one of the projects. Neither PVS-Studio nor cppcheck detect the issue.
std::function<void()> print_hello_world;
void set_callback(std::function<void()> cb) {
print_hello_world = cb;
}
void init() {
std::string hello_world = "hello world!\n";
set_callback([&](){std::cout << hello_world;});
}
int main(int argc, char *argv[]) {
init();
print_hello_world();
return 0;
}