gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/simpler/lint/lint.go (about) 1 // Copyright (c) 2013 The Go Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd. 6 7 // Package lint provides the foundation for tools like gosimple. 8 package lint // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/lint" 9 10 import ( 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/constant" 15 "go/printer" 16 "go/token" 17 "go/types" 18 "path/filepath" 19 "runtime" 20 "sort" 21 "strings" 22 "sync" 23 24 "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/ssa" 25 "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/ssa/ssautil" 26 "golang.org/x/tools/go/ast/astutil" 27 "golang.org/x/tools/go/loader" 28 ) 29 30 type Job struct { 31 Program *Program 32 33 check string 34 problems []Problem 35 } 36 37 type Ignore struct { 38 Pattern string 39 Checks []string 40 } 41 42 type Program struct { 43 SSA *ssa.Program 44 Prog *loader.Program 45 // TODO(dh): Rename to InitialPackages? 46 Packages []*Pkg 47 InitialFunctions []*ssa.Function 48 AllFunctions []*ssa.Function 49 Files []*ast.File 50 Info *types.Info 51 GoVersion int 52 53 tokenFileMap map[*token.File]*ast.File 54 astFileMap map[*ast.File]*Pkg 55 } 56 57 type Func func(*Job) 58 59 // Problem represents a problem in some source code. 60 type Problem struct { 61 Position token.Pos // position in source file 62 Text string // the prose that describes the problem 63 } 64 65 func (p *Problem) String() string { 66 return p.Text 67 } 68 69 type Checker interface { 70 Init(*Program) 71 Funcs() map[string]Func 72 } 73 74 // A Linter lints Go source code. 75 type Linter struct { 76 Checker Checker 77 Ignores []Ignore 78 GoVersion int 79 } 80 81 func (l *Linter) ignore(j *Job, p Problem) bool { 82 tf := j.Program.SSA.Fset.File(p.Position) 83 f := j.Program.tokenFileMap[tf] 84 pkg := j.Program.astFileMap[f].Pkg 85 86 for _, ig := range l.Ignores { 87 pkgpath := pkg.Path() 88 if strings.HasSuffix(pkgpath, "_test") { 89 pkgpath = pkgpath[:len(pkgpath)-len("_test")] 90 } 91 name := filepath.Join(pkgpath, filepath.Base(tf.Name())) 92 if m, _ := filepath.Match(ig.Pattern, name); !m { 93 continue 94 } 95 for _, c := range ig.Checks { 96 if m, _ := filepath.Match(c, j.check); m { 97 return true 98 } 99 } 100 } 101 return false 102 } 103 104 func (j *Job) File(node Positioner) *ast.File { 105 return j.Program.tokenFileMap[j.Program.SSA.Fset.File(node.Pos())] 106 } 107 108 // TODO(dh): switch to sort.Slice when Go 1.9 lands. 109 type byPosition struct { 110 fset *token.FileSet 111 ps []Problem 112 } 113 114 func (ps byPosition) Len() int { 115 return len(ps.ps) 116 } 117 118 func (ps byPosition) Less(i int, j int) bool { 119 pi, pj := ps.fset.Position(ps.ps[i].Position), ps.fset.Position(ps.ps[j].Position) 120 121 if pi.Filename != pj.Filename { 122 return pi.Filename < pj.Filename 123 } 124 if pi.Line != pj.Line { 125 return pi.Line < pj.Line 126 } 127 if pi.Column != pj.Column { 128 return pi.Column < pj.Column 129 } 130 131 return ps.ps[i].Text < ps.ps[j].Text 132 } 133 134 func (ps byPosition) Swap(i int, j int) { 135 ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i] 136 } 137 138 func (l *Linter) Lint(lprog *loader.Program) []Problem { 139 ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) 140 ssaprog.Build() 141 pkgMap := map[*ssa.Package]*Pkg{} 142 var pkgs []*Pkg 143 for _, pkginfo := range lprog.InitialPackages() { 144 ssapkg := ssaprog.Package(pkginfo.Pkg) 145 pkg := &Pkg{ 146 Package: ssapkg, 147 Info: pkginfo, 148 } 149 pkgMap[ssapkg] = pkg 150 pkgs = append(pkgs, pkg) 151 } 152 prog := &Program{ 153 SSA: ssaprog, 154 Prog: lprog, 155 Packages: pkgs, 156 Info: &types.Info{}, 157 GoVersion: l.GoVersion, 158 tokenFileMap: map[*token.File]*ast.File{}, 159 astFileMap: map[*ast.File]*Pkg{}, 160 } 161 initial := map[*types.Package]struct{}{} 162 for _, pkg := range pkgs { 163 initial[pkg.Info.Pkg] = struct{}{} 164 } 165 for fn := range ssautil.AllFunctions(ssaprog) { 166 if fn.Pkg == nil { 167 continue 168 } 169 prog.AllFunctions = append(prog.AllFunctions, fn) 170 if _, ok := initial[fn.Pkg.Pkg]; ok { 171 prog.InitialFunctions = append(prog.InitialFunctions, fn) 172 } 173 } 174 for _, pkg := range pkgs { 175 prog.Files = append(prog.Files, pkg.Info.Files...) 176 177 ssapkg := ssaprog.Package(pkg.Info.Pkg) 178 for _, f := range pkg.Info.Files { 179 tf := lprog.Fset.File(f.Pos()) 180 prog.tokenFileMap[tf] = f 181 prog.astFileMap[f] = pkgMap[ssapkg] 182 } 183 } 184 185 sizes := struct { 186 types int 187 defs int 188 uses int 189 implicits int 190 selections int 191 scopes int 192 }{} 193 for _, pkg := range pkgs { 194 sizes.types += len(pkg.Info.Info.Types) 195 sizes.defs += len(pkg.Info.Info.Defs) 196 sizes.uses += len(pkg.Info.Info.Uses) 197 sizes.implicits += len(pkg.Info.Info.Implicits) 198 sizes.selections += len(pkg.Info.Info.Selections) 199 sizes.scopes += len(pkg.Info.Info.Scopes) 200 } 201 prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types) 202 prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs) 203 prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses) 204 prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits) 205 prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections) 206 prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes) 207 for _, pkg := range pkgs { 208 for k, v := range pkg.Info.Info.Types { 209 prog.Info.Types[k] = v 210 } 211 for k, v := range pkg.Info.Info.Defs { 212 prog.Info.Defs[k] = v 213 } 214 for k, v := range pkg.Info.Info.Uses { 215 prog.Info.Uses[k] = v 216 } 217 for k, v := range pkg.Info.Info.Implicits { 218 prog.Info.Implicits[k] = v 219 } 220 for k, v := range pkg.Info.Info.Selections { 221 prog.Info.Selections[k] = v 222 } 223 for k, v := range pkg.Info.Info.Scopes { 224 prog.Info.Scopes[k] = v 225 } 226 } 227 l.Checker.Init(prog) 228 229 funcs := l.Checker.Funcs() 230 var keys []string 231 for k := range funcs { 232 keys = append(keys, k) 233 } 234 sort.Strings(keys) 235 236 var jobs []*Job 237 for _, k := range keys { 238 j := &Job{ 239 Program: prog, 240 check: k, 241 } 242 jobs = append(jobs, j) 243 } 244 wg := &sync.WaitGroup{} 245 for _, j := range jobs { 246 wg.Add(1) 247 go func(j *Job) { 248 defer wg.Done() 249 fn := funcs[j.check] 250 if fn == nil { 251 return 252 } 253 fn(j) 254 }(j) 255 } 256 wg.Wait() 257 258 var out []Problem 259 for _, j := range jobs { 260 for _, p := range j.problems { 261 if !l.ignore(j, p) { 262 out = append(out, p) 263 } 264 } 265 } 266 267 sort.Sort(byPosition{lprog.Fset, out}) 268 return out 269 } 270 271 // Pkg represents a package being linted. 272 type Pkg struct { 273 *ssa.Package 274 Info *loader.PackageInfo 275 } 276 277 type packager interface { 278 Package() *ssa.Package 279 } 280 281 func IsExample(fn *ssa.Function) bool { 282 if !strings.HasPrefix(fn.Name(), "Example") { 283 return false 284 } 285 f := fn.Prog.Fset.File(fn.Pos()) 286 if f == nil { 287 return false 288 } 289 return strings.HasSuffix(f.Name(), "_test.go") 290 } 291 292 func (j *Job) IsInTest(node Positioner) bool { 293 f := j.Program.SSA.Fset.File(node.Pos()) 294 return f != nil && strings.HasSuffix(f.Name(), "_test.go") 295 } 296 297 func (j *Job) IsInMain(node Positioner) bool { 298 if node, ok := node.(packager); ok { 299 return node.Package().Pkg.Name() == "main" 300 } 301 pkg := j.NodePackage(node) 302 if pkg == nil { 303 return false 304 } 305 return pkg.Pkg.Name() == "main" 306 } 307 308 type Positioner interface { 309 Pos() token.Pos 310 } 311 312 func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem { 313 problem := Problem{ 314 Position: n.Pos(), 315 Text: fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", j.check), 316 } 317 j.problems = append(j.problems, problem) 318 return &j.problems[len(j.problems)-1] 319 } 320 321 func (j *Job) Render(x interface{}) string { 322 fset := j.Program.SSA.Fset 323 var buf bytes.Buffer 324 if err := printer.Fprint(&buf, fset, x); err != nil { 325 panic(err) 326 } 327 return buf.String() 328 } 329 330 func (j *Job) RenderArgs(args []ast.Expr) string { 331 var ss []string 332 for _, arg := range args { 333 ss = append(ss, j.Render(arg)) 334 } 335 return strings.Join(ss, ", ") 336 } 337 338 func IsIdent(expr ast.Expr, ident string) bool { 339 id, ok := expr.(*ast.Ident) 340 return ok && id.Name == ident 341 } 342 343 // isBlank returns whether id is the blank identifier "_". 344 // If id == nil, the answer is false. 345 func IsBlank(id ast.Expr) bool { 346 ident, ok := id.(*ast.Ident) 347 return ok && ident.Name == "_" 348 } 349 350 func IsZero(expr ast.Expr) bool { 351 lit, ok := expr.(*ast.BasicLit) 352 return ok && lit.Kind == token.INT && lit.Value == "0" 353 } 354 355 func (j *Job) IsNil(expr ast.Expr) bool { 356 return j.Program.Info.Types[expr].IsNil() 357 } 358 359 func (j *Job) BoolConst(expr ast.Expr) bool { 360 val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() 361 return constant.BoolVal(val) 362 } 363 364 func (j *Job) IsBoolConst(expr ast.Expr) bool { 365 // We explicitly don't support typed bools because more often than 366 // not, custom bool types are used as binary enums and the 367 // explicit comparison is desired. 368 369 ident, ok := expr.(*ast.Ident) 370 if !ok { 371 return false 372 } 373 obj := j.Program.Info.ObjectOf(ident) 374 c, ok := obj.(*types.Const) 375 if !ok { 376 return false 377 } 378 basic, ok := c.Type().(*types.Basic) 379 if !ok { 380 return false 381 } 382 if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { 383 return false 384 } 385 return true 386 } 387 388 func (j *Job) ExprToInt(expr ast.Expr) (int64, bool) { 389 tv := j.Program.Info.Types[expr] 390 if tv.Value == nil { 391 return 0, false 392 } 393 if tv.Value.Kind() != constant.Int { 394 return 0, false 395 } 396 return constant.Int64Val(tv.Value) 397 } 398 399 func (j *Job) ExprToString(expr ast.Expr) (string, bool) { 400 val := j.Program.Info.Types[expr].Value 401 if val == nil { 402 return "", false 403 } 404 if val.Kind() != constant.String { 405 return "", false 406 } 407 return constant.StringVal(val), true 408 } 409 410 func (j *Job) NodePackage(node Positioner) *Pkg { 411 f := j.File(node) 412 return j.Program.astFileMap[f] 413 } 414 415 func IsGenerated(f *ast.File) bool { 416 comments := f.Comments 417 if len(comments) > 0 { 418 comment := comments[0].Text() 419 return strings.Contains(comment, "Code generated by") || 420 strings.Contains(comment, "DO NOT EDIT") 421 } 422 return false 423 } 424 425 func (j *Job) IsGoVersion(minor int) bool { 426 return j.Program.GoVersion >= minor 427 } 428 429 func (j *Job) IsCallToAST(node ast.Node, name string) bool { 430 call, ok := node.(*ast.CallExpr) 431 if !ok { 432 return false 433 } 434 sel, ok := call.Fun.(*ast.SelectorExpr) 435 if !ok { 436 return false 437 } 438 fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func) 439 return ok && fn.FullName() == name 440 } 441 442 func (j *Job) IsCallToAnyAST(node ast.Node, names ...string) bool { 443 for _, name := range names { 444 if j.IsCallToAST(node, name) { 445 return true 446 } 447 } 448 return false 449 } 450 451 func CallName(call *ssa.CallCommon) string { 452 if call.IsInvoke() { 453 return "" 454 } 455 switch v := call.Value.(type) { 456 case *ssa.Function: 457 fn, ok := v.Object().(*types.Func) 458 if !ok { 459 return "" 460 } 461 return fn.FullName() 462 case *ssa.Builtin: 463 return v.Name() 464 } 465 return "" 466 } 467 468 func IsCallTo(call *ssa.CallCommon, name string) bool { 469 return CallName(call) == name 470 } 471 472 func FilterDebug(instr []ssa.Instruction) []ssa.Instruction { 473 var out []ssa.Instruction 474 for _, ins := range instr { 475 if _, ok := ins.(*ssa.DebugRef); !ok { 476 out = append(out, ins) 477 } 478 } 479 return out 480 } 481 482 func NodeFns(pkgs []*Pkg) map[ast.Node]*ssa.Function { 483 out := map[ast.Node]*ssa.Function{} 484 485 wg := &sync.WaitGroup{} 486 chNodeFns := make(chan map[ast.Node]*ssa.Function, runtime.NumCPU()*2) 487 for _, pkg := range pkgs { 488 pkg := pkg 489 wg.Add(1) 490 go func() { 491 m := map[ast.Node]*ssa.Function{} 492 for _, f := range pkg.Info.Files { 493 ast.Walk(&globalVisitor{m, pkg, f}, f) 494 } 495 chNodeFns <- m 496 wg.Done() 497 }() 498 } 499 go func() { 500 wg.Wait() 501 close(chNodeFns) 502 }() 503 504 for nodeFns := range chNodeFns { 505 for k, v := range nodeFns { 506 out[k] = v 507 } 508 } 509 510 return out 511 } 512 513 type globalVisitor struct { 514 m map[ast.Node]*ssa.Function 515 pkg *Pkg 516 f *ast.File 517 } 518 519 func (v *globalVisitor) Visit(node ast.Node) ast.Visitor { 520 switch node := node.(type) { 521 case *ast.CallExpr: 522 v.m[node] = v.pkg.Func("init") 523 return v 524 case *ast.FuncDecl, *ast.FuncLit: 525 nv := &fnVisitor{v.m, v.f, v.pkg, nil} 526 return nv.Visit(node) 527 default: 528 return v 529 } 530 } 531 532 type fnVisitor struct { 533 m map[ast.Node]*ssa.Function 534 f *ast.File 535 pkg *Pkg 536 ssafn *ssa.Function 537 } 538 539 func (v *fnVisitor) Visit(node ast.Node) ast.Visitor { 540 switch node := node.(type) { 541 case *ast.FuncDecl: 542 var ssafn *ssa.Function 543 ssafn = v.pkg.Prog.FuncValue(v.pkg.Info.ObjectOf(node.Name).(*types.Func)) 544 v.m[node] = ssafn 545 if ssafn == nil { 546 return nil 547 } 548 return &fnVisitor{v.m, v.f, v.pkg, ssafn} 549 case *ast.FuncLit: 550 var ssafn *ssa.Function 551 path, _ := astutil.PathEnclosingInterval(v.f, node.Pos(), node.Pos()) 552 ssafn = ssa.EnclosingFunction(v.pkg.Package, path) 553 v.m[node] = ssafn 554 if ssafn == nil { 555 return nil 556 } 557 return &fnVisitor{v.m, v.f, v.pkg, ssafn} 558 case nil: 559 return nil 560 default: 561 v.m[node] = v.ssafn 562 return v 563 } 564 }