ドラゴンを訓練する方法。 clang-cの簡単な例

夕方、コンピューターの前に座って憂鬱にふけり、すべての虚弱について考えた後、私は思慮深く「LLVM」と略語を入力しました。キャッチとしましょう。



予想通り、特別なものはほとんど見つかりませんでしたが、1つの発表に興味がありました。 次の行がありました。



「実行するタスクのレベル」を確認せずに「誰を引き受けますか」:

gccでビルドされたオープンソースプロジェクト(ソースコードは10メガバイト以上)をダウンロードしました。最大のcppファイルについては、-fsyntaxのみのclangを使用してASTツリーを構築できました。

Visual C ++(ソースコードは10メガバイト以上)を使用してビルドされたオープンソースプロジェクトをダウンロードし、最大のcppファイルについては、-fsyntax-onlyを使用してclangを使用してASTツリーをビルドできました。

宣言のすべての場所とローカル変数の使用、およびこのファイルで定義されていないすべての関数を強調表示するユーティリティを作成できました



まあ、私は、まさか、夜のエンターテイメントだと思った。







最初の2つのポイントについて簡単に検討しますが、すべてが非常に単純です。



ASTを構築する方法



私たちはc ++でプロジェクトを取ります、あなたはそれ自体をclangすることができます(gccとVC ++で構築されています)



clang -std=c++11 -Xclang -ast-dump ////cpp -I/////include/ -D_ -fsyntax-only
      
      





ASTはテキスト形式で取得されます。 大きなファイルの場合、ASTは巨大です。ここではすべてのast-dumpを示しませんが、わかりやすくするために、小さな断片を示します。



clang自体のフラグメントAST
TranslationUnitDecl 0x576e190 <<invalid sloc>> <invalid sloc>

|-TypedefDecl 0x576e718 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'

| `-BuiltinType 0x576e400 '__int128'

|-TypedefDecl 0x576e778 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'

| `-BuiltinType 0x576e420 'unsigned __int128'

|-TypedefDecl 0x576eaa8 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'

| `-RecordType 0x576e860 'struct __NSConstantString_tag'

| `-CXXRecord 0x576e7c8 '__NSConstantString_tag'

|-TypedefDecl 0x576eb38 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'

| `-PointerType 0x576eb00 'char *'

| `-BuiltinType 0x576e220 'char'

|-TypedefDecl 0x576ee58 <<invalid sloc>> <invalid sloc> implicit referenced __builtin_va_list 'struct __va_list_tag [1]'

| `-ConstantArrayType 0x576ee00 'struct __va_list_tag [1]' 1

| `-RecordType 0x576ec20 'struct __va_list_tag'

