【老司机精选】使用 Clang 静态分析器尽早发现 Bug

作者:Sean,现在就职于滴滴网约车,好爱机车、吉他、旅行,坐标长期北京,欢迎约酒干饭!博客地址。

审核:酷酷的哀殿,编译器、构建体系爱好者,酷酷的哀殿

这篇文章向你展示如何在 Xcode 上运用 LLVM 内置的静态剖析器(static analyzer)帮助你找到 Bug 而且修正它。

快速运用

静态剖析器是一个 Xcode 内置的东西,运用它能够在不运转源代码的状况下进行静态的代码剖析,而且找出其间的 Bug,为 app 供给质量确保。

静态剖析器作业的时分并不会动态地履行你的代码,它只是进行静态剖析,因此它乃至会去剖析代码中没有被常规的测验用例掩盖到的代码履行途径。

静态剖析器只支撑 C/C++/Objective-C 语言,由于静态剖析器隶属于 Clang 体系中。不过即便如此,它关于 Objective-C 和 Swift 混编的工程也能够很好地支撑。

下面是一个实践的运用事例,运用 Objective-C 和 Swift 混编的工程。经过点击 Xcode 的 Product 菜单栏中的 Analyze 选项就能够让剖析器开端作业。

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

像编译相同,需要等待一段时刻,然后被静态剖析器捕获的问题就以蓝色的感叹号 在 Xcode 上方显示出来。也能够在 issue navigator 中查看蓝色的剖析器捕获的问题。

以下是现在剖析器支撑捕获的过错类型:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

其间包含了安全、逻辑、以及 API 方面的问题。剖析器能够帮你找到以上的这些问题,而且解说原因以及指出代码的履行途径。

这里是 WWDC 事例工程中被剖析器捕获的一个问题:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

能够看到这个办法声明晰回来类型为 nonnull,可是剖析器发现这个办法可能回来为 nil,这并不契合回来值界说的预期。假如想要查看回来为 nil 的详细原因,咱们能够经过点击左面 issue navigator 中的这个 issue,点击翻开后的详细进程,查看剖析器剖析到的导致问题产生的代码履行途径。

点击导致回来 nil 的最后一个进程,能够看到:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

自底向上查看,最后一个进程是 position 这个变量初始化为 nil。经过点击倒数第二个进程能够看到,regularPositionAtDate 这个办法没有被调用,由于办法的接收者是 nil。而咱们知道,在 OC 中调用办法(也叫音讯发送)的时分,假如办法的调用者(也被称为:音讯的接收者)本身为 nil,办法调用的成果会回来 nil,所以这行代码的履行成果便是 position 仍然为 nil。

那么为什么办法的接收者 object 变量是 nil 呢?继续向上查看咱们发现是由于这个 switch 语句中的 default 分支没有做任何的处理,这样 object 方针就会保持初始化的状况,即为 nil。

找到了原因,为了修正这个问题,经过把第一个 case 中的处理作为 default,对 object 赋值为 self 即可,修正之后再次运转剖析器,确保这个 bug 被成功修正。

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

假如这个 bug 没有被修正,这个 OC 的办法就很有可能回来 nil,而这个时分假如恰好是 Swift 在调用这个办法的话,就会立刻产出一个运转时反常。

凭借静态剖析器,咱们就能轻松地发现并修正这类躲藏较深的隐患!

Xcode 13 新增的查看项

在 Xcode 13 中,静态剖析器也得以升级,现在它能够捕获更多的一些逻辑问题:

  1. 死循环
  2. 无用的冗余代码(例如剩余的分支条件)
  3. 断言 Assert 的副作用
  4. C++ 中 move 和 forward 的滥用导致的潜在问题

一部分的改善来自于开源作者们对 Apple Clang 编译器的奉献。

下面举例:

NSAssert 中的副作用

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

运用 NSAssert 躲避非预期的代码逻辑是很常见的好习惯,可是不规范地运用也会带来一些副作用,例如在 NSAssert 的判别条件中对变量或内存进行修正操作。

