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// 11// This program attempts to build packages from golang.org/x/tools, 12// which must be in your GOPATH. 13package main 14 15import ( 16 "bufio" 17 "bytes" 18 "flag" 19 "fmt" 20 "go/build" 21 "go/types" 22 "internal/testenv" 23 "io" 24 "io/ioutil" 25 "log" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "runtime" 30 "strings" 31 "sync/atomic" 32) 33 34var ( 35 flagPlatforms = flag.String("p", "", "platform(s) to use e.g. linux/amd64,darwin/386") 36 flagAll = flag.Bool("all", false, "run all platforms") 37 flagNoLines = flag.Bool("n", false, "don't print line numbers") 38) 39 40var cmdGoPath string 41var failed uint32 // updated atomically 42 43func main() { 44 log.SetPrefix("vet/all: ") 45 log.SetFlags(0) 46 47 var err error 48 cmdGoPath, err = testenv.GoTool() 49 if err != nil { 50 log.Print("could not find cmd/go; skipping") 51 // We're on a platform that can't run cmd/go. 52 // We want this script to be able to run as part of all.bash, 53 // so return cleanly rather than with exit code 1. 54 return 55 } 56 57 flag.Parse() 58 switch { 59 case *flagAll && *flagPlatforms != "": 60 log.Print("-all and -p flags are incompatible") 61 flag.Usage() 62 os.Exit(2) 63 case *flagPlatforms != "": 64 vetPlatforms(parseFlagPlatforms()) 65 case *flagAll: 66 vetPlatforms(allPlatforms()) 67 default: 68 hostPlatform.vet() 69 } 70 if atomic.LoadUint32(&failed) != 0 { 71 os.Exit(1) 72 } 73} 74 75var hostPlatform = platform{os: build.Default.GOOS, arch: build.Default.GOARCH} 76 77func allPlatforms() []platform { 78 var pp []platform 79 cmd := exec.Command(cmdGoPath, "tool", "dist", "list") 80 out, err := cmd.Output() 81 if err != nil { 82 log.Fatal(err) 83 } 84 lines := bytes.Split(out, []byte{'\n'}) 85 for _, line := range lines { 86 if len(line) == 0 { 87 continue 88 } 89 pp = append(pp, parsePlatform(string(line))) 90 } 91 return pp 92} 93 94func parseFlagPlatforms() []platform { 95 var pp []platform 96 components := strings.Split(*flagPlatforms, ",") 97 for _, c := range components { 98 pp = append(pp, parsePlatform(c)) 99 } 100 return pp 101} 102 103func parsePlatform(s string) platform { 104 vv := strings.Split(s, "/") 105 if len(vv) != 2 { 106 log.Fatalf("could not parse platform %s, must be of form goos/goarch", s) 107 } 108 return platform{os: vv[0], arch: vv[1]} 109} 110 111type whitelist map[string]int 112 113// load adds entries from the whitelist file, if present, for os/arch to w. 114func (w whitelist) load(goos string, goarch string) { 115 sz := types.SizesFor("gc", goarch) 116 if sz == nil { 117 log.Fatalf("unknown type sizes for arch %q", goarch) 118 } 119 archbits := 8 * sz.Sizeof(types.Typ[types.UnsafePointer]) 120 121 // Look up whether goarch has a shared arch suffix, 122 // such as mips64x for mips64 and mips64le. 123 archsuff := goarch 124 if x, ok := archAsmX[goarch]; ok { 125 archsuff = x 126 } 127 128 // Load whitelists. 129 filenames := []string{ 130 "all.txt", 131 goos + ".txt", 132 goarch + ".txt", 133 goos + "_" + goarch + ".txt", 134 fmt.Sprintf("%dbit.txt", archbits), 135 } 136 if goarch != archsuff { 137 filenames = append(filenames, 138 archsuff+".txt", 139 goos+"_"+archsuff+".txt", 140 ) 141 } 142 143 // We allow error message templates using GOOS and GOARCH. 144 if goos == "android" { 145 goos = "linux" // so many special cases :( 146 } 147 148 // Read whitelists and do template substitution. 149 replace := strings.NewReplacer("GOOS", goos, "GOARCH", goarch, "ARCHSUFF", archsuff) 150 151 for _, filename := range filenames { 152 path := filepath.Join("whitelist", filename) 153 f, err := os.Open(path) 154 if err != nil { 155 // Allow not-exist errors; not all combinations have whitelists. 156 if os.IsNotExist(err) { 157 continue 158 } 159 log.Fatal(err) 160 } 161 scan := bufio.NewScanner(f) 162 for scan.Scan() { 163 line := scan.Text() 164 if len(line) == 0 || strings.HasPrefix(line, "//") { 165 continue 166 } 167 w[replace.Replace(line)]++ 168 } 169 if err := scan.Err(); err != nil { 170 log.Fatal(err) 171 } 172 } 173} 174 175type platform struct { 176 os string 177 arch string 178} 179 180func (p platform) String() string { 181 return p.os + "/" + p.arch 182} 183 184// ignorePathPrefixes are file path prefixes that should be ignored wholesale. 185var ignorePathPrefixes = [...]string{ 186 // These testdata dirs have lots of intentionally broken/bad code for tests. 187 "cmd/go/testdata/", 188 "cmd/vet/testdata/", 189 "go/printer/testdata/", 190} 191 192func vetPlatforms(pp []platform) { 193 for _, p := range pp { 194 p.vet() 195 } 196} 197 198func (p platform) vet() { 199 if p.os == "linux" && (p.arch == "riscv64" || p.arch == "sparc64") { 200 // TODO(tklauser): enable as soon as these ports have fully landed 201 fmt.Printf("skipping %s/%s\n", p.os, p.arch) 202 return 203 } 204 205 if p.os == "windows" && p.arch == "arm" { 206 // TODO(jordanrh1): enable as soon as the windows/arm port has fully landed 207 fmt.Println("skipping windows/arm") 208 return 209 } 210 211 if p.os == "aix" && p.arch == "ppc64" { 212 // TODO(aix): enable as soon as the aix/ppc64 port has fully landed 213 fmt.Println("skipping aix/ppc64") 214 return 215 } 216 217 var buf bytes.Buffer 218 fmt.Fprintf(&buf, "go run main.go -p %s\n", p) 219 220 // Load whitelist(s). 221 w := make(whitelist) 222 w.load(p.os, p.arch) 223 224 tmpdir, err := ioutil.TempDir("", "cmd-vet-all") 225 if err != nil { 226 log.Fatal(err) 227 } 228 defer os.RemoveAll(tmpdir) 229 230 // Build the go/packages-based vet command from the x/tools 231 // repo. It is considerably faster than "go vet", which rebuilds 232 // the standard library. 233 vetTool := filepath.Join(tmpdir, "vet") 234 cmd := exec.Command(cmdGoPath, "build", "-o", vetTool, "golang.org/x/tools/go/analysis/cmd/vet") 235 cmd.Dir = filepath.Join(runtime.GOROOT(), "src") 236 cmd.Stderr = os.Stderr 237 cmd.Stdout = os.Stderr 238 if err := cmd.Run(); err != nil { 239 log.Fatal(err) 240 } 241 242 // TODO: The unsafeptr checks are disabled for now, 243 // because there are so many false positives, 244 // and no clear way to improve vet to eliminate large chunks of them. 245 // And having them in the whitelists will just cause annoyance 246 // and churn when working on the runtime. 247 cmd = exec.Command(vetTool, 248 "-unsafeptr=0", 249 "-nilness=0", // expensive, uses SSA 250 "std", 251 "cmd/...", 252 "cmd/compile/internal/gc/testdata", 253 ) 254 cmd.Dir = filepath.Join(runtime.GOROOT(), "src") 255 cmd.Env = append(os.Environ(), "GOOS="+p.os, "GOARCH="+p.arch, "CGO_ENABLED=0") 256 stderr, err := cmd.StderrPipe() 257 if err != nil { 258 log.Fatal(err) 259 } 260 if err := cmd.Start(); err != nil { 261 log.Fatal(err) 262 } 263 264 // Process vet output. 265 scan := bufio.NewScanner(stderr) 266 var parseFailed bool 267NextLine: 268 for scan.Scan() { 269 line := scan.Text() 270 if strings.HasPrefix(line, "vet: ") { 271 // Typecheck failure: Malformed syntax or multiple packages or the like. 272 // This will yield nicer error messages elsewhere, so ignore them here. 273 274 // This includes warnings from asmdecl of the form: 275 // "vet: foo.s:16: [amd64] cannot check cross-package assembly function" 276 continue 277 } 278 279 if strings.HasPrefix(line, "panic: ") { 280 // Panic in vet. Don't filter anything, we want the complete output. 281 parseFailed = true 282 fmt.Fprintf(os.Stderr, "panic in vet (to reproduce: go run main.go -p %s):\n", p) 283 fmt.Fprintln(os.Stderr, line) 284 io.Copy(os.Stderr, stderr) 285 break 286 } 287 if strings.HasPrefix(line, "# ") { 288 // 'go vet' prefixes the output of each vet invocation by a comment: 289 // # [package] 290 continue 291 } 292 293 // Parse line. 294 // Assume the part before the first ": " 295 // is the "file:line:col: " information. 296 // TODO(adonovan): parse vet -json output. 297 var file, lineno, msg string 298 if i := strings.Index(line, ": "); i >= 0 { 299 msg = line[i+len(": "):] 300 301 words := strings.Split(line[:i], ":") 302 switch len(words) { 303 case 3: 304 _ = words[2] // ignore column 305 fallthrough 306 case 2: 307 lineno = words[1] 308 fallthrough 309 case 1: 310 file = words[0] 311 312 // Make the file name relative to GOROOT/src. 313 if rel, err := filepath.Rel(cmd.Dir, file); err == nil { 314 file = rel 315 } 316 default: 317 // error: too many columns 318 } 319 } 320 if file == "" { 321 if !parseFailed { 322 parseFailed = true 323 fmt.Fprintf(os.Stderr, "failed to parse %s vet output:\n", p) 324 } 325 fmt.Fprintln(os.Stderr, line) 326 continue 327 } 328 329 msg = strings.TrimSpace(msg) 330 331 for _, ignore := range ignorePathPrefixes { 332 if strings.HasPrefix(file, filepath.FromSlash(ignore)) { 333 continue NextLine 334 } 335 } 336 337 key := file + ": " + msg 338 if w[key] == 0 { 339 // Vet error with no match in the whitelist. Print it. 340 if *flagNoLines { 341 fmt.Fprintf(&buf, "%s: %s\n", file, msg) 342 } else { 343 fmt.Fprintf(&buf, "%s:%s: %s\n", file, lineno, msg) 344 } 345 atomic.StoreUint32(&failed, 1) 346 continue 347 } 348 w[key]-- 349 } 350 if parseFailed { 351 atomic.StoreUint32(&failed, 1) 352 return 353 } 354 if scan.Err() != nil { 355 log.Fatalf("failed to scan vet output: %v", scan.Err()) 356 } 357 err = cmd.Wait() 358 // We expect vet to fail. 359 // Make sure it has failed appropriately, though (for example, not a PathError). 360 if _, ok := err.(*exec.ExitError); !ok { 361 log.Fatalf("unexpected go vet execution failure: %v", err) 362 } 363 printedHeader := false 364 if len(w) > 0 { 365 for k, v := range w { 366 if v != 0 { 367 if !printedHeader { 368 fmt.Fprintln(&buf, "unmatched whitelist entries:") 369 printedHeader = true 370 } 371 for i := 0; i < v; i++ { 372 fmt.Fprintln(&buf, k) 373 } 374 atomic.StoreUint32(&failed, 1) 375 } 376 } 377 } 378 379 os.Stdout.Write(buf.Bytes()) 380} 381 382// archAsmX maps architectures to the suffix usually used for their assembly files, 383// if different than the arch name itself. 384var archAsmX = map[string]string{ 385 "android": "linux", 386 "mips64": "mips64x", 387 "mips64le": "mips64x", 388 "mips": "mipsx", 389 "mipsle": "mipsx", 390 "ppc64": "ppc64x", 391 "ppc64le": "ppc64x", 392} 393