aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
blob: bececee7e9399173937c73d1ed77a6ee20290bda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright 2015 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 unusedresult defines an analyzer that checks for unused
// results of calls to certain pure functions.
package unusedresult

import (
	"go/ast"
	"go/token"
	"go/types"
	"sort"
	"strings"

	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/passes/inspect"
	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
	"golang.org/x/tools/go/ast/inspector"
)

// TODO(adonovan): make this analysis modular: export a mustUseResult
// fact for each function that tail-calls one of the functions that we
// check, and check those functions too.

const Doc = `check for unused results of calls to some functions

Some functions like fmt.Errorf return a result and have no side effects,
so it is always a mistake to discard the result. This analyzer reports
calls to certain functions in which the result of the call is ignored.

The set of functions may be controlled using flags.`

var Analyzer = &analysis.Analyzer{
	Name:     "unusedresult",
	Doc:      Doc,
	Requires: []*analysis.Analyzer{inspect.Analyzer},
	Run:      run,
}

// flags
var funcs, stringMethods stringSetFlag

func init() {
	// TODO(adonovan): provide a comment syntax to allow users to
	// add their functions to this set using facts.
	funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout")
	Analyzer.Flags.Var(&funcs, "funcs",
		"comma-separated list of functions whose results must be used")

	stringMethods.Set("Error,String")
	Analyzer.Flags.Var(&stringMethods, "stringmethods",
		"comma-separated list of names of methods of type func() string whose results must be used")
}

func run(pass *analysis.Pass) (interface{}, error) {
	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

	nodeFilter := []ast.Node{
		(*ast.ExprStmt)(nil),
	}
	inspect.Preorder(nodeFilter, func(n ast.Node) {
		call, ok := analysisutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
		if !ok {
			return // not a call statement
		}
		fun := analysisutil.Unparen(call.Fun)

		if pass.TypesInfo.Types[fun].IsType() {
			return // a conversion, not a call
		}

		selector, ok := fun.(*ast.SelectorExpr)
		if !ok {
			return // neither a method call nor a qualified ident
		}

		sel, ok := pass.TypesInfo.Selections[selector]
		if ok && sel.Kind() == types.MethodVal {
			// method (e.g. foo.String())
			obj := sel.Obj().(*types.Func)
			sig := sel.Type().(*types.Signature)
			if types.Identical(sig, sigNoArgsStringResult) {
				if stringMethods[obj.Name()] {
					pass.Reportf(call.Lparen, "result of (%s).%s call not used",
						sig.Recv().Type(), obj.Name())
				}
			}
		} else if !ok {
			// package-qualified function (e.g. fmt.Errorf)
			obj := pass.TypesInfo.Uses[selector.Sel]
			if obj, ok := obj.(*types.Func); ok {
				qname := obj.Pkg().Path() + "." + obj.Name()
				if funcs[qname] {
					pass.Reportf(call.Lparen, "result of %v call not used", qname)
				}
			}
		}
	})
	return nil, nil
}

// func() string
var sigNoArgsStringResult = types.NewSignature(nil, nil,
	types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])),
	false)

type stringSetFlag map[string]bool

func (ss *stringSetFlag) String() string {
	var items []string
	for item := range *ss {
		items = append(items, item)
	}
	sort.Strings(items)
	return strings.Join(items, ",")
}

func (ss *stringSetFlag) Set(s string) error {
	m := make(map[string]bool) // clobber previous value
	if s != "" {
		for _, name := range strings.Split(s, ",") {
			if name == "" {
				continue // TODO: report error? proceed?
			}
			m[name] = true
		}
	}
	*ss = m
	return nil
}