| `-CXXRecord 0x576eb88 '__va_list_tag'

|-NamespaceDecl 0x57cc578 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:18:1, line:31:1> line:18:11 clang

| |-CXXRecordDecl 0x57cc5e0 <line:20:1, col:7> col:7 class Decl

| |-CXXRecordDecl 0x57cc6a0 <line:21:29, <scratch space>:2:1> col:1 referenced class AccessSpecDecl

| |-CXXRecordDecl 0x57cc760 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:3:1> col:1 class BlockDecl

| |-CXXRecordDecl 0x57cc820 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:4:1> col:1 class CapturedDecl

| |-CXXRecordDecl 0x57cc8e0 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:5:1> col:1 referenced class ClassScopeFunctionSpecializationDecl

| |-CXXRecordDecl 0x57cc9a0 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:6:1> col:1 class EmptyDecl

| |-CXXRecordDecl 0x57cca60 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:7:1> col:1 class ExternCContextDecl

| |-CXXRecordDecl 0x57ccb20 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:8:1> col:1 class FileScopeAsmDecl

| |-CXXRecordDecl 0x57ccbe0 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:9:1> col:1 referenced class FriendDecl

| |-CXXRecordDecl 0x57ccca0 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:10:1> col:1 referenced class FriendTemplateDecl

| |-CXXRecordDecl 0x57ccd60 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:11:1> col:1 class ImportDecl

| |-CXXRecordDecl 0x57cce20 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:12:1> col:1 class LinkageSpecDecl

| |-CXXRecordDecl 0x57ccee0 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:13:1> col:1 class NamedDecl

| |-CXXRecordDecl 0x57ccfa0 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:14:1> col:1 class LabelDecl

| |-CXXRecordDecl 0x57cd060 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:15:1> col:1 class NamespaceDecl

| |-CXXRecordDecl 0x57cd120 </home/user/LLVM/llvm-3.7.1.src/tools/cfe-3.7.1.src/include/clang/AST/ASTFwd.h:21:29, <scratch space>:16:1> col:1 class NamespaceAliasDecl









別のオプションでツリーを取得することもできます:-ast-print。 また、テキストで巨大なものになり、私もそれを提供しません。



最後に、Graphvizでツリーのグラフィカル表現を取得できます(LLVMでのデバッグに広く使用されています)。 これは、-ast-viewオプションを使用して行われます。 もちろん、Graphvizをインストールし、「ドット」および「gv」ファイルへのパスを設定する必要があります。 この場合、多数のウィンドウが開き、それぞれに小さなASTセクションがあります。たとえば、次のとおりです。







ただし、続行する前に、ASTとは何か、なぜASTが必要なのかを簡単に説明したいと思います。 コンピュータサイエンスの学位をお持ちの読者は、次のセクションを大胆に読み飛ばすことができますが、残りは興味深いかもしれません。



抽象構文ツリー



ウィキペディアで厳密な定義を確認できます。「指で」を説明しようと思います。

抽象構文ツリーは、コンパイラがプログラムのソースコードを、さらにコンパイルするのに便利な形式で提示する構造です。 ツリーの葉には変数(正確には、変数宣言へのリンク)と定数があり、他の頂点には演算子、データ型宣言などがあります。 ツリーのルートは、プログラムの「翻訳単位」です。



たとえば、簡単なプログラム:



 // file foo.h void foo(int x, int y); // file main.c #include "foo.h" typedef struct { int x, y; } st_coord; int main() { st_coord coord; foo(coord.x, coord.y); }
      
      





これにより、ASTが生成されます。



多くの手紙
TranslationUnitDecl 0x4e64ad0 <<無効なsloc >> <無効なsloc>

| -TypedefDecl 0x4e65018 <<無効なsloc >> <無効なsloc>暗黙的な__int128_t '__int128'

| `-BuiltinType 0x4e64d40 '__int128'

| -TypedefDecl 0x4e65078 <<無効なsloc >> <無効なsloc>暗黙的な__uint128_t 'unsigned __int128'

| `-BuiltinType 0x4e64d60 'unsigned __int128'

| -TypedefDecl 0x4e65338 <<無効なsloc >> <無効なsloc>暗黙的な__NSConstantString 'struct __NSConstantString_tag'

| `-RecordType 0x4e65150 'struct __NSConstantString_tag'

| `-Record 0x4e650c8 '__NSConstantString_tag'

| -TypedefDecl 0x4e653c8 <<無効なsloc >> <無効なsloc>暗黙的な__builtin_ms_va_list 'char *'

| `-PointerType 0x4e65390 'char *'

| `-BuiltinType 0x4e64b60 'char'

| -TypedefDecl 0x4e65678 <<無効なsloc >> <無効なsloc>暗黙的な__builtin_va_list 'struct __va_list_tag [1]'

| `-ConstantArrayType 0x4e65620 'struct __va_list_tag [1]' 1

| `-RecordType 0x4e654a0 'struct __va_list_tag'

| `-Record 0x4e65418 '__va_list_tag'

| -FunctionDecl 0x4ebc390 </home/user/llvm3.9/foo.h:1:1、col:22> col:6 used foo 'void(int、int)'

| | -ParmVarDecl 0x4e656d8 <col:10、col:14> col:14 x 'int'

