...
Source file src/pkg/cmd/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go
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 fn := pass.TypesInfo.Defs[n.Name].(*types.Func)
106 funcDecls[fn] = &declInfo{decl: n}
107 decls = append(decls, fn)
108
109 case *ast.FuncLit:
110 funcLits[n] = new(litInfo)
111 lits = append(lits, n)
112 }
113 })
114
115 c := &CFGs{
116 defs: pass.TypesInfo.Defs,
117 funcDecls: funcDecls,
118 funcLits: funcLits,
119 pass: pass,
120 }
121
122
123
124
125
126
127
128 for _, fn := range decls {
129 c.buildDecl(fn, funcDecls[fn])
130 }
131
132
133
134
135 for _, lit := range lits {
136 li := funcLits[lit]
137 if li.cfg == nil {
138 li.cfg = cfg.New(lit.Body, c.callMayReturn)
139 if !hasReachableReturn(li.cfg) {
140 li.noReturn = true
141 }
142 }
143 }
144
145
146 c.pass = nil
147
148 return c, nil
149 }
150
151
152 func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
153
154
155
156
157
158
159 if !di.started {
160 di.started = true
161
162 if isIntrinsicNoReturn(fn) {
163 di.noReturn = true
164 }
165 if di.decl.Body != nil {
166 di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
167 if !hasReachableReturn(di.cfg) {
168 di.noReturn = true
169 }
170 }
171 if di.noReturn {
172 c.pass.ExportObjectFact(fn, new(noReturn))
173 }
174
175
176 if false {
177 log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), di.noReturn)
178 }
179 }
180 }
181
182
183
184 func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
185 if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
186 return false
187 }
188
189
190 fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
191 if fn == nil {
192 return true
193 }
194
195
196 if di, ok := c.funcDecls[fn]; ok {
197 c.buildDecl(fn, di)
198 return !di.noReturn
199 }
200
201
202
203 return !c.pass.ImportObjectFact(fn, new(noReturn))
204 }
205
206 var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
207
208 func hasReachableReturn(g *cfg.CFG) bool {
209 for _, b := range g.Blocks {
210 if b.Live && b.Return() != nil {
211 return true
212 }
213 }
214 return false
215 }
216
217
218
219
220 func isIntrinsicNoReturn(fn *types.Func) bool {
221
222 path, name := fn.Pkg().Path(), fn.Name()
223 return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
224 path == "runtime" && name == "Goexit"
225 }
226
View as plain text