github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/honnef.co/go/tools/errcheck/errcheck.go (about)

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