上图比如中,依旧是太阳系体系的事例,数组中是一个个的天体方针,遍历天体,计算有卫星的天体数量,由于这个计数变量 objectsWithMoonsCount 是在NSAssert中做的累加操作,可是在Release形式中,为了程序的履行功率,会把assert给禁用,这样这个累加的操作就不会在Release形式下被履行,这样就导致了事务过错。

这样的过错在 Xcode 13 的静态剖析器中能够被捕获出来,不仅仅是 Objectie-C 的 NSAssert,也包含了 C 和 C++ 的 assert 函数:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

当然这个代码也很好修正,咱们只需要把累加操作外置到NSAssert函数的上面即可:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

下面是一个很常见的死循环的事例,这种稍微复杂一些的逻辑,乍眼一看,似乎没有什么问题:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

这段代码中,做了一个二维循环,可是在内层的循环中,没有对 column 做递加,而是做了 value 的递加,这个问题虽然会隐晦,可是新版别的静态查看器可不会错过它!

如图,它会捕获这个问题:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

value 替换成 column 修正这个问题:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

自界说剖析器参数

Xcode 也为静态剖析器供给了许多的自界说项,便利开发者依据本身作业流进行定制。在 BuildSetting 中经过搜索 analysis 关键字,能够筛选出跟剖析器相关的设置项。

每一次编译都履行剖析器

经过翻开 Analyze During 'Build' 能够使得每一次编译操作都履行剖析器:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

设置静态剖析器的运转形式

另一个选项,Mode of Analysis for 'Analyze' 能够装备剖析器运转的形式,Xcode 供给了两种运转形式:Shallow(faster)Deep,望文生义,Shallow躲避了去查看一些耗时复杂的查看操作,所以 Shallow 运转的更快,Deep 则进行深化的查看,能抛出更全面的过错。

专项查看装备

别的,静态剖析器也供给了一些专项查看的装备,能够依据工程情况定制挑选:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

例如,假如项目有严格的安全查看,能够翻开下图中选中的这些装备项目:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

假如静态剖析器抛出的某一类问题你并不需要重视,你想忽略掉这类问题,能够在下面这些选项中找到并封闭这类策略,从而使得静态剖析器捕获到的问题对你的工程更有参考含义:

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

单个文件的剖析

咱们也能够对单个文件进行剖析,选中你想要剖析的文件,在 Product 菜单栏下挑选 Analyze ’FileName‘ 即可进行对这个文件进行剖析,而且不会剖析import关联的一切文件。

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

这个操作非常便利,不需要编译就能够对咱们修正的代码进行剖析。

以上是 Session 10202 中的全部内容,以下是译者补充部分

静态剖析

静态剖析并不是一个新奇的东西,早在 Clang 呈现之前,C 和 C++ 的代码就有了静态剖析东西,例如:cppcheck。在曩昔的几十年中,静态剖析器已经从根本的语法查看器发展到能够经过对代码的语义进行推理得以捕获更深层的 bug 的东西。

静态剖析程运转的时分,并没有实践运转你的代码。不同于咱们常见的黄色编译正告⚠️。

与编译正告有什么区别?

当咱们第一次运转静态剖析程序的时分,看到那些被 Checker 捕获出来的问题,会觉得跟编译器的黄色正告很相似,对吧?

由于编程器也在天天跟我讲说「这个变量你界说出来可是 Never used~」,那我第一个问题当然是:

那黄色和蓝色的 warning,有什么区别呢?横竖又不影响程序运转?

编译的本质是把各种语言的代码转换到方针机器的指令集,咱们经常会评论编译器的功率、方针代码的履行功率、编译进程如何对机器码进行优化,等等这些关于速度和功率的问题。

静态剖析唯一重视的工作便是寻觅代码缺陷,它做的工作,便是用 CPU 的时刻去交换代码逻辑的强壮,尽它最大的可能去剖析找到代码中的各种 Bug,乃至于在一些问题的寻觅上,它的算法的时刻复杂度可能是指数等级的!就为了寻觅确保一个纷歧定会呈现的过错。

所以,点击「Analyze」按钮之前,你要提早心里有预期,它的速度真的不能跟编译器混为一谈。

当然静态剖析器也不能完全不管履行功率,所以虽然它尽量地去追求了一些轻量级和履行功率,可是也是被限制在一定的资源范围内,去用合理的 CPU 资源,去交换更多的查看履行,从而尽可能的确保代码质量。

