github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/src/cmd/vet/all/main.go (about)

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