问题
malloc
函数的签名定义如下:
void* malloc (size_t size);
为了将
malloc
的返回值分配给类型为
void*
以外的任何指针,你需要进行强制类型转换。虽然C++要求显式转换,但C编译器会自动进行转换。所以,即使您写了以下内容:
int *a = malloc(sizeof(*a));
编译器将隐式转换右侧表达式。这相当于
int *a = (int*) malloc(sizeof(*a));
您正在使用的是
hasParent
缩小匹配器,
它仅匹配直接父级而不匹配任何祖先。因此,您的匹配器仅能匹配没有任何类型转换的赋值语句。
并且您的
declRefExpr
也会发生类似的情况。C标准规定函数会自动衰减为指向函数的指针。Clang会隐式地将
malloc
转换为
void *(*)(size_t)
,这破坏了您匹配器的层次结构。
可能的解决方案
这取决于您实际想要做什么。首先,您通常可以使用以下代码片段修复选择malloc函数的部分:
callExpr(callee(functionDecl(hasName("malloc"))))
其余的取决于您要选择什么。
如果您只对像上面第一个示例中那样的直接匹配感兴趣,则可以使用ignoringImpCasts
匹配器。
由于某种原因,我无法按照您编写的方式将其插入到匹配器中,因此只需反转匹配器即可。
看起来是这样:
binaryOperator(
hasOperatorName("="),
hasRHS(ignoringImpCasts(
callExpr(
callee(functionDecl(hasName("malloc")))
).bind("functionCall")
))
).bind("assignment")
如果您还希望像第二个示例一样包括显式转换,使用
ignoringParenImpCasts
。
binaryOperator(
hasOperatorName("="),
hasRHS(ignoringParenImpCasts(
callExpr(
callee(functionDecl(hasName("malloc")))
).bind("functionCall")
))
).bind("assignment")
如果您对包含
malloc
的任意表达式的所有任务感兴趣,请改用
hasAncestor
。它不仅匹配直接父节点,而且会向上遍历,直到匹配到您的节点为止。
callExpr(
callee(functionDecl(hasName("malloc"))),
hasAncestor(
binaryOperator(hasOperatorName("=")).bind("assignment")
)
).bind("functionCall")
还有一件事。
你可能只对直接定义在源代码中的内容感兴趣,而不是包含在头文件中的内容。
只需在顶层匹配器中添加unless(isExpansionInSystemHeader())
,它将排除所有来自系统标头的定义。
请注意,此代码已经经过LLVM 3.7的测试,未来更改可能会破坏它。
如何调试
好吧,我们怎么知道所有这些呢?
事实证明,Clang已经为您提供了所需的全部内容 :)
具体而言,有两个功能可能会对您感兴趣。
当您使用-Xclang ast-dump -fsyntax-only
调用Clang时,它将打印出一个漂亮而丰富多彩的翻译单元AST。不要惊讶地发现一个巨大的前导,其中包含您包括的所有系统标头的声明,因为它必须先运行预处理器以生成AST。例如:
$ clang -Xclang -ast-dump -fsyntax-only example.c
...
`-FunctionDecl 0x3f2fc28 <line:19:1, line:31:1> line:19:5 main 'int ()'
`-CompoundStmt 0x3f307b8 <line:20:1, line:31:1>
|-BinaryOperator 0x3f2ff38 <line:22:3, col:29> 'int *' '='
| |-DeclRefExpr 0x3f2fd40 <col:3> 'int *' lvalue Var 0x3f2f388 'a' 'int *'
| `-ImplicitCastExpr 0x3f2ff20 <col:7, col:29> 'int *' <BitCast>
| `-CallExpr 0x3f2fef0 <col:7, col:29> 'void *'
| |-ImplicitCastExpr 0x3f2fed8 <col:7> 'void *(*)(unsigned long)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x3f2fd68 <col:7> 'void *(unsigned long)' Function 0x3f1cdd0 'malloc' 'void *(unsigned long)'
| `-BinaryOperator 0x3f2fe88 <col:15, col:28> 'unsigned long' '*'
| |-ImplicitCastExpr 0x3f2fe70 <col:15> 'unsigned long' <IntegralCast>
| | `-ImplicitCastExpr 0x3f2fe58 <col:15> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x3f2fd90 <col:15> 'int' lvalue Var 0x3f2f488 'n' 'int'
| `-UnaryExprOrTypeTraitExpr 0x3f2fe38 <col:19, col:28> 'unsigned long' sizeof
| `-ParenExpr 0x3f2fe18 <col:25, col:28> 'int' lvalue
| `-UnaryOperator 0x3f2fdf8 <col:26, col:27> 'int' lvalue prefix '*'
| `-ImplicitCastExpr 0x3f2fde0 <col:27> 'int *' <LValueToRValue>
| `-DeclRefExpr 0x3f2fdb8 <col:27> 'int *' lvalue Var 0x3f2f388 'a' 'int *'
...
而且还有 clang-query,如果您从源代码编译clang,它会与之一起构建。
它是libTooling的一个绝佳示例,同时在开发中绝对是一种令人惊叹的帮助。
您只需在示例源文件上运行它并使用它来测试匹配器(请注意,它隐式地将“root”绑定到完整匹配器):
$ <llvm>/bin/clang-query example.c --
clang-query> match callExpr(callee(functionDecl(hasName("malloc"))),hasAncestor(binaryOperator(hasOperatorName("=")).bind("assignment"))).bind("functionCall")
Match #1:
/vagrant/tests/true-valid-memsafety.c:22:3: note: "assignment" binds here
a = malloc (n * sizeof(*a));
^~~~~~~~~~~~~~~~~~~~~~~~~~~
/vagrant/tests/true-valid-memsafety.c:22:7: note: "functionCall" binds here
a = malloc (n * sizeof(*a));
^~~~~~~~~~~~~~~~~~~~~~~
/vagrant/tests/true-valid-memsafety.c:22:7: note: "root" binds here
a = malloc (n * sizeof(*a));
^~~~~~~~~~~~~~~~~~~~~~~
Match #2:
/vagrant/tests/true-valid-memsafety.c:23:3: note: "assignment" binds here
b = malloc (n * sizeof(*b));
^~~~~~~~~~~~~~~~~~~~~~~~~~~
/vagrant/tests/true-valid-memsafety.c:23:7: note: "functionCall" binds here
b = malloc (n * sizeof(*b));
^~~~~~~~~~~~~~~~~~~~~~~
/vagrant/tests/true-valid-memsafety.c:23:7: note: "root" binds here
b = malloc (n * sizeof(*b));
^~~~~~~~~~~~~~~~~~~~~~~
2 matches.
如果您对该主题有更多兴趣,可以前往 Eli Bendersky 的这篇优秀博客文章进行概览和介绍。AST matchers 的完整文档可以在这里找到。