src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa9005.go (3684B)


      1 package sa9005
      2 
      3 import (
      4 	"fmt"
      5 	"go/types"
      6 
      7 	"honnef.co/go/tools/analysis/callcheck"
      8 	"honnef.co/go/tools/analysis/code"
      9 	"honnef.co/go/tools/analysis/facts/generated"
     10 	"honnef.co/go/tools/analysis/lint"
     11 	"honnef.co/go/tools/go/types/typeutil"
     12 	"honnef.co/go/tools/internal/passes/buildir"
     13 	"honnef.co/go/tools/knowledge"
     14 
     15 	"golang.org/x/tools/go/analysis"
     16 )
     17 
     18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     19 	Analyzer: &analysis.Analyzer{
     20 		Name: "SA9005",
     21 		Requires: []*analysis.Analyzer{
     22 			buildir.Analyzer,
     23 			// Filtering generated code because it may include empty structs generated from data models.
     24 			generated.Analyzer,
     25 		},
     26 		Run: callcheck.Analyzer(rules),
     27 	},
     28 	Doc: &lint.RawDocumentation{
     29 		Title: `Trying to marshal a struct with no public fields nor custom marshaling`,
     30 		Text: `
     31 The \'encoding/json\' and \'encoding/xml\' packages only operate on exported
     32 fields in structs, not unexported ones. It is usually an error to try
     33 to (un)marshal structs that only consist of unexported fields.
     34 
     35 This check will not flag calls involving types that define custom
     36 marshaling behavior, e.g. via \'MarshalJSON\' methods. It will also not
     37 flag empty structs.`,
     38 		Since:    "2019.2",
     39 		Severity: lint.SeverityWarning,
     40 		MergeIf:  lint.MergeIfAll,
     41 	},
     42 })
     43 
     44 var Analyzer = SCAnalyzer.Analyzer
     45 
     46 var rules = map[string]callcheck.Check{
     47 	// TODO(dh): should we really flag XML? Even an empty struct
     48 	// produces a non-zero amount of data, namely its type name.
     49 	// Let's see if we encounter any false positives.
     50 	//
     51 	// Also, should we flag gob?
     52 	"encoding/json.Marshal":           check(knowledge.Arg("json.Marshal.v"), "MarshalJSON", "MarshalText"),
     53 	"encoding/json.MarshalIndent":     check(knowledge.Arg("json.MarshalIndent.v"), "MarshalJSON", "MarshalText"),
     54 	"encoding/xml.Marshal":            check(knowledge.Arg("xml.Marshal.v"), "MarshalXML", "MarshalText"),
     55 	"encoding/xml.MarshalIndent":      check(knowledge.Arg("xml.MarshalIndent.v"), "MarshalXML", "MarshalText"),
     56 	"(*encoding/json.Encoder).Encode": check(knowledge.Arg("(*encoding/json.Encoder).Encode.v"), "MarshalJSON", "MarshalText"),
     57 	"(*encoding/xml.Encoder).Encode":  check(knowledge.Arg("(*encoding/xml.Encoder).Encode.v"), "MarshalXML", "MarshalText"),
     58 
     59 	"encoding/json.Unmarshal":         check(knowledge.Arg("json.Unmarshal.v"), "UnmarshalJSON", "UnmarshalText"),
     60 	"encoding/xml.Unmarshal":          check(knowledge.Arg("xml.Unmarshal.v"), "UnmarshalXML", "UnmarshalText"),
     61 	"(*encoding/json.Decoder).Decode": check(knowledge.Arg("(*encoding/json.Decoder).Decode.v"), "UnmarshalJSON", "UnmarshalText"),
     62 	"(*encoding/xml.Decoder).Decode":  check(knowledge.Arg("(*encoding/xml.Decoder).Decode.v"), "UnmarshalXML", "UnmarshalText"),
     63 }
     64 
     65 func check(argN int, meths ...string) callcheck.Check {
     66 	return func(call *callcheck.Call) {
     67 		if code.IsGenerated(call.Pass, call.Instr.Pos()) {
     68 			return
     69 		}
     70 		arg := call.Args[argN]
     71 		T := arg.Value.Value.Type()
     72 		Ts, ok := typeutil.Dereference(T).Underlying().(*types.Struct)
     73 		if !ok {
     74 			return
     75 		}
     76 		if Ts.NumFields() == 0 {
     77 			return
     78 		}
     79 		fields := typeutil.FlattenFields(Ts)
     80 		for _, field := range fields {
     81 			if field.Var.Exported() {
     82 				return
     83 			}
     84 		}
     85 		// OPT(dh): we could use a method set cache here
     86 		ms := call.Instr.Parent().Prog.MethodSets.MethodSet(T)
     87 		// TODO(dh): we're not checking the signature, which can cause false negatives.
     88 		// This isn't a huge problem, however, since vet complains about incorrect signatures.
     89 		for _, meth := range meths {
     90 			if ms.Lookup(nil, meth) != nil {
     91 				return
     92 			}
     93 		}
     94 		arg.Invalid(fmt.Sprintf("struct type '%s' doesn't have any exported fields, nor custom marshaling", typeutil.Dereference(T)))
     95 	}
     96 }