1// Copyright 2016 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.
4
5// +build ignore
6
7// The vet/all command runs go vet on the standard library and commands.
8// It compares the output against a set of whitelists
9// maintained in the whitelist directory.
10package main
11
12import (
13	"bufio"
14	"bytes"
15	"flag"
16	"fmt"
17	"go/build"
18	"go/types"
19	"internal/testenv"
20	"io"
21	"log"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"runtime"
26	"strings"
27	"sync/atomic"
28)
29
30var (
31	flagPlatforms = flag.String("p", "", "platform(s) to use e.g. linux/amd64,darwin/386")
32	flagAll       = flag.Bool("all", false, "run all platforms")
33	flagNoLines   = flag.Bool("n", false, "don't print line numbers")
34)
35
36var cmdGoPath string
37var failed uint32 // updated atomically
38
39func main() {
40	log.SetPrefix("vet/all: ")
41	log.SetFlags(0)
42
43	var err error
44	cmdGoPath, err = testenv.GoTool()
45	if err != nil {
46		log.Print("could not find cmd/go; skipping")
47		// We're on a platform that can't run cmd/go.
48		// We want this script to be able to run as part of all.bash,
49		// so return cleanly rather than with exit code 1.
50		return
51	}
52
53	flag.Parse()
54	switch {
55	case *flagAll && *flagPlatforms != "":
56		log.Print("-all and -p flags are incompatible")
57		flag.Usage()
58		os.Exit(2)
59	case *flagPlatforms != "":
60		vetPlatforms(parseFlagPlatforms())
61	case *flagAll:
62		vetPlatforms(allPlatforms())
63	default:
64		hostPlatform.vet()
65	}
66	if atomic.LoadUint32(&failed) != 0 {
67		os.Exit(1)
68	}
69}
70
71var hostPlatform = platform{os: build.Default.GOOS, arch: build.Default.GOARCH}
72
73func allPlatforms() []platform {
74	var pp []platform
75	cmd := exec.Command(cmdGoPath, "tool", "dist", "list")
76	out, err := cmd.Output()
77	if err != nil {
78		log.Fatal(err)
79	}
80	lines := bytes.Split(out, []byte{'\n'})
81	for _, line := range lines {
82		if len(line) == 0 {
83			continue
84		}
85		pp = append(pp, parsePlatform(string(line)))
86	}
87	return pp
88}
89
90func parseFlagPlatforms() []platform {
91	var pp []platform
92	components := strings.Split(*flagPlatforms, ",")
93	for _, c := range components {
94		pp = append(pp, parsePlatform(c))
95	}
96	return pp
97}
98
99func parsePlatform(s string) platform {
100	vv := strings.Split(s, "/")
101	if len(vv) != 2 {
102		log.Fatalf("could not parse platform %s, must be of form goos/goarch", s)
103	}
104	return platform{os: vv[0], arch: vv[1]}
105}
106
107type whitelist map[string]int
108
109// load adds entries from the whitelist file, if present, for os/arch to w.
110func (w whitelist) load(goos string, goarch string) {
111	sz := types.SizesFor("gc", goarch)
112	if sz == nil {
113		log.Fatalf("unknown type sizes for arch %q", goarch)
114	}
115	archbits := 8 * sz.Sizeof(types.Typ[types.UnsafePointer])
116
117	// Look up whether goarch has a shared arch suffix,
118	// such as mips64x for mips64 and mips64le.
119	archsuff := goarch
120	if x, ok := archAsmX[goarch]; ok {
121		archsuff = x
122	}
123
124	// Load whitelists.
125	filenames := []string{
126		"all.txt",
127		goos + ".txt",
128		goarch + ".txt",
129		goos + "_" + goarch + ".txt",
130		fmt.Sprintf("%dbit.txt", archbits),
131	}
132	if goarch != archsuff {
133		filenames = append(filenames,
134			archsuff+".txt",
135			goos+"_"+archsuff+".txt",
136		)
137	}
138
139	// We allow error message templates using GOOS and GOARCH.
140	if goos == "android" {
141		goos = "linux" // so many special cases :(
142	}
143
144	// Read whitelists and do template substitution.
145	replace := strings.NewReplacer("GOOS", goos, "GOARCH", goarch, "ARCHSUFF", archsuff)
146
147	for _, filename := range filenames {
148		path := filepath.Join("whitelist", filename)
149		f, err := os.Open(path)
150		if err != nil {
151			// Allow not-exist errors; not all combinations have whitelists.
152			if os.IsNotExist(err) {
153				continue
154			}
155			log.Fatal(err)
156		}
157		scan := bufio.NewScanner(f)
158		for scan.Scan() {
159			line := scan.Text()
160			if len(line) == 0 || strings.HasPrefix(line, "//") {
161				continue
162			}
163			w[replace.Replace(line)]++
164		}
165		if err := scan.Err(); err != nil {
166			log.Fatal(err)
167		}
168	}
169}
170
171type platform struct {
172	os   string
173	arch string
174}
175
176func (p platform) String() string {
177	return p.os + "/" + p.arch
178}
179
180// ignorePathPrefixes are file path prefixes that should be ignored wholesale.
181var ignorePathPrefixes = [...]string{
182	// These testdata dirs have lots of intentionally broken/bad code for tests.
183	"cmd/go/testdata/",
184	"cmd/vet/testdata/",
185	"go/printer/testdata/",
186}
187
188func vetPlatforms(pp []platform) {
189	for _, p := range pp {
190		p.vet()
191	}
192}
193
194func (p platform) vet() {
195	var buf bytes.Buffer
196	fmt.Fprintf(&buf, "go run main.go -p %s\n", p)
197
198	// Load whitelist(s).
199	w := make(whitelist)
200	w.load(p.os, p.arch)
201
202	// 'go tool vet .' is considerably faster than 'go vet ./...'
203	// TODO: The unsafeptr checks are disabled for now,
204	// because there are so many false positives,
205	// and no clear way to improve vet to eliminate large chunks of them.
206	// And having them in the whitelists will just cause annoyance
207	// and churn when working on the runtime.
208	cmd := exec.Command(cmdGoPath, "tool", "vet", "-unsafeptr=false", "-source", ".")
209	cmd.Dir = filepath.Join(runtime.GOROOT(), "src")
210	cmd.Env = append(os.Environ(), "GOOS="+p.os, "GOARCH="+p.arch, "CGO_ENABLED=0")
211	stderr, err := cmd.StderrPipe()
212	if err != nil {
213		log.Fatal(err)
214	}
215	if err := cmd.Start(); err != nil {
216		log.Fatal(err)
217	}
218
219	// Process vet output.
220	scan := bufio.NewScanner(stderr)
221	var parseFailed bool
222NextLine:
223	for scan.Scan() {
224		line := scan.Text()
225		if strings.HasPrefix(line, "vet: ") {
226			// Typecheck failure: Malformed syntax or multiple packages or the like.
227			// This will yield nicer error messages elsewhere, so ignore them here.
228			continue
229		}
230
231		if strings.HasPrefix(line, "panic: ") {
232			// Panic in vet. Don't filter anything, we want the complete output.
233			parseFailed = true
234			fmt.Fprintf(os.Stderr, "panic in vet (to reproduce: go run main.go -p %s):\n", p)
235			fmt.Fprintln(os.Stderr, line)
236			io.Copy(os.Stderr, stderr)
237			break
238		}
239
240		fields := strings.SplitN(line, ":", 3)
241		var file, lineno, msg string
242		switch len(fields) {
243		case 2:
244			// vet message with no line number
245			file, msg = fields[0], fields[1]
246		case 3:
247			file, lineno, msg = fields[0], fields[1], fields[2]
248		default:
249			if !parseFailed {
250				parseFailed = true
251				fmt.Fprintf(os.Stderr, "failed to parse %s vet output:\n", p)
252			}
253			fmt.Fprintln(os.Stderr, line)
254		}
255		msg = strings.TrimSpace(msg)
256
257		for _, ignore := range ignorePathPrefixes {
258			if strings.HasPrefix(file, filepath.FromSlash(ignore)) {
259				continue NextLine
260			}
261		}
262
263		key := file + ": " + msg
264		if w[key] == 0 {
265			// Vet error with no match in the whitelist. Print it.
266			if *flagNoLines {
267				fmt.Fprintf(&buf, "%s: %s\n", file, msg)
268			} else {
269				fmt.Fprintf(&buf, "%s:%s: %s\n", file, lineno, msg)
270			}
271			atomic.StoreUint32(&failed, 1)
272			continue
273		}
274		w[key]--
275	}
276	if parseFailed {
277		atomic.StoreUint32(&failed, 1)
278		return
279	}
280	if scan.Err() != nil {
281		log.Fatalf("failed to scan vet output: %v", scan.Err())
282	}
283	err = cmd.Wait()
284	// We expect vet to fail.
285	// Make sure it has failed appropriately, though (for example, not a PathError).
286	if _, ok := err.(*exec.ExitError); !ok {
287		log.Fatalf("unexpected go vet execution failure: %v", err)
288	}
289	printedHeader := false
290	if len(w) > 0 {
291		for k, v := range w {
292			if v != 0 {
293				if !printedHeader {
294					fmt.Fprintln(&buf, "unmatched whitelist entries:")
295					printedHeader = true
296				}
297				for i := 0; i < v; i++ {
298					fmt.Fprintln(&buf, k)
299				}
300				atomic.StoreUint32(&failed, 1)
301			}
302		}
303	}
304
305	os.Stdout.Write(buf.Bytes())
306}
307
308// archAsmX maps architectures to the suffix usually used for their assembly files,
309// if different than the arch name itself.
310var archAsmX = map[string]string{
311	"android":  "linux",
312	"mips64":   "mips64x",
313	"mips64le": "mips64x",
314	"mips":     "mipsx",
315	"mipsle":   "mipsx",
316	"ppc64":    "ppc64x",
317	"ppc64le":  "ppc64x",
318}
319