Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Go静的解析ハンズオン

1,711 views

Published on

第6回Golang勉強会 in Okinawaで行ったハンズオンの資料です。

Published in: Technology
  • Be the first to comment

Go静的解析ハンズオン

  1. 1. Go静的解析ハンズオン The Go gopher was designed by Renee French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. 2017/06/25(日) 第6回Golang勉強会 in Okinawa 1
  2. 2. 自己紹介 メルカリ/ソウゾウ 上田拓也 twitter: @tenntenn ■ コミュニティ活動 Google Cloud Platform User Group (GCPUG) Tokyo Goビギナーズ golang.tokyo Go Conference ■ 業務 GAE/Goでメルカリカウルを作ってます GoやGCPコミュニティを盛り上げる仕事 Gopherを描く仕事(LINEスタンプ) 2
  3. 3. 2017年7月1日(土) BigQueryのハンズオン開催! https://okipug.connpass.com/event/56374/ 3
  4. 4. アジェンダ ● Goにおける静的解析の方法(復習) ● ハンズオン ○ Hello, Worldをパースしてみよう ○ 手でHello, Worldを書いてみよう ○ gopherを探せ ○ 型情報を取得みよう 4
  5. 5. Goにおける静的解析の方法 (ハッカーズチャンプルーの復習) 5
  6. 6. goパッケージ ■ 標準パッケージとして静的解析の機能を提供 go/ast 抽象構文木(AST)を提供 go/build パッケージに関する情報を集める go/constant 定数に関する型を提供 go/doc ドキュメントをASTから取り出す go/format コードフォーマッタの機能を提供 go/importer コンパイラに適したImporterを提供 go/parser 構文解析の機能を提供 go/printer ASTの表示機能を提供 go/scanner 字句解析の機能を提供 go/token トークンに関する型を提供 go/types 型チェックに関する機能を提供 6
  7. 7. 静的解析の流れ 7 ソースコード トークン 抽象構文木(AST) 型情報 構文解析 字句解析 型チェック go/scanner go/token go/parser go/ast go/types go/constant
  8. 8. 字句解析 - go/scanner,go/token ■ 文字列をトークンにしていく ● 空白などを取り除き、意味のある単位=トー クンにしていく作業 8 IDENT ADD INT トークン ソースコード v + 1
  9. 9. v + 1 構文解析 - go/parser,go/ast ■ トークンを抽象構文木(AST)にしていく ● プログラムの構造を持たせる 9 IDENT ADD INT ソースコード + v 1 BinaryExpr Ident BasicLit トークン 抽象構文木(AST)
  10. 10. n := 100 + 200 m := n + 300 型チェック - go/types,go/constant ■ 抽象構文木から型に関する情報を取得する ● 識別子の解決 ● 型の推論 ● 定数の評価 10 定数の評価 =300 型の推論 -> int 識別子の解決
  11. 11. 抽象構文木(AST)の取得 ■ go/parserパッケージの関数を使う ● ParseExpr,ParseExprFrom ○ 式をパースする ○ ParseExprはParseExprFromを簡易版 ● ParseFile ○ ファイル単位でパースする ● ParseDir ○ ディレクトリ単位でパースする ○ 中でParseFileを呼んでいる 11
  12. 12. package main import "fmt" func main() { fmt.Println("Hello, 世界") } Hello, Worldの抽象構文木の構成 12 Goの抽象構文木(AST)を手入力してHello, Worldを作る http://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c Playgroundで動かす *ast.File []ast.Decl *ast.GenDecl *ast.FuncDecl
  13. 13. 式の抽象構文木を取得する ■ 式単位を構文解析する ■ ParseExprFromでも書ける 13 expr, err := parser.ParseExpr(`v + 1`) if err != nil { /* エラー処理 */ } /* exprを解析する処理 */ fset := token.NewFileSet() // ファイル情報 src := []byte(`v + 1`) f := "" // ファイル名(式なので不要) m := 0 // モード(式なので不要) expr, err := parser.ParseExprFrom(fset, f, s, m)
  14. 14. const src = ` package main var v = 100 func main() { fmt.Println(v+1) }` fs := token.NewFileSet() f, err := parser.ParseFile(fs, "my.go", src, 0) if err != nil { /* エラー処理 */ } /* f を解析する処理 */ ソースから抽象構文木を取得する ■ Goのソースコードを構文解析する 14 引数はparse.ExprFromと 同じ構成 srcがnilだとファイル名 でファイルを開く 解析するファイルの中身
  15. 15. token.FileSetとは? ■ ファイル中の位置情報を記録する為の型 ● 位置情報は数値で表される ● 複数のファイル間で一意の値 ● 各ファイルのoffsetが記録されている ● パースする際に記録されていく 15 token.FileSetは出力引数として Parse系の関数に渡す
  16. 16. *ast.BinaryExpr *ast.Ident *ast.BasicLit v + 1 n, _ := parser.ParseExpr(`v + 1`) ast.Inspect(n, func(n ast.Node) bool { if n != nil { fmt.Printf("%Tn", n) } return true }) printer.Fprint(os.Stdout, token.NewFileSet(), n) 抽象構文木をトラバースする ■ ast.Inspectを使う 16 + v 1 構文解析 抽象構文木(AST)を探索 抽象構文木(AST)を出力 BinaryExpr Ident BasicLit Playgroundで動かす ast.Walkというのもある
  17. 17. func traverse(n ast.Node) { switch n := n.(type) { case *ast.Indent: fmt.Println(n.Name) case *ast.BinaryExpr: traverse(n.X) traverse(n.Y) case *ast.UnaryExpr: traverse(n.X) } } 抽象構文木をトラバースする ■ 再帰を使ってトラバースする 17 識別子の場合は名前を出力 二項演算式の場合は 各項を探索 単項演算式の場合は 項を探索 型でswitchする Playgroundで動かす ast.Nodeはインタフェース
  18. 18. 参考資料 ■ goパッケージで簡単に静的解析して世界を広げよう ● コードジェネレータ ○ ASTを取得する方法を調べる ○ 抽象構文木(AST)をトラバースする ○ 抽象構文木(AST)をいじってフォーマットをかける ○ Goの抽象構文木(AST)を手入力してHello, Worldを作る ○ go-app-builderのソースコードを読む ● リファクタリングツール ○ gorenameをライブラリとして使う ○ Goのスコープについて考えてみよう ○ go/typesパッケージを使い変数名をリネームしてみる ● 処理系 ○ 簡単な式の評価機を作ってみる ○ 【実践goパッケージ】文字列から複素数型の値をパースする ○ もっと楽して式の評価器を作る 18
  19. 19. ハンズオン 19
  20. 20. Hello, Worldをパースしてみよう 20 package main import "fmt" func main() { fmt.Println("Hello, 世界") } fs := token.NewFileSet() f, _ := parser.ParseFile(fs, "main.go", src, 0) ast.Print(fs, f) Playgroundで動かす
  21. 21. package main import "fmt" func main() { fmt.Println("Hello, 世界") } Hello, Worldの抽象構文木を手入力 21 Goの抽象構文木(AST)を手入力してHello, Worldを作る http://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c Playgroundで動かす *ast.File []ast.Decl *ast.GenDecl *ast.FuncDecl
  22. 22. gopherを探せ ■ ソースコードからgopherを探そう 22 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 } https://gist.github.com/tenntenn/ca92384795133b3ec5de0e0d7de5eec5
  23. 23. 型チェックをしてみよう ■ 例:int型の変数を探す 23 cfg := &types.Config{Importer: importer.Default()} info := &types.Info{ Defs: map[*ast.Ident]types.Object{}, } cfg.Check("main", fs, []*ast.File{f}, info) it := types.Universe.Lookup("int").Type() for idnt, o := range info.Defs { if o != nil && types.Identical(o.Type(), it) { fmt.Println(fs.Position(idnt.Pos()), idnt) } } https://gist.github.com/tenntenn/706d73e0d82105b0d25179578b953745
  24. 24. 型チェックをしてみよう ■ 構造体型のgopherを探そう 24 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 }
  25. 25. Thank you! twitter: @tenntenn Qiita: tenntenn connpass: tenntenn 25

×