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