github.com/jd-ly/tools@v0.5.7/go/cfg/cfg.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package cfg constructs a simple control-flow graph (CFG) of the 6 // statements and expressions within a single function. 7 // 8 // Use cfg.New to construct the CFG for a function body. 9 // 10 // The blocks of the CFG contain all the function's non-control 11 // statements. The CFG does not contain control statements such as If, 12 // Switch, Select, and Branch, but does contain their subexpressions. 13 // For example, this source code: 14 // 15 // if x := f(); x != nil { 16 // T() 17 // } else { 18 // F() 19 // } 20 // 21 // produces this CFG: 22 // 23 // 1: x := f() 24 // x != nil 25 // succs: 2, 3 26 // 2: T() 27 // succs: 4 28 // 3: F() 29 // succs: 4 30 // 4: 31 // 32 // The CFG does contain Return statements; even implicit returns are 33 // materialized (at the position of the function's closing brace). 34 // 35 // The CFG does not record conditions associated with conditional branch 36 // edges, nor the short-circuit semantics of the && and || operators, 37 // nor abnormal control flow caused by panic. If you need this 38 // information, use github.com/jd-ly/tools/go/ssa instead. 39 // 40 package cfg 41 42 import ( 43 "bytes" 44 "fmt" 45 "go/ast" 46 "go/format" 47 "go/token" 48 ) 49 50 // A CFG represents the control-flow graph of a single function. 51 // 52 // The entry point is Blocks[0]; there may be multiple return blocks. 53 type CFG struct { 54 Blocks []*Block // block[0] is entry; order otherwise undefined 55 } 56 57 // A Block represents a basic block: a list of statements and 58 // expressions that are always evaluated sequentially. 59 // 60 // A block may have 0-2 successors: zero for a return block or a block 61 // that calls a function such as panic that never returns; one for a 62 // normal (jump) block; and two for a conditional (if) block. 63 type Block struct { 64 Nodes []ast.Node // statements, expressions, and ValueSpecs 65 Succs []*Block // successor nodes in the graph 66 Index int32 // index within CFG.Blocks 67 Live bool // block is reachable from entry 68 69 comment string // for debugging 70 succs2 [2]*Block // underlying array for Succs 71 } 72 73 // New returns a new control-flow graph for the specified function body, 74 // which must be non-nil. 75 // 76 // The CFG builder calls mayReturn to determine whether a given function 77 // call may return. For example, calls to panic, os.Exit, and log.Fatal 78 // do not return, so the builder can remove infeasible graph edges 79 // following such calls. The builder calls mayReturn only for a 80 // CallExpr beneath an ExprStmt. 81 func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { 82 b := builder{ 83 mayReturn: mayReturn, 84 cfg: new(CFG), 85 } 86 b.current = b.newBlock("entry") 87 b.stmt(body) 88 89 // Compute liveness (reachability from entry point), breadth-first. 90 q := make([]*Block, 0, len(b.cfg.Blocks)) 91 q = append(q, b.cfg.Blocks[0]) // entry point 92 for len(q) > 0 { 93 b := q[len(q)-1] 94 q = q[:len(q)-1] 95 96 if !b.Live { 97 b.Live = true 98 q = append(q, b.Succs...) 99 } 100 } 101 102 // Does control fall off the end of the function's body? 103 // Make implicit return explicit. 104 if b.current != nil && b.current.Live { 105 b.add(&ast.ReturnStmt{ 106 Return: body.End() - 1, 107 }) 108 } 109 110 return b.cfg 111 } 112 113 func (b *Block) String() string { 114 return fmt.Sprintf("block %d (%s)", b.Index, b.comment) 115 } 116 117 // Return returns the return statement at the end of this block if present, nil otherwise. 118 func (b *Block) Return() (ret *ast.ReturnStmt) { 119 if len(b.Nodes) > 0 { 120 ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt) 121 } 122 return 123 } 124 125 // Format formats the control-flow graph for ease of debugging. 126 func (g *CFG) Format(fset *token.FileSet) string { 127 var buf bytes.Buffer 128 for _, b := range g.Blocks { 129 fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment) 130 for _, n := range b.Nodes { 131 fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) 132 } 133 if len(b.Succs) > 0 { 134 fmt.Fprintf(&buf, "\tsuccs:") 135 for _, succ := range b.Succs { 136 fmt.Fprintf(&buf, " %d", succ.Index) 137 } 138 buf.WriteByte('\n') 139 } 140 buf.WriteByte('\n') 141 } 142 return buf.String() 143 } 144 145 func formatNode(fset *token.FileSet, n ast.Node) string { 146 var buf bytes.Buffer 147 format.Node(&buf, fset, n) 148 // Indent secondary lines by a tab. 149 return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1)) 150 }