開発・デバッグ・テスト

静的解析

コードを実行せず不具合を検出する解析。

概要

静的解析(Static Analysis)とは、プログラムを実際に実行することなく、ソースコードやバイナリコードを解析してバグ・セキュリティ脆弱性・コーディング規約違反・未定義動作などの問題を検出する手法です。「静的コード解析」「静的検証」とも呼ばれます。

組み込み開発では、動的テスト(実行ベース)では発見しにくいメモリリーク・バッファオーバーフロー・整数オーバーフロー・ヌルポインタ参照・MISRA C規約違反などを、コンパイル前またはCI/CDパイプラインの中で早期に発見できます。

MISRA C(Motor Industry Software Reliability Association)は自動車・航空宇宙向けの安全なCコーディング標準で、静的解析ツールによるMISRA C準拠チェックが安全クリティカルな組み込み分野で義務化されています。

歴史・背景

1978年:Stephen Johnsonがlint(UNIXのCコード解析ツール)を開発。Cのソースコードの疑わしい構文をチェックする最初の静的解析ツールです。

1980〜90年代:PC-lintなどの商用静的解析ツールが登場。組み込み・自動車分野での必要性が認識されました。1998年にMISRA C 1st Editionが発行され、MISRA準拠チェックが自動車産業で標準化し始めました。

2000年代:Coverity(現Synopsys)が2002年に創業し、高度な検出アルゴリズム(パス感度解析等)を持つ商用ツールを提供。NASA・Boeing等の航空宇宙企業で採用されました。また2004年にMISRA C:2004、2012年にMISRA C:2012が発行されました。

2010年代:LLVM/Clangに内蔵の静的解析機能が充実し、オープンソースで高品質な解析が可能になりました。Cppcheck(2007〜)もオープンソースとして広く使われるようになりました。GitHub・GitLab等のCI/CDプラットフォームとの統合が容易になり、プッシュのたびに静的解析が自動実行される環境が普及しました。

2020年代:ソフトウェア・サプライチェーンセキュリティの観点からSAST(Static Application Security Testing)が重要視されるようになり、組み込みファームウェアへの適用も増加しています。

技術仕様

主要な静的解析ツール

ツール種別特徴MISRA対応
PC-lint Plus商用業界標準、高検出率、MISRA C完全対応完全対応
IAR C-STAT商用IAR EWに統合、MISRA/CWE対応完全対応
Polyspace(MathWorks)商用カラー証明(グリーン/レッド/グレー)完全対応
Coverity(Synopsys)商用大規模コードベース向け、深い解析対応
CppcheckOSS無料、誤検知が少ない部分対応
Clang Static AnalyzerOSSClangに内蔵、ビルドと統合部分対応
SonarQubeOSS/商用CI/CD統合が容易、ダッシュボード部分対応
flawfinderOSSセキュリティ脆弱性に特化なし

検出できる問題の種類

問題カテゴリ
ヌルポインタ参照malloc()の戻り値チェックなしでの使用
バッファオーバーフロー配列境界外アクセス
整数オーバーフローuint8_tに256以上を代入
未初期化変数使用初期化前の変数読み取り
メモリリークmalloc()後のfree()忘れ
ゼロ除算ゼロ確認なしでの除算
デッドコード到達不能なコード
MISRA C違反暗黙の型変換、再帰、動的メモリ等
セキュリティgets()strcpy()等の危険な関数使用
並行処理問題データ競合、ロック漏れ

MISRA C:2012 主要ルール(一部)

ルール内容種別
Rule 1.3未定義・未規定動作の禁止Required
Rule 11.3ポインタ型変換の制限Required
Rule 14.2forループの標準構造Required
Rule 15.5returnは関数末尾に1つAdvisory
Rule 17.2再帰の禁止Required
Rule 18.6ポインタへの自動変数のアドレス格納禁止Required
Rule 21.3malloc/freeの使用禁止Required
Directive 4.1ランタイムエラーゼロの要求Required

動作原理

静的解析の主な技術

1. パターンマッチング(ルールベース) コードパターンを検索して既知の問題を検出します:

// 危険なパターンの検出例
char buf[10];
strcpy(buf, user_input);  // ← strcpy のバッファサイズ未チェック → 検出

2. データフロー解析(Data Flow Analysis) 変数の値がどのように伝播するかを追跡します:

int *ptr = NULL;
if (some_condition) {
    ptr = malloc(sizeof(int));
}
*ptr = 42;  // ← some_conditionがfalseのとき NULL dereference → 検出

3. パス感度解析(Path-Sensitive Analysis) 実行パスごとに状態を追跡し、特定のパスでのみ発生する問題を検出します:

void func(int x) {
    int *p = NULL;
    if (x > 0) {
        p = malloc(sizeof(int));
    }
    if (x > 0) {
        *p = 42;  // ← パス感度解析でこのパスはNOT NULLと判断(誤検知なし)
    }
    free(p);      // ← x <= 0 のとき NULL free → 検出
}

4. 型システム解析 型変換の安全性を検証します:

