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 	"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 }
    319