...
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 Run: run,
28 ResultType: reflect.TypeOf(new(CFGs)),
29 FactTypes: []analysis.Fact{new(noReturn)},
30 Requires: []*analysis.Analyzer{inspect.Analyzer},
31 }
32
33
34 type noReturn struct{}
35
36 func (*noReturn) AFact() {}
37
38 func (*noReturn) String() string { return "noReturn" }
39
40
41
42 type CFGs struct {
43 defs map[*ast.Ident]types.Object
44 funcDecls map[*types.Func]*declInfo
45 funcLits map[*ast.FuncLit]*litInfo
46 pass *analysis.Pass
47 }
48
49
50
51
52
53
54
55 type declInfo struct {
56 decl *ast.FuncDecl
57 cfg *cfg.CFG
58 started bool
59 noReturn bool
60 }
61
62 type litInfo struct {
63 cfg *cfg.CFG
64 noReturn bool
65 }
66
67
68
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
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
86
87
88
89
90
91
92 funcDecls := make(map[*types.Func]*declInfo)
93 funcLits := make(map[*ast.FuncLit]*litInfo)
94
95 var decls []*types.Func
96 var lits []*ast.FuncLit
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
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
124
125
126
127
128
129 for _, fn := range decls {
130 c.buildDecl(fn, funcDecls[fn])
131 }
132
133
134
135
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
147 c.pass = nil
148
149 return c, nil
150 }
151
152
153 func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
154
155
156
157
158
159
160 if !di.started {
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
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
184
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
188 }
189
190
191
192
193
194
195 fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
196 if fn == nil {
197 return true
198 }
199
200
201 if di, ok := c.funcDecls[fn]; ok {
202 c.buildDecl(fn, di)
203 return !di.noReturn
204 }
205
206
207
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
223
224
225 func isIntrinsicNoReturn(fn *types.Func) bool {
226
227 path, name := fn.Pkg().Path(), fn.Name()
228 return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
229 path == "runtime" && name == "Goexit"
230 }
231
View as plain text