github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/ctrlflow/ctrlflow.go (about) 1 // Copyright 2018 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 ctrlflow is an analysis that provides a syntactic 6 // control-flow graph (CFG) for the body of a function. 7 // It records whether a function cannot return. 8 // By itself, it does not report any diagnostics. 9 package ctrlflow 10 11 import ( 12 "go/ast" 13 "go/types" 14 "log" 15 "reflect" 16 17 "golang.org/x/tools/go/analysis" 18 "golang.org/x/tools/go/analysis/passes/inspect" 19 "golang.org/x/tools/go/ast/inspector" 20 "golang.org/x/tools/go/cfg" 21 "golang.org/x/tools/go/types/typeutil" 22 ) 23 24 var Analyzer = &analysis.Analyzer{ 25 Name: "ctrlflow", 26 Doc: "build a control-flow graph", 27 Run: run, 28 ResultType: reflect.TypeOf(new(CFGs)), 29 FactTypes: []analysis.Fact{new(noReturn)}, 30 Requires: []*analysis.Analyzer{inspect.Analyzer}, 31 } 32 33 // noReturn is a fact indicating that a function does not return. 34 type noReturn struct{} 35 36 func (*noReturn) AFact() {} 37 38 func (*noReturn) String() string { return "noReturn" } 39 40 // A CFGs holds the control-flow graphs 41 // for all the functions of the current package. 42 type CFGs struct { 43 defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs 44 funcDecls map[*types.Func]*declInfo 45 funcLits map[*ast.FuncLit]*litInfo 46 pass *analysis.Pass // transient; nil after construction 47 } 48 49 // CFGs has two maps: funcDecls for named functions and funcLits for 50 // unnamed ones. Unlike funcLits, the funcDecls map is not keyed by its 51 // syntax node, *ast.FuncDecl, because callMayReturn needs to do a 52 // look-up by *types.Func, and you can get from an *ast.FuncDecl to a 53 // *types.Func but not the other way. 54 55 type declInfo struct { 56 decl *ast.FuncDecl 57 cfg *cfg.CFG // iff decl.Body != nil 58 started bool // to break cycles 59 noReturn bool 60 } 61 62 type litInfo struct { 63 cfg *cfg.CFG 64 noReturn bool 65 } 66 67 // FuncDecl returns the control-flow graph for a named function. 68 // It returns nil if decl.Body==nil. 69 func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG { 70 if decl.Body == nil { 71 return nil 72 } 73 fn := c.defs[decl.Name].(*types.Func) 74 return c.funcDecls[fn].cfg 75 } 76 77 // FuncLit returns the control-flow graph for a literal function. 78 func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG { 79 return c.funcLits[lit].cfg 80 } 81 82 func run(pass *analysis.Pass) (interface{}, error) { 83 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 84 85 // Because CFG construction consumes and produces noReturn 86 // facts, CFGs for exported FuncDecls must be built before 'run' 87 // returns; we cannot construct them lazily. 88 // (We could build CFGs for FuncLits lazily, 89 // but the benefit is marginal.) 90 91 // Pass 1. Map types.Funcs to ast.FuncDecls in this package. 92 funcDecls := make(map[*types.Func]*declInfo) // functions and methods 93 funcLits := make(map[*ast.FuncLit]*litInfo) 94 95 var decls []*types.Func // keys(funcDecls), in order 96 var lits []*ast.FuncLit // keys(funcLits), in order 97 98 nodeFilter := []ast.Node{ 99 (*ast.FuncDecl)(nil), 100 (*ast.FuncLit)(nil), 101 } 102 inspect.Preorder(nodeFilter, func(n ast.Node) { 103 switch n := n.(type) { 104 case *ast.FuncDecl: 105 // Type information may be incomplete. 106 if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok { 107 funcDecls[fn] = &declInfo{decl: n} 108 decls = append(decls, fn) 109 } 110 case *ast.FuncLit: 111 funcLits[n] = new(litInfo) 112 lits = append(lits, n) 113 } 114 }) 115 116 c := &CFGs{ 117 defs: pass.TypesInfo.Defs, 118 funcDecls: funcDecls, 119 funcLits: funcLits, 120 pass: pass, 121 } 122 123 // Pass 2. Build CFGs. 124 125 // Build CFGs for named functions. 126 // Cycles in the static call graph are broken 127 // arbitrarily but deterministically. 128 // We create noReturn facts as discovered. 129 for _, fn := range decls { 130 c.buildDecl(fn, funcDecls[fn]) 131 } 132 133 // Build CFGs for literal functions. 134 // These aren't relevant to facts (since they aren't named) 135 // but are required for the CFGs.FuncLit API. 136 for _, lit := range lits { 137 li := funcLits[lit] 138 if li.cfg == nil { 139 li.cfg = cfg.New(lit.Body, c.callMayReturn) 140 if !hasReachableReturn(li.cfg) { 141 li.noReturn = true 142 } 143 } 144 } 145 146 // All CFGs are now built. 147 c.pass = nil 148 149 return c, nil 150 } 151 152 // di.cfg may be nil on return. 153 func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) { 154 // buildDecl may call itself recursively for the same function, 155 // because cfg.New is passed the callMayReturn method, which 156 // builds the CFG of the callee, leading to recursion. 157 // The buildDecl call tree thus resembles the static call graph. 158 // We mark each node when we start working on it to break cycles. 159 160 if !di.started { // break cycle 161 di.started = true 162 163 if isIntrinsicNoReturn(fn) { 164 di.noReturn = true 165 } 166 if di.decl.Body != nil { 167 di.cfg = cfg.New(di.decl.Body, c.callMayReturn) 168 if !hasReachableReturn(di.cfg) { 169 di.noReturn = true 170 } 171 } 172 if di.noReturn { 173 c.pass.ExportObjectFact(fn, new(noReturn)) 174 } 175 176 // debugging 177 if false { 178 log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), di.noReturn) 179 } 180 } 181 } 182 183 // callMayReturn reports whether the called function may return. 184 // It is passed to the CFG builder. 185 func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) { 186 if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin { 187 return false // panic never returns 188 } 189 190 // Is this a static call? Also includes static functions 191 // parameterized by a type. Such functions may or may not 192 // return depending on the parameter type, but in some 193 // cases the answer is definite. We let ctrlflow figure 194 // that out. 195 fn := typeutil.StaticCallee(c.pass.TypesInfo, call) 196 if fn == nil { 197 return true // callee not statically known; be conservative 198 } 199 200 // Function or method declared in this package? 201 if di, ok := c.funcDecls[fn]; ok { 202 c.buildDecl(fn, di) 203 return !di.noReturn 204 } 205 206 // Not declared in this package. 207 // Is there a fact from another package? 208 return !c.pass.ImportObjectFact(fn, new(noReturn)) 209 } 210 211 var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin) 212 213 func hasReachableReturn(g *cfg.CFG) bool { 214 for _, b := range g.Blocks { 215 if b.Live && b.Return() != nil { 216 return true 217 } 218 } 219 return false 220 } 221 222 // isIntrinsicNoReturn reports whether a function intrinsically never 223 // returns because it stops execution of the calling thread. 224 // It is the base case in the recursion. 225 func isIntrinsicNoReturn(fn *types.Func) bool { 226 // Add functions here as the need arises, but don't allocate memory. 227 path, name := fn.Pkg().Path(), fn.Name() 228 return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") || 229 path == "runtime" && name == "Goexit" 230 }