...
Run Format

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

Documentation: cmd/vet/all

     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  	if p.os == "linux" && p.arch == "riscv64" {
   196  		// TODO(tklauser): enable as soon as the riscv64 port has fully landed
   197  		fmt.Println("skipping linux/riscv64")
   198  		return
   199  	}
   200  
   201  	var buf bytes.Buffer
   202  	fmt.Fprintf(&buf, "go run main.go -p %s\n", p)
   203  
   204  	// Load whitelist(s).
   205  	w := make(whitelist)
   206  	w.load(p.os, p.arch)
   207  
   208  	// 'go tool vet .' is considerably faster than 'go vet ./...'
   209  	// TODO: The unsafeptr checks are disabled for now,
   210  	// because there are so many false positives,
   211  	// and no clear way to improve vet to eliminate large chunks of them.
   212  	// And having them in the whitelists will just cause annoyance
   213  	// and churn when working on the runtime.
   214  	cmd := exec.Command(cmdGoPath, "tool", "vet", "-unsafeptr=false", "-source", ".")
   215  	cmd.Dir = filepath.Join(runtime.GOROOT(), "src")
   216  	cmd.Env = append(os.Environ(), "GOOS="+p.os, "GOARCH="+p.arch, "CGO_ENABLED=0")
   217  	stderr, err := cmd.StderrPipe()
   218  	if err != nil {
   219  		log.Fatal(err)
   220  	}
   221  	if err := cmd.Start(); err != nil {
   222  		log.Fatal(err)
   223  	}
   224  
   225  	// Process vet output.
   226  	scan := bufio.NewScanner(stderr)
   227  	var parseFailed bool
   228  NextLine:
   229  	for scan.Scan() {
   230  		line := scan.Text()
   231  		if strings.HasPrefix(line, "vet: ") {
   232  			// Typecheck failure: Malformed syntax or multiple packages or the like.
   233  			// This will yield nicer error messages elsewhere, so ignore them here.
   234  			continue
   235  		}
   236  
   237  		if strings.HasPrefix(line, "panic: ") {
   238  			// Panic in vet. Don't filter anything, we want the complete output.
   239  			parseFailed = true
   240  			fmt.Fprintf(os.Stderr, "panic in vet (to reproduce: go run main.go -p %s):\n", p)
   241  			fmt.Fprintln(os.Stderr, line)
   242  			io.Copy(os.Stderr, stderr)
   243  			break
   244  		}
   245  
   246  		fields := strings.SplitN(line, ":", 3)
   247  		var file, lineno, msg string
   248  		switch len(fields) {
   249  		case 2:
   250  			// vet message with no line number
   251  			file, msg = fields[0], fields[1]
   252  		case 3:
   253  			file, lineno, msg = fields[0], fields[1], fields[2]
   254  		default:
   255  			if !parseFailed {
   256  				parseFailed = true
   257  				fmt.Fprintf(os.Stderr, "failed to parse %s vet output:\n", p)
   258  			}
   259  			fmt.Fprintln(os.Stderr, line)
   260  		}
   261  		msg = strings.TrimSpace(msg)
   262  
   263  		for _, ignore := range ignorePathPrefixes {
   264  			if strings.HasPrefix(file, filepath.FromSlash(ignore)) {
   265  				continue NextLine
   266  			}
   267  		}
   268  
   269  		key := file + ": " + msg
   270  		if w[key] == 0 {
   271  			// Vet error with no match in the whitelist. Print it.
   272  			if *flagNoLines {
   273  				fmt.Fprintf(&buf, "%s: %s\n", file, msg)
   274  			} else {
   275  				fmt.Fprintf(&buf, "%s:%s: %s\n", file, lineno, msg)
   276  			}
   277  			atomic.StoreUint32(&failed, 1)
   278  			continue
   279  		}
   280  		w[key]--
   281  	}
   282  	if parseFailed {
   283  		atomic.StoreUint32(&failed, 1)
   284  		return
   285  	}
   286  	if scan.Err() != nil {
   287  		log.Fatalf("failed to scan vet output: %v", scan.Err())
   288  	}
   289  	err = cmd.Wait()
   290  	// We expect vet to fail.
   291  	// Make sure it has failed appropriately, though (for example, not a PathError).
   292  	if _, ok := err.(*exec.ExitError); !ok {
   293  		log.Fatalf("unexpected go vet execution failure: %v", err)
   294  	}
   295  	printedHeader := false
   296  	if len(w) > 0 {
   297  		for k, v := range w {
   298  			if v != 0 {
   299  				if !printedHeader {
   300  					fmt.Fprintln(&buf, "unmatched whitelist entries:")
   301  					printedHeader = true
   302  				}
   303  				for i := 0; i < v; i++ {
   304  					fmt.Fprintln(&buf, k)
   305  				}
   306  				atomic.StoreUint32(&failed, 1)
   307  			}
   308  		}
   309  	}
   310  
   311  	os.Stdout.Write(buf.Bytes())
   312  }
   313  
   314  // archAsmX maps architectures to the suffix usually used for their assembly files,
   315  // if different than the arch name itself.
   316  var archAsmX = map[string]string{
   317  	"android":  "linux",
   318  	"mips64":   "mips64x",
   319  	"mips64le": "mips64x",
   320  	"mips":     "mipsx",
   321  	"mipsle":   "mipsx",
   322  	"ppc64":    "ppc64x",
   323  	"ppc64le":  "ppc64x",
   324  }
   325  

View as plain text