Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package unusedresult defines an analyzer that checks for unused
     6  // results of calls to certain functions.
     7  package unusedresult
     8  
     9  // It is tempting to make this analysis inductive: for each function
    10  // that tail-calls one of the functions that we check, check those
    11  // functions too. However, just because you must use the result of
    12  // fmt.Sprintf doesn't mean you need to use the result of every
    13  // function that returns a formatted string: it may have other results
    14  // and effects.
    15  
    16  import (
    17  	_ "embed"
    18  	"go/ast"
    19  	"go/token"
    20  	"go/types"
    21  	"sort"
    22  	"strings"
    23  
    24  	"golang.org/x/tools/go/analysis"
    25  	"golang.org/x/tools/go/analysis/passes/inspect"
    26  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    27  	"golang.org/x/tools/go/ast/inspector"
    28  	"golang.org/x/tools/go/types/typeutil"
    29  	"golang.org/x/tools/internal/analysisinternal"
    30  )
    31  
    32  //go:embed doc.go
    33  var doc string
    34  
    35  var Analyzer = &analysis.Analyzer{
    36  	Name:     "unusedresult",
    37  	Doc:      analysisutil.MustExtractDoc(doc, "unusedresult"),
    38  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
    39  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    40  	Run:      run,
    41  }
    42  
    43  // flags
    44  var funcs, stringMethods stringSetFlag
    45  
    46  func init() {
    47  	// TODO(adonovan): provide a comment or declaration syntax to
    48  	// allow users to add their functions to this set using facts.
    49  	// For example:
    50  	//
    51  	//    func ignoringTheErrorWouldBeVeryBad() error {
    52  	//      type mustUseResult struct{} // enables vet unusedresult check
    53  	//      ...
    54  	//    }
    55  	//
    56  	//    ignoringTheErrorWouldBeVeryBad() // oops
    57  	//
    58  
    59  	// List standard library functions here.
    60  	// The context.With{Cancel,Deadline,Timeout} entries are
    61  	// effectively redundant wrt the lostcancel analyzer.
    62  	funcs = stringSetFlag{
    63  		"context.WithCancel":      true,
    64  		"context.WithDeadline":    true,
    65  		"context.WithTimeout":     true,
    66  		"context.WithValue":       true,
    67  		"errors.New":              true,
    68  		"fmt.Append":              true,
    69  		"fmt.Appendf":             true,
    70  		"fmt.Appendln":            true,
    71  		"fmt.Errorf":              true,
    72  		"fmt.Sprint":              true,
    73  		"fmt.Sprintf":             true,
    74  		"fmt.Sprintln":            true,
    75  		"maps.All":                true,
    76  		"maps.Clone":              true,
    77  		"maps.Collect":            true,
    78  		"maps.Equal":              true,
    79  		"maps.EqualFunc":          true,
    80  		"maps.Keys":               true,
    81  		"maps.Values":             true,
    82  		"slices.All":              true,
    83  		"slices.AppendSeq":        true,
    84  		"slices.Backward":         true,
    85  		"slices.BinarySearch":     true,
    86  		"slices.BinarySearchFunc": true,
    87  		"slices.Chunk":            true,
    88  		"slices.Clip":             true,
    89  		"slices.Clone":            true,
    90  		"slices.Collect":          true,
    91  		"slices.Compact":          true,
    92  		"slices.CompactFunc":      true,
    93  		"slices.Compare":          true,
    94  		"slices.CompareFunc":      true,
    95  		"slices.Concat":           true,
    96  		"slices.Contains":         true,
    97  		"slices.ContainsFunc":     true,
    98  		"slices.Delete":           true,
    99  		"slices.DeleteFunc":       true,
   100  		"slices.Equal":            true,
   101  		"slices.EqualFunc":        true,
   102  		"slices.Grow":             true,
   103  		"slices.Index":            true,
   104  		"slices.IndexFunc":        true,
   105  		"slices.Insert":           true,
   106  		"slices.IsSorted":         true,
   107  		"slices.IsSortedFunc":     true,
   108  		"slices.Max":              true,
   109  		"slices.MaxFunc":          true,
   110  		"slices.Min":              true,
   111  		"slices.MinFunc":          true,
   112  		"slices.Repeat":           true,
   113  		"slices.Replace":          true,
   114  		"slices.Sorted":           true,
   115  		"slices.SortedFunc":       true,
   116  		"slices.SortedStableFunc": true,
   117  		"slices.Values":           true,
   118  		"sort.Reverse":            true,
   119  	}
   120  	Analyzer.Flags.Var(&funcs, "funcs",
   121  		"comma-separated list of functions whose results must be used")
   122  
   123  	stringMethods.Set("Error,String")
   124  	Analyzer.Flags.Var(&stringMethods, "stringmethods",
   125  		"comma-separated list of names of methods of type func() string whose results must be used")
   126  }
   127  
   128  func run(pass *analysis.Pass) (any, error) {
   129  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
   130  
   131  	// Split functions into (pkg, name) pairs to save allocation later.
   132  	pkgFuncs := make(map[[2]string]bool, len(funcs))
   133  	for s := range funcs {
   134  		if i := strings.LastIndexByte(s, '.'); i > 0 {
   135  			pkgFuncs[[2]string{s[:i], s[i+1:]}] = true
   136  		}
   137  	}
   138  
   139  	nodeFilter := []ast.Node{
   140  		(*ast.ExprStmt)(nil),
   141  	}
   142  	inspect.Preorder(nodeFilter, func(n ast.Node) {
   143  		call, ok := ast.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
   144  		if !ok {
   145  			return // not a call statement
   146  		}
   147  
   148  		// Call to function or method?
   149  		fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
   150  		if !ok {
   151  			return // e.g. var or builtin
   152  		}
   153  		if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
   154  			// method (e.g. foo.String())
   155  			if types.Identical(sig, sigNoArgsStringResult) {
   156  				if stringMethods[fn.Name()] {
   157  					pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
   158  						"result of (%s).%s call not used",
   159  						sig.Recv().Type(), fn.Name())
   160  				}
   161  			}
   162  		} else {
   163  			// package-level function (e.g. fmt.Errorf)
   164  			if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
   165  				pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
   166  					"result of %s.%s call not used",
   167  					fn.Pkg().Path(), fn.Name())
   168  			}
   169  		}
   170  	})
   171  	return nil, nil
   172  }
   173  
   174  // func() string
   175  var sigNoArgsStringResult = types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])), false)
   176  
   177  type stringSetFlag map[string]bool
   178  
   179  func (ss *stringSetFlag) String() string {
   180  	var items []string
   181  	for item := range *ss {
   182  		items = append(items, item)
   183  	}
   184  	sort.Strings(items)
   185  	return strings.Join(items, ",")
   186  }
   187  
   188  func (ss *stringSetFlag) Set(s string) error {
   189  	m := make(map[string]bool) // clobber previous value
   190  	if s != "" {
   191  		for _, name := range strings.Split(s, ",") {
   192  			if name == "" {
   193  				continue // TODO: report error? proceed?
   194  			}
   195  			m[name] = true
   196  		}
   197  	}
   198  	*ss = m
   199  	return nil
   200  }
   201  

View as plain text