Visual Studio /analyze不好之处(一)
分析是一种强大的VisualC++特性,可以帮助发现bug。然而,它使用了一些相当奇特的启发式方法,这使得很难决定如何认真对待它的警告。今天我们将讨论其中一种特殊的启发式方法,并展示一个案例,其中/analyze是完全错误的。这些特殊的警告大多在VS2012中修复。在我与微软分享的repro项目中,大多数错误警告都是被处理的,所以假阳性率较低。这些测试都是用Visual Studio 2010 SP1,C/C++优化编译器版本16.00 .40219.01为80×86。
血淋淋的细节
看看这段代码:
int g_values[10]; int GetValue1(int index) { return g_values[index]; }
这段代码既不好也不坏,对也不坏。这完全取决于它是如何使用的。对于“index”在范围内,它可能应该有一个断言来添加调试生成中的运行时检查,并且索引可能应该是“unsigned”或“size_t”,但它本质上没有损坏。有人可能会争辩说,由于'index'的范围可能从INT_MIN到INT_MAX,/analyze应该警告对g_values的潜在超出范围的访问,但是如果/analyze这样做了,那么它将发出大量的警告,这将是无用的。所以/analyze,没有什么有用的话,明智地保持沉默。
这段代码是另一回事。它坏得很危险。它每次读取都超出数组的末尾,这会导致未定义的行为,通常应避免。
int GetValue2() { return g_values[_countof(g_values)]; }
/analyze足够聪明来处理这个问题。它会有点失控,会发出两个警告,但安全总比抱歉好,对吧?
warning C6200: Index ’10’ is out of valid index range ‘0’ to ‘9’ for non-stack buffer ‘int * g_values’
warning C6385: Invalid data: accessing ‘int * g_values’, the readable size is ’40’ bytes, but ’44’ bytes might be read
warning C6385: Invalid data: accessing ‘int * g_values’, the readable size is ’40’ bytes, but ’44’ bytes might be read
到现在为止,一直都还不错。现在事情变得很奇怪。从正确性的角度来看,这段代码与GetValue1相同。但是/analyze要求不同:
int GetValue3(int index) { if (index == 8 ) return 0; return g_values[index]; }
对这段良性的代码/分析说:
warning C6385: Invalid data: accessing ‘int * g_values’, the readable size is ’40’ bytes, but ’44’ bytes might be read
对与索引进行比较的值进行一些实验,可以发现/analyze似乎在思考什么。如果将“index”与一个整数进行比较,则/analyze假定这意味着您将用索引2(在本例中为索引为10)索引g_values。索引为10表示读取扩展到第44字节的第11个元素。
如果将索引与100进行比较,如下所示:
如果将索引与100进行比较,如下所示:
int GetValue4(int index) { if (index == 100) return 0; return g_values[index]; }
然后自然地/analyze假设您将索引到索引为102的g_values,因此它会发出以下警告:
warning C6385: Invalid data: accessing ‘int * g_values’, the readable size is ’40’ bytes, but ‘412’ bytes might be read
至少说来很奇怪。当在代码库中遇到没有解释的警告时,实际上不可能理解这些警告。只有当你开始为/analyze文章编写测试函数时,你才有希望弄清楚内部逻辑。
下面的代码显示了/analyze跟踪索引能力的一个同样恼人的问题。
下面的代码显示了/analyze跟踪索引能力的一个同样恼人的问题。
int GetValue6() { int sum = 0; for (int index = 0; index < _countof(g_values) * 2; ++index) if (index < _countof(g_values)) sum += g_values[index]; else sum += g_values[index – _countof(g_values)]; return sum; }
index变量在数组的两倍范围内循环,但是有仔细编写的检查,以确保索引始终正确使用。尽管这段代码被证明是正确和安全的,但是/analyze仍然给出了以下两个警告,这些警告可能会导致代码在深夜无法运行。
warning C6200: Index ’19’ is out of valid index range ‘0’ to ‘9’ for non-stack buffer ‘int * g_values’
warning C6200: Index ‘-10’ is out of valid index range ‘0’ to ‘9’ for non-stack buffer ‘int * g_values’
warning C6200: Index ‘-10’ is out of valid index range ‘0’ to ‘9’ for non-stack buffer ‘int * g_values’
总结
不幸的是,这些不恰当或错误的警告使/analyze更难使用。每一个警告都必须仔细分析,以确定它是对一个细微缺陷的精辟洞察,还是对/analyze内部工作机制的奇怪揭示,还是完全错误。这需要专家的知识,使C++的神秘规则看起来比较简单。
这些警告要么被压制,要么被标记为无趣。不管怎样,这些bug都会阻止/分析除了最敬业的开发人员以外的所有人。
这些警告要么被压制,要么被标记为无趣。不管怎样,这些bug都会阻止/分析除了最敬业的开发人员以外的所有人。
为虫子生,为虫子死,为虫子奋斗一辈子