1
2
3
4
5 package unreachable
6
7
8
9 import (
10 _ "embed"
11 "go/ast"
12 "go/token"
13 "log"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
18 "golang.org/x/tools/go/ast/inspector"
19 )
20
21
22 var doc string
23
24 var Analyzer = &analysis.Analyzer{
25 Name: "unreachable",
26 Doc: analysisutil.MustExtractDoc(doc, "unreachable"),
27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable",
28 Requires: []*analysis.Analyzer{inspect.Analyzer},
29 RunDespiteErrors: true,
30 Run: run,
31 }
32
33 func run(pass *analysis.Pass) (interface{}, error) {
34 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
35
36 nodeFilter := []ast.Node{
37 (*ast.FuncDecl)(nil),
38 (*ast.FuncLit)(nil),
39 }
40 inspect.Preorder(nodeFilter, func(n ast.Node) {
41 var body *ast.BlockStmt
42 switch n := n.(type) {
43 case *ast.FuncDecl:
44 body = n.Body
45 case *ast.FuncLit:
46 body = n.Body
47 }
48 if body == nil {
49 return
50 }
51 d := &deadState{
52 pass: pass,
53 hasBreak: make(map[ast.Stmt]bool),
54 hasGoto: make(map[string]bool),
55 labels: make(map[string]ast.Stmt),
56 }
57 d.findLabels(body)
58 d.reachable = true
59 d.findDead(body)
60 })
61 return nil, nil
62 }
63
64 type deadState struct {
65 pass *analysis.Pass
66 hasBreak map[ast.Stmt]bool
67 hasGoto map[string]bool
68 labels map[string]ast.Stmt
69 breakTarget ast.Stmt
70
71 reachable bool
72 }
73
74
75
76 func (d *deadState) findLabels(stmt ast.Stmt) {
77 switch x := stmt.(type) {
78 default:
79 log.Fatalf("%s: internal error in findLabels: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
80
81 case *ast.AssignStmt,
82 *ast.BadStmt,
83 *ast.DeclStmt,
84 *ast.DeferStmt,
85 *ast.EmptyStmt,
86 *ast.ExprStmt,
87 *ast.GoStmt,
88 *ast.IncDecStmt,
89 *ast.ReturnStmt,
90 *ast.SendStmt:
91
92
93 case *ast.BlockStmt:
94 for _, stmt := range x.List {
95 d.findLabels(stmt)
96 }
97
98 case *ast.BranchStmt:
99 switch x.Tok {
100 case token.GOTO:
101 if x.Label != nil {
102 d.hasGoto[x.Label.Name] = true
103 }
104
105 case token.BREAK:
106 stmt := d.breakTarget
107 if x.Label != nil {
108 stmt = d.labels[x.Label.Name]
109 }
110 if stmt != nil {
111 d.hasBreak[stmt] = true
112 }
113 }
114
115 case *ast.IfStmt:
116 d.findLabels(x.Body)
117 if x.Else != nil {
118 d.findLabels(x.Else)
119 }
120
121 case *ast.LabeledStmt:
122 d.labels[x.Label.Name] = x.Stmt
123 d.findLabels(x.Stmt)
124
125
126
127
128 case *ast.ForStmt:
129 outer := d.breakTarget
130 d.breakTarget = x
131 d.findLabels(x.Body)
132 d.breakTarget = outer
133
134 case *ast.RangeStmt:
135 outer := d.breakTarget
136 d.breakTarget = x
137 d.findLabels(x.Body)
138 d.breakTarget = outer
139
140 case *ast.SelectStmt:
141 outer := d.breakTarget
142 d.breakTarget = x
143 d.findLabels(x.Body)
144 d.breakTarget = outer
145
146 case *ast.SwitchStmt:
147 outer := d.breakTarget
148 d.breakTarget = x
149 d.findLabels(x.Body)
150 d.breakTarget = outer
151
152 case *ast.TypeSwitchStmt:
153 outer := d.breakTarget
154 d.breakTarget = x
155 d.findLabels(x.Body)
156 d.breakTarget = outer
157
158 case *ast.CommClause:
159 for _, stmt := range x.Body {
160 d.findLabels(stmt)
161 }
162
163 case *ast.CaseClause:
164 for _, stmt := range x.Body {
165 d.findLabels(stmt)
166 }
167 }
168 }
169
170
171
172
173
174 func (d *deadState) findDead(stmt ast.Stmt) {
175
176
177
178
179
180
181
182 if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
183 d.reachable = true
184 }
185
186 if !d.reachable {
187 switch stmt.(type) {
188 case *ast.EmptyStmt:
189
190 default:
191 d.pass.Report(analysis.Diagnostic{
192 Pos: stmt.Pos(),
193 End: stmt.End(),
194 Message: "unreachable code",
195 SuggestedFixes: []analysis.SuggestedFix{{
196 Message: "Remove",
197 TextEdits: []analysis.TextEdit{{
198 Pos: stmt.Pos(),
199 End: stmt.End(),
200 }},
201 }},
202 })
203 d.reachable = true
204 }
205 }
206
207 switch x := stmt.(type) {
208 default:
209 log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
210
211 case *ast.AssignStmt,
212 *ast.BadStmt,
213 *ast.DeclStmt,
214 *ast.DeferStmt,
215 *ast.EmptyStmt,
216 *ast.GoStmt,
217 *ast.IncDecStmt,
218 *ast.SendStmt:
219
220
221 case *ast.BlockStmt:
222 for _, stmt := range x.List {
223 d.findDead(stmt)
224 }
225
226 case *ast.BranchStmt:
227 switch x.Tok {
228 case token.BREAK, token.GOTO, token.FALLTHROUGH:
229 d.reachable = false
230 case token.CONTINUE:
231
232
233
234
235
236
237 d.reachable = false
238 }
239
240 case *ast.ExprStmt:
241
242 call, ok := x.X.(*ast.CallExpr)
243 if ok {
244 name, ok := call.Fun.(*ast.Ident)
245 if ok && name.Name == "panic" && name.Obj == nil {
246 d.reachable = false
247 }
248 }
249
250 case *ast.ForStmt:
251 d.findDead(x.Body)
252 d.reachable = x.Cond != nil || d.hasBreak[x]
253
254 case *ast.IfStmt:
255 d.findDead(x.Body)
256 if x.Else != nil {
257 r := d.reachable
258 d.reachable = true
259 d.findDead(x.Else)
260 d.reachable = d.reachable || r
261 } else {
262
263 d.reachable = true
264 }
265
266 case *ast.LabeledStmt:
267 d.findDead(x.Stmt)
268
269 case *ast.RangeStmt:
270 d.findDead(x.Body)
271 d.reachable = true
272
273 case *ast.ReturnStmt:
274 d.reachable = false
275
276 case *ast.SelectStmt:
277
278
279
280
281
282 anyReachable := false
283 for _, comm := range x.Body.List {
284 d.reachable = true
285 for _, stmt := range comm.(*ast.CommClause).Body {
286 d.findDead(stmt)
287 }
288 anyReachable = anyReachable || d.reachable
289 }
290 d.reachable = anyReachable || d.hasBreak[x]
291
292 case *ast.SwitchStmt:
293 anyReachable := false
294 hasDefault := false
295 for _, cas := range x.Body.List {
296 cc := cas.(*ast.CaseClause)
297 if cc.List == nil {
298 hasDefault = true
299 }
300 d.reachable = true
301 for _, stmt := range cc.Body {
302 d.findDead(stmt)
303 }
304 anyReachable = anyReachable || d.reachable
305 }
306 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
307
308 case *ast.TypeSwitchStmt:
309 anyReachable := false
310 hasDefault := false
311 for _, cas := range x.Body.List {
312 cc := cas.(*ast.CaseClause)
313 if cc.List == nil {
314 hasDefault = true
315 }
316 d.reachable = true
317 for _, stmt := range cc.Body {
318 d.findDead(stmt)
319 }
320 anyReachable = anyReachable || d.reachable
321 }
322 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
323 }
324 }
325
View as plain text