libclang で演算子を特定する方法
libclang とは
libclang は clang の機能にアクセスするためのライブラリです。
libclang を利用することで C/C++ のソースコード解析などが出来ます。
libclang の CXCursor
libclang で C/C++ のソースコードを解析すると、 そのソースコードの AST (Abstract Syntax Tree) にアクセスできます。
この AST には CXCursor 構造体を通してアクセスします。 AST にアクセスすることで、解析対象のソースコードにどのような関数が定義されていて、 その関数内でどのような変数にアクセスしているかや、 どのような関数をコールしているか、などを簡単に調べることができます。
しかし、残念ながらこの CXCursor からは、AST の一部の情報にアクセスできません。
そのアクセス出来ない情報には、2項演算子、単項演算子が含まれます。
CXCursor では 2項演算、単項演算をしているということは分かるのですが、 具体的に何の演算(+,-,*,/ 等)をしているかが分かりません。
処理を解析するという用途で libclang を利用する場合、 演算子が分からないのは致命的です。
特に = 演算かどうかが分からないのは致命的です。
そこで、ここでは libclang を使って 2 項演算子を特定する方法について説明します。
— ※ 追記 (2023/7/31)
数年前に提案されていた演算子取得 API が、つい先日取り込まれていた。
<https://github.com/llvm/llvm-project/commit/7fbc9de4553666a189b0529ca04e1d9966c0d4f8>
ただし、次のリリース候補の v16 には含まれなさそうなので、 その次の v17 以降になりそう。
以下の記事は v16 までの取得方法として、参考までに残しておきます。
libclang を使って 2 項演算子を特定する方法
ここで紹介する方法では、マクロ内での演算は特定できません。
AST は、その名の通り木構造になっていて、親子関係を持ちます。
2項演算 1 / 2 は、AST では次のように表現されます。
2項演算: / +----- 数値リテラル: 1
|
+----- 数値リテラル: 2
2項演算 / は、数値リテラルの 1 と 2 の子を持ちます。
AST のノードを管理するのが CXCursor です。
CXCursor は、ソースコード内での範囲情報を持ちます。
CXCursor | 開始位置 offset | 終了位置 offset | 関係 |
---|---|---|---|
2項演算 / | 1 | 6 | 親 |
数値リテラル 1 | 1 | 2 | 子(1番目) |
数値リテラル 2 | 5 | 6 | 子(2番目) |
ここで offset は、ファイルの先頭からの offset で、バイト数です。 当然 TAB は 1 としてカウントしますし、日本語コメントはマルチバイトです。
範囲情報が分かれば、後は 2 項演算の領域から数値リテラル 1 と数値リテラル 2 の 領域を除外した領域を計算すれば、そこに 2 項演算子があることが分かります。 この場合、offset 2 - 5 の領域に演算子があります。具体的には " / " となります。
なお、単項演算子も同じ考え方で取得可能です。
ただし、最初に記載したとおり、この方法ではマクロ内での演算は特定できません。
例えば次のような場合、
#define DIV 1 / 2
void sub()
{
DIV;
}
1 / 2 の CXCursor の範囲情報は次のようになります。
CXCursor | 開始位置 offset | 終了位置 offset | 関係 |
---|---|---|---|
2項演算 / | 34 | 37 | 親 |
数値リテラル 1 | 34 | 37 | 子(1番目) |
数値リテラル 2 | 34 | 37 | 子(2番目) |
ここで offset 34-37 は、 4 行目の DIV を指します。
上記のように、範囲は全て同じ値になります。
CXCursor の範囲は、マクロ展開前のソースコードの範囲を示すためです。
このため、この方法ではマクロ内での演算を特定することは不可能です。
パッチ
2017/10 現在、 libclang から演算子情報を直接得る手段はありません。
AST 自体は演算子情報を保持しているのですが、 libclang には演算子情報にアクセスするためのインタフェースが用意されていないためです。
どうしても演算子情報にアクセスする必要があれば、 次の URL に示されているパッチをあてれば、 アクセスするためのインタフェースが利用できるはずです。
https://reviews.llvm.org/D10833
libclanglua
libclanglua では、 ここで説明した方法で CXCursor から演算子を取得する API を提供しています。
具体的には次の API です。
-
clang.getUnaryOperatorTxt( cursor )
- 指定 CXCursor の単項演算子文字列を取得
-
clang.getBinOperatorTxt( cursor )
- 指定 CXCursor の 2 項演算子文字列を取得
指定した CXCursor がマクロ内の処理であった場合は nil を返します。