公開技術情報

[English] [Japanese]

libclang の AST(Abstract Syntax Tree)

libclang とは

libclang は clang の機能にアクセスするためのライブラリです。

libclang を利用することで C/C++ のソースコード解析などが出来ます。

libclang の CXCursor

libclang で C/C++ のソースコードを解析すると、 そのソースコードの AST (Abstract Syntax Tree) にアクセスできます。

この AST には CXCursor 構造体を通してアクセスします。 AST にアクセスすることで、解析対象のソースコードにどのような関数が定義されていて、 その関数内でどのような変数にアクセスしているかや、 どのような関数をコールしているか、などを簡単に調べることができます。

CXCursor とは

CXCursor は、 libclang で clang の AST にアクセスするための構造体です。

clang 内部では AST を C++ のクラスで管理していますが、 ライブラリのインタフェースではクラスを直接扱えないため、 CXCursor 構造体でラッピングしています。

アプリ -> CXCursor (C) -> libclang (C/C++)-> AST (C++)

木構造

AST (Abstract Syntax Tree)は、その名の通り木構造になっています。

例えば次のようなソースを解析すると、

int func1( void ) {
  return 0;
}
int func2( int arg1, int arg2 ) {
  return arg1 + arg2;
}

次のような情報が AST の木構造に格納されます。

digraph {
len = 1;
dpi=80;
rankdir = "LR";
root -> FunctionDecl_func1;
FunctionDecl_func1 -> CompoundStmt_func1;
CompoundStmt_func1 -> ReturnStmt_func1;
ReturnStmt_func1 -> IntegerLiteral_0;

FunctionDecl_func1 [label="FunctionDecl (func1)"];
CompoundStmt_func1 [label="CompoundStmt"];
ReturnStmt_func1 [label="ReturnStmt"];
IntegerLiteral_0 [label="IntegerLiteral (0)"];

root -> FunctionDecl_func2;
FunctionDecl_func2 -> ParmDecl_arg1
FunctionDecl_func2 -> ParmDecl_arg2
FunctionDecl_func2 -> CompoundStmt_func2;
CompoundStmt_func2 -> ReturnStmt_func2
ReturnStmt_func2 -> BinaryOperator
BinaryOperator -> UnexposedExpr_arg1
UnexposedExpr_arg1 -> DeclRefExpr_arg1
BinaryOperator -> UnexposedExpr_arg2
UnexposedExpr_arg2 -> DeclRefExpr_arg2


FunctionDecl_func2 [label="FunctionDecl (func2)"];
ParmDecl_arg1 [label="ParmDecl (arg1)"];
ParmDecl_arg2 [label="ParmDecl (arg2)"];
CompoundStmt_func2 [label="CompoundStmt"];
ReturnStmt_func2 [label="ReturnStmt"];
UnexposedExpr_arg1 [label="UnexposedExpr"];
UnexposedExpr_arg2 [label="UnexposedExpr"];
DeclRefExpr_arg1 [label="DeclRefExpr (arg1)"];
DeclRefExpr_arg2 [label="DeclRefExpr (arg2)"];

}

上記の root は木構造の先頭を意味し、root から各ノードを辿ることで、 解析したソースの構造を把握することができます。

statement, expression, literal 等でノードが生成されます。

このノードが CXCursor そのものです。

CXCursor からアクセス可能な情報

CXCursor を libclang が提供する関数に与えることで、 さまざまな情報を取得することが可能です。

例えば、上記 AST の各ノードの種別(FunctionDecl, CompoundStmt, ReturnStmt 等)は、 CXCursor:getCursorKind() で取得します。

CXCursor から取得可能な代表的な情報を挙げます。

  • CursorKind (種別)

    • CXCursor:getCursorKind()
  • 文字列 (FunctionDecl であればその関数名)

    • CXCursor:getCursorSpelling()
  • CXType (型情報)

    • CXCursor:getCursorType()
  • ソース上の位置

    • CXCursor:getCursorLocation()
  • DeclRefExpr などの参照元 CXCursor

    • CXCursor:getCursorReferenced()
  • ハッシュ

    • CXCursor:hashCursor()

型情報 CXType

CXCursor から、そのノードの型情報(CXType)を取得できます。

例えば引数宣言 ParmDecl の型情報は、宣言している引数の型を示します。

具体的には int arg1 の型情報 CXType は、 int を示す情報となります。

なお、C 言語の変数にポインタや配列があるように、 型情報 CXType はそれらポインタや配列の情報を含みます。

例えば int * pVal の型情報 CXType は、 int * を示す情報となり、 これは int val の型情報 CXType とは異なります。

C 言語の変数は int 等の primitive な型だけでなく、構造体等の型を持つこともできます。 その場合、CXType は構造体の型を示すことになります。

struct VALUE {
  int val;
};
struct VALUE val1;

例えば上記 val1 の型は構造体 VALUE です。 このとき AST は次のようになります。

digraph {
len = 1;
dpi=80;
rankdir = "LR";
root -> StructDecl;
StructDecl -> FieldDecl;
root -> VarDecl;
VarDecl -> TypeRef;

StructDecl [ label="StructDecl (VALUE)"];
FieldDecl [ label="FieldDecl (val)"];
VarDecl [ label="VarDecl (val1)" ];
}

ここで VarDecl の CXType は、構造体 VALUE を示します。

VarDecl の CXType が具体的にどのような型であるかは、 次のように処理するとこで得られます。

  • CXType:getTypeDeclaration() を使ってその型を宣言している CXCursor を取得
  • その CXCursor 情報を確認する

上記サンプルの場合、 CXType:getTypeDeclaration() で得られる CXCursor は、 VALUE の StructDecl となります。

digraph {
dpi=80;
rankdir = "LR";
root -> StructDecl;
StructDecl -> FieldDecl;
root -> VarDecl;
CXType -> StructDecl [ label = "getTypeDeclaration()", style = "dashed" ];
VarDecl -> CXType [ label = "getCursorType()", style = "dashed" ];
VarDecl -> TypeRef;

{rank=same; StructDecl; VarDecl; }
{rank=sink; TypeRef; FieldDecl }

StructDecl [ label="StructDecl (VALUE)"];
FieldDecl [ label="FieldDecl (val)"];
VarDecl [ label="VarDecl (val1)" ];
CXType [ shape=box ];
}

CXCursor はハッシュ値を持っています。 CXCursor が同一かどうかは、ハッシュ値で比較するか CXCursor:equalCursors() 関数で確認出来ます。

動かしてみよう

CXCursor と CXType の関係さえ分かれば、 後は実際に libclang を使って AST を生成し、 AST の中身を見てみるのが一番理解が早いと思います。

その際 libclanglua が役に立てれば嬉しいです。