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