github.com/MangoDowner/go-gm@v0.0.0-20180818020936-8baa2bd4408c/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  	"io"
    21  	"log"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"runtime"
    26  	"strings"
    27  	"sync/atomic"
    28  )
    29  
    30  var (
    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  
    36  var cmdGoPath string
    37  var failed uint32 // updated atomically
    38  
    39  func 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  
    71  var hostPlatform = platform{os: build.Default.GOOS, arch: build.Default.GOARCH}
    72  
    73  func 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  
    90  func 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  
    99  func 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  
   107  type whitelist map[string]int
   108  
   109  // load adds entries from the whitelist file, if present, for os/arch to w.
   110  func (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  
   171  type platform struct {
   172  	os   string
   173  	arch string
   174  }
   175  
   176  func (p platform) String() string {
   177  	return p.os + "/" + p.arch
   178  }
   179  
   180  // ignorePathPrefixes are file path prefixes that should be ignored wholesale.
   181  var 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  
   188  func vetPlatforms(pp []platform) {
   189  	for _, p := range pp {
   190  		p.vet()
   191  	}
   192  }
   193  
   194  func (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
   222  NextLine:
   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.
   310  var 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  }