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