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 }