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  }