honnef.co/go/tools@v0.4.7/staticcheck/rules.go (about) 1 package staticcheck 2 3 import ( 4 "fmt" 5 "go/constant" 6 "go/types" 7 "net" 8 "net/url" 9 "regexp" 10 "sort" 11 "strconv" 12 "strings" 13 "time" 14 "unicode/utf8" 15 16 "honnef.co/go/tools/analysis/code" 17 "honnef.co/go/tools/go/ir" 18 "honnef.co/go/tools/go/ir/irutil" 19 "honnef.co/go/tools/go/types/typeutil" 20 21 "golang.org/x/tools/go/analysis" 22 ) 23 24 const ( 25 MsgInvalidHostPort = "invalid port or service name in host:port pair" 26 MsgInvalidUTF8 = "argument is not a valid UTF-8 encoded string" 27 MsgNonUniqueCutset = "cutset contains duplicate characters" 28 ) 29 30 type Call struct { 31 Pass *analysis.Pass 32 Instr ir.CallInstruction 33 Args []*Argument 34 35 Parent *ir.Function 36 37 invalids []string 38 } 39 40 func (c *Call) Invalid(msg string) { 41 c.invalids = append(c.invalids, msg) 42 } 43 44 type Argument struct { 45 Value Value 46 invalids []string 47 } 48 49 type Value struct { 50 Value ir.Value 51 } 52 53 func (arg *Argument) Invalid(msg string) { 54 arg.invalids = append(arg.invalids, msg) 55 } 56 57 type CallCheck func(call *Call) 58 59 func extractConstExpectKind(v ir.Value, kind constant.Kind) *ir.Const { 60 k := extractConst(v) 61 if k == nil || k.Value == nil || k.Value.Kind() != kind { 62 return nil 63 } 64 return k 65 } 66 67 func extractConst(v ir.Value) *ir.Const { 68 v = irutil.Flatten(v) 69 switch v := v.(type) { 70 case *ir.Const: 71 return v 72 case *ir.MakeInterface: 73 return extractConst(v.X) 74 default: 75 return nil 76 } 77 } 78 79 func ValidateRegexp(v Value) error { 80 if c := extractConstExpectKind(v.Value, constant.String); c != nil { 81 s := constant.StringVal(c.Value) 82 if _, err := regexp.Compile(s); err != nil { 83 return err 84 } 85 } 86 return nil 87 } 88 89 func ValidateTimeLayout(v Value) error { 90 if c := extractConstExpectKind(v.Value, constant.String); c != nil { 91 s := constant.StringVal(c.Value) 92 s = strings.Replace(s, "_", " ", -1) 93 s = strings.Replace(s, "Z", "-", -1) 94 _, err := time.Parse(s, s) 95 if err != nil { 96 return err 97 } 98 } 99 return nil 100 } 101 102 func ValidateURL(v Value) error { 103 if c := extractConstExpectKind(v.Value, constant.String); c != nil { 104 s := constant.StringVal(c.Value) 105 _, err := url.Parse(s) 106 if err != nil { 107 return fmt.Errorf("%q is not a valid URL: %s", s, err) 108 } 109 } 110 return nil 111 } 112 113 func InvalidUTF8(v Value) bool { 114 if c := extractConstExpectKind(v.Value, constant.String); c != nil { 115 s := constant.StringVal(c.Value) 116 if !utf8.ValidString(s) { 117 return true 118 } 119 } 120 return false 121 } 122 123 func UnbufferedChannel(v Value) bool { 124 // TODO(dh): this check of course misses many cases of unbuffered 125 // channels, such as any in phi or sigma nodes. We'll eventually 126 // replace this function. 127 val := v.Value 128 if ct, ok := val.(*ir.ChangeType); ok { 129 val = ct.X 130 } 131 mk, ok := val.(*ir.MakeChan) 132 if !ok { 133 return false 134 } 135 if k, ok := mk.Size.(*ir.Const); ok && k.Value.Kind() == constant.Int { 136 if v, ok := constant.Int64Val(k.Value); ok && v == 0 { 137 return true 138 } 139 } 140 return false 141 } 142 143 func Pointer(v Value) bool { 144 switch v.Value.Type().Underlying().(type) { 145 case *types.Pointer, *types.Interface: 146 return true 147 } 148 return false 149 } 150 151 func ConvertedFromInt(v Value) bool { 152 conv, ok := v.Value.(*ir.Convert) 153 if !ok { 154 return false 155 } 156 b, ok := conv.X.Type().Underlying().(*types.Basic) 157 if !ok { 158 return false 159 } 160 if (b.Info() & types.IsInteger) == 0 { 161 return false 162 } 163 return true 164 } 165 166 func validEncodingBinaryType(pass *analysis.Pass, typ types.Type) bool { 167 typ = typ.Underlying() 168 switch typ := typ.(type) { 169 case *types.Basic: 170 switch typ.Kind() { 171 case types.Uint8, types.Uint16, types.Uint32, types.Uint64, 172 types.Int8, types.Int16, types.Int32, types.Int64, 173 types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid: 174 return true 175 case types.Bool: 176 return code.IsGoVersion(pass, 8) 177 } 178 return false 179 case *types.Struct: 180 n := typ.NumFields() 181 for i := 0; i < n; i++ { 182 if !validEncodingBinaryType(pass, typ.Field(i).Type()) { 183 return false 184 } 185 } 186 return true 187 case *types.Array: 188 return validEncodingBinaryType(pass, typ.Elem()) 189 case *types.Interface: 190 // we can't determine if it's a valid type or not 191 return true 192 } 193 return false 194 } 195 196 func CanBinaryMarshal(pass *analysis.Pass, v Value) bool { 197 typ := v.Value.Type().Underlying() 198 if ttyp, ok := typ.(*types.Pointer); ok { 199 typ = ttyp.Elem().Underlying() 200 } 201 if ttyp, ok := typ.(interface { 202 Elem() types.Type 203 }); ok { 204 if _, ok := ttyp.(*types.Pointer); !ok { 205 typ = ttyp.Elem() 206 } 207 } 208 209 return validEncodingBinaryType(pass, typ) 210 } 211 212 func RepeatZeroTimes(name string, arg int) CallCheck { 213 return func(call *Call) { 214 arg := call.Args[arg] 215 if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value.Kind() == constant.Int { 216 if v, ok := constant.Int64Val(k.Value); ok && v == 0 { 217 arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name)) 218 } 219 } 220 } 221 } 222 223 func validateServiceName(s string) bool { 224 if len(s) < 1 || len(s) > 15 { 225 return false 226 } 227 if s[0] == '-' || s[len(s)-1] == '-' { 228 return false 229 } 230 if strings.Contains(s, "--") { 231 return false 232 } 233 hasLetter := false 234 for _, r := range s { 235 if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { 236 hasLetter = true 237 continue 238 } 239 if r >= '0' && r <= '9' { 240 continue 241 } 242 return false 243 } 244 return hasLetter 245 } 246 247 func validatePort(s string) bool { 248 n, err := strconv.ParseInt(s, 10, 64) 249 if err != nil { 250 return validateServiceName(s) 251 } 252 return n >= 0 && n <= 65535 253 } 254 255 func ValidHostPort(v Value) bool { 256 if k := extractConstExpectKind(v.Value, constant.String); k != nil { 257 s := constant.StringVal(k.Value) 258 if s == "" { 259 return true 260 } 261 _, port, err := net.SplitHostPort(s) 262 if err != nil { 263 return false 264 } 265 // TODO(dh): check hostname 266 if !validatePort(port) { 267 return false 268 } 269 } 270 return true 271 } 272 273 // ConvertedFrom reports whether value v was converted from type typ. 274 func ConvertedFrom(v Value, typ string) bool { 275 change, ok := v.Value.(*ir.ChangeType) 276 return ok && typeutil.IsType(change.X.Type(), typ) 277 } 278 279 func UniqueStringCutset(v Value) bool { 280 if c := extractConstExpectKind(v.Value, constant.String); c != nil { 281 s := constant.StringVal(c.Value) 282 rs := runeSlice(s) 283 if len(rs) < 2 { 284 return true 285 } 286 sort.Sort(rs) 287 for i, r := range rs[1:] { 288 if rs[i] == r { 289 return false 290 } 291 } 292 } 293 return true 294 }