公開技術情報

[English] [Japanese]

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 を返します。