1
2
3
4
5
6
7
8
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 "golang.org/x/tools/internal/typesinternal"
23 )
24
25 var Analyzer = &analysis.Analyzer{
26 Name: "ctrlflow",
27 Doc: "build a control-flow graph",
28 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
29 Run: run,
30 ResultType: reflect.TypeFor[*CFGs](),
31 FactTypes: []analysis.Fact{new(noReturn)},
32 Requires: []*analysis.Analyzer{inspect.Analyzer},
33 }
34
35
36 type noReturn struct{}
37
38 func (*noReturn) AFact() {}
39
40 func (*noReturn) String() string { return "noReturn" }
41
42
43
44 type CFGs struct {
45 defs map[*ast.Ident]types.Object
46 funcDecls map[*types.Func]*declInfo
47 funcLits map[*ast.FuncLit]*litInfo
48 noReturn map[*types.Func]bool
49 pass *analysis.Pass
50 }
51
52
53
54
55
56
57
58
59
60
61 func (c *CFGs) NoReturn(fn *types.Func) bool {
62 return c.noReturn[fn]
63 }
64
65
66
67
68
69
70
71 type declInfo struct {
72 decl *ast.FuncDecl
73 cfg *cfg.CFG
74 started bool
75 }
76
77 type litInfo struct {
78 cfg *cfg.CFG
79 noReturn bool
80 }
81
82
83
84 func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG {
85 if decl.Body == nil {
86 return nil
87 }
88 fn := c.defs[decl.Name].(*types.Func)
89 return c.funcDecls[fn].cfg
90 }
91
92
93 func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG {
94 return c.funcLits[lit].cfg
95 }
96
97 func run(pass *analysis.Pass) (any, error) {
98 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
99
100
101
102
103
104
105
106
107 funcDecls := make(map[*types.Func]*declInfo)
108 funcLits := make(map[*ast.FuncLit]*litInfo)
109
110 var decls []*types.Func
111 var lits []*ast.FuncLit
112
113 nodeFilter := []ast.Node{
114 (*ast.FuncDecl)(nil),
115 (*ast.FuncLit)(nil),
116 }
117 inspect.Preorder(nodeFilter, func(n ast.Node) {
118 switch n := n.(type) {
119 case *ast.FuncDecl:
120
121 if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
122 funcDecls[fn] = &declInfo{decl: n}
123 decls = append(decls, fn)
124 }
125 case *ast.FuncLit:
126 funcLits[n] = new(litInfo)
127 lits = append(lits, n)
128 }
129 })
130
131 c := &CFGs{
132 defs: pass.TypesInfo.Defs,
133 funcDecls: funcDecls,
134 funcLits: funcLits,
135 noReturn: make(map[*types.Func]bool),
136 pass: pass,
137 }
138
139
140
141
142
143
144
145 for _, fn := range decls {
146 c.buildDecl(fn, funcDecls[fn])
147 }
148
149
150
151
152 for _, lit := range lits {
153 li := funcLits[lit]
154 if li.cfg == nil {
155 li.cfg = cfg.New(lit.Body, c.callMayReturn)
156 if li.cfg.NoReturn() {
157 li.noReturn = true
158 }
159 }
160 }
161
162
163 c.pass = nil
164
165 return c, nil
166 }
167
168
169 func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
170
171
172
173
174
175
176 if di.started {
177 return
178 }
179 di.started = true
180
181 noreturn, known := knownIntrinsic(fn)
182 if !known {
183 if di.decl.Body != nil {
184 di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
185 if di.cfg.NoReturn() {
186 noreturn = true
187 }
188 }
189 }
190 if noreturn {
191 c.pass.ExportObjectFact(fn, new(noReturn))
192 c.noReturn[fn] = true
193 }
194
195
196 if false {
197 log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), noreturn)
198 }
199 }
200
201
202
203 func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
204 if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
205 return false
206 }
207
208
209
210
211
212
213 fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
214 if fn == nil {
215 return true
216 }
217
218
219 if di, ok := c.funcDecls[fn]; ok {
220 c.buildDecl(fn, di)
221 return !c.noReturn[fn]
222 }
223
224
225
226 if c.pass.ImportObjectFact(fn, new(noReturn)) {
227 c.noReturn[fn] = true
228 return false
229 }
230
231 return true
232 }
233
234 var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
235
236
237
238
239
240
241
242 func knownIntrinsic(fn *types.Func) (noreturn, known bool) {
243
244
245
246 if typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") ||
247 typesinternal.IsFunctionNamed(fn, "runtime", "Goexit", "fatalthrow", "fatalpanic", "exit") ||
248
249
250
251
252 typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "Logger", "Fatal", "Panic") ||
253 typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "SugaredLogger", "Fatal", "Fatalw", "Fatalf", "Panic", "Panicw", "Panicf") ||
254 typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Logger", "Exit", "Panic", "Panicf", "Panicln") ||
255 typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Entry", "Panicf", "Panicln") ||
256 typesinternal.IsFunctionNamed(fn, "k8s.io/klog", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") ||
257 typesinternal.IsFunctionNamed(fn, "k8s.io/klog/v2", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") {
258 return true, true
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272 if typesinternal.IsFunctionNamed(fn, "internal/abi", "EscapeNonString") ||
273 typesinternal.IsFunctionNamed(fn, "hash/maphash", "Comparable") {
274 return false, true
275 }
276
277 return
278 }
279
View as plain text