| `-ParmVarDecl 0x4e65748 <col:17、col:21> col:21 y 'int'

| -RecordDecl 0x4ebc488 </home/user/llvm3.9/test.chaps:9、行:5:1>行:3:9構造体定義

| | -FieldDecl 0x4ebc540 <行:4:2、col:6> col:6参照x 'int'

| `-FieldDecl 0x4ebc598 <col:2、col:9> col:9参照y 'int'

| -TypedefDecl 0x4ebc630 <line:3:1、line:5:3> col:3参照st_coord 'struct st_coord': 'st_coord'

| `-ElaboratedType 0x4ebc5e0 'struct st_coord'シュガー

| `-RecordType 0x4ebc510 'st_coord'

| `` -Record 0x4ebc488 ''

`-FunctionDecl 0x4ebc6e8 <行:7:1、行:10:1>行:7:5 main 'int()'

`-CompoundStmt 0x4ebc9c8 <col:12、line:10:1>

| -DeclStmt 0x4ebc820 <行:8:2、列:16>

| `-VarDecl 0x4ebc7c0 <col:2、col:11> col:11 used coord 'st_coord': 'st_coord'

`-CallExpr 0x4ebc960 <line:9:2、col:22> 'void'

| -ImplicitCastExpr 0x4ebc948 <col:2> 'void(*)(int、int)' <FunctionToPointerDecay>

| `-DeclRefExpr 0x4ebc838 <col:2> 'void(int、int)'機能0x4ebc390 'foo' 'void(int、int)'

| -ImplicitCastExpr 0x4ebc998 <col:6、col:12> 'int' <LValueToRValue>

| `-MemberExpr 0x4ebc888 <col:6、col:12> 'int' lvalue .x 0x4ebc540

| `-DeclRefExpr 0x4ebc860 <col:6> 'st_coord': 'st_coord'左辺値Var 0x4ebc7c0 'coord' 'st_coord': 'st_coord'

`-ImplicitCastExpr 0x4ebc9b0 <col:15、col:21> 'int' <LValueToRValue>

`-MemberExpr 0x4ebc8e8 <col:15、col:21> 'int' lvalue .y 0x4ebc598

`-DeclRefExpr 0x4ebc8c0 <col:15> 'st_coord': 'st_coord'左辺値Var 0x4ebc7c0 'coord' 'st_coord': 'st_coord'



clang-c APIを使用する



clangコンパイラーでは、各ASTノードは特定のクラスのオブジェクトによって表され、3つの基本クラスのみがあります:clang :: Decl(宣言クラス)、clang :: Stmt(すべての演算子を含む)、およびclang :: Typeクラス、データ型クラス。



そのため、clangはC ++で記述され、オブジェクト指向APIを備えています。 これを使用して、clangを使用してさまざまなユーティリティやツールを作成できます。 ただし、知識のある人は、純粋なCで記述されたclang APIのラッパーである別のAPI clang-cを好みます。意味は単純です。まず、clang APIとは異なり、clang-c APIはよりシンプルで、これはリリースごとに変わります。 最後に、clang-cの使用はclang APIの使用を排除しません。







ASTツリートラバーサル



最初に行うことは、ツリーウォークを詳細に記述することです。 これは完全に標準的な操作です。



 #include <clang-c/Index.h> #include <iostream> #include <string> using namespace clang; void printCursor(CXCursor cursor) { CXString displayName = clang_getCursorDisplayName(cursor); std::cout << clang_getCString(displayName) << "\n"; clang_disposeString(displayName); } CXChildVisitResult visitor( CXCursor cursor, CXCursor /* parent */, CXClientData /*clientData*/ ) { CXSourceLocation location = clang_getCursorLocation( cursor ); if( clang_Location_isFromMainFile( location ) == 0 ) return CXChildVisit_Continue; printCursor(cursor); clang_visitChildren( cursor, visitor, nullptr ); return CXChildVisit_Continue; } int main (int argc, char** argv) { CXIndex index = clang_createIndex ( 0, // excludeDeclarationFromPCH 1 // displayDiagnostics ); CXTranslationUnit unit = clang_parseTranslationUnit ( index, // CIdx 0, // source_filename argv, // command_line_args argc, // num_command_line_args 0, // unsave_files 0, // num_unsaved_files CXTranslationUnit_None // options ); if (!unit) { std::cout << "Translation unit was not created\n"; } else { CXCursor root = clang_getTranslationUnitCursor(unit); clang_visitChildren(root, visitor, nullptr); } clang_disposeTranslationUnit(unit); clang_disposeIndex(index); }
      
      





