Simplify gcov_histogram as it's used only for ARCS counters.
[official-gcc.git] / libgo / go / cmd / vet / structtag.go
blob3bc30c47405d8f6707adb653617abcba80210317
1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // This file contains the test for canonical struct tags.
7 package main
9 import (
10 "errors"
11 "go/ast"
12 "go/token"
13 "reflect"
14 "strconv"
15 "strings"
18 func init() {
19 register("structtags",
20 "check that struct field tags have canonical format and apply to exported fields as needed",
21 checkStructFieldTags,
22 structType)
25 // checkStructFieldTags checks all the field tags of a struct, including checking for duplicates.
26 func checkStructFieldTags(f *File, node ast.Node) {
27 var seen map[[2]string]token.Pos
28 for _, field := range node.(*ast.StructType).Fields.List {
29 checkCanonicalFieldTag(f, field, &seen)
33 var checkTagDups = []string{"json", "xml"}
34 var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
36 // checkCanonicalFieldTag checks a single struct field tag.
37 func checkCanonicalFieldTag(f *File, field *ast.Field, seen *map[[2]string]token.Pos) {
38 if field.Tag == nil {
39 return
42 tag, err := strconv.Unquote(field.Tag.Value)
43 if err != nil {
44 f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value)
45 return
48 if err := validateStructTag(tag); err != nil {
49 raw, _ := strconv.Unquote(field.Tag.Value) // field.Tag.Value is known to be a quoted string
50 f.Badf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", raw, err)
53 for _, key := range checkTagDups {
54 val := reflect.StructTag(tag).Get(key)
55 if val == "" || val == "-" || val[0] == ',' {
56 continue
58 if key == "xml" && len(field.Names) > 0 && field.Names[0].Name == "XMLName" {
59 // XMLName defines the XML element name of the struct being
60 // checked. That name cannot collide with element or attribute
61 // names defined on other fields of the struct. Vet does not have a
62 // check for untagged fields of type struct defining their own name
63 // by containing a field named XMLName; see issue 18256.
64 continue
66 if i := strings.Index(val, ","); i >= 0 {
67 if key == "xml" {
68 // Use a separate namespace for XML attributes.
69 for _, opt := range strings.Split(val[i:], ",") {
70 if opt == "attr" {
71 key += " attribute" // Key is part of the error message.
72 break
76 val = val[:i]
78 if *seen == nil {
79 *seen = map[[2]string]token.Pos{}
81 if pos, ok := (*seen)[[2]string{key, val}]; ok {
82 var name string
83 if len(field.Names) > 0 {
84 name = field.Names[0].Name
85 } else {
86 name = field.Type.(*ast.Ident).Name
88 f.Badf(field.Pos(), "struct field %s repeats %s tag %q also at %s", name, key, val, f.loc(pos))
89 } else {
90 (*seen)[[2]string{key, val}] = field.Pos()
94 // Check for use of json or xml tags with unexported fields.
96 // Embedded struct. Nothing to do for now, but that
97 // may change, depending on what happens with issue 7363.
98 if len(field.Names) == 0 {
99 return
102 if field.Names[0].IsExported() {
103 return
106 for _, enc := range [...]string{"json", "xml"} {
107 if reflect.StructTag(tag).Get(enc) != "" {
108 f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc)
109 return
114 var (
115 errTagSyntax = errors.New("bad syntax for struct tag pair")
116 errTagKeySyntax = errors.New("bad syntax for struct tag key")
117 errTagValueSyntax = errors.New("bad syntax for struct tag value")
118 errTagValueSpace = errors.New("suspicious space in struct tag value")
119 errTagSpace = errors.New("key:\"value\" pairs not separated by spaces")
122 // validateStructTag parses the struct tag and returns an error if it is not
123 // in the canonical format, which is a space-separated list of key:"value"
124 // settings. The value may contain spaces.
125 func validateStructTag(tag string) error {
126 // This code is based on the StructTag.Get code in package reflect.
128 n := 0
129 for ; tag != ""; n++ {
130 if n > 0 && tag != "" && tag[0] != ' ' {
131 // More restrictive than reflect, but catches likely mistakes
132 // like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
133 return errTagSpace
135 // Skip leading space.
136 i := 0
137 for i < len(tag) && tag[i] == ' ' {
140 tag = tag[i:]
141 if tag == "" {
142 break
145 // Scan to colon. A space, a quote or a control character is a syntax error.
146 // Strictly speaking, control chars include the range [0x7f, 0x9f], not just
147 // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
148 // as it is simpler to inspect the tag's bytes than the tag's runes.
149 i = 0
150 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
153 if i == 0 {
154 return errTagKeySyntax
156 if i+1 >= len(tag) || tag[i] != ':' {
157 return errTagSyntax
159 if tag[i+1] != '"' {
160 return errTagValueSyntax
162 key := tag[:i]
163 tag = tag[i+1:]
165 // Scan quoted string to find value.
166 i = 1
167 for i < len(tag) && tag[i] != '"' {
168 if tag[i] == '\\' {
173 if i >= len(tag) {
174 return errTagValueSyntax
176 qvalue := tag[:i+1]
177 tag = tag[i+1:]
179 value, err := strconv.Unquote(qvalue)
180 if err != nil {
181 return errTagValueSyntax
184 if !checkTagSpaces[key] {
185 continue
188 switch key {
189 case "xml":
190 // If the first or last character in the XML tag is a space, it is
191 // suspicious.
192 if strings.Trim(value, " ") != value {
193 return errTagValueSpace
196 // If there are multiple spaces, they are suspicious.
197 if strings.Count(value, " ") > 1 {
198 return errTagValueSpace
201 // If there is no comma, skip the rest of the checks.
202 comma := strings.IndexRune(value, ',')
203 if comma < 0 {
204 continue
207 // If the character before a comma is a space, this is suspicious.
208 if comma > 0 && value[comma-1] == ' ' {
209 return errTagValueSpace
211 value = value[comma+1:]
212 case "json":
213 // JSON allows using spaces in the name, so skip it.
214 comma := strings.IndexRune(value, ',')
215 if comma < 0 {
216 continue
218 value = value[comma+1:]
221 if strings.IndexByte(value, ' ') >= 0 {
222 return errTagValueSpace
225 return nil