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 )
23
24 var Analyzer = &analysis.Analyzer{
25 Name: "ctrlflow",
26 Doc: "build a control-flow graph",
27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
28 Run: run,
29 ResultType: reflect.TypeOf(new(CFGs)),
30 FactTypes: []analysis.Fact{new(noReturn)},
31 Requires: []*analysis.Analyzer{inspect.Analyzer},
32 }
33
34
35 type noReturn struct{}
36
37 func (*noReturn) AFact() {}
38
39 func (*noReturn) String() string { return "noReturn" }
40
41
42
43 type CFGs struct {
44 defs map[*ast.Ident]types.Object
45 funcDecls map[*types.Func]*declInfo
46 funcLits map[*ast.FuncLit]*litInfo
47 pass *analysis.Pass
48 }
49
50
51
52
53
54
55
56 type declInfo struct {
57 decl *ast.FuncDecl
58 cfg *cfg.CFG
59 started bool
60 noReturn bool
61 }
62
63 type litInfo struct {
64 cfg *cfg.CFG
65 noReturn bool
66 }
67
68
69
70 func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG {
71 if decl.Body == nil {
72 return nil
73 }
74 fn := c.defs[decl.Name].(*types.Func)
75 return c.funcDecls[fn].cfg
76 }
77
78
79 func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG {
80 return c.funcLits[lit].cfg
81 }
82
83 func run(pass *analysis.Pass) (interface{}, error) {
84 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
85
86
87
88
89
90
91
92
93 funcDecls := make(map[*types.Func]*declInfo)
94 funcLits := make(map[*ast.FuncLit]*litInfo)
95
96 var decls []*types.Func
97 var lits []*ast.FuncLit
98
99 nodeFilter := []ast.Node{
100 (*ast.FuncDecl)(nil),
101 (*ast.FuncLit)(nil),
102 }
103 inspect.Preorder(nodeFilter, func(n ast.Node) {
104 switch n := n.(type) {
105 case *ast.FuncDecl:
106
107 if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
108 funcDecls[fn] = &declInfo{decl: n}
109 decls = append(decls, fn)
110 }
111 case *ast.FuncLit:
112 funcLits[n] = new(litInfo)
113 lits = append(lits, n)
114 }
115 })
116
117 c := &CFGs{
118 defs: pass.TypesInfo.Defs,
119 funcDecls: funcDecls,
120 funcLits: funcLits,
121 pass: pass,
122 }
123
124
125
126
127
128
129
130 for _, fn := range decls {
131 c.buildDecl(fn, funcDecls[fn])
132 }
133
134
135
136
137 for _, lit := range lits {
138 li := funcLits[lit]
139 if li.cfg == nil {
140 li.cfg = cfg.New(lit.Body, c.callMayReturn)
141 if !hasReachableReturn(li.cfg) {
142 li.noReturn = true
143 }
144 }
145 }
146
147
148 c.pass = nil
149
150 return c, nil
151 }
152
153
154 func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
155
156
157
158
159
160
161 if !di.started {
162 di.started = true
163
164 if isIntrinsicNoReturn(fn) {
165 di.noReturn = true
166 }
167 if di.decl.Body != nil {
168 di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
169 if !hasReachableReturn(di.cfg) {
170 di.noReturn = true
171 }
172 }
173 if di.noReturn {
174 c.pass.ExportObjectFact(fn, new(noReturn))
175 }
176
177
178 if false {
179 log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), di.noReturn)
180 }
181 }
182 }
183
184
185
186 func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
187 if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
188 return false
189 }
190
191
192
193
194
195
196 fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
197 if fn == nil {
198 return true
199 }
200
201
202 if di, ok := c.funcDecls[fn]; ok {
203 c.buildDecl(fn, di)
204 return !di.noReturn
205 }
206
207
208
209 return !c.pass.ImportObjectFact(fn, new(noReturn))
210 }
211
212 var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
213
214 func hasReachableReturn(g *cfg.CFG) bool {
215 for _, b := range g.Blocks {
216 if b.Live && b.Return() != nil {
217 return true
218 }
219 }
220 return false
221 }
222
223
224
225
226 func isIntrinsicNoReturn(fn *types.Func) bool {
227
228 path, name := fn.Pkg().Path(), fn.Name()
229 return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
230 path == "runtime" && name == "Goexit"
231 }
232
View as plain text