欢迎访问Sonar Source中文网站!
语言选择: ∷ 

为什么我对静态分析充满热情以及我如何帮助它变得更好

发布时间:2023-10-12浏览次数:86

我最近接受了 C++ 播客 CppCast 的采访 - “C++ 开发人员的第一个播客,面向 C++ 开发人员”。我们讨论了静态分析以及我最初是如何进入它的。然后我们讨论了 C++ 自动分析,我们已经研究了这一功能一年多,并于上个月在 SonarCloud 上发布。


您可以在此处收听播客:https ://cppcast.com/automatic_static_analysis/ 。不过,我也将介绍我们在这里讨论的大部分内容。

我是如何进入静态分析的

在我职业生涯的早期,我在金融领域工作,运行时效率通常高于一切,包括开发人员效率。我发现很多原本可以避免的工具问题导致生产力损失。我想说我们花费了大约 80% 的时间进行调试。有一天,当我正在开发一个百万行代码的利率衍生品项目时,我收到了一张关于计算错误的错误通知单。我花了两天时间才找到那个bug,结果发现它是我们依赖的一个带有副作用的表达式。有人已将其移至decltype问题是副作用不再发生,影响财务模型中的计算。


当我发现这一点时,我想知道是否还有更多类似的事情发生过,我突然想到我可以编写一个简单的脚本来帮我查看代码。我花了不到一个小时编写脚本,只用了几秒钟就运行了整个代码库。但它发现了另一个此类问题,可能会导致另一个多天的调试会话!


这段经历让我着迷于寻找可以快速自动化以显着提高生产力的东西 - 特别是在查找代码中的问题时 - 或查找可能导致问题的模式。这种热情引导我进行静态分析。

C++ 工具的挑战

无论是静态分析、代码检查工具、IDE,还是语法荧光笔或代码格式化程序,C++ 工具都比大多数其他语言复杂得多。主要是因为所有这些工具最终都依赖于解析语言的能力——而 C++ 是一种解析复杂且资源密集型的语言。有许多语法特性 - 例如令牌的含义会根据以后发生的情况而变化,或者多年的向后兼容性遗留 - 一直追溯到 C(有时甚至更早)。然后是预处理器和大量的编译器扩展,这再次让一切都受到质疑。因此,即使对于一个中等规模的全职团队来说,维护一个可靠的 C++ 解析器也是一项艰巨的任务!


自从我们有了 clang-tooling 以来,情况有所改善。现在,Clang 编译器使用的同一解析器可以由其他工具构建。然而,即使这也不是灵丹妙药。Clang 工具可以让小型、有限范围的项目走得更远——所以这很好。尽管如此,具有广泛用例的复杂且性能敏感的工具(例如 IDE 或全功能静态分析工具)必须处理许多额外的复杂性。即使在考虑到 Clang 不再是第一个实现新语言功能的事实之前,您也必须处理不完整的代码和外来的编译器扩展。Clang 可以假设代码是完整的,并根据该假设进行编译。如果不是,那就是编译器错误。但是对于在编写代码时需要理解代码的东西- 实时 - 这增加了很多额外的复杂性。此外,与通常的基于 IDE 的交互式工具相比,Clang 具有不同的性能限制。


与 C++ 不同,具有更规则语法的语言(通常在设计时考虑了工具性)更容易使用。这就是为什么 Java 或 C# 的 IDE 往往比 C++ 的 IDE 感觉更流畅、更高效,同时也更轻,即使它们都是由同一家公司构建的,例如 JetBrains IDE。遗憾的是,使用“现代 C++”工具的情况并没有好转。他们甚至变得更糟!我们现在几乎可以将任何内容编写为constexpr代码 - 这听起来像是一个伟大的胜利。然而,对于工具来说,它们现在必须有一个成熟的 C++ 解释器才能解析它!即使您渴望使用 C++20 模块来解决基于文本的 include 指令的频繁解析瓶颈,向后兼容性也会始终提醒您,对于 C++ 工具来说,没有前进的空间。

静态分析作为教育工具

我们倾向于考虑使用静态分析来查找错误(或可能导致错误的模式),而无需编译代码(与在运行时工作的动态分析相反)。当然,这非常有用。同时,一个好的静态分析器还应该帮助您理解为什么某些事情是一个问题或者为什么可能有更好的方法来做某事。如果左移的精神是在管道的早期阶段处理事情,那么首先用知识武装你以避免编写有问题的代码就是最终的左移。对我来说,这更有趣。尤其是现在 C++ 是一个快速变化的目标,像 C++ 20 这样的主要新版本经常颠覆我们认为的最佳实践。即使是最有经验的人也可能很难跟上。


因此,在 Sonar,我们努力编写良好的规则描述来帮助您理解问题 - 并且我们正在不断改进甚至较旧的规则。我们还有专门用于检测代表旧用法的模式并解释如何将它们更新为现代形式的规则 - 并且在可行的情况下为您执行此操作例如,静态分析器在检测等效代码方面可以做得非常好。我们在此基础上通过使用特定的等效 STL 算法检测原始循环来构建,并且我们鼓励您利用 STL - 如果您使用的是 C++20 或更高版本,则可能使用较新的范围算法。我们大多数人都可以更好地利用 STL 算法,所以这是一个很好的教育资源……”

路径爆炸

因此,静态分析非常适合检测代码中可能导致问题的模式 - 提示您遵循“最佳实践”。检测实际的错误 - 例如,取消引用空指针(其中指针值仅在运行时已知)也是可能的,但通常要困难得多。这不仅在检测所需的代码方面更困难,而且在需要跟踪指数级增加的可能状态的数学意义上也更困难。我们称之为“路径爆炸问题”。


