考虑这种情况:
uint64_t add(uint32_t a, uint32_t b)
{
return a + b; // programmer neglected (uint64_t) a + b.
}
我们如何获得GCC(或任何其他编译器)的C或C ++前端来警告这种情况:一个操作是在一个立即扩展的窄类型中完成的?
我已经阅读了当前的GCC文档,并尝试了各种警告,如-Wconversion
,但没有。
我不知道海湾合作委员会的旗帜会引起警告。 Coverity静态分析仪将发出OVERFLOW_BEFORE_WIDEN警告,因为它在CERT标准中被标记。
免责声明:我曾经为Coverity工作过。
由于我正在使用的代码编译为C或C ++,并且所讨论的类型都是typedef(很容易重定向到类),因此我认为C ++解决方案是可行的。以下代码示例暗示了这个想法:
#include <inttypes.h>
template <typename outer, typename inner, typename underlying> class arith {
public:
underlying val;
arith(underlying v) : val(v) { }
explicit operator underlying () const { return val; }
outer operator +(const inner &rhs) { return val + rhs.val; }
};
struct narrow;
struct narrow_result : public arith<narrow_result, narrow_result, uint32_t> {
narrow_result(uint32_t v) : arith(v) { }
narrow_result(const narrow &v);
};
struct narrow : public arith<narrow_result, narrow, uint32_t> {
narrow(uint32_t v) : arith(v) { }
narrow(const narrow_result &v) : arith(v.val) { }
};
inline narrow_result::narrow_result(const narrow &v)
: arith(v.val)
{
}
struct wide {
uint64_t val;
wide(uint64_t v) : val(v) { }
wide(const narrow &v) : val(v) { }
operator uint64_t () const { return val; }
wide operator +(const wide &rhs) { return val + rhs.val; }
};
int main()
{
narrow a = 42;
narrow b = 9;
wide c = wide(a) + b;
wide d = a + b; // line 43
narrow e = a + b;
wide f = a; // line 45
narrow g = a + b + b; // line 46
return 0;
}
在这里,GNU C ++仅诊断第43行:
overflow.cc: In function ‘int main()’:
overflow.cc:43:16: error: conversion from ‘narrow_result’ to non-scalar type ‘wide’ requested
注意,仍然允许narrow
到wide
隐式转换,如第45行所示,仅仅因为wide
有一个直接针对narrow
的转换构造函数。它只缺少一个narrow_result
。
第46行表明我们可以复合算术运算。这是可能的,因为narrow
隐式转换为narrow_result
,反之亦然。但是,这种隐式转换不会在第45行开始;添加的narrow_result
不会转换为narrow
,因此可以转换为wide
。
这可以用#ifdef __cplusplus
和条件调试宏的存在包装,同一个宏也可以为narrow
和wide
启用类型的替代定义作为typedef。当然,arith
模板库中必须支持许多其他算术运算。
它对整数溢出进行了各种检查,包括无符号运算
- C26450 RESULT_OF_ARITHMETIC_OPERATION_PROVABLY_LOSSY:[operator]操作在编译时导致溢出。使用更宽的类型来存储操作数。此警告表示算术运算在编译时可证明是有损的。当操作数都是编译时常量时,可以断言。目前,我们检查这种溢出的左移,乘法,加法和减法运算。
uint32_t multiply() { const uint32_t a = UINT_MAX; // the author used int here const uint32_t b = 2; // but I changed to unsigned for this question uint32_t c = a * b; // C26450 reported here [and also C4307] return c; }
- C26451 RESULT_OF_ARITHMETIC_OPERATION_CAST_TO_LARGER_SIZE:在[size1]字节值上使用operator [operator],然后将结果转换为[size2]字节值。在调用operator [operator]之前将值转换为更宽的类型以避免溢出。 此警告表示由整数提升规则和类型产生的错误行为导致大于通常执行算术的规则。我们检测何时将窄类型积分值向左移位,相乘,相加或相减,并将该算术运算的结果转换为更宽的类型值。如果操作溢出窄类型值,则数据丢失。您可以通过在算术运算之前将值转换为更宽的类型来防止此丢失。
void leftshift(int i) { unsigned long long x; x = i << 31; // C26451 reported here // code // Corrected source: void leftshift(int i) { unsigned long long x; x = (unsigned long long)i << 31; // OK // code }
- C26454 RESULT_OF_ARITHMETIC_OPERATION_NEGATIVE_UNSIGNED:[operator]操作包装过去0并在编译时生成一个大的无符号数 此警告表示减法操作产生否定结果,该结果在无符号上下文中计算。这会导致结果换过0并产生一个非常大的无符号数,这可能导致意外的溢出。
// Example source: unsigned int negativeunsigned() { const unsigned int x = 1u - 2u; // C26454 reported here return x; } // Corrected source: unsigned int negativeunsigned() { const unsigned int x = 4294967295; // OK return x; }
Arithmetic overflow checks in C++ Core Check
这是一个实际的例子
从上面的示例中可以看出,如果操作数是编译时常量,编译器本身也会发出警告。如果它们是变量,那么您需要静态分析器
你可以在Compiler Explorer上玩这个,虽然我不知道如何让它真的从命令行工作。如果您知道如何将参数传递给VS代码分析,请在下面评论。在MSVC GUI上,只需按Alt + F11
有关如何运行分析的信息,请阅读C++ Static Analysis Improvements for Visual Studio 2017 15.6 Preview 2
Clang没有编译时选项,但它有一个在运行时检查的选项
-fsanitize=unsigned-integer-overflow
:无符号整数溢出,其中无符号整数计算的结果无法在其类型中表示。与有符号整数溢出不同,这不是未定义的行为,但它通常是无意的。此清理程序不检查在此类计算之前执行的有损隐式转换(请参阅-fsanitize=implicit-conversion
)。
它也可以很容易地被禁用
Silencing Unsigned Integer Overflow
要使无符号整数溢出报告静音,可以设置
UBSAN_OPTIONS=silence_unsigned_overflow=1
。此功能与-fsanitize-recover=unsigned-integer-overflow
相结合,对于提供模糊信号而不会烧毁日志特别有用。
不幸的是,GCC只支持-fsanitize=signed-integer-overflow
。没有未签名的版本