- [Clang-Tidyとは]
- [Clang-TidyをmacOSにインストールする]
- [Clang-Tidyを使ってみる]
- [Clang単体でも同じことができる]
- [gccで警告を表示する]
- [まとめ]
[Clang-Tidyとは]
Clang-Tidyは、C/C++向けのリンターである。JavaでいうError Proneのようなものらしい。
最近、毎日少しずつGoogleの『Building Secure and Reliable Systems』(通称SRS本)を読み進めている。セキュアなシステムを作るためのテストという文脈で、Chapter 13: Testing Code
において、Unit Testing、Integration Testing、Fuzz Testingに続いて、Static Program Analysisつまり静的解析が取り上げられていた。
その静的解析の例として、C言語のsizeof演算子を利用する際のコード例とともに、clang-tidyによる静的解析結果が掲載されていたので、これを写経してclang-tidyで実際にリントを実行してみることにした。
なお、手元の環境は以下の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.6 BuildVersion: 19G73
[Clang-TidyをmacOSにインストールする]
サクッとインストールしてみるか、とbrewコマンドを実行したところ、エラー終了してしまった。brewではインストールできないらしい。
$ brew install clang-tidy (中略) Error: No available formula with the name "clang-tidy" ==> Searching for a previously deleted formula (in the last month)... Warning: homebrew/core is shallow clone. To get complete history run: git -C "$(brew --repo homebrew/core)" fetch --unshallow Error: No previously deleted formula found. ==> Searching for similarly named formulae... Error: No similarly named formulae found. ==> Searching taps... ==> Searching taps on GitHub... Error: No formulae found in taps.
仕方がないのでGoogle先生に尋ねると、例の如くStackoverflowにたどり着いた。
stackoverflow.comclang-tidy自体はbrewには用意されていないので、clang-tidyが付属しているllvmをインストールしてパスを通せ、ということらしい。
brew install llvmのコマンドを気楽に実行したら、ずいぶん時間がかかった。Pythonだのopensslだの、依存するインストール済みソフトウェアのバージョンが古かったらしく、ことごとくバージョンアップが必要だったのが原因のようだ。もっとも、llvm自体のインストールにもかなり時間がかかったが……。
$ brew install llvm (略) ==> Installing llvm ==> cmake -G Unix Makefiles .. -DLLVM_ENABLE_PROJECTS=clang;clang-tools-extra;ll ==> make ==> make install ==> make install-xcode-toolchain ==> Caveats To use the bundled libc++ please add the following LDFLAGS: LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib" llvm is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in parallel can cause all kinds of trouble. If you need to have llvm first in your PATH run: echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> /Users/akito/.bash_profile For compilers to find llvm you may need to set: export LDFLAGS="-L/usr/local/opt/llvm/lib" export CPPFLAGS="-I/usr/local/opt/llvm/include" ==> Summary 🍺 /usr/local/Cellar/llvm/10.0.0_3: 7,055 files, 1GB, built in 198 minutes 24 seconds
(略)
ログ上はllvmのインストールだけで198分かかったらしい。他にも色々更新が入ったりして、実際にはもっと時間がかかっていたわけだが……。
目指すclang-tidyについては、このあたりでclang-tools-extraとして各種clang-xxx系のツールがインストールされ、その一つにclang-tidyが含まれているらしい。
Stackoverflowの指示にしたがって、以下のコマンドでシンボリックリンクを作成して、clang-tidyのインストールは完了である。
$ ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" $ ls /usr/local/bin | grep clang clang-format clang-tidy git-clang-format
[Clang-Tidyを使ってみる]
インストールできたclang-tidyのバージョンは次の通り。
$ clang-tidy --version LLVM (http://llvm.org/): LLVM version 10.0.0 Optimized build. Default target: x86_64-apple-darwin19.6.0 Host CPU: skylake
早速使ってみる。テスト対象となる問題のあるコードは以下の通り(出典:Building Secure and Reliable Systems, p.293)。
sizeof.c ------------- #include <string.h> const char* kMessage = "Hello World!"; int main() { char buf[128]; memcpy(buf, kMessage, sizeof(kMessage)); return 0; }
本来はsizeofで文字数をカウントするつもりが、ポインタのサイズをカウントしてしまう、というC/C++ではありがちなミスらしい。
このファイルに対してclang-tidyを実行すると、たしかにSRS本に掲載されているとおり警告が表示された。よし。
$ clang-tidy sizeof.c (略) Running without flags. 1 warning generated. sizeof.c:5:31: warning: 'memcpy' call operates on objects of type 'const char' while the size is based on a different type 'const char *' [clang-diagnostic-sizeof-pointer-memaccess] memcpy(buf, kMessage, sizeof(kMessage)); ^ sizeof.c:5:31: note: did you mean to provide an explicit length?
[Clang単体でも同じことができる]
メモリ確保に失敗した状態でbufを出力した結果が気になったので、以下のようにコードを書き換えた。
#include <stdio.h> #include <string.h> const char* kMessage = "Hello World!"; int main() { char buf[128]; memcpy(buf, kMessage, sizeof(kMessage)); puts(buf); return 0; }
上記のソースコードをコンパイルしたバイナリを実行しようとしたところ、いくつかのことに気がついた。
macOSの場合、Xcodeに付属するclangを利用してC/C++のソースコードをコンパイルすることができる。
$ clang -v Apple clang version 11.0.3 (clang-1103.0.32.62) Target: x86_64-apple-darwin19.6.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
このclang
でコンパイルしたときにも、似たような警告が表示された*1。
$ clang sizeof.c sizeof.c:7:31: warning: 'memcpy' call operates on objects of type 'const char' while the size is based on a different type 'const char *' [-Wsizeof-pointer-memaccess] memcpy(buf, kMessage, sizeof(kMessage)); ~~~~~~~~ ^~~~~~~~ sizeof.c:7:31: note: did you mean to provide an explicit length? memcpy(buf, kMessage, sizeof(kMessage)); ^~~~~~~~ 1 warning generated.
clangでも目的としていた警告が表示できるということは、数時間かけてLLVMをインストールした時間は一体なんだったのか......。
もっとも、clangとclang-tidyの大きな違いとして、バイナリを生成するかどうか、という違いがある。バイナリは不要で、静的解析の結果だけが必要な場合にはclang-tidyを利用すると良いのかもしれない。
ちなみに、生成されたバイナリを実行してみると、以下のようになった。期待した文字列が出力されていないし、出力結果が実行する度に変わるので、バグっているな、と分かる。
$ ./a.out Hello Wod?? $ ./a.out Hello Wod"?
[gccで警告を表示する]
ちなみに、C/C++のコンパイラといえばgccが有名だ。VS Codeの拡張機能Remote - Containersを利用して、gccをインストールしてみた。
Remote - Containersで利用したDockerfileは次のとおり。
FROM ubuntu:latest RUN apt update RUN apt install -y gcc
gccのバージョン情報は次の通り。
$ gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu (略) Thread model: posix gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
普通にコンパイルしようとすると、何も警告が表示されずに終わってしまう。
$ gcc sizeof.c $ ls | grep out a.out
gccのヘルプをみると、warningsというコマンドがあることがわかった。
$ gcc --help Usage: gcc [options] file... Options: -pass-exit-codes Exit with highest error code from a phase. --help Display this information. --target-help Display target specific command line options. --help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...]. Display specific types of command line options. (略)
ということでwarningsコマンドのヘルプを参照する。
$ gcc --help=warnings The following options control compiler warning messages: --all-warnings Same as -Wall. Use the latter option instead. (略) -Wall Enable most warning messages. (略)
細かく警告すべき内容を指定できるオプションが大量に表示されたが、とりあえず全部の警告を利用すれば良さそうだったので、-Wall
を追加してgccでコンパイルしてみた。
$ gcc -Wall sizeof.c sizeof.c: In function 'main': sizeof.c:8:30: warning: argument to 'sizeof' in 'memcpy' call is the same expression as the source; did you mean to provide an explicit length? [-Wsizeof-pointer-memaccess] memcpy(buf, kMessage, sizeof(kMessage)); ^
こちらの場合も、clangでコンパイルした場合と同様、警告を表示することができた。コンパイル時のオプションを追加する必要があるので、やや面倒か。
個別に警告を出す対象の問題を指定したい場合は、今回であればgcc -Wsizeof-pointer-memaccess sizeof.c
のようにコマンドを実行すればよい。ただし、他に問題がある場合に見逃してしまうので、やはり-Wall
を利用するのが無難だろうか。
[まとめ]
- C++用のClang-Tidyというリンターを利用してみた
- macOSにLLVMをbrewでインストールするとものすごく時間がかかった
- ClangであればLLVMに付属するClang-Tidyを使わなくても、clang-tidyと同様の結果を得られた(ただしバイナリが生成される)
- gccでも
-Wall
オプションを追加するとclang-tidyと同様の結果を得られた(こちらも当然バイナリが生成される) - 今回利用したソースコードは以下のリポジトリにまとめてある