例如,如果您编写一些代码,给定两个整数,将一个整数除以另一个整数,则根据整数的值,会出现各种故障模式。一个明显的问题是如果分母为零怎么办?现在你有UB了。因此,您需要查看这些整数的来源、它们可能的值以及它们沿途采取的分支。如果您可以看到,在除法之前,会检查分母是否为零 - 如果是则分支 - 我们应该不会出现被零除的问题。我们将这种理论上的逐步执行代码的阶段称为“符号执行”。如果该检查与部门本身相当接近,那么这是可以合理实现的。但距离越远,您必须考虑的中间分支就越多。如果你跨越了功能边界,那么事情就会变得特别棘手。但是,一旦收到其他翻译单元的调用,一般情况下问题就会变得棘手。在某些特定情况下,我们可以进行整个程序分析来捕获交叉翻译单元问题,但一般情况下这样做是不可行的。为了准确地做到这一点,您需要在分析器中针对所有可能的输入范围有效地执行整个程序。您甚至可能没有所有源代码。


但尽管有其局限性,符号执行仍然非常有价值。它确实可以检测已建立的代码库中的复杂错误。这是我们在 Sonar 用来实施规则的众多技术之一 - 我们的一些最专业的开发人员正在研究它。


尽管如此,动态分析工具,例如 Valgrind 和 Clang Sanitisers(msan、asan、ubsan 等),与静态分析一起运行仍然很有价值 - 尽管它们通常只能检测运行时遇到的问题。这就是为什么我认为检测可能导致问题的模式(所谓的“代码味道”)是静态分析器可以做出的最佳贡献。如果您遵循这些最佳实践,那么我们通常可以首先避免实际的错误。std::span这里的一个很好的例子是找到我们可以使用像or之类的抽象std::stringview来代替原始指针和长度的位置。更好的可能是使用gsl::span(来自 C++ 代码指南支持库),因为这也是范围检查的。这些都是我们可以检查并警告您的模式 - 即使代码本身没有错误。

声纳工具如何安装?

我们还在这一集中讨论了我们作为声纳解决方案的一部分提供的工具。如果您正在阅读本文,您可能已经了解它们 - 但值得一提的是,我们确实拥有三种工具以及它们之间的区别。


SonarLint 可能是许多开发人员最熟悉的。它作为 IDE 中的插件运行,并在您编写代码时分析您的代码 - 按照我们已经讨论过的方式为您提供实时反馈。它还为许多问题提供快速修复,因此它甚至可以为您重写代码。这对于我们讨论的最终左移非常有用。但这只有在每个人都以相同的方式使用相同的工具时才有效。这在我们现代的异构开发团队中很难执行。因此,我们还有两个服务可以作为基于服务器的构建(我们有时称为 CI 或 CD 服务器)的一部分运行。SonarQube 和 SonarCloud 基本相同 - 但如果您是自托管,通常会使用 SonarQube;如果您希望我们托管,通常会使用 SonarCloudSonarCloud 对于开源软件项目特别有用。它们的作用不仅仅是在服务器上运行相同的分析器。例如,它们可以充当拉取请求的质量关卡,这样您就可以确保不会引入新问题。它们还支持我们的“Clean as You Code”流程 - 只需保持新提交干净,随着时间的推移,整个代码库(或具有最高流失率的重要块)就会一路得到清理。这可以防止您在第一次打开所有警告或使用新的质量工具时感到不知所措。随着时间的推移,整个代码库(或流失率最高的重要块)会逐渐被清理。这可以防止您在第一次打开所有警告或使用新的质量工具时感到不知所措。随着时间的推移,整个代码库(或流失率最高的重要块)会逐渐被清理。这可以防止您在第一次打开所有警告或使用新的质量工具时感到不知所措。

自动分析

基于服务器的工具的一个缺点是它们需要一些配置、集成到您的工具链中,并随着时间的推移进行维护。由于 C++ 构建系统的性质和广泛的编译器,这通常比其他语言生态系统涉及更多。如果您有专门的 DevOps 资源,这应该不是问题。然而,如果这是开发人员的兼职职责或者您是开源作者,那么这可能会成为进入的障碍 - 至少只是尝试一下。


因此,我们确实希望消除所有复杂性,并提供零配置选项,以便在项目中系统地合并静态分析。对于其他一些语言,我们已经这样做了一段时间了,但对于 C++,我们——甚至是我——有一段时间认为这是不可能的。幸运的是,我们去年取得了突破,并认为我们有机会做到这一点。所以,从那时起,我一直在领导一个小团队,很高兴地说,上个月,我们发布了 C++ 自动分析,我不得不说,它超出了我们的预期。它运行得非常好,我们现在建议将其作为在 SonarCloud 中设置 C++ 分析的默认方式!您所需要做的就是让 SonarCloud 访问您的源代码并告诉它对其进行分析,然后它就会消失,找出最可能的构建选项、依赖项等,并在此基础上进行分析。你自己看看吧根据我们从内部测试的大型项目库中获得的数据,我们获得了大约 95% 的准确率。对于编译来说,只有 100% 就足够了,但对于静态分析来说,95% 实际上已经很好了 - 对于大多数项目,您可能不会知道其中的区别。当然,如果您遇到特殊情况,您始终可以使用手动设置方法。


我们对我们所取得的成就感到非常自豪。我不相信其他人能够做到这一点。令我兴奋的是,这项技术现在可以向更多开发人员开放静态分析,特别是那些为开源项目做出贡献的开发人员,其中该功能是免费的!


微信扫码微信扫码 关注我们

  • 24小时咨询热线180-210-69380

  • 移动电话180-210-69380

Copyright © 2022 All Rights Reserved. 地址:上海市浦东新区崮山路538号808 苏ICP123456 XML地图