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 }