ここではすべてが非常に明確です:clang_parseTranslationUnitは、ASTの作成を含むすべてのコンパイル手順を実行する関数です。 コンパイルオプションを渡すことができます。 同時に、ファイル名は引数と直接(source_filename)の両方で転送できます。 ソーステキストは、ファイルとしてだけでなく、メモリ内のテキストを表すCXUnsavedFile構造体としても送信できます。 解析後、ツリーは深さ方向にトラバースされ、各頂点に対してvisitor関数が呼び出され、CXCursorが渡されます-ツリーの最上部を表す構造です。 また、任意のユーザーデータを表すCXClientDataパラメーターをvisitor関数に渡すことができます。



ビジター関数を書く



すべてのローカルプログラム変数を見つけてみましょう。

  CXCursorKind cursorKind = clang_getCursorKind( cursor ); // finding local variables if(clang_getCursorKind(cursor) == CXCursor_VarDecl) { if(const VarDecl* VD = dyn_cast_or_null<const VarDecl>(getCursorDecl(cursor))) { if( VD->isLocalVarDecl()) { std::cout << "local variable: "; printCursor(cursor); } } }
      
      





ここのすべても簡単です:CXCursor_VarDecl-カーソルは変数を指します。 dyn_cast_or_null-LLVMの型変換テンプレート。



LLVMおよびRTTI
LLVMはRTTIを使用せず、通常のdynamic_castは機能しません。

LLVMで型をキャストするには、次のパターンが使用されます。

isa <B>(A)-オブジェクトAがタイプBに属しているかどうかを確認します。

キャスト<B>(A)-オブジェクトAからタイプBへの変換。タイプAからタイプBへの所属の検証は実行されません。 nullptrのAのチェックは失敗します。

cast_or_null <B>(A)-オブジェクトAからタイプBへの変換。タイプAタイプBタイプはチェックされません。 A == nullptrの場合、結果はnullptrになります。

dyn_cast <B>(A)-型チェックを使用したオブジェクトAから型Bへの変換。 nullptrのAのチェックは失敗します。

dyn_cast_or_null <B>(A)-型チェックを使用してオブジェクトAを型Bに変換します。 A == nullptrの場合、結果はnullptrになります。

RTTIのLLVM実装をクラスで使用する方法は、 ここに記載されています。



次に、カーソルをVarDeclクラスのインスタンスに変換し、変数がローカルかどうかを確認します。 そうである場合、カーソルの名前とソース内のカーソルの位置を表示します。これには補助関数を使用します。



 //logging functions std::string getLocationString(CXSourceLocation Loc) { CXFile File; unsigned Line, Column; clang_getFileLocation(Loc, &File, &Line, &Column, nullptr); CXString FileName = clang_getFileName(File); std::ostringstream ostr; ostr << clang_getCString(FileName) << ":" << Line << ":" << Column; clang_disposeString(FileName); return ostr.str(); } void printCursor(CXCursor cursor) { CXString displayName = clang_getCursorDisplayName(cursor); std::cout << clang_getCString(displayName) << "@" << getLocationString(clang_getCursorLocation(cursor)) << "\n"; clang_disposeString(displayName); }
      
      





Decl、Expr、およびStmtの値を見つけるには、補助関数を使用します。



 // extracted from CXCursor.cpp const Decl *getCursorDecl(CXCursor Cursor) { return static_cast<const Decl *>(Cursor.data[0]); } const Stmt *getCursorStmt(CXCursor Cursor) { if (Cursor.kind == CXCursor_ObjCSuperClassRef || Cursor.kind == CXCursor_ObjCProtocolRef || Cursor.kind == CXCursor_ObjCClassRef) return nullptr; return static_cast<const Stmt *>(Cursor.data[1]); } const Expr *getCursorExpr(CXCursor Cursor) { return dyn_cast_or_null<Expr>(getCursorStmt(Cursor)); }
      
      