uint32_t big_value = 0x1FFFF;
uint8_t  small_var = (uint8_t)big_value;  // ← 切り詰め警告(MISRA Rule 10.3)

Cppcheckの実行例

# インストール
sudo apt install cppcheck

# 基本実行
cppcheck --enable=all --std=c11 src/

# MISRA C:2012チェック(ルールテキストファイルが必要)
cppcheck --addon=misra --suppressions-list=misra_suppressions.txt src/

# XML出力でCI/CD連携
cppcheck --xml --enable=all src/ 2> report.xml

# 出力例:
# [src/sensor.c:42]: (error) Null pointer dereference
# [src/comm.c:15]: (warning) Buffer access out of bounds: Buffer 'buf[10]' is overrun

Clang Static Analyzerの実行

# scan-buildでビルドと同時に解析
scan-build make

# または cmake と統合
cmake -DCMAKE_C_COMPILER=/usr/bin/clang ..
scan-build cmake --build .

# 出力例:
# /src/parser.c:88:9: warning: Potential null pointer dereference
#   return ptr->value;
#          ^

GCCコンパイラ警告の活用(無料の静的解析)

# 推奨警告フラグ
CFLAGS += -Wall          # 基本警告
CFLAGS += -Wextra        # 追加警告
CFLAGS += -Werror        # 警告をエラーに
CFLAGS += -Wundef        # 未定義マクロ使用
CFLAGS += -Wshadow       # 変数のシャドーイング
CFLAGS += -Wcast-align   # アライメントの問題
CFLAGS += -Wcast-qual    # const修飾子の除去
CFLAGS += -Wconversion   # 暗黙の型変換
CFLAGS += -Wdouble-promotion  # float→doubleの暗黙昇格
CFLAGS += -Wformat=2     # printf/scanf形式チェック
CFLAGS += -Wnull-dereference  # NULLポインタ参照
CFLAGS += -Wuninitialized    # 未初期化変数

用途・ユースケース

安全クリティカルな組み込みシステム

自動車(ISO 26262)・航空宇宙(DO-178C)・医療機器(IEC 62304)・産業機器(IEC 61508)では、MISRA CやCERTコーディング規約の静的解析チェックが開発プロセスの必須要件になっています:

# Polyspace による証明カラー出力例(概念):
# グリーン(Green): ランタイムエラーが発生しないことを証明
# レッド(Red):   ランタイムエラーが必ず発生する
# グレー(Gray):  デッドコード(到達不能)
# オレンジ(Orange): 不確定(エラーが発生する場合も、しない場合もある)

セキュリティ脆弱性の早期発見

IoTデバイスのファームウェアにおいて、バッファオーバーフロー・フォーマット文字列脆弱性・整数オーバーフローなどはリモート攻撃の入口になります。静的解析で開発段階から除去することが重要です:

// 危険なコード(静的解析で検出可能)
void process_network_data(char *data, int len) {
    char local_buf[256];
    memcpy(local_buf, data, len);  // ← len > 256 でバッファオーバーフロー
    // → 静的解析ツールが「サイズチェックなし」として検出
}

// 修正後
void process_network_data(char *data, int len) {
    char local_buf[256];
    if (len > sizeof(local_buf)) {
        return;  // エラー処理
    }
    memcpy(local_buf, data, len);
}

実装・開発のポイント

CI/CDへの統合

# .github/workflows/static-analysis.yml
name: Static Analysis
on: [push, pull_request]
jobs:
  cppcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Cppcheck
        run: sudo apt-get install -y cppcheck
      - name: Run Cppcheck
        run: |
          cppcheck --enable=all --error-exitcode=1 \
            --suppress=missingIncludeSystem \
            --std=c11 src/ 2>&1 | tee cppcheck-report.txt
      - name: Upload Report
        uses: actions/upload-artifact@v3
        with:
          name: static-analysis-report
          path: cppcheck-report.txt

誤検知(False Positive)の管理

// 誤検知を抑制するアノテーション(Cppcheck例)
// NOLINTNEXTLINE コメントで1行を抑制
int result = some_function(); // cppcheck-suppress nullPointer

/* cppcheck-suppress unusedFunction */
void debug_only_function(void) { /* ... */ }

段階的な導入戦略

既存コードベースに静的解析を導入する際は、全エラーを一度に修正しようとせず段階的に進めます:

# 1. まず最重要(error level)のみ修正
cppcheck --enable=error src/

# 2. 次にwarningを修正
cppcheck --enable=error,warning src/

# 3. スタイル違反、情報系を追加
cppcheck --enable=all src/

他技術との比較

比較項目静的解析ユニットテストコンパイラ警告コードレビュー
実行不要不可(実行が必要)可(コンパイル時)
自動化容易(CI/CD)容易(CI/CD)自動(ビルド時)難しい
検出の深さ深い(パス解析)実行したパスのみ浅い(構文中心)深い(文脈理解)
コスト中(商用ツール)中(テスト作成コスト)無料高(人的コスト)
MISRA対応最適一部部分的可能だが手動
対応言語C/C++中心多言語対応コンパイラ依存全言語

関連用語

参考リンク