github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/honnef.co/go/tools/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 "honnef.co/go/tools/lint" 9 10 import ( 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/build" 15 "go/constant" 16 "go/printer" 17 "go/token" 18 "go/types" 19 "path/filepath" 20 "runtime" 21 "sort" 22 "strings" 23 "sync" 24 "unicode" 25 26 "golang.org/x/tools/go/ast/astutil" 27 "golang.org/x/tools/go/loader" 28 "honnef.co/go/tools/ssa" 29 "honnef.co/go/tools/ssa/ssautil" 30 ) 31 32 type Job struct { 33 Program *Program 34 35 checker string 36 check string 37 problems []Problem 38 } 39 40 type Ignore interface { 41 Match(p Problem) bool 42 } 43 44 type LineIgnore struct { 45 File string 46 Line int 47 Checks []string 48 matched bool 49 pos token.Pos 50 } 51 52 func (li *LineIgnore) Match(p Problem) bool { 53 if p.Position.Filename != li.File || p.Position.Line != li.Line { 54 return false 55 } 56 for _, c := range li.Checks { 57 if m, _ := filepath.Match(c, p.Check); m { 58 li.matched = true 59 return true 60 } 61 } 62 return false 63 } 64 65 func (li *LineIgnore) String() string { 66 matched := "not matched" 67 if li.matched { 68 matched = "matched" 69 } 70 return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched) 71 } 72 73 type FileIgnore struct { 74 File string 75 Checks []string 76 } 77 78 func (fi *FileIgnore) Match(p Problem) bool { 79 if p.Position.Filename != fi.File { 80 return false 81 } 82 for _, c := range fi.Checks { 83 if m, _ := filepath.Match(c, p.Check); m { 84 return true 85 } 86 } 87 return false 88 } 89 90 type GlobIgnore struct { 91 Pattern string 92 Checks []string 93 } 94 95 func (gi *GlobIgnore) Match(p Problem) bool { 96 if gi.Pattern != "*" { 97 pkgpath := p.Package.Path() 98 if strings.HasSuffix(pkgpath, "_test") { 99 pkgpath = pkgpath[:len(pkgpath)-len("_test")] 100 } 101 name := filepath.Join(pkgpath, filepath.Base(p.Position.Filename)) 102 if m, _ := filepath.Match(gi.Pattern, name); !m { 103 return false 104 } 105 } 106 for _, c := range gi.Checks { 107 if m, _ := filepath.Match(c, p.Check); m { 108 return true 109 } 110 } 111 return false 112 } 113 114 type Program struct { 115 SSA *ssa.Program 116 Prog *loader.Program 117 // TODO(dh): Rename to InitialPackages? 118 Packages []*Pkg 119 InitialFunctions []*ssa.Function 120 AllFunctions []*ssa.Function 121 Files []*ast.File 122 Info *types.Info 123 GoVersion int 124 125 tokenFileMap map[*token.File]*ast.File 126 astFileMap map[*ast.File]*Pkg 127 } 128 129 type Func func(*Job) 130 131 // Problem represents a problem in some source code. 132 type Problem struct { 133 pos token.Pos 134 Position token.Position // position in source file 135 Text string // the prose that describes the problem 136 Check string 137 Checker string 138 Package *types.Package 139 Ignored bool 140 } 141 142 func (p *Problem) String() string { 143 if p.Check == "" { 144 return p.Text 145 } 146 return fmt.Sprintf("%s (%s)", p.Text, p.Check) 147 } 148 149 type Checker interface { 150 Name() string 151 Prefix() string 152 Init(*Program) 153 Funcs() map[string]Func 154 } 155 156 // A Linter lints Go source code. 157 type Linter struct { 158 Checker Checker 159 Ignores []Ignore 160 GoVersion int 161 ReturnIgnored bool 162 163 automaticIgnores []Ignore 164 } 165 166 func (l *Linter) ignore(p Problem) bool { 167 ignored := false 168 for _, ig := range l.automaticIgnores { 169 // We cannot short-circuit these, as we want to record, for 170 // each ignore, whether it matched or not. 171 if ig.Match(p) { 172 ignored = true 173 } 174 } 175 if ignored { 176 // no need to execute other ignores if we've already had a 177 // match. 178 return true 179 } 180 for _, ig := range l.Ignores { 181 // We can short-circuit here, as we aren't tracking any 182 // information. 183 if ig.Match(p) { 184 return true 185 } 186 } 187 188 return false 189 } 190 191 func (prog *Program) File(node Positioner) *ast.File { 192 return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())] 193 } 194 195 func (j *Job) File(node Positioner) *ast.File { 196 return j.Program.File(node) 197 } 198 199 // TODO(dh): switch to sort.Slice when Go 1.9 lands. 200 type byPosition struct { 201 fset *token.FileSet 202 ps []Problem 203 } 204 205 func (ps byPosition) Len() int { 206 return len(ps.ps) 207 } 208 209 func (ps byPosition) Less(i int, j int) bool { 210 pi, pj := ps.ps[i].Position, ps.ps[j].Position 211 212 if pi.Filename != pj.Filename { 213 return pi.Filename < pj.Filename 214 } 215 if pi.Line != pj.Line { 216 return pi.Line < pj.Line 217 } 218 if pi.Column != pj.Column { 219 return pi.Column < pj.Column 220 } 221 222 return ps.ps[i].Text < ps.ps[j].Text 223 } 224 225 func (ps byPosition) Swap(i int, j int) { 226 ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i] 227 } 228 229 func parseDirective(s string) (cmd string, args []string) { 230 if !strings.HasPrefix(s, "//lint:") { 231 return "", nil 232 } 233 s = strings.TrimPrefix(s, "//lint:") 234 fields := strings.Split(s, " ") 235 return fields[0], fields[1:] 236 } 237 238 func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem { 239 ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) 240 ssaprog.Build() 241 pkgMap := map[*ssa.Package]*Pkg{} 242 var pkgs []*Pkg 243 for _, pkginfo := range lprog.InitialPackages() { 244 ssapkg := ssaprog.Package(pkginfo.Pkg) 245 var bp *build.Package 246 if len(pkginfo.Files) != 0 { 247 path := lprog.Fset.Position(pkginfo.Files[0].Pos()).Filename 248 dir := filepath.Dir(path) 249 var err error 250 ctx := conf.Build 251 if ctx == nil { 252 ctx = &build.Default 253 } 254 bp, err = ctx.ImportDir(dir, 0) 255 if err != nil { 256 // shouldn't happen 257 } 258 } 259 pkg := &Pkg{ 260 Package: ssapkg, 261 Info: pkginfo, 262 BuildPkg: bp, 263 } 264 pkgMap[ssapkg] = pkg 265 pkgs = append(pkgs, pkg) 266 } 267 prog := &Program{ 268 SSA: ssaprog, 269 Prog: lprog, 270 Packages: pkgs, 271 Info: &types.Info{}, 272 GoVersion: l.GoVersion, 273 tokenFileMap: map[*token.File]*ast.File{}, 274 astFileMap: map[*ast.File]*Pkg{}, 275 } 276 277 initial := map[*types.Package]struct{}{} 278 for _, pkg := range pkgs { 279 initial[pkg.Info.Pkg] = struct{}{} 280 } 281 for fn := range ssautil.AllFunctions(ssaprog) { 282 if fn.Pkg == nil { 283 continue 284 } 285 prog.AllFunctions = append(prog.AllFunctions, fn) 286 if _, ok := initial[fn.Pkg.Pkg]; ok { 287 prog.InitialFunctions = append(prog.InitialFunctions, fn) 288 } 289 } 290 for _, pkg := range pkgs { 291 prog.Files = append(prog.Files, pkg.Info.Files...) 292 293 ssapkg := ssaprog.Package(pkg.Info.Pkg) 294 for _, f := range pkg.Info.Files { 295 prog.astFileMap[f] = pkgMap[ssapkg] 296 } 297 } 298 299 for _, pkginfo := range lprog.AllPackages { 300 for _, f := range pkginfo.Files { 301 tf := lprog.Fset.File(f.Pos()) 302 prog.tokenFileMap[tf] = f 303 } 304 } 305 306 var out []Problem 307 l.automaticIgnores = nil 308 for _, pkginfo := range lprog.InitialPackages() { 309 for _, f := range pkginfo.Files { 310 cm := ast.NewCommentMap(lprog.Fset, f, f.Comments) 311 for node, cgs := range cm { 312 for _, cg := range cgs { 313 for _, c := range cg.List { 314 if !strings.HasPrefix(c.Text, "//lint:") { 315 continue 316 } 317 cmd, args := parseDirective(c.Text) 318 switch cmd { 319 case "ignore", "file-ignore": 320 if len(args) < 2 { 321 // FIXME(dh): this causes duplicated warnings when using megacheck 322 p := Problem{ 323 pos: c.Pos(), 324 Position: prog.DisplayPosition(c.Pos()), 325 Text: "malformed linter directive; missing the required reason field?", 326 Check: "", 327 Checker: l.Checker.Name(), 328 Package: nil, 329 } 330 out = append(out, p) 331 continue 332 } 333 default: 334 // unknown directive, ignore 335 continue 336 } 337 checks := strings.Split(args[0], ",") 338 pos := prog.DisplayPosition(node.Pos()) 339 var ig Ignore 340 switch cmd { 341 case "ignore": 342 ig = &LineIgnore{ 343 File: pos.Filename, 344 Line: pos.Line, 345 Checks: checks, 346 pos: c.Pos(), 347 } 348 case "file-ignore": 349 ig = &FileIgnore{ 350 File: pos.Filename, 351 Checks: checks, 352 } 353 } 354 l.automaticIgnores = append(l.automaticIgnores, ig) 355 } 356 } 357 } 358 } 359 } 360 361 sizes := struct { 362 types int 363 defs int 364 uses int 365 implicits int 366 selections int 367 scopes int 368 }{} 369 for _, pkg := range pkgs { 370 sizes.types += len(pkg.Info.Info.Types) 371 sizes.defs += len(pkg.Info.Info.Defs) 372 sizes.uses += len(pkg.Info.Info.Uses) 373 sizes.implicits += len(pkg.Info.Info.Implicits) 374 sizes.selections += len(pkg.Info.Info.Selections) 375 sizes.scopes += len(pkg.Info.Info.Scopes) 376 } 377 prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types) 378 prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs) 379 prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses) 380 prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits) 381 prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections) 382 prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes) 383 for _, pkg := range pkgs { 384 for k, v := range pkg.Info.Info.Types { 385 prog.Info.Types[k] = v 386 } 387 for k, v := range pkg.Info.Info.Defs { 388 prog.Info.Defs[k] = v 389 } 390 for k, v := range pkg.Info.Info.Uses { 391 prog.Info.Uses[k] = v 392 } 393 for k, v := range pkg.Info.Info.Implicits { 394 prog.Info.Implicits[k] = v 395 } 396 for k, v := range pkg.Info.Info.Selections { 397 prog.Info.Selections[k] = v 398 } 399 for k, v := range pkg.Info.Info.Scopes { 400 prog.Info.Scopes[k] = v 401 } 402 } 403 l.Checker.Init(prog) 404 405 funcs := l.Checker.Funcs() 406 var keys []string 407 for k := range funcs { 408 keys = append(keys, k) 409 } 410 sort.Strings(keys) 411 412 var jobs []*Job 413 for _, k := range keys { 414 j := &Job{ 415 Program: prog, 416 checker: l.Checker.Name(), 417 check: k, 418 } 419 jobs = append(jobs, j) 420 } 421 wg := &sync.WaitGroup{} 422 for _, j := range jobs { 423 wg.Add(1) 424 go func(j *Job) { 425 defer wg.Done() 426 fn := funcs[j.check] 427 if fn == nil { 428 return 429 } 430 fn(j) 431 }(j) 432 } 433 wg.Wait() 434 435 for _, j := range jobs { 436 for _, p := range j.problems { 437 p.Ignored = l.ignore(p) 438 if l.ReturnIgnored || !p.Ignored { 439 out = append(out, p) 440 } 441 } 442 } 443 444 for _, ig := range l.automaticIgnores { 445 ig, ok := ig.(*LineIgnore) 446 if !ok { 447 continue 448 } 449 if ig.matched { 450 continue 451 } 452 for _, c := range ig.Checks { 453 idx := strings.IndexFunc(c, func(r rune) bool { 454 return unicode.IsNumber(r) 455 }) 456 if idx == -1 { 457 // malformed check name, backing out 458 continue 459 } 460 if c[:idx] != l.Checker.Prefix() { 461 // not for this checker 462 continue 463 } 464 p := Problem{ 465 pos: ig.pos, 466 Position: prog.DisplayPosition(ig.pos), 467 Text: "this linter directive didn't match anything; should it be removed?", 468 Check: "", 469 Checker: l.Checker.Name(), 470 Package: nil, 471 } 472 out = append(out, p) 473 } 474 } 475 476 sort.Sort(byPosition{lprog.Fset, out}) 477 return out 478 } 479 480 // Pkg represents a package being linted. 481 type Pkg struct { 482 *ssa.Package 483 Info *loader.PackageInfo 484 BuildPkg *build.Package 485 } 486 487 type packager interface { 488 Package() *ssa.Package 489 } 490 491 func IsExample(fn *ssa.Function) bool { 492 if !strings.HasPrefix(fn.Name(), "Example") { 493 return false 494 } 495 f := fn.Prog.Fset.File(fn.Pos()) 496 if f == nil { 497 return false 498 } 499 return strings.HasSuffix(f.Name(), "_test.go") 500 } 501 502 func (j *Job) IsInTest(node Positioner) bool { 503 f := j.Program.SSA.Fset.File(node.Pos()) 504 return f != nil && strings.HasSuffix(f.Name(), "_test.go") 505 } 506 507 func (j *Job) IsInMain(node Positioner) bool { 508 if node, ok := node.(packager); ok { 509 return node.Package().Pkg.Name() == "main" 510 } 511 pkg := j.NodePackage(node) 512 if pkg == nil { 513 return false 514 } 515 return pkg.Pkg.Name() == "main" 516 } 517 518 type Positioner interface { 519 Pos() token.Pos 520 } 521 522 func (prog *Program) DisplayPosition(p token.Pos) token.Position { 523 // The //line compiler directive can be used to change the file 524 // name and line numbers associated with code. This can, for 525 // example, be used by code generation tools. The most prominent 526 // example is 'go tool cgo', which uses //line directives to refer 527 // back to the original source code. 528 // 529 // In the context of our linters, we need to treat these 530 // directives differently depending on context. For cgo files, we 531 // want to honour the directives, so that line numbers are 532 // adjusted correctly. For all other files, we want to ignore the 533 // directives, so that problems are reported at their actual 534 // position and not, for example, a yacc grammar file. This also 535 // affects the ignore mechanism, since it operates on the position 536 // information stored within problems. With this implementation, a 537 // user will ignore foo.go, not foo.y 538 539 pkg := prog.astFileMap[prog.tokenFileMap[prog.Prog.Fset.File(p)]] 540 bp := pkg.BuildPkg 541 adjPos := prog.Prog.Fset.Position(p) 542 if bp == nil { 543 // couldn't find the package for some reason (deleted? faulty 544 // file system?) 545 return adjPos 546 } 547 base := filepath.Base(adjPos.Filename) 548 for _, f := range bp.CgoFiles { 549 if f == base { 550 // this is a cgo file, use the adjusted position 551 return adjPos 552 } 553 } 554 // not a cgo file, ignore //line directives 555 return prog.Prog.Fset.PositionFor(p, false) 556 } 557 558 func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem { 559 tf := j.Program.SSA.Fset.File(n.Pos()) 560 f := j.Program.tokenFileMap[tf] 561 pkg := j.Program.astFileMap[f].Pkg 562 563 pos := j.Program.DisplayPosition(n.Pos()) 564 problem := Problem{ 565 pos: n.Pos(), 566 Position: pos, 567 Text: fmt.Sprintf(format, args...), 568 Check: j.check, 569 Checker: j.checker, 570 Package: pkg, 571 } 572 j.problems = append(j.problems, problem) 573 return &j.problems[len(j.problems)-1] 574 } 575 576 func (j *Job) Render(x interface{}) string { 577 fset := j.Program.SSA.Fset 578 var buf bytes.Buffer 579 if err := printer.Fprint(&buf, fset, x); err != nil { 580 panic(err) 581 } 582 return buf.String() 583 } 584 585 func (j *Job) RenderArgs(args []ast.Expr) string { 586 var ss []string 587 for _, arg := range args { 588 ss = append(ss, j.Render(arg)) 589 } 590 return strings.Join(ss, ", ") 591 } 592 593 func IsIdent(expr ast.Expr, ident string) bool { 594 id, ok := expr.(*ast.Ident) 595 return ok && id.Name == ident 596 } 597 598 // isBlank returns whether id is the blank identifier "_". 599 // If id == nil, the answer is false. 600 func IsBlank(id ast.Expr) bool { 601 ident, ok := id.(*ast.Ident) 602 return ok && ident.Name == "_" 603 } 604 605 func IsZero(expr ast.Expr) bool { 606 lit, ok := expr.(*ast.BasicLit) 607 return ok && lit.Kind == token.INT && lit.Value == "0" 608 } 609 610 func (j *Job) IsNil(expr ast.Expr) bool { 611 return j.Program.Info.Types[expr].IsNil() 612 } 613 614 func (j *Job) BoolConst(expr ast.Expr) bool { 615 val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() 616 return constant.BoolVal(val) 617 } 618 619 func (j *Job) IsBoolConst(expr ast.Expr) bool { 620 // We explicitly don't support typed bools because more often than 621 // not, custom bool types are used as binary enums and the 622 // explicit comparison is desired. 623 624 ident, ok := expr.(*ast.Ident) 625 if !ok { 626 return false 627 } 628 obj := j.Program.Info.ObjectOf(ident) 629 c, ok := obj.(*types.Const) 630 if !ok { 631 return false 632 } 633 basic, ok := c.Type().(*types.Basic) 634 if !ok { 635 return false 636 } 637 if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { 638 return false 639 } 640 return true 641 } 642 643 func (j *Job) ExprToInt(expr ast.Expr) (int64, bool) { 644 tv := j.Program.Info.Types[expr] 645 if tv.Value == nil { 646 return 0, false 647 } 648 if tv.Value.Kind() != constant.Int { 649 return 0, false 650 } 651 return constant.Int64Val(tv.Value) 652 } 653 654 func (j *Job) ExprToString(expr ast.Expr) (string, bool) { 655 val := j.Program.Info.Types[expr].Value 656 if val == nil { 657 return "", false 658 } 659 if val.Kind() != constant.String { 660 return "", false 661 } 662 return constant.StringVal(val), true 663 } 664 665 func (j *Job) NodePackage(node Positioner) *Pkg { 666 f := j.File(node) 667 return j.Program.astFileMap[f] 668 } 669 670 func IsGenerated(f *ast.File) bool { 671 comments := f.Comments 672 if len(comments) > 0 { 673 comment := comments[0].Text() 674 return strings.Contains(comment, "Code generated by") || 675 strings.Contains(comment, "DO NOT EDIT") 676 } 677 return false 678 } 679 680 func Preamble(f *ast.File) string { 681 cutoff := f.Package 682 if f.Doc != nil { 683 cutoff = f.Doc.Pos() 684 } 685 var out []string 686 for _, cmt := range f.Comments { 687 if cmt.Pos() >= cutoff { 688 break 689 } 690 out = append(out, cmt.Text()) 691 } 692 return strings.Join(out, "\n") 693 } 694 695 func IsPointerLike(T types.Type) bool { 696 switch T := T.Underlying().(type) { 697 case *types.Interface, *types.Chan, *types.Map, *types.Pointer: 698 return true 699 case *types.Basic: 700 return T.Kind() == types.UnsafePointer 701 } 702 return false 703 } 704 705 func (j *Job) IsGoVersion(minor int) bool { 706 return j.Program.GoVersion >= minor 707 } 708 709 func (j *Job) IsCallToAST(node ast.Node, name string) bool { 710 call, ok := node.(*ast.CallExpr) 711 if !ok { 712 return false 713 } 714 sel, ok := call.Fun.(*ast.SelectorExpr) 715 if !ok { 716 return false 717 } 718 fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func) 719 return ok && fn.FullName() == name 720 } 721 722 func (j *Job) IsCallToAnyAST(node ast.Node, names ...string) bool { 723 for _, name := range names { 724 if j.IsCallToAST(node, name) { 725 return true 726 } 727 } 728 return false 729 } 730 731 func CallName(call *ssa.CallCommon) string { 732 if call.IsInvoke() { 733 return "" 734 } 735 switch v := call.Value.(type) { 736 case *ssa.Function: 737 fn, ok := v.Object().(*types.Func) 738 if !ok { 739 return "" 740 } 741 return fn.FullName() 742 case *ssa.Builtin: 743 return v.Name() 744 } 745 return "" 746 } 747 748 func IsCallTo(call *ssa.CallCommon, name string) bool { 749 return CallName(call) == name 750 } 751 752 func FilterDebug(instr []ssa.Instruction) []ssa.Instruction { 753 var out []ssa.Instruction 754 for _, ins := range instr { 755 if _, ok := ins.(*ssa.DebugRef); !ok { 756 out = append(out, ins) 757 } 758 } 759 return out 760 } 761 762 func NodeFns(pkgs []*Pkg) map[ast.Node]*ssa.Function { 763 out := map[ast.Node]*ssa.Function{} 764 765 wg := &sync.WaitGroup{} 766 chNodeFns := make(chan map[ast.Node]*ssa.Function, runtime.NumCPU()*2) 767 for _, pkg := range pkgs { 768 pkg := pkg 769 wg.Add(1) 770 go func() { 771 m := map[ast.Node]*ssa.Function{} 772 for _, f := range pkg.Info.Files { 773 ast.Walk(&globalVisitor{m, pkg, f}, f) 774 } 775 chNodeFns <- m 776 wg.Done() 777 }() 778 } 779 go func() { 780 wg.Wait() 781 close(chNodeFns) 782 }() 783 784 for nodeFns := range chNodeFns { 785 for k, v := range nodeFns { 786 out[k] = v 787 } 788 } 789 790 return out 791 } 792 793 type globalVisitor struct { 794 m map[ast.Node]*ssa.Function 795 pkg *Pkg 796 f *ast.File 797 } 798 799 func (v *globalVisitor) Visit(node ast.Node) ast.Visitor { 800 switch node := node.(type) { 801 case *ast.CallExpr: 802 v.m[node] = v.pkg.Func("init") 803 return v 804 case *ast.FuncDecl, *ast.FuncLit: 805 nv := &fnVisitor{v.m, v.f, v.pkg, nil} 806 return nv.Visit(node) 807 default: 808 return v 809 } 810 } 811 812 type fnVisitor struct { 813 m map[ast.Node]*ssa.Function 814 f *ast.File 815 pkg *Pkg 816 ssafn *ssa.Function 817 } 818 819 func (v *fnVisitor) Visit(node ast.Node) ast.Visitor { 820 switch node := node.(type) { 821 case *ast.FuncDecl: 822 var ssafn *ssa.Function 823 ssafn = v.pkg.Prog.FuncValue(v.pkg.Info.ObjectOf(node.Name).(*types.Func)) 824 v.m[node] = ssafn 825 if ssafn == nil { 826 return nil 827 } 828 return &fnVisitor{v.m, v.f, v.pkg, ssafn} 829 case *ast.FuncLit: 830 var ssafn *ssa.Function 831 path, _ := astutil.PathEnclosingInterval(v.f, node.Pos(), node.Pos()) 832 ssafn = ssa.EnclosingFunction(v.pkg.Package, path) 833 v.m[node] = ssafn 834 if ssafn == nil { 835 return nil 836 } 837 return &fnVisitor{v.m, v.f, v.pkg, ssafn} 838 case nil: 839 return nil 840 default: 841 v.m[node] = v.ssafn 842 return v 843 } 844 }