I am currently working with the Clang AST and would like to implement a tool based on LibTooling. This should detect VarDecls where several variables are declared in one line. So as follows:
int a = 1, b = 1;
In a further step, these VarDecls should then be split in order to have the individual variables in separate declarations. The desired result would therefore be:
int a = 1;
int b = 1;
I have seen that clang-tidy provides such a functionality. However, this seems to be limited to local variables. A look at the source code of clang-tidy shows that an ASTMatcher is used for detection. This matcher checks whether a single declaration represents a given DeclStmt or several declarations. Like this:
declStmt(hasSingleDecl(anything()))
If you take a closer look at the Clang AST for different constructs, you will notice that only local variables have a DeclStmt as a parent node. Global variables, however, do not. The following example:
int a = 1, b = 1;
void func(){
int l_a = 1, l_b = 1;
}
Results in an AST like that:
|-VarDecl 0x7ffff371f480 <test.c:3:1, col:9> col:5 a 'int' cinit
| `-IntegerLiteral 0x7ffff371f530 <col:9> 'int' 1
|-VarDecl 0x7ffff371f568 <col:1, col:16> col:12 b 'int' cinit
| `-IntegerLiteral 0x7ffff371f5d0 <col:16> 'int' 1
`-FunctionDecl 0x7ffff371f658 <line:5:1, line:7:1> line:5:6 func 'void ()'
`-CompoundStmt 0x7ffff371f870 <col:12, line:7:1>
`-DeclStmt 0x7ffff371f858 <line:6:5, col:25>
|-VarDecl 0x7ffff371f718 <col:5, col:15> col:9 l_a 'int' cinit
| `-IntegerLiteral 0x7ffff371f780 <col:15> 'int' 1
`-VarDecl 0x7ffff371f7b8 <col:5, col:24> col:18 l_b 'int' cinit
`-IntegerLiteral 0x7ffff371f820 <col:24> 'int' 1
Now to my questions: Why is there a difference between VarDecls and DeclStmts for local and global variables? And how could one alternatively detect global variables where several VarDecls are given by one statement?
I would be very grateful for any help.
As far as I can tell, the Clang AST does not have any direct indication of when two global variables are declared as part of the same declaration. However, it is possible to look at the location of the type specifier in a
DeclaratorDeclby callingclang::DeclaratorDecl::getTypeSpecStartLoc. If twoDeclaratorDeclnodes have the same type specifier start location, but the declarators have distinct locations (this condition is needed to not report template instantiations), then we can conclude they share a declaration.There's no way to search for nodes satisfying that condition using an AST matcher expression. Instead, one has to examine all of the declarations, build a map, and then walk the map afterward to detect declarators sharing type specifier locations.
Here is a complete stand-alone program demonstrating the method:
Makefile:When run on the input:
it produces the output:
It should be straightforward to incorporate this logic into a
clang-tidycheck.