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  }