この記事は「C++のカレンダー | Advent Calendar 2021 - Qiita」の9日目です。
Infer とは
Infer は Facebook 社改め Meta 社が作った静的解析ツールです。
今回は C++ Advent Calendar ということで C++ の静的解析ツールとして紹介しますが、Infer 自体は OCaml で書かれています。
また、C++ の他に Java/C/Objective-C の解析も可能です。
C++ の静的解析ツールというと Cppcheck や Coverity 、コンパイラ付属のものがあったりします。筆者はこれまでこれらのツールを使ってきました。
Infer は 2015 年にOSS化して公開されたのですが、ずっと使ってみたいと思いつつ機会がありませんでした。
今回こうして記事にしているのは、仕事で必要になったからとか趣味でやる気になったからではなく、社名が変わったからネタとして触ることにしました。
理由はともあれ、以下で紹介していきます!
ローカル環境で使う場合
本記事は表題どおり「GitHub Actions」で Infer を使う手順を紹介しますが、ローカル環境で実行する場合も軽く触れておきます。
まず大事なこととして、Windows 非対応です!
Mac の場合は brew install infer でインストールできます。Linux の場合はビルド済みバイナリをダウンロードするか、ソースコードビルドで使うことができます。Docker という手もあります。
詳しいことは公式の「Getting Started with Infer」を見てください。
セットアップできたら「infer run -- ビルドコマンド」のようにコマンド実行すると以下のように結果が出ます。
ビルドコマンドは対象によって変わると思いますが、コンパイラー以外にも cmake や make 、mvn などのビルドツールにも対応しています。
ビルドコマンドが bash script になっているような場合はキャプチャーするコマンドを認識できないので、--force-integration オプションで教えてあげてください。
対応している integration は --force-integration オプションのヘルプで確認できます。
e.g. infer run --force-integration clang -- build.sh
GitHub Actions で使う
では、ここから本題です。
GitHub Actions で Infer を使うために、Setup Infer action (srz-zumix/setup-infer) と Infer reviewdog action (srz-zumix/reviewdog-action-infer) を作成しました。
setup-infer
Infer を現在の環境にセットアップするアクションです。
当然ですが Windows は非対応です。また Mac の場合はバージョン指定はできず、 brew install で取得できるバージョンがインストールされます。
(要望あれば対応しようかと思いますので、issues へどうぞ)
reviewdog-action-infer
Infer の結果を reviewdog を使って PR にコメントするアクションです。
Infer の実行結果が入っている infer-out から result.txt を参照し、reviewdog を使って PR に問題をコメントします。
なぜ action を分けたのか
最初は Infer インストールや解析込みの action を書こうと思ったのですが、ビルドツールや環境はユーザーによってバラバラで、action 側ですべてに対応するのは難しいと考えてこのようになりました。
Infer の実行
上記2つの action は infer での解析処理をしませんので、ワークフローで適宜呼び出してください。
個人開発してるテストフレームワーク(iutest)の例を記載しておきます。
infer:
runs-on: ubuntu-latest
needs: prepare
steps:
- uses: actions/checkout@v2
- uses: srz-zumix/setup-infer@v1
- name: infer
run: |
infer -- make -C test IUTEST_USE_PYTHON=0
- name: Check Infer report
uses: srz-zumix/reviewdog-action-infer@v1
with:
reporter: github-pr-review
結果
iutest のワークフローの結果はこちらです。
静的解析なしだと 10 分くらいのテストが、Infer ありだと 25 分弱と少し時間がかかってますが、まぁ趣味開発なので許容範囲です。
速度向上
「Recommended flow for CI | Infer」こちらで CI 向けのおすすめ実行方法が書いてあります。うまくキャッシュできるのであれば --reactive オプションでキャプチャーした情報を再利用すれば早くなると思います。
reportdiff は reviewdog で同等のことができるのでどちらでも。
また --changed-files-index オプションで差分ファイルのリストを指定すれば、そのファイルの解析だけが行われるため、速度向上可能です。
ただし、C++ の場合はヘッダーファイルを指定しても解析されません。.cpp ファイルを指定しないとダメです。(※ --changed-files-index は analyze 時に有効なので capture 時間の短縮にはならない)
Infer capture/analyze TIPS
capture/analyze 中にクラッシュする場合
--skip-analysis-in-path で除外指定できるのでそれをおすすめします。
-g オプションでデバッグモードになるので原因調査ができるかもしれませんが、ストレージを圧迫するかもしれません。
(3,000 弱のソースコードファイル数からなるプロジェクトでやったら 600GB 越えたあたりでディスクフルになって PC 自体が落ちました。。)
解析途中でエラーが出る場合
--keep-going オプションを付けると続行可能です。
compile_commands.json の場合 --clang-blacklisted-flags/--clang-blacklisted-flags-with-arg は効かない
そのまんまです。特に xcodebuild の場合 infer が使う clang が知らないオプションを使ってることがあり、「error: unknown argument: '-index-store-path'」のように失敗していまいます。 infer はデフォルトで --clang-blacklisted-flags-with-arg に -index-store-path が入ってますが、 compile_commands.json から capture をする場合は --clang-blacklisted-flags(-with-arg) オプションは考慮されないので、json ファイルの command から削除してください。
--continue-analyz
仕事のプロジェクトに Infer 掛けてみたら analyze でクラッシュするわ、エラー起こるわ、なんか応答なくなるわ、そもそも解析時間がクソ長いわ、で辛かったんですが、--continue-analyz オプションつけると続きから解析してくれるので作業が無駄になりません。
(最初に知りたかった)
.inferconfig にオプションを書ける
infer のコマンドラインオプションは .inferconfig ファイルに書いておくことができます。
ファイルの中身は JSON 形式でオプションから -- を除いた名前をキーに設定をします。
例えば、上記で紹介した --skip-analysis-in-path や、 infer の clang にインクルードパスを追加する場合は以下のように書けます。
{
"skip-analysis-in-path": [
"path/to/lib/xxx/src",
],
"Xclang": [
"-I /path/to/dir/include",
]
}
まとめ
静的解析ツールは1つかければ十分というものではないので、ぜひ試してみてください。
以上。