github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1003/sa1003.go (about)

     1  package sa1003
     2  
     3  import (
     4  	"fmt"
     5  	"go/types"
     6  
     7  	"github.com/amarpal/go-tools/analysis/callcheck"
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/internal/passes/buildir"
    11  	"github.com/amarpal/go-tools/knowledge"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  )
    15  
    16  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    17  	Analyzer: &analysis.Analyzer{
    18  		Name:     "SA1003",
    19  		Requires: []*analysis.Analyzer{buildir.Analyzer},
    20  		Run:      callcheck.Analyzer(checkEncodingBinaryRules),
    21  	},
    22  	Doc: &lint.Documentation{
    23  		Title: `Unsupported argument to functions in \'encoding/binary\'`,
    24  		Text: `The \'encoding/binary\' package can only serialize types with known sizes.
    25  This precludes the use of the \'int\' and \'uint\' types, as their sizes
    26  differ on different architectures. Furthermore, it doesn't support
    27  serializing maps, channels, strings, or functions.
    28  
    29  Before Go 1.8, \'bool\' wasn't supported, either.`,
    30  		Since:    "2017.1",
    31  		Severity: lint.SeverityError,
    32  		MergeIf:  lint.MergeIfAny,
    33  	},
    34  })
    35  
    36  var Analyzer = SCAnalyzer.Analyzer
    37  
    38  var checkEncodingBinaryRules = map[string]callcheck.Check{
    39  	"encoding/binary.Write": func(call *callcheck.Call) {
    40  		arg := call.Args[knowledge.Arg("encoding/binary.Write.data")]
    41  		if !CanBinaryMarshal(call.Pass, call.Parent, arg.Value) {
    42  			arg.Invalid(fmt.Sprintf("value of type %s cannot be used with binary.Write", arg.Value.Value.Type()))
    43  		}
    44  	},
    45  }
    46  
    47  func CanBinaryMarshal(pass *analysis.Pass, node code.Positioner, v callcheck.Value) bool {
    48  	typ := v.Value.Type().Underlying()
    49  	if ttyp, ok := typ.(*types.Pointer); ok {
    50  		typ = ttyp.Elem().Underlying()
    51  	}
    52  	if ttyp, ok := typ.(interface {
    53  		Elem() types.Type
    54  	}); ok {
    55  		if _, ok := ttyp.(*types.Pointer); !ok {
    56  			typ = ttyp.Elem()
    57  		}
    58  	}
    59  
    60  	return validEncodingBinaryType(pass, node, typ)
    61  }
    62  
    63  func validEncodingBinaryType(pass *analysis.Pass, node code.Positioner, typ types.Type) bool {
    64  	typ = typ.Underlying()
    65  	switch typ := typ.(type) {
    66  	case *types.Basic:
    67  		switch typ.Kind() {
    68  		case types.Uint8, types.Uint16, types.Uint32, types.Uint64,
    69  			types.Int8, types.Int16, types.Int32, types.Int64,
    70  			types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid:
    71  			return true
    72  		case types.Bool:
    73  			return code.StdlibVersion(pass, node) >= 8
    74  		}
    75  		return false
    76  	case *types.Struct:
    77  		n := typ.NumFields()
    78  		for i := 0; i < n; i++ {
    79  			if !validEncodingBinaryType(pass, node, typ.Field(i).Type()) {
    80  				return false
    81  			}
    82  		}
    83  		return true
    84  	case *types.Array:
    85  		return validEncodingBinaryType(pass, node, typ.Elem())
    86  	case *types.Interface:
    87  		// we can't determine if it's a valid type or not
    88  		return true
    89  	}
    90  	return false
    91  }