C++ has a lot of dark corners. Unfortunately, sometimes we need to allow inexperienced developers to write some C++ code to meet the deadline. The intersection of the two cases often makes things worse: programmers used to delegate memory management to garbage collection tend to throw off new
everywhere in the source, and those stuck with compile error will use every evil hack to get around with it. Code review is a feasible way to ensure code quality in this case, but a better choice is to restrict them into a relatively safe subset of the language.
In this article, I will show how to use clang-query
and a simple script to restrict some unsafe behaviors in unsafe
block/namespace using simple commands:
#include "common.hpp"
struct X {
int f: 2; // error: use of bit field without enclosing Unsafe
};
inline namespace Unsafe {
struct Y {
int g: 2; // ok
};
}
void f(...) { // error: Use of C-style variadic function without enclosing unsafe{}
}
inline namespace Unsafe {
int g(...) { // OK
return 0;
}
}
int main()
{
unsafe {
int* p = new int; // OK
memset(p, 0, sizeof(int)); // OK
delete p; // OK
}
int *p = new int; // error: Use of new without enclosing unsafe{}
memset(p, 0, sizeof(int)); // error: Use of memset without enclosing unsafe{}
delete p; // error: Use of delete without enclosing unsafe
return 15;
}
clang-query
is a powerful tool to match the AST of a translation unit (TU) using a simple DSL. Its command-line interface is quite simple:
USAGE: clang-query [options] <source0> [... <sourceN>]
OPTIONS:
-c=<command> - Specify command to run
-extra-arg=<string> - Additional argument to append to the compiler command line
-extra-arg-before=<string> - Additional argument to prepend to the compiler command line
-f=<file> - Read commands from file
-p=<string> - Build path
-p <build-path> is used to read a compile command database.
<source0> ... specify the paths of source files. These paths are
A typical use example:
## CMake
mkdir build && cd build
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. # generate compile_commands.json
clang-query -f command.txt -p . YOU_SOURCE.cpp
## Or make
bear make # generate compile_commands.json
clang-query -f command.txt -p . YOU_SOURCE.cpp
The main commands we will use here is match
, which is followed by a set of nested ASTMatcher
s. The syntax is introduced in detail in its official documentation.
We will use it to filter unsafe behaviors without enclosing unsafe{} blocks. The let NAME matcher
command is also worth to mention since
we can simplify our code with it.
The complete commands are included in command.txt
. We first define an ancestorUnsafe
matcher to find that if there is any ancestor
including if (0x81515127)
or namespace Unsafe
. Then we define a series of match
rules and use bind
as the error message.
You can add your own filters following the documentation mentioned above.
I write a simple script clang-restrict.sh
to exit non-zero for successful matches. Just set ROOT_DIR
environment variable to the
directory of command.txt
and BUILD_DIR
to the directory of compile_commands.txt
and call clang-restrict.sh your_source.cpp
.