nh2*_*nh2 37 c c++ gcc clang compiler-warnings
C 或 C++ 中错误的常见来源是这样的代码:
size_t n = // ...
for (unsigned int i = 0; i < n; i++) // ...
Run Code Online (Sandbox Code Playgroud)
当溢出时可以无限循环unsigned int。
例如,在 Linux 上,unsigned int是 32 位,而size_t是 64 位,因此 if n = 5000000000,我们会得到一个无限循环。
我如何使用 GCC 或 Clang 获得有关此问题的警告?
#include <stdint.h>
void f(uint64_t n)
{
for (uint32_t i = 0; i < n; ++i) {
}
}
Run Code Online (Sandbox Code Playgroud)
gcc-13 -std=c17 \
-Wall -Wextra -Wpedantic \
-Warray-bounds -Wconversion \
-fanalyzer \
-c -o 76840686.o 76840686.c
Run Code Online (Sandbox Code Playgroud)
(无输出)
n成为编译时常量的解决方案。答案表明当前编译器中不存在此类警告。我已经开始提交上游功能请求:
Sco*_*eak 24
似乎没有内置警告选项gcc或
clang执行所要求的操作。不过,我们可以用
clang-query
它来代替。
下面的clang-query命令将报告 32 位和 64 位整数的比较,假设int是 32 位和
long64 位。(下面有更多相关内容。)
#!/bin/sh
PATH=$HOME/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04/bin:$PATH
# In this query, the comments are ignored because clang-query (not the
# shell) recognizes and discards them.
query='m
binaryOperator( # Find a binary operator expression
anyOf( # such that any of:
hasOperatorName("<"), # is operator <, or
hasOperatorName("<="), # is operator <=, or
hasOperatorName(">"), # is operator >, or
hasOperatorName(">="), # is operator >=, or
hasOperatorName("=="), # is operator ==, or
hasOperatorName("!=") # is operator !=;
),
hasEitherOperand( # and where either operand
implicitCastExpr( # is an implicit cast
has( # from
expr( # an expression
hasType( # whose type
hasCanonicalType( # after resolving typedefs
anyOf( # is either
asString("int"), # int or
asString("unsigned int") # unsigned int,
)
)
),
unless( # unless that expression
integerLiteral() # is an integer literal,
)
)
),
hasImplicitDestinationType( # and to a type
hasCanonicalType( # that after typedefs
anyOf( # is either
asString("long"), # long or
asString("unsigned long") # unsigned long.
)
)
)
).bind("operand")
)
)
'
# Run the query on test.c.
clang-query \
-c="set bind-root false" \
-c="$query" \
test.c -- -w
# EOF
Run Code Online (Sandbox Code Playgroud)
当在以下情况下运行时,test.c它会报告所有指定的情况:
// test.c
// Demonstrate reporting comparisons of different-size operands.
#include <stddef.h> // size_t
#include <stdint.h> // int32_t, etc.
void test(int32_t i32, int64_t i64, uint32_t u32, uint64_t u64)
{
i32 < i32; // Not reported: same sizes.
i32 < i64; // reported
i64 < i64;
u32 < u32;
u32 < u64; // reported
u64 < u64;
i32 < u64; // reported
u32 < i64; // reported
i32 <= i64; // reported
i64 > i32; // reported
i64 >= i32; // reported
i32 == i64; // reported
u64 != u32; // reported
i32 + i64; // Not reported: not a comparison operator.
((int64_t)i32) < i64; // Not reported: explicit cast.
u64 < 3; // Not reported: comparison with integer literal.
// Example #1 in question.
size_t n = 0;
for (unsigned int i = 0; i < n; i++) {} // reported
}
// Example #2 in question.
void f(uint64_t n)
{
for (uint32_t i = 0; i < n; ++i) { // reported
}
}
// EOF
Run Code Online (Sandbox Code Playgroud)
有关该命令的一些详细信息clang-query:
该命令传递-w到clang-query以抑制其他警告。那只是因为我编写测试的方式会引发有关未使用值的警告,而对于正常代码来说这是不必要的。
它通过了set bind-root false,因此唯一报告的站点是感兴趣的操作数,而不是报告整个表达式。
不幸的是,不可能让查询也打印所涉及类型的名称。尝试使用绑定来执行此操作会导致clang-query抱怨“Matcher 不支持绑定”。
该查询令人不满意的方面是它显式列出了源类型和目标类型。不幸的是,clang-query没有匹配器来报告任何 32 位类型,因此必须单独列出它们。您可能想[unsigned] long long在目标端添加。[unsigned] long如果使用针对 Windows 等 IL32 平台的编译器选项运行此代码,您可能还需要删除。
相关地,请注意clang-query接受 后的编译器选项--,或者在
compile_commands.json
文件中接受编译器选项。不幸的是,没有专门的clang-query
命令行文档,甚至它也--help没有提到--命令行选项。我可以链接的最好的内容是
libtooling 的文档,因为clang-query在内部使用该库进行命令行处理。
最后,我要指出的是,我没有在实际代码上对此查询进行任何“调整”。它可能会产生大量噪音,需要进一步调整。有关如何使用 的教程,我推荐
Stephen Kelly 撰写的clang-query博客文章《
探索 Clang 工具第 2 部分:使用 clang-query 检查 Clang AST 》。还有
AST Matcher Reference,但那里的文档非常简洁。
这并没有直接回答问题(提供警告),但是您会考虑一种完全避免该问题的替代方案吗?
size_t n = // ...
for (typeof(n) i = 0; i < n; i++) // ...
Run Code Online (Sandbox Code Playgroud)
现在 n 是什么类型并不重要,因为它i总是与 相同的类型,因此您永远不会遇到因类型较小或范围小于 而n导致的无限循环问题。in
PVS Studio 可以发出此类警告(以及更多警告),这里是他们文档中几乎相同的示例:
https://pvs-studio.com/en/docs/warnings/v104/
它是一个付费工具,但他们为开源项目提供免费许可。
我在Clang-tidy中没有找到这样的警告,这是一个来自 LLVM 项目的免费 linter 工具,但是添加对不同大小的整数的比较的检查会非常简单(Scott McPeak 后来用出色的 clang-query 进行了回复)大部分工作 - 剩余部分只是将此查询插入 clang-tidy)。但这会是非常吵闹的检查。人们可以通过限制对循环条件的检查来限制噪音,这也可以使用 Clang-tidy 来完成,但使用 AST 匹配器需要做更多的工作。
gcc 的最新版本似乎支持 -Warith-conversion此目的:
-Warith-conversion即使将操作数转换为相同类型无法更改其值,也要警告算术运算的隐式转换。这会影响来自
-Wconversion、-Wfloat-conversion和 的警告-Wsign-conversion。Run Code Online (Sandbox Code Playgroud)void f (char c, int i) { c = c + i; // warns with -Wconversion c = c + 1; // only warns with -Warith-conversion }
但它不适用于您的示例,可能是因为i < n不是算术表达式。对于通用二进制表达式,此警告似乎没有变体。