.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / go / analysis / passes / lostcancel / lostcancel.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/go/analysis/passes/lostcancel/lostcancel.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/go/analysis/passes/lostcancel/lostcancel.go
new file mode 100644 (file)
index 0000000..de6f840
--- /dev/null
@@ -0,0 +1,330 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package lostcancel defines an Analyzer that checks for failure to
+// call a context cancellation function.
+package lostcancel
+
+import (
+       "fmt"
+       "go/ast"
+       "go/types"
+
+       "golang.org/x/tools/go/analysis"
+       "golang.org/x/tools/go/analysis/passes/ctrlflow"
+       "golang.org/x/tools/go/analysis/passes/inspect"
+       "golang.org/x/tools/go/ast/inspector"
+       "golang.org/x/tools/go/cfg"
+)
+
+const Doc = `check cancel func returned by context.WithCancel is called
+
+The cancellation function returned by context.WithCancel, WithTimeout,
+and WithDeadline must be called or the new context will remain live
+until its parent context is cancelled.
+(The background context is never cancelled.)`
+
+var Analyzer = &analysis.Analyzer{
+       Name: "lostcancel",
+       Doc:  Doc,
+       Run:  run,
+       Requires: []*analysis.Analyzer{
+               inspect.Analyzer,
+               ctrlflow.Analyzer,
+       },
+}
+
+const debug = false
+
+var contextPackage = "context"
+
+// checkLostCancel reports a failure to the call the cancel function
+// returned by context.WithCancel, either because the variable was
+// assigned to the blank identifier, or because there exists a
+// control-flow path from the call to a return statement and that path
+// does not "use" the cancel function.  Any reference to the variable
+// counts as a use, even within a nested function literal.
+// If the variable's scope is larger than the function
+// containing the assignment, we assume that other uses exist.
+//
+// checkLostCancel analyzes a single named or literal function.
+func run(pass *analysis.Pass) (interface{}, error) {
+       // Fast path: bypass check if file doesn't use context.WithCancel.
+       if !hasImport(pass.Pkg, contextPackage) {
+               return nil, nil
+       }
+
+       // Call runFunc for each Func{Decl,Lit}.
+       inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+       nodeTypes := []ast.Node{
+               (*ast.FuncLit)(nil),
+               (*ast.FuncDecl)(nil),
+       }
+       inspect.Preorder(nodeTypes, func(n ast.Node) {
+               runFunc(pass, n)
+       })
+       return nil, nil
+}
+
+func runFunc(pass *analysis.Pass, node ast.Node) {
+       // Find scope of function node
+       var funcScope *types.Scope
+       switch v := node.(type) {
+       case *ast.FuncLit:
+               funcScope = pass.TypesInfo.Scopes[v.Type]
+       case *ast.FuncDecl:
+               funcScope = pass.TypesInfo.Scopes[v.Type]
+       }
+
+       // Maps each cancel variable to its defining ValueSpec/AssignStmt.
+       cancelvars := make(map[*types.Var]ast.Node)
+
+       // TODO(adonovan): opt: refactor to make a single pass
+       // over the AST using inspect.WithStack and node types
+       // {FuncDecl,FuncLit,CallExpr,SelectorExpr}.
+
+       // Find the set of cancel vars to analyze.
+       stack := make([]ast.Node, 0, 32)
+       ast.Inspect(node, func(n ast.Node) bool {
+               switch n.(type) {
+               case *ast.FuncLit:
+                       if len(stack) > 0 {
+                               return false // don't stray into nested functions
+                       }
+               case nil:
+                       stack = stack[:len(stack)-1] // pop
+                       return true
+               }
+               stack = append(stack, n) // push
+
+               // Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]:
+               //
+               //   ctx, cancel    := context.WithCancel(...)
+               //   ctx, cancel     = context.WithCancel(...)
+               //   var ctx, cancel = context.WithCancel(...)
+               //
+               if !isContextWithCancel(pass.TypesInfo, n) || !isCall(stack[len(stack)-2]) {
+                       return true
+               }
+               var id *ast.Ident // id of cancel var
+               stmt := stack[len(stack)-3]
+               switch stmt := stmt.(type) {
+               case *ast.ValueSpec:
+                       if len(stmt.Names) > 1 {
+                               id = stmt.Names[1]
+                       }
+               case *ast.AssignStmt:
+                       if len(stmt.Lhs) > 1 {
+                               id, _ = stmt.Lhs[1].(*ast.Ident)
+                       }
+               }
+               if id != nil {
+                       if id.Name == "_" {
+                               pass.ReportRangef(id,
+                                       "the cancel function returned by context.%s should be called, not discarded, to avoid a context leak",
+                                       n.(*ast.SelectorExpr).Sel.Name)
+                       } else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok {
+                               // If the cancel variable is defined outside function scope,
+                               // do not analyze it.
+                               if funcScope.Contains(v.Pos()) {
+                                       cancelvars[v] = stmt
+                               }
+                       } else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok {
+                               cancelvars[v] = stmt
+                       }
+               }
+               return true
+       })
+
+       if len(cancelvars) == 0 {
+               return // no need to inspect CFG
+       }
+
+       // Obtain the CFG.
+       cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
+       var g *cfg.CFG
+       var sig *types.Signature
+       switch node := node.(type) {
+       case *ast.FuncDecl:
+               sig, _ = pass.TypesInfo.Defs[node.Name].Type().(*types.Signature)
+               if node.Name.Name == "main" && sig.Recv() == nil && pass.Pkg.Name() == "main" {
+                       // Returning from main.main terminates the process,
+                       // so there's no need to cancel contexts.
+                       return
+               }
+               g = cfgs.FuncDecl(node)
+
+       case *ast.FuncLit:
+               sig, _ = pass.TypesInfo.Types[node.Type].Type.(*types.Signature)
+               g = cfgs.FuncLit(node)
+       }
+       if sig == nil {
+               return // missing type information
+       }
+
+       // Print CFG.
+       if debug {
+               fmt.Println(g.Format(pass.Fset))
+       }
+
+       // Examine the CFG for each variable in turn.
+       // (It would be more efficient to analyze all cancelvars in a
+       // single pass over the AST, but seldom is there more than one.)
+       for v, stmt := range cancelvars {
+               if ret := lostCancelPath(pass, g, v, stmt, sig); ret != nil {
+                       lineno := pass.Fset.Position(stmt.Pos()).Line
+                       pass.ReportRangef(stmt, "the %s function is not used on all paths (possible context leak)", v.Name())
+                       pass.ReportRangef(ret, "this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno)
+               }
+       }
+}
+
+func isCall(n ast.Node) bool { _, ok := n.(*ast.CallExpr); return ok }
+
+func hasImport(pkg *types.Package, path string) bool {
+       for _, imp := range pkg.Imports() {
+               if imp.Path() == path {
+                       return true
+               }
+       }
+       return false
+}
+
+// isContextWithCancel reports whether n is one of the qualified identifiers
+// context.With{Cancel,Timeout,Deadline}.
+func isContextWithCancel(info *types.Info, n ast.Node) bool {
+       sel, ok := n.(*ast.SelectorExpr)
+       if !ok {
+               return false
+       }
+       switch sel.Sel.Name {
+       case "WithCancel", "WithTimeout", "WithDeadline":
+       default:
+               return false
+       }
+       if x, ok := sel.X.(*ast.Ident); ok {
+               if pkgname, ok := info.Uses[x].(*types.PkgName); ok {
+                       return pkgname.Imported().Path() == contextPackage
+               }
+               // Import failed, so we can't check package path.
+               // Just check the local package name (heuristic).
+               return x.Name == "context"
+       }
+       return false
+}
+
+// lostCancelPath finds a path through the CFG, from stmt (which defines
+// the 'cancel' variable v) to a return statement, that doesn't "use" v.
+// If it finds one, it returns the return statement (which may be synthetic).
+// sig is the function's type, if known.
+func lostCancelPath(pass *analysis.Pass, g *cfg.CFG, v *types.Var, stmt ast.Node, sig *types.Signature) *ast.ReturnStmt {
+       vIsNamedResult := sig != nil && tupleContains(sig.Results(), v)
+
+       // uses reports whether stmts contain a "use" of variable v.
+       uses := func(pass *analysis.Pass, v *types.Var, stmts []ast.Node) bool {
+               found := false
+               for _, stmt := range stmts {
+                       ast.Inspect(stmt, func(n ast.Node) bool {
+                               switch n := n.(type) {
+                               case *ast.Ident:
+                                       if pass.TypesInfo.Uses[n] == v {
+                                               found = true
+                                       }
+                               case *ast.ReturnStmt:
+                                       // A naked return statement counts as a use
+                                       // of the named result variables.
+                                       if n.Results == nil && vIsNamedResult {
+                                               found = true
+                                       }
+                               }
+                               return !found
+                       })
+               }
+               return found
+       }
+
+       // blockUses computes "uses" for each block, caching the result.
+       memo := make(map[*cfg.Block]bool)
+       blockUses := func(pass *analysis.Pass, v *types.Var, b *cfg.Block) bool {
+               res, ok := memo[b]
+               if !ok {
+                       res = uses(pass, v, b.Nodes)
+                       memo[b] = res
+               }
+               return res
+       }
+
+       // Find the var's defining block in the CFG,
+       // plus the rest of the statements of that block.
+       var defblock *cfg.Block
+       var rest []ast.Node
+outer:
+       for _, b := range g.Blocks {
+               for i, n := range b.Nodes {
+                       if n == stmt {
+                               defblock = b
+                               rest = b.Nodes[i+1:]
+                               break outer
+                       }
+               }
+       }
+       if defblock == nil {
+               panic("internal error: can't find defining block for cancel var")
+       }
+
+       // Is v "used" in the remainder of its defining block?
+       if uses(pass, v, rest) {
+               return nil
+       }
+
+       // Does the defining block return without using v?
+       if ret := defblock.Return(); ret != nil {
+               return ret
+       }
+
+       // Search the CFG depth-first for a path, from defblock to a
+       // return block, in which v is never "used".
+       seen := make(map[*cfg.Block]bool)
+       var search func(blocks []*cfg.Block) *ast.ReturnStmt
+       search = func(blocks []*cfg.Block) *ast.ReturnStmt {
+               for _, b := range blocks {
+                       if seen[b] {
+                               continue
+                       }
+                       seen[b] = true
+
+                       // Prune the search if the block uses v.
+                       if blockUses(pass, v, b) {
+                               continue
+                       }
+
+                       // Found path to return statement?
+                       if ret := b.Return(); ret != nil {
+                               if debug {
+                                       fmt.Printf("found path to return in block %s\n", b)
+                               }
+                               return ret // found
+                       }
+
+                       // Recur
+                       if ret := search(b.Succs); ret != nil {
+                               if debug {
+                                       fmt.Printf(" from block %s\n", b)
+                               }
+                               return ret
+                       }
+               }
+               return nil
+       }
+       return search(defblock.Succs)
+}
+
+func tupleContains(tuple *types.Tuple, v *types.Var) bool {
+       for i := 0; i < tuple.Len(); i++ {
+               if tuple.At(i) == v {
+                       return true
+               }
+       }
+       return false
+}