mirror of
https://github.com/Crocmagnon/fatcontext.git
synced 2024-11-09 17:53:59 +01:00
3e9e29f41c
As long as the context is rooted in a non-pointer value that has a new copy in the loop, it is as safe to copy that value as it is to copy the context. So only report such cases when they are indirected and thus shared.
140 lines
2.8 KiB
Go
140 lines
2.8 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/printer"
|
|
"go/token"
|
|
|
|
"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: "fatcontext",
|
|
Doc: "detects nested contexts in loops",
|
|
Run: run,
|
|
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
|
}
|
|
|
|
var errUnknown = errors.New("unknown node type")
|
|
|
|
func run(pass *analysis.Pass) (interface{}, error) {
|
|
inspctr := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
|
|
|
nodeFilter := []ast.Node{
|
|
(*ast.ForStmt)(nil),
|
|
(*ast.RangeStmt)(nil),
|
|
}
|
|
|
|
inspctr.Preorder(nodeFilter, func(node ast.Node) {
|
|
body, err := getBody(node)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, stmt := range body.List {
|
|
assignStmt, ok := stmt.(*ast.AssignStmt)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
t := pass.TypesInfo.TypeOf(assignStmt.Lhs[0])
|
|
if t == nil {
|
|
continue
|
|
}
|
|
|
|
if t.String() != "context.Context" {
|
|
continue
|
|
}
|
|
|
|
if assignStmt.Tok == token.DEFINE {
|
|
break
|
|
}
|
|
|
|
if lhs := getRootIdent(pass, assignStmt.Lhs[0]); lhs != nil {
|
|
if obj := pass.TypesInfo.ObjectOf(lhs); obj != nil {
|
|
if obj.Pos() >= body.Pos() && obj.Pos() < body.End() {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
suggestedStmt := ast.AssignStmt{
|
|
Lhs: assignStmt.Lhs,
|
|
TokPos: assignStmt.TokPos,
|
|
Tok: token.DEFINE,
|
|
Rhs: assignStmt.Rhs,
|
|
}
|
|
suggested, err := render(pass.Fset, &suggestedStmt)
|
|
|
|
var fixes []analysis.SuggestedFix
|
|
if err == nil {
|
|
fixes = append(fixes, analysis.SuggestedFix{
|
|
Message: "replace `=` with `:=`",
|
|
TextEdits: []analysis.TextEdit{
|
|
{
|
|
Pos: assignStmt.Pos(),
|
|
End: assignStmt.End(),
|
|
NewText: []byte(suggested),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
pass.Report(analysis.Diagnostic{
|
|
Pos: assignStmt.Pos(),
|
|
Message: "nested context in loop",
|
|
SuggestedFixes: fixes,
|
|
})
|
|
|
|
break
|
|
}
|
|
})
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func getBody(node ast.Node) (*ast.BlockStmt, error) {
|
|
forStmt, ok := node.(*ast.ForStmt)
|
|
if ok {
|
|
return forStmt.Body, nil
|
|
}
|
|
|
|
rangeStmt, ok := node.(*ast.RangeStmt)
|
|
if ok {
|
|
return rangeStmt.Body, nil
|
|
}
|
|
|
|
return nil, errUnknown
|
|
}
|
|
|
|
func getRootIdent(pass *analysis.Pass, node ast.Node) *ast.Ident {
|
|
for {
|
|
switch n := node.(type) {
|
|
case *ast.Ident:
|
|
return n
|
|
case *ast.IndexExpr:
|
|
node = n.X
|
|
case *ast.SelectorExpr:
|
|
if sel, ok := pass.TypesInfo.Selections[n]; ok && sel.Indirect() {
|
|
return nil // indirected (pointer) roots don't imply a (safe) copy
|
|
}
|
|
node = n.X
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// render returns the pretty-print of the given node
|
|
func render(fset *token.FileSet, x interface{}) (string, error) {
|
|
var buf bytes.Buffer
|
|
if err := printer.Fprint(&buf, fset, x); err != nil {
|
|
return "", fmt.Errorf("printing node: %w", err)
|
|
}
|
|
return buf.String(), nil
|
|
}
|