C ソースをパースしてシンボルを抽出する Go ライブラリ symc を作った
注意 このライブラリは個人的な問題を解決するために作成したツールのため、かなり適当です. ご使用の場合はご注意ください.
C ソースをパースして変数, 関数の定義や使用箇所を抽出する Go ライブラリを作成した.
https://github.com/kita127/symc1
作った背景
普段 C ソースを相手に仕事をしてるが, ソースを静的に解析する機会など多い. ソース内にある識別子がどこで定義されどこで使用されるかなどの情報がそういった解析時に欲しくなる時がある. ソースを解析して識別子情報だけを手軽に抽出するライブラリを作成すれば, 応用して色々なツールが作れるのではないかと考えた. ツール類は Go で作成する機会が多いため, Go ライブラリとして作成.
機能
主な機能は以下.
- プリプロ展開後の C ソースを入力とする
- 解析したモジュールに対して以下の情報を抽出する
- 定義している変数
- 定義している関数
- extern 宣言している変数
- プロトタイプ宣言している関数
- 関数内で参照している変数
- 関数内でコールしている関数の情報
使い方
使い方は以下のとおり.
package main import ( "fmt" "github.com/kita127/symc" ) func main() { cSrc := ` int variable; int extFunc( int a ); int func( void ){ variable++; return extFunc( variable ); } ` module := symc.ParseModule(string(cSrc)) fmt.Println(module) }
$go build && ./main Module : Symbols={ VariableDef : Name=variable, PrototypeDecl : Name=extFunc, FunctionDef : Name=func, Params=[], Symbols=[Assigne : Name=variable CallFunc : Name=extFunc, Args=[RefVar : Name=variable]] }
github.com/kita127/symc
をインポートして, symc.ParseModule()
に C ソースの文字列を渡せば,
そのソースの識別子情報を AST の構造体として返してくれる.
上記では C ソース解析後, そのソースの情報を持つ AST である symc.Module
構造体のポインタを取得している.
取得した symc.Module
構造体のポインタは 変数の定義VariableDef
や関数の定義FunctionDef
情報などを保持してる.
関数の定義情報の中にはその関数内で参照している変数などの情報も取得できる.
その他の機能
PrettyString()
解析した情報を少し見やすくする Pretty string 機能.
package main import ( "fmt" "io/ioutil" "os" "github.com/kita127/symc" ) cSrc := ` int variable; int extFunc( int a ); int func( void ){ variable++; return extFunc( variable ); } ` func main() { module := symc.ParseModule(string(cSrc)) fmt.Println(module.PrettyString()) }
>go build && ./main
DEFINITION variable
PROTOTYPE extFunc
FUNC func() {
ASSIGNE variable
extFunc(variable)
}
Inspect()
引数に解析済みの AST と処理関数を受け取り, AST を深さ優先で走査しながら引数の関数を適用する.
go 標準の ast パッケージにも同様の機能がありそちらを参考にした.
以下は AST を走査し, 変数定義の場合は変数名を大文字に変更する例.
引数で渡す関数が false
を返すとそのタイミングで走査をやめることもできる.
以下の例は常に true
を返すため, AST を走査し終わるまで走査をやめない.
symc.Symbol
は変数定義や関数定義などシンボル情報が実装しているインターフェースであり,
AST を構成している型は全て実装している. そのため Inspect()
に渡す値は *Module
以外の
*VariableDef
や *FunctionDef
等でも良い.
package main import ( "fmt" "strings" "github.com/kita127/symc" ) func main() { cSrc := ` int g_var; void func(int a) { int hoge; char fuga; fuga = cal( hoge ); } ` module := symc.ParseModule(cSrc) // To uppercase only variable definitions. symc.Inspect(module, func(s symc.Symbol) bool { if v, ok := s.(*symc.VariableDef); ok { v.Name = strings.ToUpper(v.Name) } return true }) fmt.Println(module) }
$ go build && ./main Module : Symbols={ VariableDef : Name=G_VAR, FunctionDef : Name=func, Params=[VariableDef : Name=A], Symbols=[VariableDef : Name=HOGE VariableDef : Name=FUGA Assigne : Name=fuga CallFunc : Name=cal, Args=[RefVar : Name=hoge]] }
ReduceLocalVar()
ローカル変数の情報は不要だったり, 冗長だったりする場合もあるため, 削除するメソッドを実装.
以下の例では, module の情報を生成後, ReduceLocalVar() メソッドでローカル変数の定義, 参照, 代入の情報のみ削除している.
package main import ( "fmt" "github.com/kita127/symc" ) func main() { cSrc := ` int g_var; static int s_var; extern short ext_var; void func(int arg) { int i; int l_var; for(i = 0; i < 10; i++){ g_var++; } l_var = ext_var; l_var += s_var; cal( l_var ); g_var = l_var; } ` module := symc.ParseModule(cSrc) fmt.Println("before reducing") fmt.Println(module.PrettyString()) // Reducing local variable infomation. fmt.Println("after reducing") module.ReduceLocalVar() fmt.Println(module.PrettyString()) }
$ go build && ./main before reducing DEFINITION g_var DEFINITION s_var DECLARE ext_var FUNC func(DEFINITION arg) { DEFINITION i DEFINITION l_var ASSIGNE i i ASSIGNE i ASSIGNE g_var ASSIGNE l_var ext_var ASSIGNE l_var s_var cal(l_var) ASSIGNE g_var l_var } after reducing DEFINITION g_var DEFINITION s_var DECLARE ext_var FUNC func(DEFINITION arg) { ASSIGNE g_var ext_var s_var cal() ASSIGNE g_var }
注意点
Mac の gcc(clang?) でしか確認していないため他のコンパイラでプリプロ展開したソースは試していない. モダンな C 言語の構文には対応していない. 多分, C90 くらいまでの構文はいけると思う(適当).
また冒頭の注意でも記載している通り, 個人的な問題を解決するために作成したライブラリのため, さほど厳格には作っていない. ご使用の場合はその辺りを留意した上でお願いします.
反省点とその他
ライブラリ作成途中に goyacc という Go 用のパーサジェネレータの存在を知った. 本来であればこういった構文解析系のライブラリは既存のパーサジェネレータ等を使うべきだが, かなり作ってから知ったのでそのまま作り切った. (ちなみに自前で字句解析・構文解析している)
一応, ユニットテストをこまめに作りながら作成を進めたので大体いけているとは思う.