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) (any, 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
192
193
194 d.pass.Report(analysis.Diagnostic{
195 Pos: stmt.Pos(),
196 End: stmt.End(),
197 Message: "unreachable code",
198 SuggestedFixes: []analysis.SuggestedFix{{
199 Message: "Remove",
200 TextEdits: []analysis.TextEdit{{
201 Pos: stmt.Pos(),
202 End: stmt.End(),
203 }},
204 }},
205 })
206 d.reachable = true
207 }
208 }
209
210 switch x := stmt.(type) {
211 default:
212 log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
213
214 case *ast.AssignStmt,
215 *ast.BadStmt,
216 *ast.DeclStmt,
217 *ast.DeferStmt,
218 *ast.EmptyStmt,
219 *ast.GoStmt,
220 *ast.IncDecStmt,
221 *ast.SendStmt:
222
223
224 case *ast.BlockStmt:
225 for _, stmt := range x.List {
226 d.findDead(stmt)
227 }
228
229 case *ast.BranchStmt:
230 switch x.Tok {
231 case token.BREAK, token.GOTO, token.FALLTHROUGH:
232 d.reachable = false
233 case token.CONTINUE:
234
235
236
237
238
239
240 d.reachable = false
241 }
242
243 case *ast.ExprStmt:
244
245 call, ok := x.X.(*ast.CallExpr)
246 if ok {
247 name, ok := call.Fun.(*ast.Ident)
248 if ok && name.Name == "panic" && name.Obj == nil {
249 d.reachable = false
250 }
251 }
252
253 case *ast.ForStmt:
254 d.findDead(x.Body)
255 d.reachable = x.Cond != nil || d.hasBreak[x]
256
257 case *ast.IfStmt:
258 d.findDead(x.Body)
259 if x.Else != nil {
260 r := d.reachable
261 d.reachable = true
262 d.findDead(x.Else)
263 d.reachable = d.reachable || r
264 } else {
265
266 d.reachable = true
267 }
268
269 case *ast.LabeledStmt:
270 d.findDead(x.Stmt)
271
272 case *ast.RangeStmt:
273 d.findDead(x.Body)
274 d.reachable = true
275
276 case *ast.ReturnStmt:
277 d.reachable = false
278
279 case *ast.SelectStmt:
280
281
282
283
284
285 anyReachable := false
286 for _, comm := range x.Body.List {
287 d.reachable = true
288 for _, stmt := range comm.(*ast.CommClause).Body {
289 d.findDead(stmt)
290 }
291 anyReachable = anyReachable || d.reachable
292 }
293 d.reachable = anyReachable || d.hasBreak[x]
294
295 case *ast.SwitchStmt:
296 anyReachable := false
297 hasDefault := false
298 for _, cas := range x.Body.List {
299 cc := cas.(*ast.CaseClause)
300 if cc.List == nil {
301 hasDefault = true
302 }
303 d.reachable = true
304 for _, stmt := range cc.Body {
305 d.findDead(stmt)
306 }
307 anyReachable = anyReachable || d.reachable
308 }
309 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
310
311 case *ast.TypeSwitchStmt:
312 anyReachable := false
313 hasDefault := false
314 for _, cas := range x.Body.List {
315 cc := cas.(*ast.CaseClause)
316 if cc.List == nil {
317 hasDefault = true
318 }
319 d.reachable = true
320 for _, stmt := range cc.Body {
321 d.findDead(stmt)
322 }
323 anyReachable = anyReachable || d.reachable
324 }
325 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
326 }
327 }
328
View as plain text