一、前言
我司今年开始尝试一些代码质量相关建设,比如组织 codereview、修复代码扫描漏洞.这是一个很好的现象,当我们为了快速迭代,往往为了让需求上线,导致代码并不是很规范,时间长了就留下了一堆技术债.
前日的一天,iOS 老哥找我说让看看能不能弄弄 IOS 代码扫描,扫描出一些代码漏洞,尝试去修复漏洞和 bug.
于是乎,下面就是我记录一下折腾了几天完成的 IOS 代码扫描初探的过程.
二、工具选择
从去年开始,就一直研究 IOS 代码扫描这款.无奈乎,IOS 在代码扫描这个领域能选的工具其实不算太多.
这次主要介绍如下几个工具:
- oclint
- infer
- sonar-swift
1、Oclint SonarQube方案
所需安装工具一览
下面是在mac机器上安装的工具
- homebrew(mac命令管理软件工具)
- Java JDK(推荐jdk而不是jre,最新的即可)
- maven
- xcode(通过appstore下面)
- xcpretty(用于对xcodebuild的输出进行格式化)
- sonarqube(代码扫描平台)
- sonar-scanner(扫描工具)
- oclint
- SonarQube Plugin for Objective C(扫描插件)
oclint
OCLint是基于Clang Tooling开发的静态分析工具,主要用来发现编译器检查不到的那些潜在的关键技术问题.
命令安装
代码语言:javascript复制brew tap oclint/formulae
brew install oclint
下载安装包安装
代码语言:javascript复制https://github.com/oclint/oclint/releases
配置环境变量
代码语言:javascript复制OCLint_PATH=/Users/xinxi/Documents/oclint/build/oclint-release
export PATH=$OCLint_PATH/bin:$PATH
source .bash_profile
验证是否安装成功。在终端输入 oclint --version
xcpretty
用于对xcodebuild的输出进行格式化
代码语言:javascript复制gem install xcpretty
用法:
紧跟在xcodebuild 相关语句后面,比如:
代码语言:javascript复制xcodebuild [flags] | xcpretty
可以结合tee进行日志收集
代码语言:javascript复制xcodebuild [flags] | tee xcodebuild.log | xcpretty
SonarQube安装
官网地址,sonarqube分社区版本和商业化版本,能扫描多种语言并且开源
代码语言:javascript复制https://www.sonarqube.org/downloads/
docker 安装
代码语言:javascript复制docker pull sonarqube:8.6-community
二进制文件安装
在bin/macosx-universal-64目录下的输入:
代码语言:javascript复制sh sonar.sh start
控制台输出"Started SonarQube"说明启动成功.
在浏览器访问,能打开页面说明启动成功.
代码语言:javascript复制http://127.0.0.1:9000/
需要说明的是SonarQube如果想持久化保存数据,是需要依赖mysql数据库的.
SonarQube 默认提供H2存储,只能暂时存储一些小项目结果,仅为了演示使用.
在 conf/sonar.properties 下配置数据库地址即可.
可选 MySQL、Oracle、PostgreSQL
sonar-objective-c插件
sonarqube 默认没有扫描 oc 的检查,sonarqube 官方的 sonar-objective-c 插件是收费的.
需要在找一个免费的插件,在github找到两个项目
插件一
代码语言:javascript复制https://github.com/Backelite/sonar-objective-c
这个插件在三年前没有修改了,在使用中发现有些扫描规则并没有.
插件二
这个项目稍微更新的时间短一些,有些规则适当的更新了
代码语言:javascript复制https://github.com/raatiniemi/sonar-objective-c
下载插件放到 /extensions/plugins 目录下
sonar-scanner
sonar-scanner用来扫描本地代码,并且上传到SonarQube平台中.
下载地址: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/
按照不同的操作系统选择不同安装包即可.
配置环境变量:
代码语言:javascript复制vim /etc/profile
export SONAR_HOME=/usr/local/sonarqube-6.7.5
export SONAR_SCANNER_HOME=/usr/local/sonar-scanner
PATH=$PATH:$SONAR_HOME/bin:$SONAR_RUNNER_HOME/bin
source /etc/profile
sonar-scanner分为两种使用方式:
配置文件方式:
在项目根目录下新建 sonar-project.properties 文件,内容如下:
代码语言:javascript复制//项目的key
sonar.projectKey=projectKey
//项目的名字
sonar.projectName=projectName
//项目的版本
sonar.projectVersion=1.0.0
//需要分析的源码的目录,多个目录用英文逗号隔开
sonar.sources=D:/workspace/Demo/src
进入项目根目录下,然后输入“sonar-scanner”命令,执行代码分析
命令行方式:
在命令中设置了参数
代码语言:javascript复制sonar-scanner -Dsonar.host.url=http://sonarqube -Dsonar.projectKey=app -Dsonar.sources=.
项目实验
上面的软件安装完成后,基本上具备的代码扫描的条件.找一个开源项目实验下
使用网络库AFNetworking项目:https://github.com/AFNetworking/AFNetworking
脚本
- 首先clone代码到本地,然后再清理项目工程
xcodebuild -workspace AFNetworking.xcworkspace -scheme AFNetworking iOS -sdk iphonesimulator11.2 -configuration Debug clean
- 生成compile_commands.json
xcodebuild -workspace AFNetworking.xcworkspace -scheme AFNetworking iOS -sdk iphonesimulator11.2 -configuration Debug COMPILER_INDEX_STORE_ENABLE=NO | xcpretty -r json-compilation-database -o compile_commands.json
- 生成oclint.xml
oclint-json-compilation-database -- -report-type pmd -o oclint.xml -max-priority-1 100000 -max-priority-2 100000 -max-priority-3 100000
- 处理oclint.xml
oclint生成的报告中如下形式的规则会导致Objective-c分析插件出错(ERROR: The rule 'OCLint:compiler warning' does not exist, 刚才上面提到的sonar-objective-c插件并没有处理warning这些规则. 所以需要通过脚本删除这个结果.
脚本
代码语言:javascript复制#!/usr/bin/python
import xml.etree.ElementTree as ET
import os
os.system('mv oclint.xml oclint.xml.origin')
tree = ET.ElementTree(file='oclint.xml.origin')
root = tree.getroot()
del_items = []
for child in root:
for one in child:
if one.attrib['ruleset'] == 'clang':
print child.attrib['name']
del_items.append(child)
break
for del_item in del_items:
root.remove(del_item)
tree.write('oclint.xml')
在和 oclint.xml 一个目录下,执行该脚本
- 生成 Sonar 报告
将如下内容保存为 sonar-project.properties 文件,放到 AFNetworking 目录下
代码语言:javascript复制sonar.projectKey=AFNetworking
sonar.host.url=http://localhost:9000
sonar.login=admin
sonar.password=admin
sonar.language=objc
sonar.objectivec.workspace=AFNetworking.xcworkspace
sonar.objectivec.appScheme=AFNetworking iOS
sonar.sources=AFNetworking
sonar.objectivec.oclint.report=oclint.xml
- Sonar平台展示
扫描结果图一:
扫描结果图二:
问题记录
在使用demo中非常顺滑,没什么问题.但是接入了实际项目,出现了如下问题.
- 问题一:编译项目失败
解决方案:
命令行编译的问题,必须携带参数" COMPILER_INDEX_STORE_ENABLE=NO"
- 问题二:oclint: error: violations exceed threshold
解决方案:
代码语言:javascript复制maxPriority=15000
代码语言:javascript复制
${oclint_in} $${oclint_ex} -- -o=$$BUILD_WORK_DIR/oclint/lint.xml -report-type=pmd -stats -max-priority-1=$$maxPriority -max-priority-2=$$maxPriority -max-priority-3=$maxPriority -rc LONG_LINE=500 -rc LONG_VARIABLE_NAME=10
- 问题三:如果扫描的生成的 compile_commands.json 文件过大,oclint-json-compilation-database会提示出错“OSError: [Errno 7] Argument list too long”
这个问题在网上看了很多帖子都是如下解决方案,但是实际中使用根本没有解决问题.
https://github.com/oclint/oclint/issues/233有网友给出解决方案https://github.com/wuwen1030/oclint_argument_list_too_long_solution/tree/master
解决方案:
oclint-json-compilation-database可以过滤不想扫描的文件和需要扫描的文件夹
-e忽略扫描和-i是指定扫描路径
代码语言:javascript复制oclint-json-compilation-database -e pods -i build
虽然使用上面的命令扫描,不报错误,但是在平台中扫描的bug数是0,这个问题目前一直未解决.
- 问题四:mysql存储问题
2、infer sonar-swift
基于上面失败方案一度想放弃,但是无意中在社区中,看到了好未来开源的iOS代码扫描的帖子"我们开源了一款SonarQube iOS代码扫描插件",https://testerhome.com/topics/26967, 又激起了我想重新尝试的勇气.
github地址:
代码语言:javascript复制https://github.com/tal-tech/sonar-swift
简单看了一下需要工具,需要infer、xcpretty、sonar、sonar-swift插件.
扫描规则:
代码语言:javascript复制https://github.com/tal-tech/sonar-swift/blob/master/docs/rule.md
这次尝试并没有急于着手干,看到帖子下面有个微信群并加了群,询问了开发者一些细节,确认是可以扫描 oc 项目的.
sonar-swift
插件地址:
代码语言:javascript复制https://github.com/tal-tech/sonar-swift/releases
当时我下载的是v1.0.2版本,把插件放到 /extensions/plugins 目录下,重启sonar
脚本
官方提供的脚本
代码语言:javascript复制xcodebuild clean build -workspace app.xcworkspace -scheme scheme -destination 'generic/platform=iOS' COMPILER_INDEX_STORE_ENABLE=NO | tee xcodebuild.log > /dev/null
xcpretty -r json-compilation-database -o compile_commands.json < xcodebuild.log > /dev/null
# --skip-analysis-in-path 是忽略扫描目录
infer run --skip-analysis-in-path Pods --compilation-database compile_commands.json
# 可选,如果有 swift 语言使用
swiftlint lint > swiftlint.txt
lizard --xml > lizard-report.xml
sonar-scanner -Dsonar.host.url=http://sonarqube -Dsonar.projectKey=app -Dsonar.sources=. -Dsonar.swift.swiftlint.report=swiftlint.txt -Dsonar.swift.lizard.report=lizard-report.xml -Dsonar.swift.infer.report=infer-out/report.json
infer
infer是facebook开源的一款代码扫描软件,可以分析 Objective-C, Java 或者 C 代码,报告潜在的问题
在releases页面中下载二进制文件
代码语言:javascript复制https://github.com/facebook/infer/releases
设置环境变量
代码语言:javascript复制tar xf infer-osx-vXX.tar.xz
# this assumes you use bash, adapt to your needs in case you use
# another shell
echo "export PATH=$PATH:`pwd`/infer-osx/infer/infer/bin"
>> ~/.bashrc && source ~/.bashrc
通过infer --version查看infer版本信息,说明安装成功.
扫描iOS命令:
代码语言:javascript复制infer -- xcodebuild -workspace "test.xcworkspace" -scheme "scheme"
扫描出的结果会在工程目录下的infer-out文件中,其中具体的代码会以csv,txt,json的格式分别存在对应的文件中。可以供我们分析.
扫描过程
infer扫描阶段
扫描的bug数量
扫描规则
结果上传成功
sonar 平台展示数据
扫描结果图三:
扫描结果图四:
从下载代码到上传扫描结果,大概1小时30分支,和项目规模成正比.
问题记录
问题1
解决方案: lizard 这个报告不要了,暂时去掉
问题2:java包中没有这个规则
解决方案:
1、用 -Dsonar.exclusions=文件路径这个排除 2、在report.json中删除这个规则
问题3:没有这个规则
解决方案:使用新版本的jar包
问题4:有个异常,去掉-Dsonar.swift.swiftlint.report=swiftlint.txt
问题5
因为每次扫描都是增量扫描,如果使用多个分支同一个项目扫描,结果会被覆盖,sonar本身也不支持多个分支扫描. 每次扫描的时候想知道是扫描的哪个版本的数据,通过参数-Dsonar.projectVersion参数可以上传版本号.
shell中获取版本号
代码语言:javascript复制version_number=`sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ./PROJECTNAME.xcodeproj/project.pbxproj`
shell中获取版本号构建号
代码语言:javascript复制build_number=`sed -n '/CURRENT_PROJECT_VERSION/{s/CURRENT_PROJECT_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ./PROJECTNAME.xcodeproj/project.pbxproj`
在活动页面展示了版本号
结语
经过折腾了几天,也算顺利的完成了基础环境搭建,能正常扫描出结果来了.
给我最大的启发是: 做事需要有专业的"社区",如果我没有去社区中有浏览的习惯,也很难找到不错的工具. 做事需要有专业的"圈子",专业的人做专业事,方可事半功倍.
参考资料:
- [1]:如何使用脚本读取Xcode 11中的当前应用程序版本(https://stackoom.com/question/3q09t/如何使用脚本读取Xcode-中的当前应用程序版本)
- [2]:iOS Jenkins持续构建-代码扫描(https://www.jianshu.com/p/c0d49bcefeb0)
- [3]:使用Jenkins OCLint SonarCube对iOS项目进行代码分析(https://juejin.cn/post/6844903575680729102)
- [4]:iOS 静态代码扫描平台 Sonarqube 实战 Objective-C、Swift(https://testerhome.com/topics/13158)
- [5]:ios fastline sonarqube(https://medium.com/@aamir.ali/sonarqube-integration-with-fastlane-in-ios-3cd33e5abdc8)
- [6]:oclint_argument_list_too_long_solution解决方案(https://github.com/wuwen1030/oclint_argument_list_too_long_solution)
- [7]:OCLint静态代码检测实践(https://juejin.cn/post/6844904017424809998)
- [8]:OCLint基本使用(https://www.jianshu.com/p/b2513f16d246)
- [9]:iOS 静态代码扫描平台 Sonarqube 实战 Objective-C、Swift(https://testerhome.com/topics/13158)
- [10]:Sonarqube & ObjectiveC 环境搭建(https://www.jianshu.com/p/5a01e56176bf)