フューチャー Advent Calendar 2020の8日目の記事です。
普段はJavaの静的解析をすることが多いですが、今回はGoの静的解析に手をつけてみました。本記事ではGoでコールグラフ自作をしてみたいと思います。すでにofabry/go-callvis: Visualize call graph of a Go program using dot (Graphviz)といったコールグラフ作成ツールがありますが、今回はあえてコールグラフを自作して既存のツールを使うだけではできない拡張をしてみました。
なお私はGoはあまり書いたことがないのでツッコミどころは多々あるかと思います。気になる点などございましたらご指摘いただけると幸いです。
シンプルなコールグラフ作成
GoではGoのソースコードの静的解析を行うライブラリが準標準パッケージとして用意されているようで、それを使うだけで簡単にコールグラフを可視化することができました。まずは何も考えずにコールグラフを作成してみました。
今回はisucon/isucon10-qualify: ISUCON10予選のソースコードを対象にコールグラフを作成してみましょう。コールグラフは関数をノード、関数呼び出しをエッジとしており、ノードにはパッケージ名.関数名
というラベルを、エッジにはfile=ファイル名, Line=行番号
というラベルを表示しています。
解析結果 その1(何も考えず全て表示)
下記のようなグラフになりました(オリジナルの画像は大きすぎるせいかQiitaにアップロードできなかったので縮小しています)
解析に使用したソースコードは解析結果 その1です。
あまりにも巨大で何が何だかよくわかりませんね。グラフを見てみると、ISUCONではそんなに重要でないログ出力などが多数含まれています。まずは特に重要なCRUDに注目したいということで、思い切ってcalleeがsql, sqlx以外のpackageへ繋がるエッジは除外してみましょう。
解析結果 その2(sql,sqlx以外のエッジを除去)
これくらいなら全容が把握できそうと思える規模になってきました。
解析に使用したソースコードは解析結果 その2です。
これを見ればどの関数でCRUDが行われているか把握できそうですね。
ただし、これだけではどのエンドポイントからどのCRUDが行われているかわからないので今度はエンドポイントを表すノードを追加してみましょう。
解析結果 その3(エンドポイントのノードを追加)
どのエンドポイントでCRUDが行われているかわかるようになりました。本当はどのテーブルのどのカラムにアクセスしているかといった情報も載せたかったのですが、そこまでは手が回りませんでした。
解析に使用したソースコードは解析結果 その3です。
(わかりやすいようにエンドポイントはlightgreenで着色しています)
まとめ
Goの準標準パッケージがとても充実しており、サクッと静的解析できて素晴らしいなと感動しました。
単にコールグラフを作るだけなら既存のツール1を使えば済みますが、自作することで自由にカスタマイズすることができ、知りたいこと、伝えたいことにフォーカスしたコールグラフが作成できそうですね。
また今回のようにCRUDだけに注目するのではなく、開発したアプリをうまく可視化することで後からチームに入ってきた人が設計を理解する資料の一つとして使ったり、あるいは既存のLintでは対応できない項目のチェックができたり2とうまく活用すれば静的解析はとても有用そうだと思いました。
(余談ですがssautilパッケージのAllPackages関数はssautil · pkg.go.devのドキュメントには Load function in LoadAllSyntax mode.
と書かれているので LoadAllSyntax
という定数でモード指定することを推奨しているような気がするのですが、このpackages · pkg.go.devをみるとLoadAllSyntax
はdeprecatedなんですよね。使うべきなのかそうでないのか…)
参考
- 逆引きGoによる静的解析入門 - tenntenn - BOOTH
- ofabry/go-callvis: Visualize call graph of a Go program using dot (Graphviz)