Skip to content

Commit 22db3c5

Browse files
valyalarobpike
authored andcommitted
cmd/vet: improve checking unkeyed fields in composite literals
- Simplified the code. - Removed types for slice aliases from composite literals' whitelist, since they are properly handled by vet. Fixes #15408 Updates #9171 Updates #11041 Change-Id: Ia1806c9eb3f327c09d2e28da4ffdb233b5a159b0 Reviewed-on: https://go-review.googlesource.com/22318 Run-TryBot: Rob Pike <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rob Pike <[email protected]>
1 parent 80e9a7f commit 22db3c5

File tree

4 files changed

+82
-139
lines changed

4 files changed

+82
-139
lines changed

src/cmd/vet/composite.go

+33-77
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"cmd/vet/internal/whitelist"
1111
"flag"
1212
"go/ast"
13+
"go/types"
1314
"strings"
1415
)
1516

@@ -25,102 +26,57 @@ func init() {
2526
// checkUnkeyedLiteral checks if a composite literal is a struct literal with
2627
// unkeyed fields.
2728
func checkUnkeyedLiteral(f *File, node ast.Node) {
28-
c := node.(*ast.CompositeLit)
29-
typ := c.Type
30-
for {
31-
if typ1, ok := c.Type.(*ast.ParenExpr); ok {
32-
typ = typ1
33-
continue
34-
}
35-
break
36-
}
29+
cl := node.(*ast.CompositeLit)
3730

38-
switch typ.(type) {
39-
case *ast.ArrayType:
40-
return
41-
case *ast.MapType:
31+
typ := f.pkg.types[cl].Type
32+
if typ == nil {
33+
// cannot determine composite literals' type, skip it
4234
return
43-
case *ast.StructType:
44-
return // a literal struct type does not need to use keys
45-
case *ast.Ident:
46-
// A simple type name like t or T does not need keys either,
47-
// since it is almost certainly declared in the current package.
48-
// (The exception is names being used via import . "pkg", but
49-
// those are already breaking the Go 1 compatibility promise,
50-
// so not reporting potential additional breakage seems okay.)
35+
}
36+
typeName := typ.String()
37+
if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] {
38+
// skip whitelisted types
5139
return
5240
}
53-
54-
// Otherwise the type is a selector like pkg.Name.
55-
// We only care if pkg.Name is a struct, not if it's a map, array, or slice.
56-
isStruct, typeString := f.pkg.isStruct(c)
57-
if !isStruct {
41+
if _, ok := typ.Underlying().(*types.Struct); !ok {
42+
// skip non-struct composite literals
5843
return
5944
}
60-
61-
if typeString == "" { // isStruct doesn't know
62-
typeString = f.gofmt(typ)
45+
if isLocalType(f, typeName) {
46+
// allow unkeyed locally defined composite literal
47+
return
6348
}
6449

65-
// It's a struct, or we can't tell it's not a struct because we don't have types.
66-
67-
// Check if the CompositeLit contains an unkeyed field.
50+
// check if the CompositeLit contains an unkeyed field
6851
allKeyValue := true
69-
for _, e := range c.Elts {
52+
for _, e := range cl.Elts {
7053
if _, ok := e.(*ast.KeyValueExpr); !ok {
71-
if cl, ok := e.(*ast.CompositeLit); !ok || cl.Type != nil {
72-
allKeyValue = false
73-
break
74-
}
54+
allKeyValue = false
55+
break
7556
}
7657
}
7758
if allKeyValue {
59+
// all the composite literal fields are keyed
7860
return
7961
}
8062

81-
// Check that the CompositeLit's type has the form pkg.Typ.
82-
s, ok := c.Type.(*ast.SelectorExpr)
83-
if !ok {
84-
return
85-
}
86-
pkg, ok := s.X.(*ast.Ident)
87-
if !ok {
88-
return
89-
}
63+
f.Badf(cl.Pos(), "%s composite literal uses unkeyed fields", typeName)
64+
}
9065

91-
// Convert the package name to an import path, and compare to a whitelist.
92-
path := pkgPath(f, pkg.Name)
93-
if path == "" {
94-
f.Badf(c.Pos(), "unresolvable package for %s.%s literal", pkg.Name, s.Sel.Name)
95-
return
96-
}
97-
typeName := path + "." + s.Sel.Name
98-
if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] {
99-
return
66+
func isLocalType(f *File, typeName string) bool {
67+
if strings.HasPrefix(typeName, "struct{") {
68+
// struct literals are local types
69+
return true
10070
}
10171

102-
f.Bad(c.Pos(), typeString+" composite literal uses unkeyed fields")
103-
}
72+
pkgname := f.pkg.path
73+
if strings.HasPrefix(typeName, pkgname+".") {
74+
return true
75+
}
10476

105-
// pkgPath returns the import path "image/png" for the package name "png".
106-
//
107-
// This is based purely on syntax and convention, and not on the imported
108-
// package's contents. It will be incorrect if a package name differs from the
109-
// leaf element of the import path, or if the package was a dot import.
110-
func pkgPath(f *File, pkgName string) (path string) {
111-
for _, x := range f.file.Imports {
112-
s := strings.Trim(x.Path.Value, `"`)
113-
if x.Name != nil {
114-
// Catch `import pkgName "foo/bar"`.
115-
if x.Name.Name == pkgName {
116-
return s
117-
}
118-
} else {
119-
// Catch `import "pkgName"` or `import "foo/bar/pkgName"`.
120-
if s == pkgName || strings.HasSuffix(s, "/"+pkgName) {
121-
return s
122-
}
123-
}
77+
// treat types as local inside test packages with _test name suffix
78+
if strings.HasSuffix(pkgname, "_test") {
79+
pkgname = pkgname[:len(pkgname)-len("_test")]
12480
}
125-
return ""
81+
return strings.HasPrefix(typeName, pkgname+".")
12682
}

src/cmd/vet/internal/whitelist/whitelist.go

+5-31
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,9 @@
55
// Package whitelist defines exceptions for the vet tool.
66
package whitelist
77

8-
// UnkeyedLiteral are types that are actually slices, but
9-
// syntactically, we cannot tell whether the Typ in pkg.Typ{1, 2, 3}
10-
// is a slice or a struct, so we whitelist all the standard package
11-
// library's exported slice types.
8+
// UnkeyedLiteral is a white list of types in the standard packages
9+
// that are used with unkeyed literals we deem to be acceptable.
1210
var UnkeyedLiteral = map[string]bool{
13-
/*
14-
find $GOROOT/src -type f | grep -v _test.go | grep -v /internal/ | grep -v /testdata/ | \
15-
xargs grep '^type.*\[\]' | grep -v ' func(' | \
16-
grep -v ' map\[' | sed 's,/[^/]*go.type,,' | sed 's,.*src/,,' | \
17-
sed 's, ,.,' | sed 's, .*,,' | grep -v '\.[a-z]' | \
18-
sort | awk '{ print "\"" $0 "\": true," }'
19-
*/
20-
"crypto/x509/pkix.RDNSequence": true,
21-
"crypto/x509/pkix.RelativeDistinguishedNameSET": true,
22-
"database/sql.RawBytes": true,
23-
"debug/macho.LoadBytes": true,
24-
"encoding/asn1.ObjectIdentifier": true,
25-
"encoding/asn1.RawContent": true,
26-
"encoding/json.RawMessage": true,
27-
"encoding/xml.CharData": true,
28-
"encoding/xml.Comment": true,
29-
"encoding/xml.Directive": true,
30-
"go/scanner.ErrorList": true,
31-
"image/color.Palette": true,
32-
"net.HardwareAddr": true,
33-
"net.IP": true,
34-
"net.IPMask": true,
35-
"sort.Float64Slice": true,
36-
"sort.IntSlice": true,
37-
"sort.StringSlice": true,
38-
"unicode.SpecialCase": true,
39-
4011
// These image and image/color struct types are frozen. We will never add fields to them.
4112
"image/color.Alpha16": true,
4213
"image/color.Alpha": true,
@@ -45,10 +16,13 @@ var UnkeyedLiteral = map[string]bool{
4516
"image/color.Gray": true,
4617
"image/color.NRGBA64": true,
4718
"image/color.NRGBA": true,
19+
"image/color.NYCbCrA": true,
4820
"image/color.RGBA64": true,
4921
"image/color.RGBA": true,
5022
"image/color.YCbCr": true,
5123
"image.Point": true,
5224
"image.Rectangle": true,
5325
"image.Uniform": true,
26+
27+
"unicode.Range16": true,
5428
}

src/cmd/vet/testdata/composite.go

+44-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ package testdata
99
import (
1010
"flag"
1111
"go/scanner"
12+
"image"
1213
"unicode"
14+
15+
"path/to/unknownpkg"
1316
)
1417

1518
var Okay1 = []string{
@@ -34,34 +37,67 @@ var Okay3 = struct {
3437
"DefValue",
3538
}
3639

40+
var Okay4 = []struct {
41+
A int
42+
B int
43+
}{
44+
{1, 2},
45+
{3, 4},
46+
}
47+
3748
type MyStruct struct {
3849
X string
3950
Y string
4051
Z string
4152
}
4253

43-
var Okay4 = MyStruct{
54+
var Okay5 = &MyStruct{
4455
"Name",
4556
"Usage",
4657
"DefValue",
4758
}
4859

60+
var Okay6 = []MyStruct{
61+
{"foo", "bar", "baz"},
62+
{"aa", "bb", "cc"},
63+
}
64+
4965
// Testing is awkward because we need to reference things from a separate package
5066
// to trigger the warnings.
5167

52-
var BadStructLiteralUsedInTests = flag.Flag{ // ERROR "unkeyed fields"
68+
var goodStructLiteral = flag.Flag{
69+
Name: "Name",
70+
Usage: "Usage",
71+
}
72+
var badStructLiteral = flag.Flag{ // ERROR "unkeyed fields"
5373
"Name",
5474
"Usage",
5575
nil, // Value
5676
"DefValue",
5777
}
5878

59-
// SpecialCase is an (aptly named) slice of CaseRange to test issue 9171.
60-
var GoodNamedSliceLiteralUsedInTests = unicode.SpecialCase{
79+
// SpecialCase is a named slice of CaseRange to test issue 9171.
80+
var goodNamedSliceLiteral = unicode.SpecialCase{
6181
{Lo: 1, Hi: 2},
82+
unicode.CaseRange{Lo: 1, Hi: 2},
83+
}
84+
var badNamedSliceLiteral = unicode.SpecialCase{
85+
{1, 2}, // ERROR "unkeyed fields"
86+
unicode.CaseRange{1, 2}, // ERROR "unkeyed fields"
87+
}
88+
89+
// ErrorList is a named slice, so no warnings should be emitted.
90+
var goodScannerErrorList = scanner.ErrorList{
91+
&scanner.Error{Msg: "foobar"},
92+
}
93+
var badScannerErrorList = scanner.ErrorList{
94+
&scanner.Error{"foobar"}, // ERROR "unkeyed fields"
6295
}
6396

64-
// Used to test the check for slices and arrays: If that test is disabled and
65-
// vet is run with --compositewhitelist=false, this line triggers an error.
66-
// Clumsy but sufficient.
67-
var scannerErrorListTest = scanner.ErrorList{nil, nil}
97+
// Check whitelisted structs: if vet is run with --compositewhitelist=false,
98+
// this line triggers an error.
99+
var whitelistedPoint = image.Point{1, 2}
100+
101+
// Do not check type from unknown package.
102+
// See issue 15408.
103+
var unknownPkgVar = unknownpkg.Foobar{"foo", "bar"}

src/cmd/vet/types.go

-23
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,6 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
8585
return err
8686
}
8787

88-
// isStruct reports whether the composite literal c is a struct.
89-
// If it is not (probably a struct), it returns a printable form of the type.
90-
func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) {
91-
// Check that the CompositeLit's type is a slice or array (which needs no field keys), if possible.
92-
typ := pkg.types[c].Type
93-
// If it's a named type, pull out the underlying type. If it's not, the Underlying
94-
// method returns the type itself.
95-
actual := typ
96-
if actual != nil {
97-
actual = actual.Underlying()
98-
}
99-
if actual == nil {
100-
// No type information available. Assume true, so we do the check.
101-
return true, ""
102-
}
103-
switch actual.(type) {
104-
case *types.Struct:
105-
return true, typ.String()
106-
default:
107-
return false, ""
108-
}
109-
}
110-
11188
// matchArgType reports an error if printf verb t is not appropriate
11289
// for operand arg.
11390
//

0 commit comments

Comments
 (0)