Go
静的解析

golang.tokyo #22+Okayama.go/Sendai.go 概要まとめ


はじめに

golang.tokyo #22+Okayama.go/Sendai.go - connpass

このイベントに行ってまいりました。

内容としては、このイベントの概要となります。

今回のテーマは、静的解析でした。

登壇者:

@tenntenn

資料:

A Tour of Static Analysis - Google スライド

このページについて | golang.tokyo コードラボ


そもそも静的解析ってなんの役に立つ


  • go vetとかgolintみたいなツールを作るのに向いている

  • コンパイラじゃ見つけてくれないバグを探すのに使える

  • Goの文法に詳しくなれる


静的解析の流れ

image.png


  • 字句解析

    文字列をトークンへ!!
    字句解析で、文字列の塊だったソースコードが予約語のfuncなのか、識別子(変数名や関数名など)なのか、数値リテラルなのかなどを区別することができるトークンの塊に変換される。


字句解析についてはgo/parserパッケージ内でgo/scannerパッケージを用いて行われています。


go/parserで自動でやられるので点線表記みたいですね


  • 構文解析

    トークンをASTへ!!
    構文解析を行うと、どの部分が関数定義で、どの部分がその引数の定義なのか、などを抽象構文木から取得することができるようになる。

  • 型チェック

    最後に型チェックを行うことで、抽象構文木から型情報を抽出

    型チェックは次の3つの工程から成る。

    1.識別子の解決

    2.型の推論

    3.定数の評価

    この3つの工程を行い、型情報を抽出することで、どの変数(識別子)がどういうデータ型でどこで定義され、どこで使用されているかなどを知ることができる。


Gopherを探せ!ハンズオン

資料:

Gopherを探せ!ハンズオン資料


概要


go@_gopher.go

package main

import (
"fmt"
)

type Gopher struct {
Gopher string `json:"gopher"`
}

func main() {
const gopher = "GOPHER"
gogopher := GOPHER()
gogopher.Gopher = gopher
fmt.Println(gogopher)
}

func GOPHER() (gopher *Gopher) {
gopher = &Gopher{Gopher: "gopher"}
return
}


上記のファイルから、名前付き型かつGopherという名前の識別子だけを

サーチするにはどうしたらいいかが


  • grepコマンドの限界

  • 式の構造解析

  • ファイルの構造解析

  • 型チェック

という流れで学べる、すごくわかりやすいハンズオンになっていました。


ハンズオンを書き換えて、理解を深める

ここからは個人で勝手にやった内容となります。

リテラルの"gopher"文字列をサーチするように書き換えてみます。


go@main.go

package main

import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"strconv"
)

func main() {
// ファイルごとのトークンの位置を記録するFileSetを作成する
fset := token.NewFileSet()

// ファイル単位で構文解析を行う
f, err := parser.ParseFile(fset, "_gopher.go", nil, 0)
if err != nil {
log.Fatal("Error:", err)
}

// 識別子が定義または利用されてる部分を記録する
defsOrUses := map[*ast.Ident]types.Object{}
info := &types.Info{
Defs: defsOrUses,
Uses: defsOrUses,
}

// 型チェックを行うための設定
config := &types.Config{
Importer: importer.Default(),
}

// 型チェックを行う
_, err = config.Check("main", fset, []*ast.File{f}, info)
if err != nil {
log.Fatal("Error:", err)
}

// 抽象構文木を深さ優先で探索する
ast.Inspect(f, func(n ast.Node) bool {

// リテラルではない場合は無視
basic, ok := n.(*ast.BasicLit)
if !ok {
return true
}

basicValue,err := strconv.Unquote(basic.Value)
if err !=nil{
panic(err)
}

//リテラルが"gopher"という値でなければ無視
if basicValue != "gopher" && basicValue != `json:"gopher"` {
return true
}

fmt.Println(fset.Position(basic.Pos()))

return true
})
}


takafk9@narikawakiyoshinoMacBook-Pro [16時39分00秒] [~/go/src/github.com/golangtokyo/codelab/find-gophers/3_typecheck] [master *]

-> % go run main.go
_gopher.go:8:16
_gopher.go:19:27

きちんとリテラルを捕まえてますね

structタグのastが

Tag: *ast.BasicLit {

 ValuePos: 70
Kind: STRING
Value: "`json:\"gopher\"`"
}

なようなので、

if basicValue  != "gopher" && basicValue != `json:"gopher"`

という規制で、タグ内の"gopher"も捕まえられました

(slackで回答していただいた方々、ありがとうございました!)


感想

静的解析についてかなりディープで難しい領域に思っていましたが、今回のハンズオンで

少しイメージが変わりました。

(この流れをモジュール化したAnalyzerまで手を出せなかったので、時間あるときに、そちらもやっていきたい)

golang.tokyoのイベントにお邪魔するのは初めてなのですが、Slackでも疑問に即レスで答えていただけて、非常に素晴らしいイベントでした。

ちょっとastにおこして解析するだけで、かなり文法の知識を深められたので、

今度は自作で静的解析ツールを作りたいと思います。

ありがとうございました!


その他有益な資料まとめ

analysis.Analyzerを使っている今回のようなサンプルプログラム

Goの式の定義

静的解析のサンプルコード

tenntennさんが作ったAnalyzerを使った静的解析CLIを作り始めるのに便利奴

knsh14さんによるanalysisパッケージの解説記事

Goの標準パッケージではじめる静的解析入門①準備編 · mom0tomo

Goにおける静的解析のモジュール化について

analysis pkgh設計者(“プログラミング言語Go”執筆者)によるtypesの説明

motemenさんによるgo/astパッケージやgo/typesパッケージ


余談

Gopherオタクになりたて人間には最高のサイト、、、