diff options
| author | Taras Madan <tarasmadan@google.com> | 2025-01-22 16:07:17 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2025-01-23 10:42:36 +0000 |
| commit | 7b4377ad9d8a7205416df8d6217ef2b010f89481 (patch) | |
| tree | e6fec4fd12ff807a16d847923f501075bf71d16c /vendor/github.com/kyoh86/exportloopref/exportloopref.go | |
| parent | 475a4c203afb8b7d3af51c4fd32bb170ff32a45e (diff) | |
vendor: delete
Diffstat (limited to 'vendor/github.com/kyoh86/exportloopref/exportloopref.go')
| -rw-r--r-- | vendor/github.com/kyoh86/exportloopref/exportloopref.go | 334 |
1 files changed, 0 insertions, 334 deletions
diff --git a/vendor/github.com/kyoh86/exportloopref/exportloopref.go b/vendor/github.com/kyoh86/exportloopref/exportloopref.go deleted file mode 100644 index d071d5c35..000000000 --- a/vendor/github.com/kyoh86/exportloopref/exportloopref.go +++ /dev/null @@ -1,334 +0,0 @@ -package exportloopref - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" -) - -var Analyzer = &analysis.Analyzer{ - Name: "exportloopref", - Doc: "checks for pointers to enclosing loop variables", - Run: run, - RunDespiteErrors: true, - Requires: []*analysis.Analyzer{inspect.Analyzer}, -} - -func run(pass *analysis.Pass) (interface{}, error) { - inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - - search := &Searcher{ - LoopVars: map[token.Pos]struct{}{}, - LocalVars: map[token.Pos]map[token.Pos]struct{}{}, - Pass: pass, - } - - nodeFilter := []ast.Node{ - (*ast.RangeStmt)(nil), - (*ast.ForStmt)(nil), - (*ast.DeclStmt)(nil), - (*ast.AssignStmt)(nil), - (*ast.UnaryExpr)(nil), - } - - inspect.WithStack(nodeFilter, search.CheckAndReport) - - return nil, nil -} - -type Searcher struct { - // LoopVars is positions that loop-variables are declared like below. - // - for <KEY>, <VALUE> := range ... - // - for <VALUE> := <INIT>; <CONDITION>; <INCREMENT> - LoopVars map[token.Pos]struct{} - // LocalVars is positions of loops and the variables declared in them. - // Use this to determine if a point assignment is an export outside the loop. - LocalVars map[token.Pos]map[token.Pos]struct{} - - Pass *analysis.Pass -} - -// CheckAndReport inspects each node with stack. -// It is implemented as the I/F of the "golang.org/x/tools/go/analysis/passes/inspect".Analysis.WithStack. -func (s *Searcher) CheckAndReport(n ast.Node, push bool, stack []ast.Node) bool { - id, insert, digg := s.Check(n, stack) - if id == nil { - // no prob. - return digg - } - - // suggests fix - var suggest []analysis.SuggestedFix - if insert != token.NoPos { - suggest = []analysis.SuggestedFix{{ - Message: fmt.Sprintf("loop variable %s should be pinned", id.Name), - TextEdits: []analysis.TextEdit{{ - Pos: insert, - End: insert, - NewText: []byte(fmt.Sprintf("%[1]s := %[1]s\n", id.Name)), - }}, - }} - } - - // report a diagnostic - d := analysis.Diagnostic{Pos: id.Pos(), - End: id.End(), - Message: fmt.Sprintf("exporting a pointer for the loop variable %s", id.Name), - Category: "exportloopref", - SuggestedFixes: suggest, - } - s.Pass.Report(d) - return digg -} - -// Check each node and stack, whether it exports loop variables or not. -// Finding export, report the *ast.Ident of exported loop variable, -// and token.Pos to insert assignment to fix the diagnostic. -func (s *Searcher) Check(n ast.Node, stack []ast.Node) (loopVar *ast.Ident, insertPos token.Pos, digg bool) { - switch typed := n.(type) { - case *ast.RangeStmt: - s.parseRangeStmt(typed) - case *ast.ForStmt: - s.parseForStmt(typed) - case *ast.DeclStmt: - s.parseDeclStmt(typed, stack) - case *ast.AssignStmt: - s.parseAssignStmt(typed, stack) - - case *ast.UnaryExpr: - return s.checkUnaryExpr(typed, stack) - } - return nil, token.NoPos, true -} - -// parseRangeStmt will check range statement (i.e. `for <KEY>, <VALUE> := range ...`), -// and collect positions of <KEY> and <VALUE>. -func (s *Searcher) parseRangeStmt(n *ast.RangeStmt) { - s.storeLoopVars(n.Key) - s.storeLoopVars(n.Value) -} - -// parseForStmt will check for statement (i.e. `for <VALUE> := <INIT>; <CONDITION>; <INCREMENT>`), -// and collect positions of <VALUE>. -func (s *Searcher) parseForStmt(n *ast.ForStmt) { - switch post := n.Post.(type) { - case *ast.AssignStmt: - // e.g. for p = head; p != nil; p = p.next - for _, lhs := range post.Lhs { - s.storeLoopVars(lhs) - } - case *ast.IncDecStmt: - // e.g. for i := 0; i < n; i++ - s.storeLoopVars(post.X) - } -} - -func (s *Searcher) storeLoopVars(expr ast.Expr) { - if id, ok := expr.(*ast.Ident); ok { - s.LoopVars[id.Pos()] = struct{}{} - } -} - -// parseDeclStmt will parse declaring statement (i.e. `var`, `type`, `const`), -// and store the position if it is "var" declaration and is in any loop. -func (s *Searcher) parseDeclStmt(n *ast.DeclStmt, stack []ast.Node) { - genDecl, ok := n.Decl.(*ast.GenDecl) - if !ok { - // (dead branch) - // if the Decl is not GenDecl (i.e. `var`, `type` or `const` statement), it is ignored - return - } - if genDecl.Tok != token.VAR { - // if the Decl is not `var` (may be `type` or `const`), it is ignored - return - } - - loop, _ := s.innermostLoop(stack) - if loop == nil { - return - } - - // Register declared variables - for _, spec := range genDecl.Specs { - for _, name := range spec.(*ast.ValueSpec).Names { - s.storeLocalVar(loop, name) - } - } -} - -// parseDeclStmt will parse assignment statement (i.e. `<VAR> = <VALUE>`), -// and store the position if it is . -func (s *Searcher) parseAssignStmt(n *ast.AssignStmt, stack []ast.Node) { - if n.Tok != token.DEFINE { - // if the statement is simple assignment (without definement), it is ignored - return - } - - loop, _ := s.innermostLoop(stack) - if loop == nil { - return - } - - // Find statements declaring local variable - for _, h := range n.Lhs { - s.storeLocalVar(loop, h) - } -} - -func (s *Searcher) storeLocalVar(loop ast.Node, expr ast.Expr) { - loopPos := loop.Pos() - id, ok := expr.(*ast.Ident) - if !ok { - return - } - vars, ok := s.LocalVars[loopPos] - if !ok { - vars = map[token.Pos]struct{}{} - } - vars[id.Obj.Pos()] = struct{}{} - s.LocalVars[loopPos] = vars -} - -func insertionPosition(block *ast.BlockStmt) token.Pos { - if len(block.List) > 0 { - return block.List[0].Pos() - } - return token.NoPos -} - -func (s *Searcher) innermostLoop(stack []ast.Node) (ast.Node, token.Pos) { - for i := len(stack) - 1; i >= 0; i-- { - switch typed := stack[i].(type) { - case *ast.RangeStmt: - return typed, insertionPosition(typed.Body) - case *ast.ForStmt: - return typed, insertionPosition(typed.Body) - } - } - return nil, token.NoPos -} - -// checkUnaryExpr check unary expression (i.e. <OPERATOR><VAR> like `-x`, `*p` or `&v`) and stack. -// THIS IS THE ESSENTIAL PART OF THIS PARSER. -func (s *Searcher) checkUnaryExpr(n *ast.UnaryExpr, stack []ast.Node) (*ast.Ident, token.Pos, bool) { - if n.Op != token.AND { - return nil, token.NoPos, true - } - - loop, insert := s.innermostLoop(stack) - if loop == nil { - return nil, token.NoPos, true - } - - // Get identity of the referred item - id := s.getIdentity(n.X) - if id == nil { - return nil, token.NoPos, true - } - - // If the identity is not the loop statement variable, - // it will not be reported. - if _, isDecl := s.LoopVars[id.Obj.Pos()]; !isDecl { - return nil, token.NoPos, true - } - - // check stack append(), []X{}, map[Type]X{}, Struct{}, &Struct{}, X.(Type), (X) - // in the <outer> = - var mayRHPos token.Pos - for i := len(stack) - 2; i >= 0; i-- { - switch typed := stack[i].(type) { - case (*ast.UnaryExpr): - // noop - case (*ast.CompositeLit): - // noop - case (*ast.KeyValueExpr): - // noop - case (*ast.CallExpr): - fun, ok := typed.Fun.(*ast.Ident) - if !ok { - return nil, token.NoPos, false // it's calling a function other of `append`. It cannot be checked - } - - if fun.Name != "append" { - return nil, token.NoPos, false // it's calling a function other of `append`. It cannot be checked - } - - case (*ast.AssignStmt): - if len(typed.Rhs) != len(typed.Lhs) { - return nil, token.NoPos, false // dead logic - } - - // search x where Rhs[x].Pos() == mayRHPos - var index int - for ri, rh := range typed.Rhs { - if rh.Pos() == mayRHPos { - index = ri - break - } - } - - // check Lhs[x] is not local variable - lh := typed.Lhs[index] - isVar := s.isVar(loop, lh) - if !isVar { - return id, insert, false - } - - return nil, token.NoPos, true - default: - // Other statement is not able to be checked. - return nil, token.NoPos, false - } - - // memory an expr that may be right-hand in the AssignStmt - mayRHPos = stack[i].Pos() - } - return nil, token.NoPos, true -} - -func (s *Searcher) isVar(loop ast.Node, expr ast.Expr) bool { - vars := s.LocalVars[loop.Pos()] // map[token.Pos]struct{} - if vars == nil { - return false - } - switch typed := expr.(type) { - case (*ast.Ident): - if typed.Obj == nil { - return false // global var in another file (ref: #13) - } - _, isVar := vars[typed.Obj.Pos()] - return isVar - case (*ast.IndexExpr): // like X[Y], check X - return s.isVar(loop, typed.X) - case (*ast.SelectorExpr): // like X.Y, check X - return s.isVar(loop, typed.X) - } - return false -} - -// Get variable identity -func (s *Searcher) getIdentity(expr ast.Expr) *ast.Ident { - switch typed := expr.(type) { - case *ast.SelectorExpr: - // Ignore if the parent is pointer ref (fix for #2) - if _, ok := s.Pass.TypesInfo.Types[typed.X].Type.(*types.Pointer); ok { - return nil - } - - // Get parent identity; i.e. `a.b` of the `a.b.c`. - return s.getIdentity(typed.X) - - case *ast.Ident: - // Get simple identity; i.e. `a` of the `a`. - if typed.Obj == nil { - return nil - } - return typed - } - return nil -} |
