// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build ignore // The vet/all command runs go vet on the standard library and commands. // It compares the output against a set of whitelists // maintained in the whitelist directory. // // This program attempts to build packages from golang.org/x/tools, // which must be in your GOPATH. package main import ( "bufio" "bytes" "flag" "fmt" "go/build" "go/types" "internal/testenv" "io" "io/ioutil" "log" "os" "os/exec" "path/filepath" "runtime" "strings" "sync/atomic" ) var ( flagPlatforms = flag.String("p", "", "platform(s) to use e.g. linux/amd64,darwin/386") flagAll = flag.Bool("all", false, "run all platforms") flagNoLines = flag.Bool("n", false, "don't print line numbers") ) var cmdGoPath string var failed uint32 // updated atomically func main() { log.SetPrefix("vet/all: ") log.SetFlags(0) var err error cmdGoPath, err = testenv.GoTool() if err != nil { log.Print("could not find cmd/go; skipping") // We're on a platform that can't run cmd/go. // We want this script to be able to run as part of all.bash, // so return cleanly rather than with exit code 1. return } flag.Parse() switch { case *flagAll && *flagPlatforms != "": log.Print("-all and -p flags are incompatible") flag.Usage() os.Exit(2) case *flagPlatforms != "": vetPlatforms(parseFlagPlatforms()) case *flagAll: vetPlatforms(allPlatforms()) default: hostPlatform.vet() } if atomic.LoadUint32(&failed) != 0 { os.Exit(1) } } var hostPlatform = platform{os: build.Default.GOOS, arch: build.Default.GOARCH} func allPlatforms() []platform { var pp []platform cmd := exec.Command(cmdGoPath, "tool", "dist", "list") out, err := cmd.Output() if err != nil { log.Fatal(err) } lines := bytes.Split(out, []byte{'\n'}) for _, line := range lines { if len(line) == 0 { continue } pp = append(pp, parsePlatform(string(line))) } return pp } func parseFlagPlatforms() []platform { var pp []platform components := strings.Split(*flagPlatforms, ",") for _, c := range components { pp = append(pp, parsePlatform(c)) } return pp } func parsePlatform(s string) platform { vv := strings.Split(s, "/") if len(vv) != 2 { log.Fatalf("could not parse platform %s, must be of form goos/goarch", s) } return platform{os: vv[0], arch: vv[1]} } type whitelist map[string]int // load adds entries from the whitelist file, if present, for os/arch to w. func (w whitelist) load(goos string, goarch string) { sz := types.SizesFor("gc", goarch) if sz == nil { log.Fatalf("unknown type sizes for arch %q", goarch) } archbits := 8 * sz.Sizeof(types.Typ[types.UnsafePointer]) // Look up whether goarch has a shared arch suffix, // such as mips64x for mips64 and mips64le. archsuff := goarch if x, ok := archAsmX[goarch]; ok { archsuff = x } // Load whitelists. filenames := []string{ "all.txt", goos + ".txt", goarch + ".txt", goos + "_" + goarch + ".txt", fmt.Sprintf("%dbit.txt", archbits), } if goarch != archsuff { filenames = append(filenames, archsuff+".txt", goos+"_"+archsuff+".txt", ) } // We allow error message templates using GOOS and GOARCH. if goos == "android" { goos = "linux" // so many special cases :( } // Read whitelists and do template substitution. replace := strings.NewReplacer("GOOS", goos, "GOARCH", goarch, "ARCHSUFF", archsuff) for _, filename := range filenames { path := filepath.Join("whitelist", filename) f, err := os.Open(path) if err != nil { // Allow not-exist errors; not all combinations have whitelists. if os.IsNotExist(err) { continue } log.Fatal(err) } scan := bufio.NewScanner(f) for scan.Scan() { line := scan.Text() if len(line) == 0 || strings.HasPrefix(line, "//") { continue } w[replace.Replace(line)]++ } if err := scan.Err(); err != nil { log.Fatal(err) } } } type platform struct { os string arch string } func (p platform) String() string { return p.os + "/" + p.arch } // ignorePathPrefixes are file path prefixes that should be ignored wholesale. var ignorePathPrefixes = [...]string{ // These testdata dirs have lots of intentionally broken/bad code for tests. "cmd/go/testdata/", "cmd/vet/testdata/", "go/printer/testdata/", } func vetPlatforms(pp []platform) { for _, p := range pp { p.vet() } } func (p platform) vet() { if p.os == "linux" && (p.arch == "riscv64" || p.arch == "sparc64") { // TODO(tklauser): enable as soon as these ports have fully landed fmt.Printf("skipping %s/%s\n", p.os, p.arch) return } if p.os == "windows" && p.arch == "arm" { // TODO(jordanrh1): enable as soon as the windows/arm port has fully landed fmt.Println("skipping windows/arm") return } if p.os == "aix" && p.arch == "ppc64" { // TODO(aix): enable as soon as the aix/ppc64 port has fully landed fmt.Println("skipping aix/ppc64") return } var buf bytes.Buffer fmt.Fprintf(&buf, "go run main.go -p %s\n", p) // Load whitelist(s). w := make(whitelist) w.load(p.os, p.arch) tmpdir, err := ioutil.TempDir("", "cmd-vet-all") if err != nil { log.Fatal(err) } defer os.RemoveAll(tmpdir) // Build the go/packages-based vet command from the x/tools // repo. It is considerably faster than "go vet", which rebuilds // the standard library. vetTool := filepath.Join(tmpdir, "vet") cmd := exec.Command(cmdGoPath, "build", "-o", vetTool, "golang.org/x/tools/go/analysis/cmd/vet") cmd.Dir = filepath.Join(runtime.GOROOT(), "src") cmd.Stderr = os.Stderr cmd.Stdout = os.Stderr if err := cmd.Run(); err != nil { log.Fatal(err) } // TODO: The unsafeptr checks are disabled for now, // because there are so many false positives, // and no clear way to improve vet to eliminate large chunks of them. // And having them in the whitelists will just cause annoyance // and churn when working on the runtime. cmd = exec.Command(vetTool, "-unsafeptr=0", "-nilness=0", // expensive, uses SSA "std", "cmd/...", "cmd/compile/internal/gc/testdata", ) cmd.Dir = filepath.Join(runtime.GOROOT(), "src") cmd.Env = append(os.Environ(), "GOOS="+p.os, "GOARCH="+p.arch, "CGO_ENABLED=0") stderr, err := cmd.StderrPipe() if err != nil { log.Fatal(err) } if err := cmd.Start(); err != nil { log.Fatal(err) } // Process vet output. scan := bufio.NewScanner(stderr) var parseFailed bool NextLine: for scan.Scan() { line := scan.Text() if strings.HasPrefix(line, "vet: ") { // Typecheck failure: Malformed syntax or multiple packages or the like. // This will yield nicer error messages elsewhere, so ignore them here. // This includes warnings from asmdecl of the form: // "vet: foo.s:16: [amd64] cannot check cross-package assembly function" continue } if strings.HasPrefix(line, "panic: ") { // Panic in vet. Don't filter anything, we want the complete output. parseFailed = true fmt.Fprintf(os.Stderr, "panic in vet (to reproduce: go run main.go -p %s):\n", p) fmt.Fprintln(os.Stderr, line) io.Copy(os.Stderr, stderr) break } if strings.HasPrefix(line, "# ") { // 'go vet' prefixes the output of each vet invocation by a comment: // # [package] continue } // Parse line. // Assume the part before the first ": " // is the "file:line:col: " information. // TODO(adonovan): parse vet -json output. var file, lineno, msg string if i := strings.Index(line, ": "); i >= 0 { msg = line[i+len(": "):] words := strings.Split(line[:i], ":") switch len(words) { case 3: _ = words[2] // ignore column fallthrough case 2: lineno = words[1] fallthrough case 1: file = words[0] // Make the file name relative to GOROOT/src. if rel, err := filepath.Rel(cmd.Dir, file); err == nil { file = rel } default: // error: too many columns } } if file == "" { if !parseFailed { parseFailed = true fmt.Fprintf(os.Stderr, "failed to parse %s vet output:\n", p) } fmt.Fprintln(os.Stderr, line) continue } msg = strings.TrimSpace(msg) for _, ignore := range ignorePathPrefixes { if strings.HasPrefix(file, filepath.FromSlash(ignore)) { continue NextLine } } key := file + ": " + msg if w[key] == 0 { // Vet error with no match in the whitelist. Print it. if *flagNoLines { fmt.Fprintf(&buf, "%s: %s\n", file, msg) } else { fmt.Fprintf(&buf, "%s:%s: %s\n", file, lineno, msg) } atomic.StoreUint32(&failed, 1) continue } w[key]-- } if parseFailed { atomic.StoreUint32(&failed, 1) return } if scan.Err() != nil { log.Fatalf("failed to scan vet output: %v", scan.Err()) } err = cmd.Wait() // We expect vet to fail. // Make sure it has failed appropriately, though (for example, not a PathError). if _, ok := err.(*exec.ExitError); !ok { log.Fatalf("unexpected go vet execution failure: %v", err) } printedHeader := false if len(w) > 0 { for k, v := range w { if v != 0 { if !printedHeader { fmt.Fprintln(&buf, "unmatched whitelist entries:") printedHeader = true } for i := 0; i < v; i++ { fmt.Fprintln(&buf, k) } atomic.StoreUint32(&failed, 1) } } } os.Stdout.Write(buf.Bytes()) } // archAsmX maps architectures to the suffix usually used for their assembly files, // if different than the arch name itself. var archAsmX = map[string]string{ "android": "linux", "mips64": "mips64x", "mips64le": "mips64x", "mips": "mipsx", "mipsle": "mipsx", "ppc64": "ppc64x", "ppc64le": "ppc64x", }