Home | History | Annotate | Download | only in 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 	"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 }
    333