...
Run Format

Source file src/cmd/vet/all/main.go

Documentation: cmd/vet/all

  // 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.
  package main
  
  import (
  	"bufio"
  	"bytes"
  	"flag"
  	"fmt"
  	"go/build"
  	"go/types"
  	"internal/testenv"
  	"io"
  	"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() {
  	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)
  
  	// 'go tool vet .' is considerably faster than 'go vet ./...'
  	// 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(cmdGoPath, "tool", "vet", "-unsafeptr=false", "-source", ".")
  	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.
  			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
  		}
  
  		fields := strings.SplitN(line, ":", 3)
  		var file, lineno, msg string
  		switch len(fields) {
  		case 2:
  			// vet message with no line number
  			file, msg = fields[0], fields[1]
  		case 3:
  			file, lineno, msg = fields[0], fields[1], fields[2]
  		default:
  			if !parseFailed {
  				parseFailed = true
  				fmt.Fprintf(os.Stderr, "failed to parse %s vet output:\n", p)
  			}
  			fmt.Fprintln(os.Stderr, line)
  		}
  		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",
  }
  

View as plain text