如何看待静态代码分析中的误报?
多年前,静态代码分析最大的挑战是试图找到更多更有趣的东西来检查。在90年代初Parasoft最初的CodeWizard产品中,有了根据Scott Meyers的《Effective C++》一书中的项目制定的30多条规则。我喜欢把它看作是“程序员的直觉”。我曾经跟Scott提到过这件事,虽然他没有这样想过......但这确实让他笑得很开心。
从那时起,静态分析研究人员就不断努力推动可以检测的范围,扩大静态分析的范围,识别缺陷而不仅仅是一段弱代码。但它仍然存在误报的问题。静态分析改变了用户的关注点,从硬化代码,到搜索bug,这是很好的,但现在人们在静态代码分析中遇到的最常见的障碍之一是试图理解他们得到的结果。
虽然人们确实会说“我希望静态分析能抓住____”(说出你最喜欢的找不到的bug),但更常见的是听到“哇,我的结果太多了!”或“静态分析太吵了!”或“静态分析的误报太多!”因此,作为一家软件测试机构,Parasoft的工作就是继续为客户解决这个问题——继续提供工具和功能,帮助你梳理得到的结果,并了解哪些问题代表了最大的风险。
什么是静态分析中的误报?
在静态分析中,当静态分析工具错误地报告违反了静态分析规则时,就会出现“误报”。当然,这可能是主观的。有时,开发人员会掉进一个陷阱,把任何他们不喜欢的错误信息都贴上“误报”的标签,但这其实并不正确。在很多情况下,他们只是不同意这个规则,他们不理解这个规则在这种情况下是如何应用的,或者他们认为它在一般情况下(或者在这个特殊情况下)并不重要。我把这叫做噪音,而不是误报。我在这里发现的有趣的事情是,工具越是聪明,就越有可能产生一个开发者乍一看可能不理解的发现。
基于模式的分析中的误报
基于模式的静态分析实际上并不存在误报。如果工具报告说某条静态分析规则被违反了,而实际上并没有,这说明规则有bug(因为规则不应该是模棱两可的)。如果规则没有明确的模式可寻,这就是一个坏规则。
我并不是说每一个被报告的规则违规都表示存在缺陷。违规只是意味着发现了模式,表明代码的弱点,容易有缺陷。
当我看到一个违规行为时,我会问自己这个规则是否适用于我的代码。如果适用,我就修复代码。如果不适用,我就抑制这个违规行为。最好直接在代码中压制静态分析违规行为,这样对团队成员来说是可见的,而且你最终也不会再去审查第二次。否则,你就会不断地重复审查同样的违规行为;这就像试图拼写检查,但永远不会在它的字典中添加你的“特殊”单词一样。代码内抑制的好处是,它独立于静态分析引擎。任何人都可以查看代码,并看到代码已经被审查过,并且这个模式被认为在这个代码中是可以接受的。如果你需要证明符合编码标准,这一点特别有用。而如果你确实需要符合标准,那么很容易使用现有的配置来满足这些标准,如CWE、MISRA、IEC 62304、DO-178B/C等。
基于流量的分析中的误报
对于基于流程的分析,误报不仅是方法所固有的,而且是相关的--需要解决。流程分析无法避免误报,原因与单元测试无法生成完美的单元测试用例一样。分析必须对代码的预期行为做出判断。有时选项太多,无法知道什么是现实的;有时你根本没有足够的信息来了解系统其他部分的情况。
这里重要的是,真正的误报是完全错误的东西。例如,假设你正在使用的静态分析工具说你正在读取一个空指针。如果你看了一下代码,发现这其实是不可能的,那么你就有一个误报。
另一方面,如果你根本不担心这段代码中的空值,因为它们在其他地方被处理了,那么这个消息(虽然对你来说并不重要)并不是一个误报。它是真实的,而且恰好是不重要的。流程分析工具的消息从“真实且重要”到“真实且不重要”,从“真实且不可能”到“不真实”。这里有很多变化,每个人都应该有不同的处理方式。
这里也有一个常见的陷阱。就像上面的null例子一样,你可能认为一个null值不可能做到这一点,但是工具找到了一种方法让它发生。如果它对你的应用很重要,一定要检查,并可能对此进行保护。
关键是要明白,流分析有力量也有弱点。流程分析的强大之处在于,它通过代码,试图找到热点,并围绕热点发现问题。弱点是它必须做出假设来尝试遍历代码,而且越是遍历,越容易产生不可能的路径。
真正的问题是,如果你开始认为你已经清理了所有的代码,因为你的流程分析是干净的,你是在欺骗自己。真的,你已经发现了一些错误,你应该为此感到庆幸。没有流程分析错误只是意味着你没有发现任何东西,而不是说代码很干净。如果你正在构建安全关键型软件,最好确保你使用的是C/C++test、dotTEST或Jtest这样的工具,它同时具有两种类型的静态分析功能。
运行时错误检测
一个很好的,但通常被忽视的补充流分析的方法是运行时错误检测。运行时错误检测可以帮助你发现比流程分析能检测到的更复杂的问题,而且你有信心该条件确实发生了。运行时错误检测不会像静态分析那样出现误报。当它发现一个缺陷时,是因为它在执行过程中实际观察到了它的发生——不涉及任何假设。
你的运行时规则集应该与静态分析规则集紧密匹配。这些规则可以发现相同类型的问题,但运行时分析有大量的执行路径可供它使用。这是因为在运行时,存根、设置、初始化等都不像流分析那样成为问题。唯一的限制是,它只和你的测试套件一样好,因为它检查你的测试套件恰好执行的路径。如果你是用C或C++编程,特别是像物联网这样的嵌入式设备,可以看看Insure++——它可以在运行时找到比其他任何工具更多的bug。而不是被线程问题、内存泄漏和竞赛条件等棘手的问题所困扰,你可以在运行时准确地找到它们。
值得花时间吗?
我对待误报的方法是这样的。如果要花3天时间来修复一个bug,那还不如花20分钟去看一个误报......只要我可以标记它,永远不用再看它。这是个正确看待的问题。比如说你的线程有问题。线程的问题是急剧难以发现的。如果你想找到一个与线程有关的问题,你可能要花上几周的时间去追踪它。我更希望在写代码的时候,首先不能出现问题。换句话说,我试图将我的过程从检测转向预防。
静态分析,如果部署得当,并不一定是一种嘈杂不愉快的体验。看看Parasoft是如何以不同的方式做事的,特别是使用Parasoft DTP的全部功能,通过智能分析来管理结果,让你专注于软件中的风险,而不是追逐不重要的问题。