github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/errcheck/errcheck.go (about)

     1  package errcheck
     2  
     3  import (
     4  	"go/types"
     5  
     6  	"github.com/golangci/go-tools/functions"
     7  	"github.com/golangci/go-tools/lint"
     8  	. "github.com/golangci/go-tools/lint/lintdsl"
     9  	"github.com/golangci/go-tools/ssa"
    10  )
    11  
    12  type Checker struct {
    13  	funcDescs *functions.Descriptions
    14  }
    15  
    16  func NewChecker() *Checker {
    17  	return &Checker{}
    18  }
    19  
    20  func (*Checker) Name() string   { return "errcheck" }
    21  func (*Checker) Prefix() string { return "ERR" }
    22  
    23  func (c *Checker) Checks() []lint.Check {
    24  	return []lint.Check{
    25  		{ID: "ERR1000", FilterGenerated: false, Fn: c.CheckErrcheck},
    26  	}
    27  }
    28  
    29  func (c *Checker) Init(prog *lint.Program) {
    30  	c.funcDescs = functions.NewDescriptions(prog.SSA)
    31  }
    32  
    33  func (c *Checker) CheckErrcheck(j *lint.Job) {
    34  	for _, ssafn := range j.Program.InitialFunctions {
    35  		for _, b := range ssafn.Blocks {
    36  			for _, ins := range b.Instrs {
    37  				ssacall, ok := ins.(ssa.CallInstruction)
    38  				if !ok {
    39  					continue
    40  				}
    41  
    42  				switch CallName(ssacall.Common()) {
    43  				case "fmt.Print", "fmt.Println", "fmt.Printf":
    44  					continue
    45  				}
    46  				isRecover := false
    47  				if builtin, ok := ssacall.Common().Value.(*ssa.Builtin); ok {
    48  					isRecover = ok && builtin.Name() == "recover"
    49  				}
    50  
    51  				switch ins := ins.(type) {
    52  				case ssa.Value:
    53  					refs := ins.Referrers()
    54  					if refs == nil || len(FilterDebug(*refs)) != 0 {
    55  						continue
    56  					}
    57  				case ssa.Instruction:
    58  					// will be a 'go' or 'defer', neither of which has usable return values
    59  				default:
    60  					// shouldn't happen
    61  					continue
    62  				}
    63  
    64  				if ssacall.Common().IsInvoke() {
    65  					if sc, ok := ssacall.Common().Value.(*ssa.Call); ok {
    66  						// TODO(dh): support multiple levels of
    67  						// interfaces, not just one
    68  						ssafn := sc.Common().StaticCallee()
    69  						if ssafn != nil {
    70  							ct := c.funcDescs.Get(ssafn).ConcreteReturnTypes
    71  							// TODO(dh): support >1 concrete types
    72  							if len(ct) == 1 {
    73  								// TODO(dh): do we have access to a
    74  								// cached method set somewhere?
    75  								ms := types.NewMethodSet(ct[0].At(ct[0].Len() - 1).Type())
    76  								// TODO(dh): where can we get the pkg
    77  								// for Lookup? Passing nil works fine
    78  								// for exported methods, but will fail
    79  								// on unexported ones
    80  								// TODO(dh): holy nesting and poor
    81  								// variable names, clean this up
    82  								fn, _ := ms.Lookup(nil, ssacall.Common().Method.Name()).Obj().(*types.Func)
    83  								if fn != nil {
    84  									ssafn := j.Program.SSA.FuncValue(fn)
    85  									if ssafn != nil {
    86  										if c.funcDescs.Get(ssafn).NilError {
    87  											continue
    88  										}
    89  									}
    90  								}
    91  							}
    92  						}
    93  					}
    94  				} else {
    95  					ssafn := ssacall.Common().StaticCallee()
    96  					if ssafn != nil {
    97  						if c.funcDescs.Get(ssafn).NilError {
    98  							// Don't complain when the error is known to be nil
    99  							continue
   100  						}
   101  					}
   102  				}
   103  				switch CallName(ssacall.Common()) {
   104  				case "(*os.File).Close":
   105  					recv := ssacall.Common().Args[0]
   106  					if isReadOnlyFile(recv, nil) {
   107  						continue
   108  					}
   109  				}
   110  
   111  				res := ssacall.Common().Signature().Results()
   112  				if res.Len() == 0 {
   113  					continue
   114  				}
   115  				if !isRecover {
   116  					last := res.At(res.Len() - 1)
   117  					if types.TypeString(last.Type(), nil) != "error" {
   118  						continue
   119  					}
   120  				}
   121  				j.Errorf(ins, "unchecked error")
   122  			}
   123  		}
   124  	}
   125  }
   126  
   127  func isReadOnlyFile(val ssa.Value, seen map[ssa.Value]bool) bool {
   128  	if seen == nil {
   129  		seen = map[ssa.Value]bool{}
   130  	}
   131  	if seen[val] {
   132  		return true
   133  	}
   134  	seen[val] = true
   135  	switch val := val.(type) {
   136  	case *ssa.Phi:
   137  		for _, edge := range val.Edges {
   138  			if !isReadOnlyFile(edge, seen) {
   139  				return false
   140  			}
   141  		}
   142  		return true
   143  	case *ssa.Extract:
   144  		call, ok := val.Tuple.(*ssa.Call)
   145  		if !ok {
   146  			return false
   147  		}
   148  		switch CallName(call.Common()) {
   149  		case "os.Open":
   150  			return true
   151  		case "os.OpenFile":
   152  			flags, ok := call.Common().Args[1].(*ssa.Const)
   153  			return ok && flags.Uint64() == 0
   154  		}
   155  		return false
   156  	}
   157  	return false
   158  }