さんぽしのBLOG

さんぽしのBLOG

非理系の大学生が頑張りに頑張り抜いてエリートエンジニアになるシンデレラストーリー(予定)です。

メルカリのインターンに参加して静的解析をゴッツリ学んできた

こんばんわ さんぽしです

8/31 - 9/4でメルカリのOnline Summer Internship for Gophers 2020に参加してきました。

内容としては以下の通りで1週間を通して、Golangの静的解析について学びました。

前半2日間は、Goの静的解析に関する講義やプログラミング言語Go完全入門の資料で参加者が興味を持つ領域を中心とした講義をWorkshopを交えながら実施。後半3日間では、静的解析ツールまたはその周辺ツールの開発に取り組んでいただきます。

「Online Summer Internship for Gophers 2020」の募集を開始しましたぁぁ! #メルカリな日々 | mercan (メルカン)

資料に関してはすでに公開されているので興味のある方は是非!

僕が作ったもの

上記の説明の通り3日間かけて静的解析ツールの実際の開発を行いました。

僕は

  • 不要な代入の検出ツール
  • 代入文の後にデバック文を入れてくれるツール

を開発しました。

自分で作っておきながらどっちも凡庸的に使える便利くんだと思うので是非使ってみてくれよなっっっ

不要な代入の検出

github.com

このツールでは具体的に

  • 代入されたけどreturnまでその代入された値が使用されることはなかった
  • 代入されたけど代入された値が用いられることなく、別の値に変更された

と言った物を検出します。

Golangは完全な未使用変数は教えてくれるけど、定義されてから一度使われている変数に対する再代入は教えてくれないよね。というモチベです

具体的な使用例はこちらです

f:id:sanpo_shiho:20200906085644p:plain

コメントの通り、このコードに対しては3回ツールによる警告が行われます。

  • 一つ目はどのルートを通ってもuseOutOfIfもう一度定義されるので1行目で定義する必要性がない
  • 二つ目はuseOutOfIfに対して再代入が行われているが、その後すぐにreturnされているので再代入の必要性がない
  • 三つ目はdoHoge()の返り値として受け取った変数errを使いまわしてdoFuga()のエラーを受け取っているが、その後使用されていないので再代入の必要性がない

このツールを使用するメリットとしては

  • 無駄な代入文を省くことによる可読性アップ
  • 無駄な再代入を検出することによる使用忘れの確認(上記画像で言うところのerrハンドリング忘れなど

があります。

前者に関しては必ずしも可読性がアップするかというと議論の余地はあるかもしれませんが、使用しないのであればブランク変数で受け取るなりした方が読む方としては明示的に使わないということがわかり、読みやすいと思います。

また、使用しないことが明示的にわかることで、

  • なぜ使用しないのか
  • 関数の返り値として返す必要がそもそもないのではないか(上記画像で言うと、doFuga()はそもそもエラーを返す必要がないのではないか

などの議論が生まれるきっかけとなります。

どう言う仕組みなの?

どう言う仕組みで解析しているかを簡単に紹介します。

このツールではSSA静的単一代入形式)と言う形式を利用して不要な(再)代入を検出しています。SSAに関して詳しくは序盤に紹介した講義資料を参照してみてください

流れとしては

  • Localの変数に対するssa.Store命令(= 変数への代入文)を探す(変数hogeに対するssa.Store命令が発生していたと仮定する)
  • ssa.Store命令が発生していたブロックから遷移し得るブロック全てに探索をかけ、次にhogeがどこで使用されるかを探す

この流れでhoge代入が起こった後にhogeがどのように使われるかの可能性を全て列挙することができます。

そして、hogeの次の使用の可能性が全てhogeに対する代入であった場合は探索の元になった方のssa.Store命令は無駄な命令だと判別ができます。

また、hogeに対する操作の可能性がなかった場合も不要な再代入であるとの判別ができます。

代入文の後にデバック文を入れてくれるツール

github.com

こちらは個人的にとても欲しかったので作成しました。

ソースコードの中の変数に対する代入が発生している箇所を検出してそのすぐ後にその変数をデバックするための式を挿入してくれる物です。

↓Before

f:id:sanpo_shiho:20200906093200p:plain

↓After

f:id:sanpo_shiho:20200906093139p:plain

このツールによって挿入されたデバック文をさくっと消すことができるのも推しポイントです。(コマンドにflagで-mode 1を渡すとデバック文消去が可能)(勿論このツールによって挿入されたデバック文のみしか消しません)

また、このツールではデバック用の関数dmpがファイル内に作成され、デフォルトではそこでシンプルにfmt.Printfするだけになっていますが、この関数を編集することで、loggerを使うなり、ファイルに出力するなりに変更を加えることができるようにしています。

どう言う仕組みなの?

こちらは一つ目のツールよりはかなりシンプルな作りになっています。

SSAではなく、ASTの状態でソースコードを解析し、

  • ast.AssignStmt(= 変数への代入)を探す
  • その次にデバック用の関数dmpの呼び出しを追加する

と言う流れになっています。

削除はその逆で

  • ast.CallExprでデバック用の関数dmpの呼び出している位置を探す
  • 消す

と言う流れになります。

また、if文などが挟まっているときには再帰でその中まで探しに行くと言うことをしています。

総じて感想

僕は参加前はセイテキカイセキ?って感じのレベルでしたが実際に講義を通してGolangにおける静的解析ツールの作成を一通り学べ、Golangがこれほどまでに静的解析周りの環境が整っていることにもとてもびっくりしていました。

普段の開発ライフだと確実に手を出すことのない分野だったのでとても良いきっかけになりました。

Twitter#mercari_internと検索すると他の人が何をやっていたかなどが出てくると思うのでそちらもチェックしてみてください!!

メンターさんをはじめとするサポートしてくださった社員の皆さん、5日間ありがとうございました!!!!!