こんにちは!開発環境アーキテクトの先輩です。
毎日のコーディング、本当にお疲れ様です。C言語を書いているとき、「なんで標準ライブラリの `strcpy` や `gets` なんて危険な関数が、現代のコンパイラでも警告レベルで素通りしてしまうんだ……」「誰かがレビューで指摘する前に、コンパイラ自身にバグの芽をバッサリ切り落としてほしい!」と思ったことはありませんか?
実は、C/C++のモダンなコンパイラである Clang を使えば、自分だけの「カスタムルール(独自警告・エラー)」をいとも簡単に組み込むことができるんです。
今回は、Clangの強力な武器である AST Matchers(抽象構文木マッチャーズ) を使って、「特定の禁止された関数呼び出しを検知した瞬間、コンパイルを容赦なく失敗させる自作プラグイン」の開発工程を、一緒にステップバイステップで紐解いていきましょう。
これをマスターすれば、チーム全体のコード品質を根本から守る「最強の守護神」をあなたの手で作り上げることができますよ。さあ、低レイヤの深淵へ出発しましょう!
—
1. なぜClangプラグインなのか?(ツールの役割とアーキテクチャ)
世の中にはLinterや静的解析ツール(clang-tidyなど)が無数に存在しますが、「独自の厳格なコーディング規約を、CI/CDパイプラインや手元のビルドプロセスに完全に組み込みたい」という現場の要望には、既存のツールだけでは手が届かないことがあります。
ここで登場するのが Clang プラグイン機構 です。
Clangが内部で行っていること
私たちが書いたC言語のソースコードは、Clangによって以下のプロセスを経てマシン語に翻訳されます。
1. Lexer(字句解析): ソースコードを単語(トークン)にバラす。
2. Parser(構文解析): トークンを組み立てて、文法上のツリー構造を作る。
3. AST(Abstract Syntax Tree: 抽象構文木)生成: 言語の構造を保持したメモリ上のツリー構造(AST)を構築する。
4. Code Generation: ASTを元に機械語やLLVM IRを吐き出す。
Clangプラグインの真骨頂は、「3番目のASTが生成された瞬間、コンパイルのパイプラインに割り込み、そのツリー構造を自由自在に検査・改変できる」という点にあります。ソースコードの文字列置換ではなく、文法上の意味(「この関数がこの引数で呼ばれている」という事実)を正確に理解した上で検知するため、誤検知が極めて少ないのが特徴です。
—
2. 開発環境のセットアップと基礎知識
まずは、Clangプラグインを開発するための強固な土台を作ります。Clangのプラグインを書くには、LLVM/ClangのライブラリとリンクしたC++プログラムをビルドする必要があります。
必要なパッケージのインストール(Ubuntu / Debianの例)
以下のコマンドを実行して、Clangのコンパイルとプラグイン開発に必要なヘッダやライブラリ一式を導入します。
LLVMとClangの開発用ヘッダ、およびビルドツールをまとめてインストール
sudo apt-get update && sudo apt-get install -y \
clang \
llvm-dev \
libclang-dev \
cmake \
build-essential
- `llvm-dev` / `libclang-dev`: 私たちが書くプラグインから、Clangの内部構造(ASTやコンパイラのアクション)を操作するために不可欠な開発キットです。
—
3. 実装:特定の関数呼び出しを検知するプラグインを作る
それでは、今回のメインディッシュである「特定の関数(例:`strcpy`)の呼び出しを検知してコンパイルエラーを吐くプラグイン」のソースコードを書き上げていきます。
プラグインの構造は大きく分けて以下の2つで構成されます。
1. ASTMatcher: ソースコードのASTから「やり玉に挙げたい特定の関数呼び出し」を見つけ出すパターン定義。
2. FrontendAction / ASTConsumer: 見つけた瞬間にコンパイルエラーを発生させるハンドラ。
`NoStrcpyPlugin.cpp` の全コード
プロジェクト用のディレクトリ(例: `clang-plugin-sample`)を作成し、その中に以下のファイルを配置してください。
include “clang/AST/AST.h”
include “clang/AST/ASTConsumer.h”
include “clang/ASTMatchers/ASTMatchFinder.h”
include “clang/ASTMatchers/ASTMatchers.h”
include “clang/Frontend/CompilerInstance.h”
include “clang/Frontend/FrontendPluginRegistry.h”
include “clang/Rewrite/Core/Rewriter.h”
using namespace clang;
using namespace ast_matchers;
namespace {
// 1. AST Matcherがマッチした際に呼び出されるコールバッククラス
class ForbiddenFunctionHandler : public MatchFinder::MatchCallback {
private:
CompilerInstance &CI;
public:
ForbiddenFunctionHandler(CompilerInstance &CI) : CI(CI) {}
virtual void run(const MatchFinder::MatchResult &Result) {
// マッチしたASTノードから「関数呼び出し式 (CallExpr)」を取り出す
if (const CallExpr CE = Result.Nodes.getNodeAs
// 診断エンジンの取得(これを使ってコンパイルエラーや警告を飛ばす)
DiagnosticsEngine &Diags = CI.getDiagnostics();
// 独自のカスタムエラーIDを登録して発行
// ※ 実際の現場では事前にID定義しますが、ここではシンプルに警告IDを取得
unsigned DiagID = Diags.getCustomDiagID(
DiagnosticsEngine::Error,
“セキュリティポリシー違反: 関数 ‘%0’ の使用は禁止されています!安全な代替関数を使用してください。”
);
// 関数名(Callee)の情報を取得してエラーメッセージに埋め込む
if (const FunctionDecl FD = CE->getDirectCallee()) {
Diags.Report(CE->getBeginLoc(), DiagID) << FD->getNameAsString();
}
}
}
};
// 2. プラグイン全体の動作を統括するConsumer
class ForbiddenFunctionConsumer : public ASTConsumer {
private:
MatchFinder Finder;
ForbiddenFunctionHandler Handler;
public:
ForbiddenFunctionConsumer(CompilerInstance &CI) : Handler(CI) {
// AST Matcherの定義:
// 「名前が ‘strcpy’ である関数への呼び出し (callExpr)」をターゲットに指定し、
// “forbiddenCall” というラベルを貼る
Finder.addMatcher(
callExpr(callee(functionDecl(hasName(“strcpy”)))).bind(“forbiddenCall”),
&Handler
);
}
// 翻訳単位(TranslationUnit)全体のAST構築が完了したタイミングでマッチングを実行
virtual void HandleTranslationUnit(ASTContext &Context) {
Finder.matchAST(Context);
}
};
// 3. Clangのフロントエンドにプラグインを登録するためのアクションクラス
class ForbiddenFunctionAction : public PluginASTAction {
protected:
std::unique_ptr
llvm::StringRef) override {
return std::make_unique
}
bool ParseArgs(const CompilerInstance &CI,
const std::vector
// プラグイン引数を受け取る必要がある場合はここに記述(今回は未使用)
return true;
}
};
} // namespace
// Clangのプラグインレジストリに登録
static FrontendPluginRegistry::Add
X(“no-strcpy”, “Prohibit the use of strcpy function”);
コードの解説
- `callExpr(callee(functionDecl(hasName(“strcpy”))))`: これがClangのAST Matchersの神髄です。Cのソースコードの構文木から「`strcpy` という名前の関数宣言」を指す「関数呼び出し」のノードを、まるでデータベースを検索するかのように美しくピンポイントで抽出します。
- `Diags.getCustomDiagID(…)`: Clang標準の診断エンジンを叩き、独自のコンパイルエラーメッセージを生成しています。これにより、IDEの赤波線やビルドログに直接メッセージを流し込むことができます。
—
4. CMakeによるビルド設定
このC++ファイルをコンパイルして、Clangが読み込める共有ライブラリ(`.so` や `.dylib`)にするための `CMakeLists.txt` を用意します。
cmake_minimum_required(VERSION 3.13)
project(NoStrcpyPlugin)
LLVM/ClangのCMakeパッケージを読み込む
find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
C++の標準規格を設定(Clangのコードベースに合わせるためC++17以上推奨)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
共有ライブラリ(モジュール)としてビルド
add_library(NoStrcpyPlugin MODULE
NoStrcpyPlugin.cpp
)
プラグインとしての拡張子設定(プラットフォーム依存を解消)
set_target_properties(NoStrcpyPlugin PROPERTIES
PREFIX “”
SUFFIX “.so”
)
ビルドの実行
以下のコマンドでプラグインをビルドします。
mkdir build && cd build
cmake ..
make
成功すると、カレントディレクトリに `NoStrcpyPlugin.so` という共有ライブラリが生成されます。これがあなたのチーム専用の「カスタムコンパイラ拡張」です。
—
5. 精度高い「HelloWorld」動作確認
それでは、このプラグインが実際に機能するかどうか、テスト用のC言語ソースコードを用意して実験してみましょう。
テスト用コード: `test.c`
include
include
int main() {
char dest[10];
char src = “Hello, Clang!”;
// ここで禁止されている strcpy を呼び出す
strcpy(dest, src);
printf(“Result: %s\n”, dest);
return 0;
}
プラグインを読み込ませてコンパイル実行!
Clangに対して `-fplugin` オプションで自作の共有ライブラリを指定し、さらにプラグイン固有の動作を有効にするフラグを渡します。
clang -Xclang -load -Xclang ./build/NoStrcpyPlugin.so \
-Xclang -plugin -Xclang no-strcpy \
test.c -o test
実行結果(期待されるエラーログ)
コマンドを実行した瞬間、次のような美しい(?)コンパイルエラーがコンソールに轟きます。
test.c:9:5: error: セキュリティポリシー違反: 関数 ‘strcpy’ の使用は禁止されています!安全な代替関数を使用してください。
strcpy(dest, src);
^
1 error generated.
見事に `strcpy` の呼び出しをピンポイントで検知し、安全にコンパイルをブロックすることができました!
もちろん、`strcpy` を安全な `strncpy` や独自のセキュア関数に書き換えれば、エラーは消えて何事もなかったかのようにビルドが成功します。
—
6. おわりに:毎日のコーディングが劇的に楽になる理由
いかがでしたでしょうか? 今回はClangのAST Matchersを用いたカスタム警告プラグインの実装を体験しました。
「なぜこの設定や実装が必要なのか」というと、「人間のコードレビューや属人的なチェックに依存するセキュリティ担保を、機械的・絶対的なビルドプロセスの一部へと昇華させるため」です。
これをプロジェクトのMakefileやCMake、あるいはCI/CDのビルドステップ(GitHub Actionsなど)に組み込んでおけば、開発者がうっかり危険な関数を書いた瞬間にプルリクエストのビルドが落ちるため、レビューの手間が激減し、セキュアなコードベースを半永久的に維持できるようになります。
これをマスターすれば、あなたのチームのコード品質は一段上のステージに引き上げられます。ぜひ、社内のレガシーな規約や「絶対にやってほしくないコーディングパターン」を、あなた自身の的手でClangに教え込んでみてください。
それでは、素晴らしい開発ライフを!