前情描述
针对iOS客户端部署一套完善的静态代码检测系统。
目前市面开源的方案
方案 | 语言支持 | 特点 | 平台支持 |
---|---|---|---|
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优缺点
优点
- 多语言支持
- SonarQube 支持广泛的编程语言,包括 Java, JavaScript, Python, Objective-C, Swift, C/C++, Go 等。对于多语言项目来说,它是一个集中的代码质量分析工具。
- 全面的代码质量分析 SonarQube 能够从多维度分析代码质量,包括:
- 代码规范:检查编码风格是否符合项目的约定;
- 代码异味:识别潜在的维护性问题,比如复杂代码、重复代码等;
- 安全漏洞:自动检测代码中的潜在安全漏洞;
- 技术债务:帮助开发团队量化技术债务,展示修复问题的代价。
- 可视化的质量报告
- SonarQube 提供详细的图形化质量报告,展示代码中问题的种类、严重程度和历史变化。开发者可以通过图表查看项目代码质量的进展,便于项目持续改进。
- 集成 CI/CD 流程
- 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优缺点
优点
- 高效发现常见编程错误
- 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 的功能相对专注于特定类型的错误,对于语言的支持范围有限,且在其他非移动开发领域中表现一般。因此,适合特定需求的团队使用,但对于更综合的代码质量分析可能需要搭配其他工具。
Clang静态分析器(Clang Static Analyzer)
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 静态分析的独特性
- 直接内嵌在 Clang 编译器前端,在编译过程中完成代码分析,无需额外的工具链集成;
- 优势:高效利用已有的编译阶段数据结构(如 AST、CFG),避免重复构建和解析代码,提高分析性能。
- SonarQube 和 Infer 的区别
- SonarQube:作为独立的工具运行,分析代码需要额外配置解析规则;
- Infer:虽然也采用编译时插桩的方式,但其分析需要预处理阶段,与 Clang 的原生紧密性不同。
- Clang 静态分析的独特性
模块化和可扩展性
- Clang 静态分析
- 模块化设计,用户可以通过自定义检查器快速扩展分析功能;
- 内置检查器专注于低级问题(如内存管理、指针安全),为开发者提供定制化分析能力。
- SonarQube 的规则扩展能力较强,但更偏重业务规则检测(如编码规范);
- Infer 侧重高层次问题的检测,定制化分析规则较为复杂。
- Clang 静态分析
符号执行的深度优化
- Clang 静态分析, 引入路径敏感的符号执行引擎(Symbolic Execution),支持细粒度的程序状态模拟:
- 关键创新:对程序的每个执行路径进行建模,推导潜在问题;
- 实际优势:相比 Infer 的符号执行实现,Clang 更专注于精确建模低级语义(如 C/C++ 的指针、内存分配等)。
- Infer 的符号执行主要聚焦于高层次的逻辑错误(如空指针异常、资源泄漏),但对路径分支的深度分析有限;
- SonarQube 不直接采用符号执行技术,而是依赖规则匹配和数据流分析。
- Clang 静态分析, 引入路径敏感的符号执行引擎(Symbolic Execution),支持细粒度的程序状态模拟:
路径敏感分析(Path-Sensitive Analysis)
- Clang 静态分析:能够识别和模拟条件分支的所有可能路径,避免遗漏关键问题。
- Infer 的路径敏感性较弱,偏向实际的调用链追踪;
- SonarQube 不支持路径敏感分析,主要基于数据流规则进行检测。
语法级别的深度解析
- Clang 静态分析:基于完整的 AST 和 CFG,能够深入解析语言特性(如 C++ 的模板、宏定义等复杂语法)。
- SonarQube:主要依赖抽象语法树和规则引擎,难以处理复杂的动态语言特性;
- Infer:对特定场景的语法处理有限,侧重于资源管理问题。
编译阶段的增量式分析
- Clang 静态分析:利用编译器前端架构,只对变化部分代码进行增量分析,节省分析时间。
- SonarQube 通常对整个项目进行全面扫描,分析耗时较长;
- Infer 的分析性能较好,但依赖预处理阶段插桩,仍需一定的额外开销。
精度与效率的平衡
- Clang 静态分析:路径敏感分析结合上下文限定,降低误报率(False Positive)。
- SonarQube 偏向全面检测,但误报率相对较高;
- Infer 的分析范围较窄,但对特定问题有较高的准确性。
对低级问题的支持
- Clang 静态分析
- 针对 C/C++ 语言的低级问题(如指针运算、内存管理)提供了精细化支持;
- 擅长检测未初始化变量、数组越界、空指针等底层错误。
- SonarQube 更适合编码规范、质量指标检测,不深入底层细节;
- Infer 偏重于 Java/Objective-C 的高层逻辑问题。
- Clang 静态分析
跨平台开发的优势
- Clang 静态分析:通过 LLVM 框架支持多语言开发,尤其适合 C/C++ 的跨平台项目。
- SonarQube 偏向企业软件的多语言静态检测,但对性能和底层语义分析支持有限;
- Infer 主要面向移动端开发(iOS/Android),对其他平台的支持较少。
总结
Clang 静态分析的技术创新点体现在以下方面:
- 架构集成:深度嵌入 Clang 编译器,性能高效且与工具链无缝结合;
- 符号执行和路径敏感分析:深度优化路径分析和状态推导,专注于 C/C++ 的低级问题检测;
- 语法解析能力:通过 AST 和 CFG,深入支持复杂语法特性;
- 灵活扩展性:模块化设计,便于定制和集成。
相比 SonarQube 和 Infer,Clang 静态分析在底层问题检测、编译阶段的高效性和多语言支持方面具有显著优势,是低级编程语言(如 C/C++)项目的优选工具。
静态分析器介绍
静态分析器是一种针对源代码的分析工具,目的是分析出代码中可能存在的风险,以提醒程序员。有时候程序员会用它来自查,更多的时候是部署在版本控制环节,在代码提交或者合并之前做一次自动检查。它与编译器编译时告警的不同之处在于,编译告警的问题一般相对简单直接,例如程序使用了已标记废弃的函数。而静态分析器往往能进行更加深度的分析,例如内存泄漏、未关闭流等。不过另一方面,既然分析逻辑更复杂,它的运行速度自然相对较低,因此一般不会频繁运行,而只设计在关键环节。
Clang Static Analyzer
Clang在静态分析这个方面显得非常出色,因为Clang从一开始就把提供极有价值的错误和警告信息作为设计目标之一。Clang Static Analyzer 不仅提供了独立的命令行工具,还被很好的集成到了 Xcode 当中,我们可以在 Xcode 中非常方便的使用它,使用快捷键 Command+Shift+B 即可。我们来看一个例子:我们声明一个函数返回值为非空字符串,但是在代码逻辑中有一个分支将其设为了空,先来看看编译器是否会报警告。
以下是一个简单的测试方法
可以看到编译并运行成功,再来看静态分析结果
以及具体的分析信息
可以看到静态分析器不仅给出了警告,而且很详细把每个分支的条件清楚的展示给我们,就好像它真多做了一次覆盖完整的单元测试一样。
静态分析原理
Clang如何做到如此精准的分析的呢?如果是我们自己人工分析逻辑的话,我们会如何处理?我想关键点在于关注变量ret的状态,然后构造分支逻辑成立的条件,最后判断每个分支逻辑的结果是否满足预期。
Clang的静态分析基于一个称为符号执行引擎的程序,和我们人工分析的过程类似,符号执行引擎会分析程序逻辑的各个分支满足的条件,然后提前把这些条件准备好,如果有多个独立条件并存就会用排列组合的方式设计条件列表,再将这些不同预设的条件放到引擎中模拟执行,观察并记录符号的状态变化,具体某一项分析检查器只需要在合适的时机去读取符号的当前状态,判定是否有误即可知道代码是否存在问题了。
符号引擎不同于我们平时debug,只会走一条路径,符号引擎会构建一棵树,把所有可能的路径都走一遍,但又不是真正的执行,它模拟的执行仅限于明确的行为,例如变量赋值、四则运算等,如果是调用其他程序模拟就可能会影响分析的结果。我们下面来修改一下前面的程序再分析一遍试试看。
我们在程序的最后调用了一个函数,并且将变量ret 的地址作为参数传入这个函数,虽然这个函数sn_assign 实际上没有做任何事,根本不会引起ret 的值,但分析器似很傻,再也分析不出问题了。其实这是因为对外部程序的调用可能会影响ret 的值,但符号执行引擎并不会真的去深度分析那个外部程序的过程,而是直接认为执行只会ret 的状态变为”未知”,于是无法判定最终是否为空了。Clang 之所以不做更深的分析,我想大概有两个原因,一是外部程序有太多不确定性,很多外部程序并不是当前文件定义的,那就更加不可能知道其内容了。二是会进一步加重分析的效率,静态分析本身已经很消耗计算资源了,再深度分析会大大影响效率,所以需要在效率和效果之间做权衡。
如何使用静态分析器
查看可用分析器列表
1 | clang -cc1 -analyzer-checker-help |
通过这条命令可以查看当前clang 全部可用的分析器。
可以看到,分析器的命名是有命名空间的,其中alpha 开头的是正在开发的实验性功能,core 开头的是基础的,等等。
使用指定的分析器分析代码
1 | clang -analyzer-checker=core ViewController.m |
我们试着运行,结果clang 报了编译错误。这是因为clang 不知道去哪里找
<UIKit/UIKit.h>。这个问题牵扯其他内容,我们此处暂不解决,先绕过去,把代码简化一下,去掉对UIKit 的依赖。
1 | clang -cc1 -analyzer-checker=core TestStaticAnalyze/TestStaticAnalyze/ViewController.m |
优化后的代码如下所示TestMain.c文件
1 |
|
再次执行命令
1 | clang --analyze TestStaticAnalyze/TestStaticAnalyze/TestMain.c |
得到结果
生成报告
1 | clang --analyze TestStaticAnalyze/TestStaticAnalyze/TestMain.c -o report |
应对大型工程
前面我们讲述了如何使用 clang 针对单个文件进行分析,但实际的场景中,我们一般并不会单个的去编译一个个文件,而是利用针对工程的集成构建工具,例如CMake
或者 IDE 来完成。这种情况,Clang
也给出了非常好的解决方案。例如我们刚才的工程,使用了系统提供的 Foundation
库,而我们编译的时候并不关心这个库在什么位置,而是利用 Xcode 整体编译,Xcode 会帮我们把工程设置好,库的位置自然不需要我们操心。如果我们想用命令行去分析整个工程,而又想使用 Xcode 一样方便的去自动管理编译参数,那么我们可以使用 scan-build
这个命令行工具来实现。我们指定 Xcode 还提供了一个命令行版本的构建工具 xcodebuild
,使用它来构建就和在 Xcode 中点击构建按钮一样,下面我们就用命令行构建 Xcode 工程。
1 | 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
都用于静态分析代码,但它们的执行方式和应用场景略有不同。
- Product > Analyze
- 功能:这是一个常规的静态代码分析功能,用于查找潜在的代码问题。它会对整个项目(包括所有目标和文件)进行静态分析,检查代码中的潜在问题,例如内存泄漏、未初始化的变量、逻辑错误等;
- 执行命令:实际上是调用 xcodebuild analyze 命令,针对当前 Scheme 的所有目标执行静态分析;
- 适用场景:如果你希望对整个项目或所有目标执行静态分析,通常使用这个选项。
- Product > Perform Action > Analyze
- 功能:这个选项类似于Analyze,但它通常用于对当前正在查看的文件或选定的目标进行分析,而不是整个项目。因此,它的执行更快,因为仅分析了部分代码;
- 执行命令:虽然 Xcode 并未将此操作直接映射到一个 xcodebuild 命令中,但本质上也是对选定文件或目标执行部分分析。它可能通过内部 API 或针对特定文件的 clang 静态分析器来实现;
- 适用场景:当你仅对特定文件或部分代码有分析需求时,可以使用这个选项来节省时间,而无需分析整个项目。
总结区别:
1. Product > Analyze 会对整个项目进行静态分析,适用于全面检查;
2. Product > Perform Action > Analyze 仅分析当前文件或选定目标,适用于快速检查。
结束语
最后在项目中选择了Clang Static Analyzer作为可持续集成和自动化的方案,因为xcodebuild analyze只支持全量分析,所以通过脚本先把更新的文件筛选出来,然后通过这个改动文件去筛选分析日志,最后把日志格式化XML文件,这样就可以进行高度定制和展示了。