Upgrade to Pro — share decks privately, control downloads, hide ads and more …

字句解析からポインタ解析までGoの静的解析のすべて - tenntenn Conference 2022

字句解析からポインタ解析までGoの静的解析のすべて - tenntenn Conference 2022

この資料はtenntenn Conference 2022にて発表を行った際に用いた資料です。

■ tenntenn Conferenceとは
tenntenn Conferenceはtenntennが主催し、そしてすべてのセッションがtenntennによる登壇のカンファレンスです。

イベントページ:https://tenntenn.connpass.com/event/226562/
ハッシュタグ:#tennconn
資料(Google スライド):https://tenn.in/analysis-conn22
動画:https://tenn.in/analysis-conn22-video
再生リスト:https://tenn.in/conn22-videolist

■ 内容
このセッションではGoにおける静的解析について字句解析からポインタ解析までどのような解析ができるのか網羅的に説明しています。静的解析という言葉を聞いたことがあるけど、イマイチ何ができるのかピンと来てない方にちょうどいい内容です。

・静的解析
・goパッケージ
・字句解析
・構文解析/抽象構文木(Abstract Syntax Tree; AST)
・型チェック
・静的単一代入(Static Single Assign; SSA)形式
・ポインタ解析
・コールグラフ

■ 登壇者&主催者

・名前:tenntenn / 上田拓也
・HP:https://tenntenn.dev
・Twitter:https://twitter.com/tenntenn

メルカリ/メルペイ所属。バックエンドエンジニアとして日々Goを書いている。Google Developer Expert (Go)。一般社団法人Gophers Japan代表。Go Conference主催者。大学時代にGoに出会い、それ以来のめり込む。人類をGopherにしたいと考え、Goの普及に取り組んでいる。複数社でGoに関する技術アドバイザーをしている。大学においてGoに関する集中講義も担当している。マスコットのGopherの絵を描くのも好き。

■ Gopher道場 自習室

https://gopherdojo.org/studyroom/

Gopher道場とは、実践的なGoを体系的に学べる場です。
Gopher道場 自習室では、以下のようなコンテンツや学びの場を提供します。

・Gopher道場の講義を録画した動画(10時間以上分)
・Slackにおける受講者同士のコミュニティ
・Gopher道場卒業生による課題のレビュー(ボランティアでご協力頂いているのでベストエフォートです)

■ Meety(カジュアル面談)

・ソフトウェアエンジニアの地方移住ってどうなの?:https://meety.net/matches/jyZgDkEEwmMk
・メルカリグループにおけるGoの使いどころ:https://meety.net/matches/LbeVbIACxLqk
・地方からの技術コミュニティへの貢献:https://meety.net/matches/gVeMtImLkWJE

■ お仕事の依頼について

副業にて技術顧問やアドバイザーなどを行っています。過去の実績や問い合わせフォームは以下のURLからご確認ください。
https://tenntenn.dev/ja/job/

tenntenn - Takuya Ueda

January 15, 2022
Tweet

More Decks by tenntenn - Takuya Ueda

Other Decks in Programming

