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