github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/golinters/scopelint.go (about)

     1  package golinters
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"sync"
     8  
     9  	"golang.org/x/tools/go/analysis"
    10  
    11  	"github.com/elek/golangci-lint/pkg/golinters/goanalysis"
    12  	"github.com/elek/golangci-lint/pkg/lint/linter"
    13  	"github.com/elek/golangci-lint/pkg/result"
    14  )
    15  
    16  const scopelintName = "scopelint"
    17  
    18  func NewScopelint() *goanalysis.Linter {
    19  	var mu sync.Mutex
    20  	var resIssues []goanalysis.Issue
    21  
    22  	analyzer := &analysis.Analyzer{
    23  		Name: scopelintName,
    24  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    25  	}
    26  	return goanalysis.NewLinter(
    27  		scopelintName,
    28  		"Scopelint checks for unpinned variables in go programs",
    29  		[]*analysis.Analyzer{analyzer},
    30  		nil,
    31  	).WithContextSetter(func(lintCtx *linter.Context) {
    32  		analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
    33  			var res []result.Issue
    34  			for _, file := range pass.Files {
    35  				n := Node{
    36  					fset:          pass.Fset,
    37  					DangerObjects: map[*ast.Object]int{},
    38  					UnsafeObjects: map[*ast.Object]int{},
    39  					SkipFuncs:     map[*ast.FuncLit]int{},
    40  					issues:        &res,
    41  				}
    42  				ast.Walk(&n, file)
    43  			}
    44  
    45  			if len(res) == 0 {
    46  				return nil, nil
    47  			}
    48  
    49  			mu.Lock()
    50  			for i := range res {
    51  				resIssues = append(resIssues, goanalysis.NewIssue(&res[i], pass))
    52  			}
    53  			mu.Unlock()
    54  
    55  			return nil, nil
    56  		}
    57  	}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
    58  		return resIssues
    59  	}).WithLoadMode(goanalysis.LoadModeSyntax)
    60  }
    61  
    62  // The code below is copy-pasted from https://github.com/kyoh86/scopelint 92cbe2cc9276abda0e309f52cc9e309d407f174e
    63  
    64  // Node represents a Node being linted.
    65  type Node struct {
    66  	fset          *token.FileSet
    67  	DangerObjects map[*ast.Object]int
    68  	UnsafeObjects map[*ast.Object]int
    69  	SkipFuncs     map[*ast.FuncLit]int
    70  	issues        *[]result.Issue
    71  }
    72  
    73  // Visit method is invoked for each node encountered by Walk.
    74  // If the result visitor w is not nil, Walk visits each of the children
    75  // of node with the visitor w, followed by a call of w.Visit(nil).
    76  //nolint:gocyclo,gocritic
    77  func (f *Node) Visit(node ast.Node) ast.Visitor {
    78  	switch typedNode := node.(type) {
    79  	case *ast.ForStmt:
    80  		switch init := typedNode.Init.(type) {
    81  		case *ast.AssignStmt:
    82  			for _, lh := range init.Lhs {
    83  				switch tlh := lh.(type) {
    84  				case *ast.Ident:
    85  					f.UnsafeObjects[tlh.Obj] = 0
    86  				}
    87  			}
    88  		}
    89  
    90  	case *ast.RangeStmt:
    91  		// Memory variables declared in range statement
    92  		switch k := typedNode.Key.(type) {
    93  		case *ast.Ident:
    94  			f.UnsafeObjects[k.Obj] = 0
    95  		}
    96  		switch v := typedNode.Value.(type) {
    97  		case *ast.Ident:
    98  			f.UnsafeObjects[v.Obj] = 0
    99  		}
   100  
   101  	case *ast.UnaryExpr:
   102  		if typedNode.Op == token.AND {
   103  			switch ident := typedNode.X.(type) {
   104  			case *ast.Ident:
   105  				if _, unsafe := f.UnsafeObjects[ident.Obj]; unsafe {
   106  					f.errorf(ident, "Using a reference for the variable on range scope %s", formatCode(ident.Name, nil))
   107  				}
   108  			}
   109  		}
   110  
   111  	case *ast.Ident:
   112  		if _, obj := f.DangerObjects[typedNode.Obj]; obj {
   113  			// It is the naked variable in scope of range statement.
   114  			f.errorf(node, "Using the variable on range scope %s in function literal", formatCode(typedNode.Name, nil))
   115  			break
   116  		}
   117  
   118  	case *ast.CallExpr:
   119  		// Ignore func literals that'll be called immediately.
   120  		switch funcLit := typedNode.Fun.(type) {
   121  		case *ast.FuncLit:
   122  			f.SkipFuncs[funcLit] = 0
   123  		}
   124  
   125  	case *ast.FuncLit:
   126  		if _, skip := f.SkipFuncs[typedNode]; !skip {
   127  			dangers := map[*ast.Object]int{}
   128  			for d := range f.DangerObjects {
   129  				dangers[d] = 0
   130  			}
   131  			for u := range f.UnsafeObjects {
   132  				dangers[u] = 0
   133  				f.UnsafeObjects[u]++
   134  			}
   135  			return &Node{
   136  				fset:          f.fset,
   137  				DangerObjects: dangers,
   138  				UnsafeObjects: f.UnsafeObjects,
   139  				SkipFuncs:     f.SkipFuncs,
   140  				issues:        f.issues,
   141  			}
   142  		}
   143  
   144  	case *ast.ReturnStmt:
   145  		unsafe := map[*ast.Object]int{}
   146  		for u := range f.UnsafeObjects {
   147  			if f.UnsafeObjects[u] == 0 {
   148  				continue
   149  			}
   150  			unsafe[u] = f.UnsafeObjects[u]
   151  		}
   152  		return &Node{
   153  			fset:          f.fset,
   154  			DangerObjects: f.DangerObjects,
   155  			UnsafeObjects: unsafe,
   156  			SkipFuncs:     f.SkipFuncs,
   157  			issues:        f.issues,
   158  		}
   159  	}
   160  	return f
   161  }
   162  
   163  // The variadic arguments may start with link and category types,
   164  // and must end with a format string and any arguments.
   165  //nolint:interfacer
   166  func (f *Node) errorf(n ast.Node, format string, args ...interface{}) {
   167  	pos := f.fset.Position(n.Pos())
   168  	f.errorAtf(pos, format, args...)
   169  }
   170  
   171  func (f *Node) errorAtf(pos token.Position, format string, args ...interface{}) {
   172  	*f.issues = append(*f.issues, result.Issue{
   173  		Pos:        pos,
   174  		Text:       fmt.Sprintf(format, args...),
   175  		FromLinter: scopelintName,
   176  	})
   177  }