用最通俗的语言,描述最难懂的技术
01
前情描述
针对iOS客户端部署一套完善的静态代码检测系统。
02
目前市面开源的方案
方案 | 语言支持 | 特点 | 平台支持 |
---|---|---|---|
SonarQube | Objective-C, Swift, Java, JavaScript, HTML5(通过插件可以扩展到30多种语言)。 | SonarQube 是一个强大的静态代码分析平台,支持多种语言,可以集成到 CI/CD 管道中进行持续代码质量检查,特别适合大规模项目和团队开发。支持代码安全性、技术债务、编码标准检查等功能。 | Linux, macOS, Windows |
Infer | Objective-C, Swift, Java, C/C++ | Infer 是由 Facebook 开发的静态代码分析工具,主要用于捕获 iOS 和 Android 应用中的潜在问题。它能自动检查 null 指针引用、资源泄漏和竞态条件等问题。 | Linux, macOS |
SwiftLint | Swift | SwiftLint 是专为 Swift 语言开发的静态分析工具,能够根据 Swift 编码规范进行代码样式和潜在问题的检查。它可以通过配置自定义规则,非常适合 iOS 开发。 | macOS、Linux |
Checkstyle | Java | Checkstyle 是一款用于检查 Java 代码风格的静态代码分析工具,可以发现代码中的潜在问题、风格违规等。非常适用于 Android 项目的 Java 代码质量检查。 | Linux, macOS, Windows |
ESLint | JavaScript | ESLint 是专门用于 JavaScript 的静态代码检查工具,支持 H5 和前端开发的 JavaScript 代码。通过配置,可以灵活地支持不同的代码风格和自定义规则。 | Linux, macOS, Windows |
Clang Static Analyzer | Objc,C/C++,Swift | Xcode内置的静态分析工具。 | macOS |
Huawei DevEco Lint | Java, JavaScript(主要支持鸿蒙应用开发) | 这是华为鸿蒙系统开发的官方工具,专门用于静态代码检查和分析鸿蒙开发中的潜在问题,包括 Java 和 JS 的检查。 | Linux, macOS, Windows |
SonarQube
支持广泛的编程语言,包括 Java, JavaScript, Python, Objective-C, Swift, C/C++, Go
等。对于多语言项目来说,它是一个集中的代码质量分析工具。
SonarQube
能够从多维度分析代码质量,包括:
SonarQube
提供详细的图形化质量报告,展示代码中问题的种类、严重程度和历史变化。开发者可以通过图表查看项目代码质量的进展,便于项目持续改进。
SonarQube
可以与主流的 CI/CD
工具(如 Jenkins、GitLab CI、Travis CI
等)无缝集成。代码提交时自动运行代码质量检查,能够及时发现问题,提升开发效率。
SonarQube
的 Marketplace
提供了大量插件,可以为不同的编程语言或特定分析规则扩展功能,例如支持特定的框架、工具或语言(如Swift、Objective-C、PHP
等)。
它可以跟踪项目的代码质量历史,帮助团队识别和解决长期存在的代码质量问题,促进代码改进。
SonarQube
提供大量预定义规则,并允许开发团队根据项目需求自定义规则。它可以设置质量门槛(Quality Gates
),确保每次构建必须达到某些标准才算合格。
SonarQube
尤其在分析大型项目时,可能会占用大量的 CPU
和内存资源,影响服务器性能。对于大型代码库和频繁的代码提交 SonarQube
可能会成为系统瓶颈。
对于多语言项目或需要自定义规则的项目,SonarQube
的配置和集成可能比较复杂。尤其是首次使用时,设置服务器、数据库和扫描器的步骤较多。
虽然 SonarQube
的开源版本功能丰富,但一些高级功能(如更细化的代码分支分析、安全扫描、开发者角色权限管理等)需要付费的企业版。对于预算有限的团队,可能无法获得这些高级功能。
虽然 SonarQube
提供大量插件支持各种编程语言和规则,但一些社区开发的插件质量不高,可能存在不稳定或规则不完善的问题。
尽管 SonarQube
允许自定义规则,但有时根据项目特定需求定制复杂的规则可能需要一定的编写和维护成本,特别是对于不熟悉规则引擎的团队。
虽然 SonarQube
支持多种编程语言,但一些新兴语言和框架的支持度不如主流语言。如果项目使用比较新的技术栈,可能无法得到完善的规则支持。
SonarQube
的分析是离线执行的,通常是与 CI/CD
结合使用,这意味着开发人员在编写代码时无法立即获得反馈。而一些现代开发工具(如 ESLint
或 SwiftLint
)可以实时在 IDE 中提供代码检查提示。
SonarQube 是一个功能强大、灵活的静态代码分析工具,尤其适合大中型团队进行代码质量的持续监控和改进。它的多语言支持、集成 CI/CD 的能力以及丰富的可视化报告使其成为开发过程中重要的工具之一。然而,对于小型项目或初学者来说,SonarQube 的资源需求、配置复杂性以及一些高级功能的付费限制可能会带来一定的使用门槛。
在测试Sonar
的时候遇到了很多兼容问题,问题如下
Sonar
使用brew
安装后无法启动,报错日志显示java
版本兼容;
然后降低Sonar
版本为9.9.0
社区版本成功,MacOS
多环境安装java17 SDK
;
同时最高版本不支持汉化,降低版本后汉化成功;
此时进行导入开源的最新开源第三方插件,发现无法启动成功,去社区找原因,发现还是版本问题,此时找到对应的版本成功启动;
此时服务都搭建好了,进行项目测试,编译输出了json
文件,发现插件(infer
和sonar swift plugin
)无法运行成功,去社区找答案,发现是Xcode
版本问题,这个问题在Xcode15
就无人处理,至此Sonar
的测试结束。
Infer
擅长捕捉 空指针异常(Null Pointer Exceptions)、资源泄漏(如文件流未关闭)、内存泄漏 以及 竞争条件 等常见的编程错误。它能在早期阶段找到这些隐患,从而提升代码的健壮性。
Infer
支持 增量式代码分析,即它不会重新分析整个代码库,而是仅分析自上次分析后更改的部分代码。这个功能极大提高了在大型项目中的分析速度,使其适合集成在 CI/CD
环境中。
Infer
能够很好地集成到现有的 CI/CD 工具(如 Jenkins、GitLab、Travis CI
等)以及构建工具(如 Gradle、Maven、Xcode
)。它支持命令行使用,易于自动化,尤其适合持续集成和自动化测试过程。
Infer
支持多种编程语言,包括 Java、C、C++、Objective-C 和 Swift。因此,它在 Android
和 iOS
应用开发中尤其常用。对于开发移动端应用的团队,Infer 提供了高度的兼容性。
相较于一些更耗资源的静态分析工具,Infer 的增量分析和目标导向的设计使其对系统资源的消耗相对较少,分析速度较快。
Infer
是一个 开源工具,由 Facebook 推出并持续维护。它的代码库公开在 GitHub
上,因此开发者可以自由地修改和定制它的行为,或为其贡献代码。
Infer
尤其适合用于移动平台(iOS
和 Android
)开发。在处理 Objective-C
和 Swift
代码时,它能很好地发现内存管理和空指针相关的缺陷,是很多移动开发团队的重要工具。
虽然 Infer
支持 Java、C、C++、Objective-C
和 Swift
,但与 SonarQube
等工具相比,它的语言支持面较窄。它不支持更广泛的现代编程语言(如 JavaScript、Python、Go
等),在多语言项目中可能受限。
Infer 的分析主要集中在查找常见的编程错误,如 空指针引用、内存泄漏、资源泄漏 等。相比一些功能更丰富的工具(如 SonarQube),Infer 的检测维度较为有限,不能全面评估代码质量(如代码复杂度、重复代码、编码规范等)。
和大多数静态分析工具一样,Infer
可能会产生 误报(报告实际上并不存在的缺陷)或 漏报(未能检测到存在的问题)。在大规模代码库中,这种情况可能会影响分析的准确性和开发效率。
误报往往需要开发者手动处理和标记,增加了额外的工作量。
Infer 的配置和使用对新手来说可能有一定难度,尤其是在与现有构建工具或 CI
系统集成时。虽然其文档较为详尽,但对一些复杂项目来说,配置和调整Infer
的分析规则和输出结果可能需要一定的学习成本。
虽然Infer
在移动开发中表现突出,但在其他领域(如后端、Web
开发)中,它的适用性较弱。其功能和适配更多针对于移动端特定的语言和场景,而不适合所有项目类型。
Infer
的报告格式相对简洁,但对于一些复杂问题,它的输出可能不够直观或详细,开发人员有时需要花费更多时间来理解错误的根源或上下文。相比之下,像 SonarQube
这种工具会生成更丰富、可视化的分析报告。
虽然 Infer
是一个由Facebook
推出的开源项目,但它的社区活跃度不如一些更流行的静态分析工具。对于需要定制功能或寻求社区支持的开发者来说,资源和帮助可能相对有限。
Infer
是一个非常有效的工具,特别适合移动应用开发中的静态分析,能够高效发现内存泄漏、空指针等常见问题。它的增量分析功能使得它能够快速处理大型项目的代码变动,并且它的开源特性和易于集成的特点使其在 CI/CD
流程中广泛使用。然而,Infer
的功能相对专注于特定类型的错误,对于语言的支持范围有限,且在其他非移动开发领域中表现一般。因此,适合特定需求的团队使用,但对于更综合的代码质量分析可能需要搭配其他工具。
Xcode
内置的Clang Static Analyzer
是一个非常实用的静态分析工具,特别适用于iOS
和macOS
开发。它直接集成在Xcode
中,可以帮助开发者在编译期间发现代码中的潜在问题。
Clang Static Analyzer
是Xcode的一部分,无需额外安装和配置,能够与Xcode
编译流程紧密结合,在编写代码时实时提供反馈。
它能够有效检测内存泄漏、空指针引用、未定义行为等问题,尤其是针对Objective-C
和Swift
中的ARC(自动引用计数)问题。
分析结果能够以图形化的形式在Xcode
中展示,易于理解和定位问题。这对于初学者或开发团队非常友好,帮助开发者快速发现问题。
Clang Static Analyzer
能够对项目中的改动部分进行增量分析,而不是每次都分析整个项目,节省了时间和资源。
随着每个Xcode
版本的发布,Clang Static Analyzer
也会随着Xcode的更新保持兼容,并能利用最新的iOS/macOS平台特性。
相比其他专用的静态分析工具(如SonarQube
或Infer
),Clang Static Analyzer
的功能相对基础。它主要专注于内存和线程相关的问题,不能全面覆盖代码质量、复杂度、风格等方面的分析。
Clang Static Analyzer
有时会出现误报,尤其是在处理复杂的代码逻辑或某些特定的设计模式时,这可能导致开发者在过滤误报时耗费时间。
Clang Static Analyzer
不像其他第三方工具那样可以通过插件或脚本进行定制或扩展。它无法满足一些特定的代码审查需求,比如复杂的业务逻辑验证或安全漏洞检测。
虽然提供了图形化界面,但它的报告格式较为简单,无法像SonarQube等工具那样生成复杂的、可定制的报表。对于团队项目管理和代码健康追踪,这可能是一个限制。
Clang Static Analyzer
作为一个免费、内置的工具,非常适合在开发过程中快速检测和修复基础错误。但如果你需要更复杂的代码质量分析和安全审计,可能需要结合其他静态分析工具一起使用。
Clang 静态分析
引入路径敏感的符号执行引擎(Symbolic Execution),支持细粒度的程序状态模拟:
SonarQube 和 Infer
Clang 静态分析的技术创新点体现在以下方面:
相比 SonarQube 和 Infer,Clang 静态分析在底层问题检测、编译阶段的高效性和多语言支持方面具有显著优势,是低级编程语言(如 C/C++)项目的优选工具。
03
静态分析器介绍
静态分析器是一种针对源代码的分析工具,目的是分析出代码中可能存在的风险,以提醒程序员。有时候程序员会用它来自查,更多的时候是部署在版本控制环节,在代码提交或者合并之前做一次自动检查。它与编译器编译时告警的不同之处在于,编译告警的问题一般相对简单直接,例如程序使用了已标记废弃的函数。而静态分析器往往能进行更加深度的分析,例如内存泄漏、未关闭流等。不过另一方面,既然分析逻辑更复杂,它的运行速度自然相对较低,因此一般不会频繁运行,而只设计在关键环节。
Clang
在静态分析这个方面显得非常出色,因为Clang
从一开始就把提供极有价值的错误和警告信息作为设计目标之一。Clang Static Analyzer
不仅提供了独立的命令行工具,还被很好的集成到了 Xcode
当中,我们可以在 Xcode
中非常方便的使用它,使用快捷键 Command+Shift+B
即可。我们来看一个例子:我们声明一个函数返回值为非空字符串,但是在代码逻辑中有一个分支将其设为了空,先来看看编译器是否会报警告。
以下是一个简单的测试方法
可以看到编译并运行成功,再来看静态分析结果
以及具体的分析信息
可以看到静态分析器不仅给出了警告,而且很详细把每个分支的条件清楚的展示给我们,就好像它真多做了一次覆盖完整的单元测试一样。
Clang
如何做到如此精准的分析的呢?如果是我们自己人工分析逻辑的话,我们会如何处理?我想关键点在于关注变量ret
的状态,然后构造分支逻辑成立的条件,最后判断每个分支逻辑的结果是否满足预期。
Clang
的静态分析基于一个称为符号执行引擎的程序,和我们人工分析的过程类似,符号执行引擎会分析程序逻辑的各个分支满足的条件,然后提前把这些条件准备好,如果有多个独立条件并存就会用排列组合的方式设计条件列表,再将这些不同预设的条件放到引擎中模拟执行,观察并记录符号的状态变化,具体某一项分析检查器只需要在合适的时机去读取符号的当前状态,判定是否有误即可知道代码是否存在问题了。
符号引擎不同于我们平时debug
,只会走一条路径,符号引擎会构建一棵树,把所有可能的路径都走一遍,但又不是真正的执行,它模拟的执行仅限于明确的行为,例如变量赋值、四则运算等,如果是调用其他程序模拟就可能会影响分析的结果。我们下面来修改一下前面的程序再分析一遍试试看。
我们在程序的最后调用了一个函数,并且将变量ret
的地址作为参数传入这个函数,虽然这个函数sn_assign
实际上没有做任何事,根本不会引起ret
的值,但分析器似很傻,再也分析不出问题了。其实这是因为对外部程序的调用可能会影响ret
的值,但符号执行引擎并不会真的去深度分析那个外部程序的过程,而是直接认为执行只会ret
的状态变为“未知”,于是无法判定最终是否为空了。Clang
之所以不做更深的分析,我想大概有两个原因,一是外部程序有太多不确定性,很多外部程序并不是当前文件定义的,那就更加不可能知道其内容了。二是会进一步加重分析的效率,静态分析本身已经很消耗计算资源了,再深度分析会大大影响效率,所以需要在效率和效果之间做权衡。
clang -cc1 -analyzer-checker-help
通过这条命令可以查看当前clang
全部可用的分析器。
可以看到,分析器的命名是有命名空间的,其中alpha
开头的是正在开发的实验性功能,core
开头的是基础的,等等。
clang -analyzer-checker=core ViewController.m
我们试着运行,结果clang
报了编译错误。这是因为clang
不知道去哪里找 <UIKit/UIKit.h>
。这个问题牵扯其他内容,我们此处暂不解决,先绕过去,把代码简化一下,去掉对UIKit
的依赖。
clang -cc1 -analyzer-checker=core TestStaticAnalyze/TestStaticAnalyze/ViewController.m
In file included from TestStaticAnalyze/TestStaticAnalyze/ViewController.m:8:
TestStaticAnalyze/TestStaticAnalyze/ViewController.h:8:9: fatal error: 'UIKit/UIKit.h' file not found
8 | #import <UIKit/UIKit.h>
| ^~~~~~~~~~~~~~~
1 error generated.
优化后的代码如下所示TestMain.c
文件
#include "TestMain.h"
void sn_assgin(char **str) {
if(!str) {
*str = "1";
}
}
char * _Nonnull testFuncReturnNotNil(int v)
{
char *ret = NULL;
if(v > 0) {
ret = "positive";
} else if(v == 0) {
ret = NULL;
} else {
ret = "negative";
}
sn_assgin(&ret);
return ret;
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!");
return 0;
}
再次执行命令
clang --analyze TestStaticAnalyze/TestStaticAnalyze/TestMain.c
得到结果
clang --analyze TestStaticAnalyze/TestStaticAnalyze/TestMain.c -o report
前面我们讲述了如何使用 clang 针对单个文件进行分析,但实际的场景中,我们一般并不会单个的去编译一个个文件,而是利用针对工程的集成构建工具,例如CMake
或者 IDE
来完成。这种情况,Clang
也给出了非常好的解决方案。例如我们刚才的工程,使用了系统提供的 Foundation
库,而我们编译的时候并不关心这个库在什么位置,而是利用 Xcode
整体编译,Xcode
会帮我们把工程设置好,库的位置自然不需要我们操心。如果我们想用命令行去分析整个工程,而又想使用 Xcode
一样方便的去自动管理编译参数,那么我们可以使用 scan-build
这个命令行工具来实现。我们指定 Xcode
还提供了一个命令行版本的构建工具 xcodebuild
,使用它来构建就和在 Xcode
中点击构建按钮一样,下面我们就用命令行构建 Xcode
工程。
xcodebuild clean analyze -workspace testMain.xcworkspace -scheme testMain -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 16' | tee clang_analyzer.log
在 Xcode 中,Product > Perform Action > Analyze
和 Product > Analyze
都用于静态分析代码,但它们的执行方式和应用场景略有不同。
功能:这是一个常规的静态代码分析功能,用于查找潜在的代码问题。它会对整个项目(包括所有目标和文件)进行静态分析,检查代码中的潜在问题,例如内存泄漏、未初始化的变量、逻辑错误等;
执行命令:实际上是调用 xcodebuild analyze
命令,针对当前 Scheme 的所有目标执行静态分析;
适用场景:如果你希望对整个项目或所有目标执行静态分析,通常使用这个选项。
功能:这个选项类似于Analyze
,但它通常用于对当前正在查看的文件或选定的目标进行分析,而不是整个项目。因此,它的执行更快,因为仅分析了部分代码;
执行命令:虽然 Xcode 并未将此操作直接映射到一个 xcodebuild
命令中,但本质上也是对选定文件或目标执行部分分析。它可能通过内部 API 或针对特定文件的 clang
静态分析器来实现;
适用场景:当你仅对特定文件或部分代码有分析需求时,可以使用这个选项来节省时间,而无需分析整个项目。
总结区别:
04
结束语
最后在项目中选择了Clang Static Analyzer
作为可持续集成和自动化的方案,因为xcodebuild analyze
只支持全量分析,所以通过脚本先把更新的文件筛选出来,然后通过这个改动文件去筛选分析日志,最后把日志格式化XML
文件,这样就可以进行高度定制和展示了。
05
参考文档
Sonar官方地址:https://www.sonarsource.com/
SonarQube官方文档:
https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/introduction/
3.infer: https://github.com/facebook/infer
4.Clang Static Analyzer:
https://developer.apple.com/library/archive/featuredarticles/Static Analysis/FeaturedArticle.html
5.理解静态分析器:
https://github.com/tuoxie007/play_with_llvm/blob/50d8c5828f7d6a9f464ff555a3021e8b64d52b4a/ch05.md