GoogleTestとSanitizerを組み合わせて動的解析

Calendar for ソフトウェアテストの小ネタ | Advent Calendar 2022 - Qiita」の記事です。

C++のメジャーなテスティングフレームワークGoogleTestは、gccやclangに組み込まれたSanitizerと連動することで、不正なメモリ操作や不適切なスレッド間データ共有、リスクある未規定処理の実行などの異常を、テスト上で検出できるようになります。今回はそのGoogleTestとSanitizerの連携で、コードレベルの動的解析の環境を構築する例を解説します。

Sanitizerとの連携

GoogleTestのテストコード上で以下の関数を定義することで、Sanitizerがエラーを検出したときの処理をHookできるようになります。

  • address sanitizer :void __asan_on_error()
    • 不正なメモリ操作を検出
  • behavior sanitizer : void __ubsan_on_report()
    • 致命的なエラーや例外を発生させる不正な動作を検出
  • thread sanitizer : void __tsan_on_report()
    • スレッド間の不適切なデータ共有を検出

例えばGoogleTestのテスト実行中、Address Sanitizerが異常なメモリ操作を検出した際に、特定の文字列を出力するとともにテストをFailさせたいならば、以下をテストコード上で定義します。

extern "C" {
    void __asan_on_error() {
      FAIL() << "address sanitizer error!!!!";
    }
} // extern "C"

注意点として、Sanitizerが異常を検出したタイミングでテストコードは強制終了されます。その際、--gtest_outputによる結果ファイル出力は動作しません。そのため、CIなどで自動実行する際は、XMLなどの出力ファイルでなく、コンソールログ解析でテスト結果を判定する必要があります。

実装例

以下のテスト対象をテストするとします。範囲外への不正なメモリアクセスを行っています。

int target() {
    int a[5];
    return a[5];
}

通常のユニットテストでは、上記を実行した時のふるまいは不定です。ただ特に何もなくテストが終了する場合もあります。
ここで上記のような不正なメモリアクセスをSanitizerで見逃しなく検出させる場合、テストコードを次のように記述します。

//test_hoge.cpp
#include <gtest/gtest.h>

...

extern "C" {
    void __asan_on_error() {
        FAIL() << "Encountered an address sanitizer error";
    }
}  // extern "C"

TEST(TestCase, dummy) {
    EXPECT_EQ(0, target());//サンプル例示のための適当な確認
}

このテストコードをビルドする際に、Address Sanitizerを有効化します。例えば次のようなオプションで記述します。

g++ test_hoge.cpp -lgtest_main -lgtest -lpthread -fsanitize=address -o test_hoge

これを実行すると、a[5]にアクセスしたタイミングで、「Encountered an address sanitizer error」が出力され、テストがFailします。具体的には以下のようなメッセージが出力されます。

[==========] Running 1 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 tests from TestCase
[ RUN      ] TestCase.dummy
=================================================================
test_hoge.cpp:8: Failure
Failed
Encountered an address sanitizer error
==468==ERROR: AddressSanitizer: stack-buffer-overflow on address
... 以下、不正メモリアクセスを検出した旨のSanitizerの出力メッセージ ...

上記のようなアプローチで、Sanitizerの機能を使った動的解析を、GoogleTestのテストコードとして記述できるようになります。