次に、ローカル変数のすべての使用を探します。



 // finding referenced variables if(cursorKind == CXCursor_DeclRefExpr) { if(const DeclRefExpr* DRE = dyn_cast_or_null<const DeclRefExpr>(getCursorExpr(cursor))) { if(const VarDecl* VD = dyn_cast_or_null<const VarDecl>(DRE->getDecl())) { if(VD->isLocalVarDecl()) { std::cout << "reference to local variable: "; printCursor(cursor); } } } }
      
      





最後に、このファイルで定義されていない関数の呼び出しをすべて見つけます。



  // finding functions not defined in the module if(cursorKind == CXCursor_CallExpr) { if (const Expr *E = getCursorExpr(cursor)) { if(isa<const CallExpr>(E)) { CXCursor Definition = clang_getCursorDefinition(cursor); if (clang_equalCursors(Definition, clang_getNullCursor())) { std::cout << "function is not defined here: "; printCursor(cursor); } } } }
      
      





ここで、カーソルが関数呼び出し(CXCursor_CallExpr)であるかどうかを確認します。 ただし、CXCursor_CallExprは関数呼び出しであるだけでなく、コンストラクタ、デストラクタ、およびメソッドの呼び出しでもあるため、追加のチェック(isa)が必要であることに注意してください。 その後、関数定義(clang_getCursorDefinition)を探し、(clang_equalCursors(Definition、clang_getNullCursor()))が見つからない場合、このファイルで定義されていない関数を見つけました。





テスト



テストのために、2つの単純なプログラムを作成します。1つはC、もう1つはC ++です。

それで、Cプログラムは:



 //file func.h void foo_ext(int x); //file simple.c #include "func.h" int global1; int foo(int x) { return x; } int global2; int main(int arg) { int local; local = arg; foo_ext(arg); return foo(local); }
      
      





ユーティリティを実行し、出力を取得します。



 local variable: local@simple.c:13:9 reference to local variable: local@simple.c:14:5 function is not defined here: foo_ext@simple.c:15:5 reference to local variable: local@simple.c:16:16
      
      





すべてが正しいようです。 次に、C ++でファイルを確認します。



 #include "func.h" class MyClass { public: MyClass() { int SomeLocal_1; } void foo() { int SomeLocal_2; } ~MyClass() { int SomeLocal_3; } }; MyClass myClass_global; int foo(int x) {return 0;} int main(int argc, char** argv) { int local; MyClass myClass_local; foo(argc); foo_ext(local); return 1; }
      
      





出力を取得します。



 local variable: SomeLocal_1@cpptest.cpp:6:13 local variable: SomeLocal_2@cpptest.cpp:9:13 local variable: SomeLocal_3@cpptest.cpp:12:13 local variable: local@cpptest.cpp:22:9 local variable: myClass_local@cpptest.cpp:23:13 function is not defined here: foo_ext@cpptest.cpp:25:5 reference to local variable: local@cpptest.cpp:25:13
      
      





OK、すべてが機能しているようです。



これはどのように使用できますか?



clangの広範な機能は、C、C ++、Objective Cソースの分析と変換など、さまざまな目的に使用できます。



もっと
また、仕事を検索するためにそれを使用することができますが、私はこれをすべて書いている間、広告はサイトから消えました。 ああ。



文学



トピックのソースのリスト:



1. Gihubプロジェクトコード

2.http//bastian.rieck.ru/blog/posts/2015/baby_steps_libclang_ast/

3.http//bastian.rieck.ru/blog/posts/2016/baby_steps_libclang_function_extents/

4. https://jonasdevlieghere.com/understanding-the-clang-ast/

5. https://habrahabr.ru/post/148508/



All Articles