Source file src/cmd/vendor/golang.org/x/tools/go/cfg/cfg.go
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 // also, each block records the control statement (Block.Stmt) that 14 // gave rise to it and its relationship (Block.Kind) to that statement. 15 // 16 // For example, this source code: 17 // 18 // if x := f(); x != nil { 19 // T() 20 // } else { 21 // F() 22 // } 23 // 24 // produces this CFG: 25 // 26 // 1: x := f() Body 27 // x != nil 28 // succs: 2, 3 29 // 2: T() IfThen 30 // succs: 4 31 // 3: F() IfElse 32 // succs: 4 33 // 4: IfDone 34 // 35 // The CFG does contain Return statements; even implicit returns are 36 // materialized (at the position of the function's closing brace). 37 // 38 // The CFG does not record conditions associated with conditional branch 39 // edges, nor the short-circuit semantics of the && and || operators, 40 // nor abnormal control flow caused by panic. If you need this 41 // information, use golang.org/x/tools/go/ssa instead. 42 package cfg 43 44 import ( 45 "bytes" 46 "fmt" 47 "go/ast" 48 "go/format" 49 "go/token" 50 ) 51 52 // A CFG represents the control-flow graph of a single function. 53 // 54 // The entry point is Blocks[0]; there may be multiple return blocks. 55 type CFG struct { 56 fset *token.FileSet 57 Blocks []*Block // block[0] is entry; order otherwise undefined 58 } 59 60 // A Block represents a basic block: a list of statements and 61 // expressions that are always evaluated sequentially. 62 // 63 // A block may have 0-2 successors: zero for a return block or a block 64 // that calls a function such as panic that never returns; one for a 65 // normal (jump) block; and two for a conditional (if) block. 66 type Block struct { 67 Nodes []ast.Node // statements, expressions, and ValueSpecs 68 Succs []*Block // successor nodes in the graph 69 Index int32 // index within CFG.Blocks 70 Live bool // block is reachable from entry 71 Kind BlockKind // block kind 72 Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details) 73 74 succs2 [2]*Block // underlying array for Succs 75 } 76 77 // A BlockKind identifies the purpose of a block. 78 // It also determines the possible types of its Stmt field. 79 type BlockKind uint8 80 81 const ( 82 KindInvalid BlockKind = iota // Stmt=nil 83 84 KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt 85 KindBody // function body BlockStmt 86 KindForBody // body of ForStmt 87 KindForDone // block after ForStmt 88 KindForLoop // head of ForStmt 89 KindForPost // post condition of ForStmt 90 KindIfDone // block after IfStmt 91 KindIfElse // else block of IfStmt 92 KindIfThen // then block of IfStmt 93 KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label) 94 KindRangeBody // body of RangeStmt 95 KindRangeDone // block after RangeStmt 96 KindRangeLoop // head of RangeStmt 97 KindSelectCaseBody // body of SelectStmt 98 KindSelectDone // block after SelectStmt 99 KindSelectAfterCase // block after a CommClause 100 KindSwitchCaseBody // body of CaseClause 101 KindSwitchDone // block after {Type.}SwitchStmt 102 KindSwitchNextCase // secondary expression of a multi-expression CaseClause 103 ) 104 105 func (kind BlockKind) String() string { 106 return [...]string{ 107 KindInvalid: "Invalid", 108 KindUnreachable: "Unreachable", 109 KindBody: "Body", 110 KindForBody: "ForBody", 111 KindForDone: "ForDone", 112 KindForLoop: "ForLoop", 113 KindForPost: "ForPost", 114 KindIfDone: "IfDone", 115 KindIfElse: "IfElse", 116 KindIfThen: "IfThen", 117 KindLabel: "Label", 118 KindRangeBody: "RangeBody", 119 KindRangeDone: "RangeDone", 120 KindRangeLoop: "RangeLoop", 121 KindSelectCaseBody: "SelectCaseBody", 122 KindSelectDone: "SelectDone", 123 KindSelectAfterCase: "SelectAfterCase", 124 KindSwitchCaseBody: "SwitchCaseBody", 125 KindSwitchDone: "SwitchDone", 126 KindSwitchNextCase: "SwitchNextCase", 127 }[kind] 128 } 129 130 // New returns a new control-flow graph for the specified function body, 131 // which must be non-nil. 132 // 133 // The CFG builder calls mayReturn to determine whether a given function 134 // call may return. For example, calls to panic, os.Exit, and log.Fatal 135 // do not return, so the builder can remove infeasible graph edges 136 // following such calls. The builder calls mayReturn only for a 137 // CallExpr beneath an ExprStmt. 138 func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { 139 b := builder{ 140 mayReturn: mayReturn, 141 cfg: new(CFG), 142 } 143 b.current = b.newBlock(KindBody, body) 144 b.stmt(body) 145 146 // Compute liveness (reachability from entry point), breadth-first. 147 q := make([]*Block, 0, len(b.cfg.Blocks)) 148 q = append(q, b.cfg.Blocks[0]) // entry point 149 for len(q) > 0 { 150 b := q[len(q)-1] 151 q = q[:len(q)-1] 152 153 if !b.Live { 154 b.Live = true 155 q = append(q, b.Succs...) 156 } 157 } 158 159 // Does control fall off the end of the function's body? 160 // Make implicit return explicit. 161 if b.current != nil && b.current.Live { 162 b.add(&ast.ReturnStmt{ 163 Return: body.End() - 1, 164 }) 165 } 166 167 return b.cfg 168 } 169 170 func (b *Block) String() string { 171 return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil)) 172 } 173 174 func (b *Block) comment(fset *token.FileSet) string { 175 s := b.Kind.String() 176 if fset != nil && b.Stmt != nil { 177 s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line) 178 } 179 return s 180 } 181 182 // Return returns the return statement at the end of this block if present, nil 183 // otherwise. 184 // 185 // When control falls off the end of the function, the ReturnStmt is synthetic 186 // and its [ast.Node.End] position may be beyond the end of the file. 187 func (b *Block) Return() (ret *ast.ReturnStmt) { 188 if len(b.Nodes) > 0 { 189 ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt) 190 } 191 return 192 } 193 194 // Format formats the control-flow graph for ease of debugging. 195 func (g *CFG) Format(fset *token.FileSet) string { 196 var buf bytes.Buffer 197 for _, b := range g.Blocks { 198 fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset)) 199 for _, n := range b.Nodes { 200 fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) 201 } 202 if len(b.Succs) > 0 { 203 fmt.Fprintf(&buf, "\tsuccs:") 204 for _, succ := range b.Succs { 205 fmt.Fprintf(&buf, " %d", succ.Index) 206 } 207 buf.WriteByte('\n') 208 } 209 buf.WriteByte('\n') 210 } 211 return buf.String() 212 } 213 214 // Dot returns the control-flow graph in the [Dot graph description language]. 215 // Use a command such as 'dot -Tsvg' to render it in a form viewable in a browser. 216 // This method is provided as a debugging aid; the details of the 217 // output are unspecified and may change. 218 // 219 // [Dot graph description language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) 220 func (g *CFG) Dot(fset *token.FileSet) string { 221 var buf bytes.Buffer 222 buf.WriteString("digraph CFG {\n") 223 buf.WriteString(" node [shape=box];\n") 224 for _, b := range g.Blocks { 225 // node label 226 var text bytes.Buffer 227 text.WriteString(b.comment(fset)) 228 for _, n := range b.Nodes { 229 fmt.Fprintf(&text, "\n%s", formatNode(fset, n)) 230 } 231 232 // node and edges 233 fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text) 234 for _, succ := range b.Succs { 235 fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index) 236 } 237 } 238 buf.WriteString("}\n") 239 return buf.String() 240 } 241 242 func formatNode(fset *token.FileSet, n ast.Node) string { 243 var buf bytes.Buffer 244 format.Node(&buf, fset, n) 245 // Indent secondary lines by a tab. 246 return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1)) 247 } 248