程序靜態分析(Program Static Analysis)是指在不運行代碼的方式下,經過詞法分析、語法分析、控制流、數據流分析等技術對程序代碼進行掃描,驗證代碼是否知足規範性、安全性、可靠性、可維護性等指標的一種代碼分析技術。(來自百度百科)html
詞法分析,語法分析等工做是由編譯器進行的,因此對iOS項目爲了完成靜態分析,咱們須要藉助於編譯器。對於OC語言的靜態分析能夠徹底經過Clang,對於Swift的靜態分析除了Clange還須要藉助於SourceKit。java
Swift語言對應的靜態分析工具是SwiftLint,OC語言對應的靜態分析工具備Infer和OCLitn。如下會是對各個靜態分析工具的安裝和使用作一個介紹。git
SourceKit包含在Swift項目的主倉庫,它是一套工具集,支持Swift的大多數源代碼操做特性:源代碼解析、語法突出顯示、排版、自動完成、跨語言頭生成等工做。github
安裝有兩種方式,任選其一: 方式一:經過Homebrewshell
$ brew install swiftlint
複製代碼
這種是全局安裝,各個應用均可以使用。 方式二:經過CocoaPodsjson
pod 'SwiftLint', :configurations => ['Debug'] 複製代碼
這種方式至關於把SwiftLint做爲一個三方庫集成進了項目,由於它只是調試工具,因此咱們應該將其指定爲僅Debug環境下生效。swift
咱們須要在項目中的Build Phases
,添加一個Run Script Phase
。若是是經過homebrew安裝的,你的腳本應該是這樣的。xcode
if which swiftlint >/dev/null; then
swiftlint
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
複製代碼
若是是經過cocoapods安裝的,你得腳本應該是這樣的:安全
"${PODS_ROOT}/SwiftLint/swiftlint"
複製代碼
鍵入CMD + B
編譯項目,在編譯完後會運行咱們剛纔加入的腳本,以後咱們就能看到項目中大片的警告信息。有時候build信息並不能填入項目代碼中,咱們能夠在編譯的log日誌裏查看。ruby
SwiftLint規則太多了,若是咱們不想執行某一規則,或者想要濾掉對Pods庫的分析,咱們能夠對SwfitLint進行配置。
在項目根目錄新建一個.swiftlint.yml
文件,而後填入以下內容:
disabled_rules: # rule identifiers to exclude from running - colon - trailing_whitespace - vertical_whitespace - function_body_length opt_in_rules: # some rules are only opt-in - empty_count # Find all the available rules by running: # swiftlint rules included: # paths to include during linting. `--path` is ignored if present. - Source excluded: # paths to ignore during linting. Takes precedence over `included`. - Carthage - Pods - Source/ExcludedFolder - Source/ExcludedFile.swift - Source/*/ExcludedFile.swift # Exclude files with a wildcard analyzer_rules: # Rules run by `swiftlint analyze` (experimental) - explicit_self # configurable rules can be customized from this configuration file # binary rules can set their severity level force_cast: warning # implicitly force_try: severity: warning # explicitly # rules that have both warning and error levels, can set just the warning level # implicitly line_length: 110 # they can set both implicitly with an array type_body_length: - 300 # warning - 400 # error # or they can set both explicitly file_length: warning: 500 error: 1200 # naming rules can set warnings/errors for min_length and max_length # additionally they can set excluded names type_name: min_length: 4 # only warning max_length: # warning and error warning: 40 error: 50 excluded: iPhone # excluded via string allowed_symbols: ["_"] # these are allowed in type names identifier_name: min_length: # only min_length error: 4 # only error excluded: # excluded via string array - id - URL - GlobalAPIKey reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) 複製代碼
一條rules提示以下,其對應的rules名就是function_body_length
。
! Function Body Length Violation: Function body should span 40 lines or less excluding comments and whitespace: currently spans 43 lines (function_body_length)
複製代碼
disabled_rules
下填入咱們不想遵循的規則。
excluded
設置咱們想跳過檢查的目錄,Carthage、Pod、SubModule這些通常能夠過濾掉。
其餘的一些像是文件長度(file_length),類型名長度(type_name),咱們能夠經過設置具體的數值來調節。
另外SwiftLint也支持自定義規則,咱們能夠根據本身的需求,定義本身的rule
。
若是咱們想將這次分析生成一份報告,也是能夠的(該命令是經過homebrew安裝的swiftlint):
# reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) $ swiftlint lint --reporter html > swiftlint.html 複製代碼
xcodebuild是xcode內置的編譯命令,咱們能夠用它來編譯打包咱們的iOS項目,接下來介紹的Infer和OCLint都是基於xcodebuild的編譯產物進行分析的,因此有必要簡單介紹一下它。
通常編譯一個項目,咱們須要指定項目名,configuration,scheme,sdk等信息如下是幾個簡單的命令及說明。
# 不帶pod的項目,target名爲TargetName,在Debug下,指定模擬器sdk環境進行編譯 xcodebuild -target TargetName -configuration Debug -sdk iphonesimulator # 帶pod的項目,workspace名爲TargetName.xcworkspace,在Release下,scheme爲TargetName,指定真機環境進行編譯。不指定模擬器環境會驗證證書 xcodebuild -workspace WorkspaceName.xcworkspace -scheme SchemeName Release # 清楚項目的編譯產物 xcodebuild -workspace WorkspaceName.xcworkspace -scheme SchemeName Release clean 複製代碼
以後對xcodebuild命令的使用都須要將這些參數替換爲本身項目的參數。
暫不支持自定義規則。
$ brew install infer
複製代碼
運行infer
$ cd projectDir # 跳過對Pods的分析 $ infer run --skip-analysis-in-path Pods -- xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator 複製代碼
咱們會獲得一個infer-out
的文件夾,裏面是各類代碼分析的文件,有txt,json等文件格式,當這樣不方便查看,咱們能夠將其轉成html格式:
$ infer explore --html
複製代碼
點擊trace,咱們會看到該問題代碼的上下文。
由於Infer默認是增量編譯,只會分析變更的代碼,若是咱們想總體編譯的話,須要clean一下項目:
$ xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator clean 複製代碼
再次運行Infer去編譯。
$ infer run --skip-analysis-in-path Pods -- xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator 複製代碼
Infer的靜態分析主要分兩個階段:
一、捕獲階段
Infer 捕獲編譯命令,將文件翻譯成 Infer 內部的中間語言。
這種翻譯和編譯相似,Infer 從編譯過程獲取信息,並進行翻譯。這就是咱們調用 Infer 時帶上一個編譯命令的緣由了,好比: infer -- clang -c file.c
, infer -- javac File.java
。結果就是文件照常編譯,同時被 Infer 翻譯成中間語言,留做第二階段處理。特別注意的就是,若是沒有文件被編譯,那麼也沒有任何文件會被分析。
Infer 把中間文件存儲在結果文件夾中,通常來講,這個文件夾會在運行 infer
的目錄下建立,命名是 infer-out/
。
二、分析階段
在分析階段,Infer 分析 infer-out/
下的全部文件。分析時,會單獨分析每一個方法和函數。
在分析一個函數的時候,若是發現錯誤,將會中止分析,但這不影響其餘函數的繼續分析。
因此你在檢查問題的時候,修復輸出的錯誤以後,須要繼續運行 Infer 進行檢查,知道確認全部問題都已經修復。
錯誤除了會顯示在標準輸出以外,還會輸出到文件 infer-out/bug.txt
中,咱們過濾這些問題,僅顯示最有可能存在的。
在結果文件夾中(infer-out
),同時還有一個 csv 文件 report.csv
,這裏包含了全部 Infer 產生的信息,包括:錯誤,警告和信息。
OCLint是基於Clange Tooling編寫的庫,它支持擴展,檢測的範圍比Infer要大。不光是隱藏bug,一些代碼規範性的問題,例如命名和函數複雜度也均在檢測範圍以內。
OCLint通常經過Homebrew安裝
$ brew tap oclint/formulae
$ brew install oclint
複製代碼
經過Hombrew安裝的版本爲0.13。
$ oclint --version
LLVM (http://llvm.org/):
LLVM version 5.0.0svn-r313528
Optimized build.
Default target: x86_64-apple-darwin19.0.0
Host CPU: skylake
OCLint (http://oclint.org/):
OCLint version 0.13.
Built Sep 18 2017 (08:58:40).
複製代碼
我分別用Xcode11在兩個項目上運行過OCLint,一個實例項目能夠正常運行,另外一個複雜的項目卻運行失敗,報以下錯誤:
1 error generated
1 error generated
...
oclint: error: cannot open report output file ..../onlintReport.html
複製代碼
我並不清楚緣由,若是你想試試0.13可否使用的話,直接跳到安裝xcpretty。若是你也遇到了這個問題,能夠回來安裝oclint0.15版本。
我在oclint issuse #547這裏找到了這個問題和對應的解決方案。
咱們須要更新oclint至0.15版本。brew上的最新版本是0.13,github上的最新版本是0.15。我下載github上的release0.15版本,可是這個包並非編譯過的,不清楚是否是官方本身搞錯了,只能手動編譯了。由於編譯要下載llvm和clange,這兩個包較大,因此我將編譯事後的包直接傳到了這裏CodeChecker。
若是不關心編譯過程,能夠下載編譯好的包,跳到設置環境變量那一步。
編譯OCLint
$ brew install cmake ninja
複製代碼
二、clone OCLint項目
$ git clone https://github.com/oclint/oclint 複製代碼
三、進入oclint-scripts目錄,執行make命令
$ ./make
複製代碼
成功以後會出現build文件夾,裏面有個oclint-release就是編譯成功的oclint工具。
設置oclint工具的環境變量
設置環境變量的目的是爲了咱們可以快捷訪問。而後咱們須要配置PATH環境變量,注意OCLint_PATH的路徑爲你存放oclint-release的路徑。將其添加到.zshrc
,或者.bash_profile
文件末尾:
OCLint_PATH=/Users/zhangferry/oclint/build/oclint-release export PATH=$OCLint_PATH/bin:$PATH 複製代碼
執行source .zshrc
,刷新環境變量,而後驗證oclint是否安裝成功:
$ oclint --version
OCLint (http://oclint.org/):
OCLint version 0.15.
Built May 19 2020 (11:48:49).
複製代碼
出現這個介紹就說明咱們已經完成了安裝。
xcpretty是一個格式化xcodebuild輸出內容的腳本工具,oclint的解析依賴於它的輸出。它的安裝方式爲:
$ gem install xcpretty 複製代碼
在使用OCLint以前還須要一些準備工做,須要將編譯項COMPILER_INDEX_STORE_ENABLE
設置爲NO。
COMPILER_INDEX_STORE_ENABLE
設置爲 NOpost_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO" end end end 複製代碼
一、進入項目根目錄,運行以下腳本:
$ xcodebuild -workspace ProjectName.xcworkspace -scheme ProjectScheme -configuration Debug -sdk iphonesimulator | xcpretty -r json-compilation-database -o compile_commands.json 複製代碼
會將xcodebuild編譯過程當中的一些信息記錄成一個文件compile_commands.json
,若是咱們在項目根目錄看到了該文件,且裏面是有內容的,證實咱們完成了第一步。
二、咱們將這個json文件轉成方便查看的html,過濾掉對Pods文件的分析,爲了防止行數上限,咱們加上行數的限制:
$ oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html -rc LONG_LINE=9999 -max-priority-1=9999 -max-priority-2=9999 -max-priority-3=9999 複製代碼
最終會產生一個oclintReport.html
文件。
OCLint支持自定義規則,由於其自己規則已經很豐富了,自定義規則的需求應該很小,也就沒有嘗試。
封裝腳本
OCLint跟Infer同樣都是經過運行幾個腳本語言進行執行的,咱們能夠將這幾個命令封裝成一個腳本文件,以OCLint爲例,Infer也相似:
#!/bin/bash # mark sure you had install the oclint and xcpretty # You need to replace these values with your own project configuration workspace_name="WorkSpaceName.xcworkspace" scheme_name="SchemeName" # remove history rm compile_commands.json rm oclint_result.xml # clean project # -sdk iphonesimulator means run simulator xcodebuild -workspace $workspace_name -scheme $scheme_name -configuration Debug -sdk iphonesimulator clean || (echo "command failed"; exit 1); # export compile_commands.json xcodebuild -workspace $workspace_name -scheme $scheme_name -configuration Debug -sdk iphonesimulator \ | xcpretty -r json-compilation-database -o compile_commands.json \ || (echo "command failed"; exit 1); # export report html # you can run `oclint -help` to see all USAGE oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html \ -disable-rule ShortVariableName \ -rc LONG_LINE=1000 \ || (echo "command failed"; exit 1); open -a "/Applications/Safari.app" oclintReport.html 複製代碼
oclint-json-compilation-database
命令的幾個參數說明:
-e
須要忽略分析的文件,這些文件的警告不會出如今報告中
-rc
須要覆蓋的規則的閥值,這裏能夠自定義項目的閥值,默認閥值
-enable-rule
支持的規則,默認是oclint提供的都支持,能夠組合-disable-rule來過濾掉一些規則 規則列表
-disable-rule
須要忽略的規則,根據項目需求設置
由於OCLint提供了xcode格式的輸出樣式,因此咱們能夠將它做爲一個腳本放在Xcode中。
一、在項目的 TARGETS 下面,點擊下方的 "+" ,選擇 cross-platform 下面的 Aggregate。輸入名字,這裏命名爲 OCLint
二、選中該Target,進入Build Phases,添加Run Script,寫入下面腳本:
# Type a script or drag a script file from your workspace to insert its path. # 內置變量 cd ${SRCROOT} xcodebuild clean xcodebuild | xcpretty -r json-compilation-database oclint-json-compilation-database -e Pods -- -report-type xcode 複製代碼
能夠看出該腳本跟上面的腳本同樣,只不過 將oclint-json-compilation-database
命令的-report-type
由html
改成了xcode
。而OCLint做爲一個target自己就運行在特定的環境下,因此xcodebuild能夠省去配置參數。
三、經過CMD + B
咱們編譯一下項目,執行腳本任務,會獲得可以定位到代碼的warning信息:
如下是對這幾種靜態分析方案的對比,咱們能夠根據需求選擇適合本身的靜態分析方案。
SwiftLint | Infer | OCLint | |
---|---|---|---|
支持語言 | Swift | C、C++、OC、Java | C、C++、OC |
易用性 | 簡單 | 較簡單 | 較簡單 |
可否集成進Xcode | 能夠 | 不能集成進xcode | 能夠 |
自帶規則豐富度 | 較多,包含代碼規範 | 相對較少,主要檢測潛在問題 | 較多,包含代碼規範 |
規則擴展性 | 能夠 | 不能夠 | 能夠 |