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