别的从范围视点来回答这个问题:LLVM 的编译流程触及三部分:前端代码编译(Clang)、IR 中间代码生成优化、方针机器代码翻译优化(x86/arm64等)。静态剖析器是编译器前端的一部分。

局限性与继续性

静态剖析器的原理很好理解,它由一个入口引擎和许多许多的 Checker 来完结。

引擎担任读取装备参数进行启动,以及管理这些若干 Checker 来履行各自的捕获算法,就像自动化测验中自动履行测验用例相同。

所以也就像自动化测验相同,静态剖析能捕获出来的问题,全部都是经过专门规划的。

由于 Checker 的履行逻辑是有限的,静态剖析能做的工作当然也是有限的。作为一个代码健壮性的辅助东西,更多的逻辑过错和规划方案上的问题只能开发规划人员自己来担任。

虽然静态剖析有自己本身的局限性,可是拿 Clang 静态剖析器来说,在到达那个峰值之前,还有很长的一段路要走,以后每年随着 Xcode 的更新,也会有越来越多更有用的剖析项供给给开发者们运用!

关于 Clang 静态剖析器

Xcode 于 3.2 版别内置集成了 Clang 静态剖析器。在此之前它是以指令行的形式供给给开发人员运用,下面有指令介绍和深化链接。

Clang 静态剖析器的方针是供给一个工业等级的静态剖析结构,用于剖析 C、C++ 和 Objective-C 程序代码,它是免费可用的、可扩展的,而且拥有极高的代码质量。

Clang 静态剖析器根据 Clang 和 LLVM 之上,LLVM 之父 Chris Lattner 对外介绍 LLVM 称之为「一系列接口清晰的可重用库」,Clang 便是这些库中的一个,所以它也具备很好的可重用性。

Clang 是 LLVM 体系中的编译器独立前端,为 C/C++/Objective-C 三种语言(Clang 便是 C Language 的意思),Clang 的 Static Analyzer,是 Clang 的子东西,编译 Clang 后可独立运转,意图便是为了对代码进行静态查看、捕获问题然后报告问题。

源码结构

下图是在 LLVM 中,它的模块方位:

/llvm/tools/clang/lib/StaticAnalyzer

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

咱们能够看到,这个 Checkers 目录下便是一个又一个的查看器,每一个查看器担任一个独立的查看项目,许多的复用结构,共用逻辑部分下沉到 Core 模块中。

由于整个模块位于 Clang 和 LLVM 的上层,所以 Core 模块中大量依赖了 LLVM、Clang 中的库。

示例:下图是 Checkers 文件夹中,数组越界查看的类文件,能够看到他仅向外暴露一个查看函数,履行查看算法,捕获问题传递给调用者。

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

woboq 是一个在线的源码阅读东西,点击链接能够能够看到 Checkers 途径下的一切 Checkers 文件源码,以及上一级的 Core & Frontend。

scan-build 和 scan-view

scan-build 担任对方针代码进行剖析,并生成 html 样式的剖析报告。

scan-view 担任在本地运转一个简易的 web server,用来让运用者便利的查看生成的报告。

编译 LLVM 编译后生成的 scan-buildview 两个指令的方位:

/build/tools/clang/tools/scam-build
/build/tools/clang/tools/scam-view

【老司机精选】使用 Clang 静态分析器尽早发现 Bug

假如想了解更多关于这两个指令的运用细节,请查阅官方文档: scan-build: running the analyzer from the command line Clang Static Analyzer

重视咱们

咱们是「老司机技能周报」,一个继续追求精品 iOS 内容的技能大众号。欢迎重视。

重视有礼,重视【老司机技能周报】,回复「2021」,收取 2017/2018/2019/2020 内参

支撑作者

在这里给大家推荐一下 《WWDC21 内参》 这个专栏,一共有 102 篇关于 WWDC21 的内容,本文的内容也来历于此。假如对其他内容感兴趣,欢迎戳链接阅读更多 ~

WWDC 内参 系列是由老司机牵头安排的精品原创内容系列。 已经做了几年了,口碑一向不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实践开发经验、苹果文档和视频内容做二次创作。