Skip to content

Commit 60e1ae4

Browse files
authored
adds checks for protobuf struct tags (#707)
* adds checks for protobuf struct tags * use actual tag numbers as key instead of strings removes debug println
1 parent e9d5b48 commit 60e1ae4

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed

rule/struct-tag.go

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (*StructTagRule) Name() string {
3535

3636
type lintStructTagRule struct {
3737
onFailure func(lint.Failure)
38-
usedTagNbr map[string]bool // list of used tag numbers
38+
usedTagNbr map[int]bool // list of used tag numbers
3939
usedTagName map[string]bool // list of used tag keys
4040
}
4141

@@ -45,7 +45,7 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
4545
if n.Fields == nil || n.Fields.NumFields() < 1 {
4646
return nil // skip empty structs
4747
}
48-
w.usedTagNbr = map[string]bool{} // init
48+
w.usedTagNbr = map[int]bool{} // init
4949
w.usedTagName = map[string]bool{} // init
5050
for _, f := range n.Fields.List {
5151
if f.Tag != nil {
@@ -74,6 +74,10 @@ func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool)
7474
}
7575

7676
tagName := w.getTagName(tag)
77+
if tagName == "" {
78+
return "", true // No tag name found
79+
}
80+
7781
// We concat the key and name as the mapping key here
7882
// to allow the same tag name in different tag type.
7983
key := tag.Key + ":" + tagName
@@ -94,7 +98,7 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
9498
return strings.TrimLeft(option, "name=")
9599
}
96100
}
97-
return "protobuf tag lacks name"
101+
return "" //protobuf tag lacks 'name' option
98102
default:
99103
return tag.Name
100104
}
@@ -139,7 +143,10 @@ func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
139143
w.addFailure(f.Tag, msg)
140144
}
141145
case "protobuf":
142-
// Not implemented yet
146+
msg, ok := w.checkProtobufTag(tag)
147+
if !ok {
148+
w.addFailure(f.Tag, msg)
149+
}
143150
case "required":
144151
if tag.Name != "true" && tag.Name != "false" {
145152
w.addFailure(f.Tag, "required should be 'true' or 'false'")
@@ -170,10 +177,14 @@ func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string,
170177
if strings.HasPrefix(opt, "tag:") {
171178
parts := strings.Split(opt, ":")
172179
tagNumber := parts[1]
173-
if w.usedTagNbr[tagNumber] {
174-
return fmt.Sprintf("duplicated tag number %s", tagNumber), false
180+
number, err := strconv.Atoi(tagNumber)
181+
if err != nil {
182+
return fmt.Sprintf("ASN1 tag must be a number, got '%s'", tagNumber), false
183+
}
184+
if w.usedTagNbr[number] {
185+
return fmt.Sprintf("duplicated tag number %v", number), false
175186
}
176-
w.usedTagNbr[tagNumber] = true
187+
w.usedTagNbr[number] = true
177188

178189
continue
179190
}
@@ -275,6 +286,57 @@ func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
275286
return typeMatches
276287
}
277288

289+
func (w lintStructTagRule) checkProtobufTag(tag *structtag.Tag) (string, bool) {
290+
// check name
291+
switch tag.Name {
292+
case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64":
293+
// do nothing
294+
default:
295+
return fmt.Sprintf("invalid protobuf tag name '%s'", tag.Name), false
296+
}
297+
298+
// check options
299+
seenOptions := map[string]bool{}
300+
for _, opt := range tag.Options {
301+
if number, err := strconv.Atoi(opt); err == nil {
302+
_, alreadySeen := w.usedTagNbr[number]
303+
if alreadySeen {
304+
return fmt.Sprintf("duplicated tag number %v", number), false
305+
}
306+
w.usedTagNbr[number] = true
307+
continue // option is an integer
308+
}
309+
310+
switch {
311+
case opt == "opt" || opt == "proto3" || opt == "rep" || opt == "req":
312+
// do nothing
313+
case strings.Contains(opt, "="):
314+
o := strings.Split(opt, "=")[0]
315+
_, alreadySeen := seenOptions[o]
316+
if alreadySeen {
317+
return fmt.Sprintf("protobuf tag has duplicated option '%s'", o), false
318+
}
319+
seenOptions[o] = true
320+
continue
321+
}
322+
}
323+
_, hasName := seenOptions["name"]
324+
if !hasName {
325+
return "protobuf tag lacks mandatory option 'name'", false
326+
}
327+
328+
for k := range seenOptions {
329+
switch k {
330+
case "name", "json":
331+
// do nothing
332+
default:
333+
return fmt.Sprintf("unknown option '%s' in protobuf tag", k), false
334+
}
335+
}
336+
337+
return "", true
338+
}
339+
278340
func (w lintStructTagRule) addFailure(n ast.Node, msg string) {
279341
w.onFailure(lint.Failure{
280342
Node: n,

testdata/struct-tag.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ type TestContextSpecificTags2 struct {
4646
B int `asn1:"tag:2"`
4747
S string `asn1:"tag:0,utf8"`
4848
Ints []int `asn1:"set"`
49-
Version int `asn1:"optional,explicit,default:0,tag:0"` // MATCH /duplicated tag number 0/
50-
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option 'other' in ASN1 tag/
49+
Version int `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0/
50+
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option 'other' in ASN1 tag/
51+
X int `asn1:"explicit,tag:invalid"` // MATCH /ASN1 tag must be a number, got 'invalid'/
5152
}
5253

5354
type VirtualMachineRelocateSpecDiskLocator struct {
@@ -102,3 +103,20 @@ type HelmChartArgs struct {
102103
ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
103104
ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"`
104105
}
106+
107+
// Test message for holding primitive types.
108+
type Simple struct {
109+
OBool *bool `protobuf:"varint,1,req,json=oBool"` // MATCH /protobuf tag lacks mandatory option 'name'/
110+
OInt32 *int32 `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"` // MATCH /unknown option 'jsonx' in protobuf tag/
111+
OInt32Str *int32 `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"` // MATCH /protobuf tag has duplicated option 'name'/
112+
OInt64 *int64 `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /protobuf tag has duplicated option 'json'/
113+
OSint32Str *int32 `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
114+
OSint64Str *int64 `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicate tag name: 'o_sint32_str'/
115+
OFloat *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
116+
ODouble *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"` // MATCH /duplicated tag number 14/
117+
ODoubleStr *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid protobuf tag name 'fixed6'/
118+
OString *string `protobuf:"bytes,18,opt,name=o_string,json=oString"`
119+
XXX_NoUnkeyedLiteral struct{} `json:"-"`
120+
XXX_unrecognized []byte `json:"-"`
121+
XXX_sizecache int32 `json:"-"`
122+
}

0 commit comments

Comments
 (0)