Arduinoをclangの静的解析に掛けたい
Arduinoはgccでビルドされるのが前提なため、gccでビルドするしか無い。しかし、できればclangの静的解析を活用したい。double-freeのようなアホを事前に抽出できる意義はそれなりに有るのではないかと感じている。
手動で試す
前回の 手動Arduino で、Arduinoスケッチをビルドするのに必要なコマンドラインはライブラリ類を含めて既知になったので、
- 一旦 gcc でスケッチをプリプロセスする
- プリプロセスして出てきたソースコードをclangに掛ける
の手順でできるかどうか試してみた。
#include <Arduino.h>
#include "FreeRTOS.h"
/* OpenOCD stub */
const volatile UBaseType_t uxTopUsedPriority = configMAX_PRIORITIES - 1U;
void setup() {
(void) uxTopUsedPriority;
pinMode(LED_BUILTIN, OUTPUT);
}
void act(void* p){
free(p);
}
void loop() {
void* p;
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
p = malloc(12);
act(p);
free(p);
}
- 静的解析 (
--analyze
オプションを渡す)
clang -target arm-none-eabihf --analyze out.cpp
../blink.cpp:24:3: warning: Attempt to free released memory [unix.Malloc]
free(p);
^~~~~~~
1 warning generated.
静的解析は正しくdouble-freeを検出できている。
- 警告 (
-S
オプションを渡す)
clang -Wall -Wextra -target arm-none-eabihf -S out.cpp
In file included from ../blink.cpp:1:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/Arduino.h:41:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/WVariant.h:23:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk/nrf.h
:164:
../Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk/compiler_abstraction.h:154:
16: warning:
variable 'sp' is uninitialized when used here [-Wuninitialized]
return sp;
^~
../Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk/compiler_abstraction.h:153:
29: note:
initialize the variable 'sp' to silence this warning
register unsigned sp __asm("sp");
^
= 0
In file included from ../blink.cpp:1:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/Arduino.h:58:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/HardwarePWM.h:39:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/common_inc.h:47:
../Adafruit_nRF52_Arduino/cores/nRF5/verify.h:64:18: warning: format string is
not a string literal (potentially insecure) [-Wformat-security]
::printf(_fstr(_status));
^~~~~~~~~~~~~~
../Adafruit_nRF52_Arduino/cores/nRF5/verify.h:64:18: note: treat the string as
an argument to avoid this
::printf(_fstr(_status));
^
"%s",
2 warnings generated.
-Wuninitialized
警告は謎だな。。
#define GET_SP() gcc_current_sp()
static inline unsigned int gcc_current_sp(void)
{
register unsigned sp __ASM("sp");
return sp;
}
これでuninitializedになっちゃうのはちょっと不味くない。。?
gccとclangでasmの解釈が違う
試しに:
int another(int a, int b);
static inline unsigned int gcc_current_sp(void) {
register unsigned sp __asm("sp");
return sp;
}
int
work(void){
return another(gcc_current_sp(), gcc_current_sp());
}
を -O3
でビルドしてみた。
- gcc
work:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
mov r1, sp
mov r0, sp
b another
- clang
work:
.fnstart
b another
.Lfunc_end0:
.size work, .Lfunc_end0-work
.cantunwind
.fnend
clangの出力には mov
命令が無い。つまり、 asm
文によるレジスタアクセスができていないことがわかる。... まぁすげぇそもそも論として、このような方法でレジスタをフェッチすること自体に移植性が無いので仕方ない気もするが。。
clangのドキュメント によると:
clang only supports global register variables when the register specified is non-allocatable (e.g. the stack pointer).
で、これはgccで言うところの Local register variable は含んでない。ということかな。。
作戦を考える
とりあえず↑のような問題はsuppression file的な奴を用意できれば解決するとして、それを適用するためには機械可読な出力がツールチェインから出てきて欲しい。。。が clangの SARIF サポートは開発中 で今のところStaticAnalyzerしかサポートしていない。
まず前処理段階が本当にできるのかを確認する必要があるな。つまり、
- ビルド時のコマンドラインの収集 -- これは 前作った奴 がある。
- 収集したコマンドラインデータをCMakeから読める形に整形する -- まぁ単純な.tsvとかテキストファイルにすれば十分だろう
- (コマンドラインに現われるパスを正規化する -- 今回は実装しない)
- (正規化されたコマンドラインを元にコンパイラ起動を一意に識別するhashを用意する -- 今回は実装しないで単純にコマンドラインを連結してソート、連番を振る)
- コマンドラインを書き換えて gccで プリプロセスする。出力ファイル名としてはhashを使う。
- プリプロセス結果を clangで 静的解析する
正規化はちょっと良いアイデアがない。。ビルドディレクトリとかはCIの都合で固定できない可能性があるから正規化を前提にした方が良いと思うんだけど。。