gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/gordonklaus/ineffassign/ineffassign.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "go/ast" 7 "go/parser" 8 "go/token" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 ) 14 15 const invalidArgumentExitCode = 3 16 17 var dontRecurseFlag = flag.Bool("n", false, "don't recursively check paths") 18 19 func main() { 20 flag.Parse() 21 22 if len(flag.Args()) == 0 { 23 fmt.Println("missing argument: filepath") 24 os.Exit(invalidArgumentExitCode) 25 } 26 27 lintFailed := false 28 for _, path := range flag.Args() { 29 root, err := filepath.Abs(path) 30 if err != nil { 31 fmt.Printf("Error finding absolute path: %s", err) 32 os.Exit(invalidArgumentExitCode) 33 } 34 if walkPath(root) { 35 lintFailed = true 36 } 37 } 38 if lintFailed { 39 os.Exit(1) 40 } 41 } 42 43 func walkPath(root string) bool { 44 lintFailed := false 45 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 46 if err != nil { 47 fmt.Printf("Error during filesystem walk: %v\n", err) 48 return nil 49 } 50 if fi.IsDir() { 51 if path != root && (*dontRecurseFlag || 52 filepath.Base(path) == "testdata" || 53 filepath.Base(path) == "vendor") { 54 return filepath.SkipDir 55 } 56 return nil 57 } 58 if !strings.HasSuffix(path, ".go") { 59 return nil 60 } 61 fset, _, ineff := checkPath(path) 62 for _, id := range ineff { 63 fmt.Printf("%s: ineffectual assignment to %s\n", fset.Position(id.Pos()), id.Name) 64 lintFailed = true 65 } 66 return nil 67 }) 68 return lintFailed 69 } 70 71 func checkPath(path string) (*token.FileSet, []*ast.CommentGroup, []*ast.Ident) { 72 fset := token.NewFileSet() 73 f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) 74 if err != nil { 75 return nil, nil, nil 76 } 77 78 bld := &builder{vars: map[*ast.Object]*variable{}} 79 bld.walk(f) 80 81 chk := &checker{vars: bld.vars, seen: map[*block]bool{}} 82 for _, b := range bld.roots { 83 chk.check(b) 84 } 85 sort.Sort(chk.ineff) 86 87 return fset, f.Comments, chk.ineff 88 } 89 90 type builder struct { 91 roots []*block 92 block *block 93 vars map[*ast.Object]*variable 94 results []*ast.FieldList 95 breaks branchStack 96 continues branchStack 97 gotos branchStack 98 labelStmt *ast.LabeledStmt 99 } 100 101 type block struct { 102 children []*block 103 ops map[*ast.Object][]operation 104 } 105 106 func (b *block) addChild(c *block) { 107 b.children = append(b.children, c) 108 } 109 110 type operation struct { 111 id *ast.Ident 112 assign bool 113 } 114 115 type variable struct { 116 fundept int 117 escapes bool 118 } 119 120 func (bld *builder) walk(n ast.Node) { 121 if n != nil { 122 ast.Walk(bld, n) 123 } 124 } 125 126 func (bld *builder) Visit(n ast.Node) ast.Visitor { 127 switch n := n.(type) { 128 case *ast.FuncDecl: 129 if n.Body != nil { 130 bld.fun(n.Type, n.Body) 131 } 132 case *ast.FuncLit: 133 bld.fun(n.Type, n.Body) 134 case *ast.IfStmt: 135 bld.walk(n.Init) 136 bld.walk(n.Cond) 137 b0 := bld.block 138 bld.newBlock(b0) 139 bld.walk(n.Body) 140 b1 := bld.block 141 if n.Else != nil { 142 bld.newBlock(b0) 143 bld.walk(n.Else) 144 b0 = bld.block 145 } 146 bld.newBlock(b0, b1) 147 case *ast.ForStmt: 148 lbl := bld.stmtLabel(n) 149 brek := bld.breaks.push(lbl) 150 continu := bld.continues.push(lbl) 151 bld.walk(n.Init) 152 start := bld.newBlock(bld.block) 153 bld.walk(n.Cond) 154 cond := bld.block 155 bld.newBlock(cond) 156 bld.walk(n.Body) 157 continu.setDestination(bld.newBlock(bld.block)) 158 bld.walk(n.Post) 159 bld.block.addChild(start) 160 brek.setDestination(bld.newBlock(cond)) 161 bld.breaks.pop() 162 bld.continues.pop() 163 case *ast.RangeStmt: 164 lbl := bld.stmtLabel(n) 165 brek := bld.breaks.push(lbl) 166 continu := bld.continues.push(lbl) 167 bld.walk(n.X) 168 pre := bld.newBlock(bld.block) 169 start := bld.newBlock(pre) 170 if n.Key != nil { 171 lhs := []ast.Expr{n.Key} 172 if n.Value != nil { 173 lhs = append(lhs, n.Value) 174 } 175 bld.walk(&ast.AssignStmt{Lhs: lhs, Tok: n.Tok, TokPos: n.TokPos, Rhs: []ast.Expr{&ast.Ident{NamePos: n.X.End()}}}) 176 } 177 bld.walk(n.Body) 178 bld.block.addChild(start) 179 continu.setDestination(pre) 180 brek.setDestination(bld.newBlock(pre, bld.block)) 181 bld.breaks.pop() 182 bld.continues.pop() 183 case *ast.SwitchStmt: 184 bld.walk(n.Init) 185 bld.walk(n.Tag) 186 bld.swtch(n, n.Body.List) 187 case *ast.TypeSwitchStmt: 188 bld.walk(n.Init) 189 bld.walk(n.Assign) 190 bld.swtch(n, n.Body.List) 191 case *ast.SelectStmt: 192 brek := bld.breaks.push(bld.stmtLabel(n)) 193 for _, c := range n.Body.List { 194 c := c.(*ast.CommClause).Comm 195 if s, ok := c.(*ast.AssignStmt); ok { 196 bld.walk(s.Rhs[0]) 197 } else { 198 bld.walk(c) 199 } 200 } 201 b0 := bld.block 202 exits := make([]*block, len(n.Body.List)) 203 dfault := false 204 for i, c := range n.Body.List { 205 c := c.(*ast.CommClause) 206 bld.newBlock(b0) 207 bld.walk(c) 208 exits[i] = bld.block 209 dfault = dfault || c.Comm == nil 210 } 211 if !dfault { 212 exits = append(exits, b0) 213 } 214 brek.setDestination(bld.newBlock(exits...)) 215 bld.breaks.pop() 216 case *ast.LabeledStmt: 217 bld.gotos.get(n.Label).setDestination(bld.newBlock(bld.block)) 218 bld.labelStmt = n 219 bld.walk(n.Stmt) 220 case *ast.BranchStmt: 221 switch n.Tok { 222 case token.BREAK: 223 bld.breaks.get(n.Label).addSource(bld.block) 224 bld.newBlock() 225 case token.CONTINUE: 226 bld.continues.get(n.Label).addSource(bld.block) 227 bld.newBlock() 228 case token.GOTO: 229 bld.gotos.get(n.Label).addSource(bld.block) 230 bld.newBlock() 231 } 232 233 case *ast.AssignStmt: 234 if n.Tok == token.QUO_ASSIGN || n.Tok == token.REM_ASSIGN { 235 bld.maybePanic() 236 } 237 238 for _, x := range n.Rhs { 239 bld.walk(x) 240 } 241 for i, x := range n.Lhs { 242 if id, ok := ident(x); ok { 243 if n.Tok >= token.ADD_ASSIGN && n.Tok <= token.AND_NOT_ASSIGN { 244 bld.use(id) 245 } 246 // Don't treat explicit initialization to zero as assignment; it is often used as shorthand for a bare declaration. 247 if n.Tok == token.DEFINE && i < len(n.Rhs) && isZeroLiteral(n.Rhs[i]) { 248 bld.use(id) 249 } else { 250 bld.assign(id) 251 } 252 } else { 253 bld.walk(x) 254 } 255 } 256 case *ast.GenDecl: 257 if n.Tok == token.VAR { 258 for _, s := range n.Specs { 259 s := s.(*ast.ValueSpec) 260 for _, x := range s.Values { 261 bld.walk(x) 262 } 263 for _, id := range s.Names { 264 if len(s.Values) > 0 { 265 bld.assign(id) 266 } else { 267 bld.use(id) 268 } 269 } 270 } 271 } 272 case *ast.IncDecStmt: 273 if id, ok := ident(n.X); ok { 274 bld.use(id) 275 bld.assign(id) 276 } else { 277 bld.walk(n.X) 278 } 279 case *ast.Ident: 280 bld.use(n) 281 case *ast.ReturnStmt: 282 for _, x := range n.Results { 283 bld.walk(x) 284 } 285 res := bld.results[len(bld.results)-1] 286 if res == nil { 287 break 288 } 289 for _, f := range res.List { 290 for _, id := range f.Names { 291 if n.Results != nil { 292 bld.assign(id) 293 } 294 bld.use(id) 295 } 296 } 297 case *ast.SendStmt: 298 bld.maybePanic() 299 return bld 300 301 case *ast.BinaryExpr: 302 if n.Op == token.EQL || n.Op == token.QUO || n.Op == token.REM { 303 bld.maybePanic() 304 } 305 return bld 306 case *ast.CallExpr: 307 bld.maybePanic() 308 return bld 309 case *ast.IndexExpr: 310 bld.maybePanic() 311 return bld 312 case *ast.UnaryExpr: 313 id, ok := ident(n.X) 314 if ix, isIx := n.X.(*ast.IndexExpr); isIx { 315 // We don't care about indexing into slices, but without type information we can do no better. 316 id, ok = ident(ix.X) 317 } 318 if ok && n.Op == token.AND { 319 if v, ok := bld.vars[id.Obj]; ok { 320 v.escapes = true 321 } 322 } 323 return bld 324 case *ast.SelectorExpr: 325 bld.maybePanic() 326 // A method call (possibly delayed via a method value) might implicitly take 327 // the address of its receiver, causing it to escape. 328 // We can't do any better here without knowing the variable's type. 329 if id, ok := ident(n.X); ok { 330 if v, ok := bld.vars[id.Obj]; ok { 331 v.escapes = true 332 } 333 } 334 return bld 335 case *ast.SliceExpr: 336 bld.maybePanic() 337 // We don't care about slicing into slices, but without type information we can do no better. 338 if id, ok := ident(n.X); ok { 339 if v, ok := bld.vars[id.Obj]; ok { 340 v.escapes = true 341 } 342 } 343 return bld 344 case *ast.StarExpr: 345 bld.maybePanic() 346 return bld 347 case *ast.TypeAssertExpr: 348 bld.maybePanic() 349 return bld 350 351 default: 352 return bld 353 } 354 return nil 355 } 356 357 func isZeroLiteral(x ast.Expr) bool { 358 b, ok := x.(*ast.BasicLit) 359 if !ok { 360 return false 361 } 362 switch b.Value { 363 case "0", "0.0", "0.", ".0", `""`: 364 return true 365 } 366 return false 367 } 368 369 func (bld *builder) fun(typ *ast.FuncType, body *ast.BlockStmt) { 370 for _, v := range bld.vars { 371 v.fundept++ 372 } 373 bld.results = append(bld.results, typ.Results) 374 375 b := bld.block 376 bld.newBlock() 377 bld.roots = append(bld.roots, bld.block) 378 bld.walk(typ) 379 bld.walk(body) 380 bld.block = b 381 382 bld.results = bld.results[:len(bld.results)-1] 383 for _, v := range bld.vars { 384 v.fundept-- 385 } 386 } 387 388 func (bld *builder) swtch(stmt ast.Stmt, cases []ast.Stmt) { 389 brek := bld.breaks.push(bld.stmtLabel(stmt)) 390 b0 := bld.block 391 list := b0 392 exits := make([]*block, 0, len(cases)+1) 393 var dfault, fallthru *block 394 for _, c := range cases { 395 c := c.(*ast.CaseClause) 396 397 if c.List != nil { 398 list = bld.newBlock(list) 399 for _, x := range c.List { 400 bld.walk(x) 401 } 402 } 403 404 parents := []*block{} 405 if c.List != nil { 406 parents = append(parents, list) 407 } 408 if fallthru != nil { 409 parents = append(parents, fallthru) 410 fallthru = nil 411 } 412 bld.newBlock(parents...) 413 if c.List == nil { 414 dfault = bld.block 415 } 416 for _, s := range c.Body { 417 bld.walk(s) 418 if s, ok := s.(*ast.BranchStmt); ok && s.Tok == token.FALLTHROUGH { 419 fallthru = bld.block 420 } 421 } 422 423 if fallthru == nil { 424 exits = append(exits, bld.block) 425 } 426 } 427 if dfault != nil { 428 list.addChild(dfault) 429 } else { 430 exits = append(exits, b0) 431 } 432 brek.setDestination(bld.newBlock(exits...)) 433 bld.breaks.pop() 434 } 435 436 // An operation that might panic marks named function results as used. 437 func (bld *builder) maybePanic() { 438 if len(bld.results) == 0 { 439 return 440 } 441 res := bld.results[len(bld.results)-1] 442 if res == nil { 443 return 444 } 445 for _, f := range res.List { 446 for _, id := range f.Names { 447 bld.use(id) 448 } 449 } 450 } 451 452 func (bld *builder) newBlock(parents ...*block) *block { 453 bld.block = &block{ops: map[*ast.Object][]operation{}} 454 for _, b := range parents { 455 b.addChild(bld.block) 456 } 457 return bld.block 458 } 459 460 func (bld *builder) stmtLabel(s ast.Stmt) *ast.Object { 461 if ls := bld.labelStmt; ls != nil && ls.Stmt == s { 462 return ls.Label.Obj 463 } 464 return nil 465 } 466 467 func (bld *builder) assign(id *ast.Ident) { 468 bld.newOp(id, true) 469 } 470 471 func (bld *builder) use(id *ast.Ident) { 472 bld.newOp(id, false) 473 } 474 475 func (bld *builder) newOp(id *ast.Ident, assign bool) { 476 if id.Name == "_" || id.Obj == nil { 477 return 478 } 479 480 v, ok := bld.vars[id.Obj] 481 if !ok { 482 v = &variable{} 483 bld.vars[id.Obj] = v 484 } 485 v.escapes = v.escapes || v.fundept > 0 || bld.block == nil 486 487 if b := bld.block; b != nil { 488 b.ops[id.Obj] = append(b.ops[id.Obj], operation{id, assign}) 489 } 490 } 491 492 type branchStack []*branch 493 494 type branch struct { 495 label *ast.Object 496 srcs []*block 497 dst *block 498 } 499 500 func (s *branchStack) push(lbl *ast.Object) *branch { 501 br := &branch{label: lbl} 502 *s = append(*s, br) 503 return br 504 } 505 506 func (s *branchStack) get(lbl *ast.Ident) *branch { 507 for i := len(*s) - 1; i >= 0; i-- { 508 if br := (*s)[i]; lbl == nil || br.label == lbl.Obj { 509 return br 510 } 511 } 512 return s.push(lbl.Obj) 513 } 514 515 func (br *branch) addSource(src *block) { 516 br.srcs = append(br.srcs, src) 517 if br.dst != nil { 518 src.addChild(br.dst) 519 } 520 } 521 522 func (br *branch) setDestination(dst *block) { 523 br.dst = dst 524 for _, src := range br.srcs { 525 src.addChild(dst) 526 } 527 } 528 529 func (s *branchStack) pop() { 530 *s = (*s)[:len(*s)-1] 531 } 532 533 func ident(x ast.Expr) (*ast.Ident, bool) { 534 if p, ok := x.(*ast.ParenExpr); ok { 535 return ident(p.X) 536 } 537 id, ok := x.(*ast.Ident) 538 return id, ok 539 } 540 541 type checker struct { 542 vars map[*ast.Object]*variable 543 seen map[*block]bool 544 ineff idents 545 } 546 547 func (chk *checker) check(b *block) { 548 if chk.seen[b] { 549 return 550 } 551 chk.seen[b] = true 552 553 for obj, ops := range b.ops { 554 if chk.vars[obj].escapes { 555 continue 556 } 557 ops: 558 for i, op := range ops { 559 if !op.assign { 560 continue 561 } 562 if i+1 < len(ops) { 563 if ops[i+1].assign { 564 chk.ineff = append(chk.ineff, op.id) 565 } 566 continue 567 } 568 seen := map[*block]bool{} 569 for _, b := range b.children { 570 if used(obj, b, seen) { 571 continue ops 572 } 573 } 574 chk.ineff = append(chk.ineff, op.id) 575 } 576 } 577 578 for _, b := range b.children { 579 chk.check(b) 580 } 581 } 582 583 func used(obj *ast.Object, b *block, seen map[*block]bool) bool { 584 if seen[b] { 585 return false 586 } 587 seen[b] = true 588 589 if ops := b.ops[obj]; len(ops) > 0 { 590 return !ops[0].assign 591 } 592 for _, b := range b.children { 593 if used(obj, b, seen) { 594 return true 595 } 596 } 597 return false 598 } 599 600 type idents []*ast.Ident 601 602 func (ids idents) Len() int { return len(ids) } 603 func (ids idents) Less(i, j int) bool { return ids[i].Pos() < ids[j].Pos() } 604 func (ids idents) Swap(i, j int) { ids[i], ids[j] = ids[j], ids[i] }