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/analysis/passes/internal/ctrlflowinternal"
20 "golang.org/x/tools/go/ast/inspector"
21 "golang.org/x/tools/go/cfg"
22 "golang.org/x/tools/go/types/typeutil"
23 "golang.org/x/tools/internal/cfginternal"
24 "golang.org/x/tools/internal/typesinternal"
25 )
26
27 var Analyzer = &analysis.Analyzer{
28 Name: "ctrlflow",
29 Doc: "build a control-flow graph",
30 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
31 Run: run,
32 ResultType: reflect.TypeFor[*CFGs](),
33 FactTypes: []analysis.Fact{new(noReturn)},
34 Requires: []*analysis.Analyzer{inspect.Analyzer},
35 }
36
37
38 type noReturn struct{}
39
40 func (*noReturn) AFact() {}
41
42 func (*noReturn) String() string { return "noReturn" }
43
44
45
46 type CFGs struct {
47 defs map[*ast.Ident]types.Object
48 funcDecls map[*types.Func]*declInfo
49 funcLits map[*ast.FuncLit]*litInfo
50 noReturn map[*types.Func]bool
51 pass *analysis.Pass
52 }
53
54
55 func (c *CFGs) isNoReturn(fn *types.Func) bool {
56 return c.noReturn[fn]
57 }
58
59 func init() {
60
61 ctrlflowinternal.NoReturn = func(c any, fn *types.Func) bool {
62 return c.(*CFGs).isNoReturn(fn)
63 }
64 }
65
66
67
68
69
70
71
72 type declInfo struct {
73 decl *ast.FuncDecl
74 cfg *cfg.CFG
75 started bool
76 }
77
78 type litInfo struct {
79 cfg *cfg.CFG
80 noReturn bool
81 }
82
83
84
85 func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG {
86 if decl.Body == nil {
87 return nil
88 }
89 fn := c.defs[decl.Name].(*types.Func)
90 return c.funcDecls[fn].cfg
91 }
92
93
94 func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG {
95 return c.funcLits[lit].cfg
96 }
97
98 func run(pass *analysis.Pass) (any, error) {
99 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
100
101
102
103
104
105
106
107
108 funcDecls := make(map[*types.Func]*declInfo)
109 funcLits := make(map[*ast.FuncLit]*litInfo)
110
111 var decls []*types.Func
112 var lits []*ast.FuncLit
113
114 nodeFilter := []ast.Node{
115 (*ast.FuncDecl)(nil),
116 (*ast.FuncLit)(nil),
117 }
118 inspect.Preorder(nodeFilter, func(n ast.Node) {
119 switch n := n.(type) {
120 case *ast.FuncDecl:
121
122 if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
123 funcDecls[fn] = &declInfo{decl: n}
124 decls = append(decls, fn)
125 }
126 case *ast.FuncLit:
127 funcLits[n] = new(litInfo)
128 lits = append(lits, n)
129 }
130 })
131
132 c := &CFGs{
133 defs: pass.TypesInfo.Defs,
134 funcDecls: funcDecls,
135 funcLits: funcLits,
136 noReturn: make(map[*types.Func]bool),
137 pass: pass,
138 }
139
140
141
142
143
144
145
146 for _, fn := range decls {
147 c.buildDecl(fn, funcDecls[fn])
148 }
149
150
151
152
153 for _, lit := range lits {
154 li := funcLits[lit]
155 if li.cfg == nil {
156 li.cfg = cfg.New(lit.Body, c.callMayReturn)
157 if cfginternal.IsNoReturn(li.cfg) {
158 li.noReturn = true
159 }
160 }
161 }
162
163
164 c.pass = nil
165
166 return c, nil
167 }
168
169
170 func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
171
172
173
174
175
176
177 if di.started {
178 return
179 }
180 di.started = true
181
182 noreturn := isIntrinsicNoReturn(fn)
183
184 if di.decl.Body != nil {
185 di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
186 if cfginternal.IsNoReturn(di.cfg) {
187 noreturn = true
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 func isIntrinsicNoReturn(fn *types.Func) bool {
240
241 return typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") ||
242 typesinternal.IsFunctionNamed(fn, "runtime", "Goexit")
243 }
244
View as plain text