概要
静的解析(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) | 商用 | 大規模コードベース向け、深い解析 | 対応 |
| Cppcheck | OSS | 無料、誤検知が少ない | 部分対応 |
| Clang Static Analyzer | OSS | Clangに内蔵、ビルドと統合 | 部分対応 |
| SonarQube | OSS/商用 | CI/CD統合が容易、ダッシュボード | 部分対応 |
| flawfinder | OSS | セキュリティ脆弱性に特化 | なし |
検出できる問題の種類
| 問題カテゴリ | 例 |
|---|---|
| ヌルポインタ参照 | malloc()の戻り値チェックなしでの使用 |
| バッファオーバーフロー | 配列境界外アクセス |
| 整数オーバーフロー | uint8_tに256以上を代入 |
| 未初期化変数使用 | 初期化前の変数読み取り |
| メモリリーク | malloc()後のfree()忘れ |
| ゼロ除算 | ゼロ確認なしでの除算 |
| デッドコード | 到達不能なコード |
| MISRA C違反 | 暗黙の型変換、再帰、動的メモリ等 |
| セキュリティ | gets()・strcpy()等の危険な関数使用 |
| 並行処理問題 | データ競合、ロック漏れ |
MISRA C:2012 主要ルール(一部)
| ルール | 内容 | 種別 |
|---|---|---|
| Rule 1.3 | 未定義・未規定動作の禁止 | Required |
| Rule 11.3 | ポインタ型変換の制限 | Required |
| Rule 14.2 | forループの標準構造 | Required |
| Rule 15.5 | returnは関数末尾に1つ | Advisory |
| Rule 17.2 | 再帰の禁止 | Required |
| Rule 18.6 | ポインタへの自動変数のアドレス格納禁止 | Required |
| Rule 21.3 | malloc/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++中心 | 多言語対応 | コンパイラ依存 | 全言語 |