Transcript

  1. The Go gopher was designed by Renée French.
    The gopher stickers was made by Takuya Ueda.
    Licensed under the Creative Commons 3.0 Attributions license.
    字句解析からポインタ解析まで
    Goの静的解析のすべて
    2022/01/15(土)
    tenntenn Conference 2022
    資料:https://tenn.in/analysis-conn22
    動画:https://tenn.in/analysis-conn22-video

    View full-size slide

  2. 上田拓也
    Go ビギナーズ

    Go Conference

    @tenntenn
    tenntenn.dev
    Google Developer Expert (Go)
    一般社団法人 Gophers Japan 代表理事
    Experts Team

    View full-size slide

  3. この活動を支える企業
    ■ 株式会社アンドパッド
    ● https://andpad.co.jp/
    ● https://engineer.andpad.co.jp/
    ● 採用情報:https://hrmos.co/pages/andpad/jobs/0000131

    View full-size slide

  4. 早い段階でバグを見つけた方がよい
    ■ あとのフェーズになると深刻化・発見が困難になる
    ● テスト時よりQAの時に見つかる方がコストが高い
    ● リリース後に見つかるとユーザにも影響を与えてしまう
    ● 実環境で動いているサービスから問題を見つけるのは困難
    ○ 参考:メルペイでのSpannerとの戦いの日々
    4
    コーディング
    01010
    01010
    00101
    000
    コンパイル
    01010
    01010
    00101
    000
    デプロイ リリース
    テスト QA 監視
    深刻化していく・・・

    View full-size slide

  5. コンパイル前にバグを見つける方法
    ■ 静的解析ツールを使用する
    ● ソースコードレベルでバグがないか調べる
    ● コンパイルエラーにはならないバグを見つける
    5
    コーディング
    010100
    101000
    101000
    コンパイル
    010100
    101000
    101000
    デプロイ リリース
    テスト QA 監視
    静的解析

    View full-size slide

  6. go vet
    ■ 公式の静的解析ツール
    ● コンパイラでは発見できないバグを見つける
    ● go testを走らせれば自動で実行される(Go1.10から)
    ● The Go Playgroundでも実行される
    ○ go.dev/playでは実行されなくなった!
    6
    package main
    import "fmt"
    func main() {
    fmt.Printf("%s\n", 100)
    } %sなのに数値が渡されている

    View full-size slide

  7. 静的解析ツールを自作する
    ■ 自作する必要性
    ● プロジェクトの独自ルール
    ○ 例:インポートルール
    ● 決められたパッケージからしかインポートできない
    ● レイヤードアーキテクチャのレイヤーをまたぐインポートの場合など
    ● 特定のライブラリの使い方を検証したい
    ○ 例:github.com/gcpug/zagane
    ● Google Cloud Spannerのよくあるミスを静的解析ツールで指摘する
    ● Google Cloud Spannerのセッションリークを静的解析で防ぐ - Mercari
    Engineering Blog
    ● 欲しい静的解析ツールがない
    ○ かゆいところに手が届くものがない
    ○ なければ作るしかない
    ○ レビューで毎回指摘しているようなものはツールにする

    View full-size slide

  8. 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 型チェックに関する機能を提供
    8

    View full-size slide

  9. x/tools/goパッケージ
    analysis 静的解析ツールをモジュール化するパッケージ
    ast AST関連のユーティリティ
    callgraph call graph関連
    cfg control flow graph関連
    expect 構造化されたコメントを処理する
    packages Go Modulesを前提としたパッケージ情報の収集から構文解析、
    型チェックまでを行うパッケージ
    pointer ポインタ解析
    ssa Static Single Assignment (SSA) 関連
    types 型情報関連
    9

    View full-size slide

  10. 静的解析とコンパイラ 10
    ■ goパッケージはコンパイラでは使われていない
    ● あくまで静的解析用のパッケージ
    ● GoのコンパイラはもともとCで書かれていた
    ● コンパイラで用いているデータ構造と異なる場合がある
    ○ 特に静的単一代入形式はまったく異なる
    ■ リリース当初からgoパッケージは存在する
    ● 言語設計時からツールが作りやすさを念頭においていた
    ○ gofmt, go fix, godoc
    ● 標準パッケージでサポートしている重要性
    ○ 言語仕様に追従していく
    参考:Go at Google: Language Design in the Service of Software Engineering

    View full-size slide

  11. 静的解析のフェーズ
    ■ 静的解析はいくつかのフェーズに分かれている
    ● 後のフェーズにいくこと、さらに詳しい情報が手に入る
    ● 各フェーズで手に入る情報を使い分けながら解析する
    ● 各フェーズで手に入る情報の紐付けの仕方がキモ
    ○ このノードの型情報は?など
    11
    構文解析
    型チェック
    静的単一代入形式
    ポインタ解析

    View full-size slide

  12. ■ 入力された文字列をトークンとして分解
    字句解析- go/scanner,go/token
    IDENT ADD INT
    トークン
    ソースコード: v + 1
    12

    View full-size slide

  13. 構文解析 - go/parser,go/ast
    ■ トークンを抽象構文木(AST)に変換
    ● AST: Abstract Syntax Tree
    v + 1
    IDENT ADD INT
    ソースコード:
    +
    v 1
    BinaryExpr
    Ident BasicLit
    トークン:
    抽象構文木(AST):
    13

    View full-size slide

  14. 例:import文の重複
    ■ 同じインポートパスのimport文を見つける
    ● 別名をつけると同じインポートパスのパッケージをインポート可能
    ● https://github.com/gostaticanalysis/dupimport
    14
    package main
    import fmt1 "fmt"
    import fmt2 "fmt"
    func main() {
    fmt1.Println("Hello")
    fmt2.Println("World")
    }
    *File
    []Decl
    *GenDecl *FuncDecl
    *ImportSpec

    View full-size slide

  15. 構文解析で分からないこと
    ■ 型情報
    ● 型の不一致など
    ● 変数の型や式の型
    ■ 定数式の結果
    ● 定数式の計算や定数の型
    ■ 識別子の解決
    ● どの識別子がどこで定義されているのか
    ● どの識別子がどこで使用されているのか
    15
    +
    100 "hoge"
    BinaryExpr
    BasicLit
    BasicLit
    100 + "hello"
    型が合わなくても文法上は問題はない

    View full-size slide

  16. 型チェック - go/types,go/constant
    ■ 型情報を抽象構文木から抽出
    ● 識別子の解決
    ● 型の推論
    ● 定数の評価
    n := 100 + 200
    m := n + 300
    定数の評価
    = 300
    型の推論
    -> int
    識別子の解決
    16

    View full-size slide

  17. 型チェックで分かること
    ■ 型情報
    ● 型の不一致など
    ● 変数の型や式の型
    ■ 定数式の結果
    ● 定数式の計算や定数の型
    ■ 識別子の解決
    ● どの識別子がどこで定義されているのか
    ● どの識別子がどこで使用されているのか
    17

    View full-size slide

  18. 静的解析ツールのモジュール化
    ■ golang.org/x/tools/go/analysisパッケージ
    ● 静的解析ツールのモジュール化を提供するパッケージ
    ● Go1.12からgo vetでも使われるようになった

    View full-size slide

  19. analysis.Analyzer
    ■ go/analysisの静的解析の1つの単位を表す構造体
    ● Runフィールドに処理の本体を書く
    ● Requiresに依存するAnalyzerを書く
    type Analyzer struct {
    Name string
    Doc string
    Flags flag.FlagSet
    Run func(*analysis.Pass) (interface{}, error)
    RunDespiteErrors bool
    Requires []*analysis.Analyzer
    ResultType reflect.Type
    FactTypes []Fact
    }

    View full-size slide

  20. analysis.Pass
    ■ 静的解析に使う情報が入った構造体
    ● Analyzer.Runフィールドの引数で用いられる
    type Pass struct {
    Analyzer *analysis.Analyzer
    Fset *token.FileSet
    Files []*ast.File
    OtherFiles []string
    Pkg *types.Package
    TypesInfo *types.Info
    TypesSizes types.Sizes
    Report func(analysis.Diagnostic)
    ResultOf map[*analysis.Analyzer]interface{}
    ImportObjectFact func(obj types.Object, fact analysis.Fact) bool
    ImportPackageFact func(pkg *types.Package, fact analysis.Fact) bool
    ExportObjectFact func(obj types.Object, fact analysis.Fact)
    ExportPackageFact func(fact analysis.Fact)
    }
    ファイル上の位置に関する情報
    構文解析の結果の抽象構文木
    型チェックの結果の型情報
    Analyzerの結果

    View full-size slide

  21. analysis.Diagnostic
    ■ token.Pos(位置)に関連付けられた静的解析結果
    ● 任意の位置へのエラーを表現するために使う
    ○ 例:〇行目に〇〇というエラーがあります
    ■ (*analysis.Pass).Reportfメソッド
    ● Diagnosticを生成するメソッド
    ● fmt.Fprintf感覚で使える
    func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{})

    View full-size slide

  22. 簡単なAnalyzerの例 22
    var Analyzer = &analysis.Analyzer{
    Name: "simple",
    Doc: "simple is simple Analyzer",
    Run: run,
    Requires: []*analysis.Analyzer{inspect.Analyzer},
    }
    func run(pass *analysis.Pass) (interface{}, error) {
    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    inspect.Preorder([]ast.Node{new(ast.Ident)}, func(n ast.Node) {
    switch n := n.(type) {
    case *ast.Ident:
    if n.Name == "gopher" {
    pass.Reportf(n.Pos(), "identifier is gopher")
    }
    }
    })
    return nil, nil
    }

    View full-size slide

  23. analysistestパッケージ
    ■ Analyzerのテストを簡単に行うためのパッケージ
    ● 実際の解析対象となるソースコードをファイルとして用意すればよい
    ● テストデータはtestdata以下に用意する
    ● コメントによって該当行にDiagnosticが出力されるかチェックする
    func Test(t *testing.T) {
    testdata := analysistest.TestData()
    analysistest.Run(t, testdata, fourcetypeassert.Analyzer, "a")
    }

    View full-size slide

  24. テストデータ
    package a
    func f() {
    var a interface{}
    _ = a.(int) // want "must not do fource type assertion"
    _, _ = a.(int) // OK
    switch a := a.(type) { // OK
    case int:
    println(a)
    }
    }
    ■ wantで始まるコメントで期待する結果を書く
    ● 対応するDiagnosticが出力されていればOK
    正規表現が使える

    View full-size slide

  25. unitchecker
    ■ unitchecker.Main
    ● 複数のAnalyzerからなるmain関数を提供する
    ● 各Analyzerはゴールーチンで1回だけ実行される
    ● go vet -vettool によって呼ばれる前提
    ● 各種設定はgo vetからもらう
    package main
    import (
    "simple"
    "golang.org/x/tools/go/analysis/unitchecker"
    )
    func main() { unitchecker.Main(simple.Analyzer) }

    View full-size slide

  26. go vetからの実行
    ■ go vetの-vettoolオプションを用いる
    ● unitchecker.Main関数を呼び出す実行可能ファイルを指定できる
    ● 相対パスではなく、絶対パス
    ● フラグは-called.funcs=log.Fatalのように指定できる
    26
    $ go vet -vettool=$(which myvet) pkgname

    View full-size slide

  27. skeleton
    ■ go/analysis用のスケルトンコードジェネレータ
    ● https://github.com/gostaticanalysis/skeleton
    ● 簡単に静的解析ツールを始めることができる
    ● Analyzer、テストコード、main.goの雛形作ってくれる
    $ skeleton myanalyzer
    myanalyzer
    ├── cmd
    │ └── myanalyzer
    │ └── main.go
    ├── myanalyzer.go
    ├── myanalyzer_test.go
    └── testdata
    └── src
    └── a
    └── a.go

    View full-size slide

  28. skeletonを使えば簡単につくれる
    ■ 思い立ったらすぐに作れる
    11:46
    12:46
    簡単なものは1時間で作れる!

    View full-size slide

  29. gostaticanalysis
    ■ Goの静的解析に関するリポジトリを集めたもの
    ● https://github.com/gostaticanalysis
    ● 便利なライブラリやAnalyzerを提供している
    ○ skeleton
    ○ analysisutilパッケージ
    ○ astquery
    ○ knife

    View full-size slide

  30. 静的解析とコード生成
    ■ ソースコードをプログラムによって生成する
    ● 冗長なコードの自動生成
    ● テストコードの生成
    ● 静的解析の解析結果やデータベースのスキーマなどから生成
    静的解析
    コード生成
    ソースコード
    抽象構文木
    型情報
    DB
    スキーマ

    View full-size slide

  31. コード生成の必要性 31
    ■ 冗長なコードの自動生成
    ● 自動生成が可能だけど人力で書くのは手間
    ● テストコードの生成
    ● 最適化されたテクニカルなコード
    ○ 可読性が低いがパフォーマンスが良いコード

    View full-size slide

  32. コード生成と静的解析
    ■ 静的解析した結果を元にコードを生成する
    ● 型や構造体タグやコメントを解析
    32
    package main
    //go:generate stringer -type=MyStatus
    type MyStatus int
    const (
    A MyStatus = iota
    B
    C
    )
    // Code generated by "stringer -type=MyStatus"; DO NOT EDIT.
    package main
    import "strconv"
    /* 略 */
    const _MyStatus_name = "ABC"
    var _MyStatus_index = [...]uint8{0, 1, 2, 3}
    func (i MyStatus) String() string {
    if i < 0 || i >= MyStatus(len(_MyStatus_index)-1) {
    return "MyStatus(" + strconv.FormatInt(int64(i), 10) + ")"
    }
    return _MyStatus_name[_MyStatus_index[i]:_MyStatus_index[i+1]]
    }
    コード生成

    View full-size slide

  33. 静的単一代入(SSA)形式 33
    ■ 変数への入力を1回だけに制限した形式
    ● gc(Goのコンパイラ)の中でも使われている技術
    ○ 別パッケージで表現方法は異なる
    ○ 最適化に使われている
    ● golang.org/x/tools/go/ssaパッケージが担当
    ○ 静的解析ツール用のパッケージ
    ○ gcでは使われていない
    n := 10
    n += 10
    n0 := 10
    n1 := n0 + 10

    View full-size slide

  34. 静的単一形式の構成
    ■ x/tools/go/ssaパッケージのSSAの構成
    ● 関数が基本ブロックで構成されている
    ● 基本ブロックは命令で構成されている
    ● 命令は複数のオペランドを持つ
    34
    Program
    Package
    Function


    BasicBlock


    Instruction
    オペランド
    Value

    View full-size slide

  35. 基本ブロック
    ■ 関数を構成する単位
    ● 条件分岐単位などで基本ブロックに分けられる
    ● 関数は基本ブロックをノードとしたコントロールフローグラフを構成する
    ● Goのif、for、switchなどは全部If命令とJump命令になる
    35
    func f() {
    n := 10
    if n < 10 {
    println("n < 10")
    } else {
    println("n >= 10")
    }
    }

    View full-size slide

  36. 例:エラー処理のミス
    ■ nil以外を返すべきところでnilを返してるバグを発見
    ● https://github.com/gostaticanalysis/nilerr
    ● err != nilと比較しているのにnilを返している
    ● 基本ブロックのフローを調べることで簡単に見つけられる
    ○ If命令のジャンプ先がReturn命令でnilを返していたら
    36
    func f() error {
    err := do()
    if err != nil {
    return nil // ミス
    }
    } Return命令(nil)
    If命令(err != nil)
    Jump命令
    ・・・
    then else

    View full-size slide

  37. 例:Spannerのセッションリーク検出
    ■ gcpug/zagane
    ● https://github.com/gcpug/zagane
    ● *spanner.RowIteratorのStopメソッド(またはDoメソッド)が呼ばれてい
    るかだけチェックできる
    ○ 呼ばれていないとセッションリークする可能性がある
    ● 参考:Google Cloud Spannerのセッションリークを静的解析で防ぐ
    37
    iter := client.Single().Query(ctx, stmt)
    for {
    row, err := iter.Next()
    // (略)
    }
    iter := client.Single().Query(ctx, stmt)
    defer iter.Stop()
    for {
    row, err := iter.Next()
    // (略)
    }

    View full-size slide

  38. セッションリークの検出方法 - 1 -
    ■ コントロールフローグラフを有向グラフとして処理
    ● 基本ブロックをノードとする
    ● 注目している基本ブロックを始端とする
    ● 関数のreturn文を終端とする
    ● StopまたはDoメソッドを呼んでいるノードに☆マークをつける
    38
    始端
    終端
    終端
    Stop()
    Do()

    View full-size slide

  39. セッションリークの検出方法 - 2 -
    ■ StopまたはDoメソッドを呼んでいるノードを取り除く
    ● ☆マークのついたノードをグラフから取り除く
    ● 残ったエッジを通って始端から終端までいけるか検証する
    ● 終端に行ける場合はセッションリークが発生する可能性がある
    39
    始端
    終端
    終端
    Stop()
    Do()

    View full-size slide

  40. 静的単一代入形式で分かること
    ■ コントロールフローグラフ
    ● 分岐や繰り返しの簡略化
    ● 処理の前後関係を追いやすい
    ● 有向グラフなのでグラフ理論のアルゴリズムが使える
    ■ 単一の代入であることが保証されている
    ● 同じ値に対する処理を見つけやすい
    ○ ある値のメソッドを呼び出しているか?
    ● https://github.com/gostaticanalysis/called
    40

    View full-size slide

  41. 静的単一代入形式で分からないこと
    ■ 公開された変数への代入
    ● 外部パッケージから変更される可能性がある
    ■ ポインタを介した変更
    ● どう変更されるか分からない
    ● unsafe.Pointerを用いるとポインタ演算されてしまう
    ■ インタフェースを介した処理
    ● どう代入されるか分からない
    ■ リフレクションを介した動作
    ● 動的に決まるので難しい
    41

    View full-size slide

  42. buildssaパッケージ
    ■ 静的単一代入形式を構築するAnalyzerを提供
    ● Analyzer.Requiresフィールドにbuildssa.Analyzer変数を指定
    ● SSA.SrcFuncsフィールドからソースコード中の関数を取得
    42
    func run(pass *analysis.Pass) (interface{}, error) {
    s := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
    for _, f := range s.SrcFuncs {
    fmt.Println(f)
    for _, b := range f.Blocks {
    fmt.Printf("\tBlock %d\n", b.Index)
    for _, instr := range b.Instrs {
    fmt.Printf("\t\t%[1]T\t%[1]v(%[1]p)\n", instr)
    for _, v := range instr.Operands(nil) {
    if v != nil { fmt.Printf("\t\t\t%[1]T\t%[1]v(%[1]p)\n", *v) }
    }
    }
    }
    }
    return nil, nil
    }
    type SSA struct {
    Pkg *ssa.Package
    SrcFuncs []*ssa.Function
    }

    View full-size slide

  43. コールグラフ
    ■ 関数の呼び出しグラフ
    ● golang.org/x/tools/go/callgraphで表される
    ● コールグラフを生成するアルゴリズムでいくつか種類がある
    ○ golang.org/x/tools/go/callgraph/static
    ○ golang.org/x/tools/go/callgraph/cha
    ○ golang.org/x/tools/go/callgraph/rta
    ○ golang.org/x/tools/go/callgraph/vta
    ○ golang.org/x/tools/go/pointer
    43
    main() f1() f2() f3()
    f4()

    View full-size slide

  44. コールグラフを表す型
    ■ golang.org/x/tools/go/callgraphで定義される
    44
    type Graph struct {
    Root *Node // ルートノード
    Nodes map[*ssa.Function]*Node // 関数との対応
    }
    type Node struct {
    Func *ssa.Function // どの関数に対応するか
    ID int // 0ベースの連番
    In []*Edge // ソートされてない入力エッジのスライス(n.In[*].Callee == n)
    Out []*Edge // ソートされてない出力エッジのスライス(n.Out[*].Caller == n)
    }
    type Edge struct {
    Caller *Node
    Site ssa.CallInstruction
    Callee *Node
    }

    View full-size slide

  45. アルゴリズムの違い 45
    アルゴリズム 説明 パッケージ
    静的取得 静的に決まる関数呼び出しのみ取得する x/tools/go/callgraph/static
    Andersenのアルゴリズム ポインタ解析とともに取得 x/tools/go/pointer
    Class Hierarchy Analysis (CHA) インタフェースを実装しているすべての方
    のメソッドにエッジを結ぶ
    x/tools/go/callgraph/cha
    Rapid Type Analysis (RTA) ポインタ解析のものより精度は低くなるが
    高速
    x/tools/go/callgraph/rta
    Variable Type Analysis (VTA) 変数を起点として解析を行う、精度は良い
    がコストが高い。
    x/tools/go/callgraph/vta
    参考:https://ben-holland.com/call-graph-construction-algorithms-explained/

    View full-size slide

  46. ポインタ解析 46
    ■ ポインタがどこを指しているのか解析する
    ● x/tools/go/pointerパッケージを用いる
    ● ポインタpがどの変数を指し示すのか
    ■ エスケープ解析の一部で利用される技術
    ● コンパイラ内部で行われる
    ● スタックまたはヒープに割り当てるべきか解析する
    func f() {
    var n int
    p := &n
    g(p)
    var m int
    g(&m)
    }
    func g(p *int) { /* 略 */ }

    View full-size slide

  47. ポインタ解析で分かること
    ■ どのポインタがどの変数を指すか
    ● ポインタから指し示される可能性のある変数を取得できる
    ■ インタフェースを介したメソッド呼び出しを追える
    ● 呼び出されたメソッドの実態を取得できる
    47
    type C struct{}
    func (C) m() { /* 略 */ }
    func f() {
    var i I = C{}
    g(i)
    }
    func g(i I) {
    i.m()
    }
    実態を辿ることができる

    View full-size slide

  48. ポインタ解析によるコールグラフ生成
    ■ ポインタ経由の関数呼び出しも取得できる
    ● poniter.Config構造体のBuildCallGraphフィールドをtrueにする
    ● pointer.Result構造体のCallGraphフィールドから取得できる
    48
    func callgraph(result *pointer.Result) {
    var edges []string
    callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error {
    caller := edge.Caller.Func
    if caller.Pkg == mainPkg {
    edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func))
    }
    return nil
    })
    sort.Strings(edges)
    for _, edge := range edges { fmt.Println(edge) }
    fmt.Println()
    }

    View full-size slide

  49. 型パラメタと静的解析
    ■ Go1.18から型パラメタが導入される
    ● 当然、goパッケージのアップデートが入る
    ○ https://github.com/golang/go/issues/47781
    ○ https://github.com/golang/go/issues/47916
    ○ 抽象構文木(AST)や型情報で型パラメタが扱える
    ■ 詳しくはmercari.go#18で話ます
    ● https://mercari.connpass.com/event/235116/

    View full-size slide

  50. まとめ
    ■ 静的解析は面白い
    ● ソースコードから知れることは多い
    ○ 静的解析 = 抽象構文木(AST)の解析だけではない
    ● リファクタリングなどにも使える
    ○ 大きい関数を洗い出す
    ● コードリーディングルールは静的解析で担保する

    View full-size slide