github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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 "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.Fatal("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 if p.arch == "s390x" { 201 // TODO: reinstate when s390x gets vet support (issue 15454) 202 return 203 } 204 var buf bytes.Buffer 205 fmt.Fprintf(&buf, "go run main.go -p %s\n", p) 206 207 // Load whitelist(s). 208 w := make(whitelist) 209 w.load(p.os, p.arch) 210 211 env := append(os.Environ(), "GOOS="+p.os, "GOARCH="+p.arch) 212 213 // Do 'go install std' before running vet. 214 // It is cheap when already installed. 215 // Not installing leads to non-obvious failures due to inability to typecheck. 216 // TODO: If go/loader ever makes it to the standard library, have vet use it, 217 // at which point vet can work off source rather than compiled packages. 218 cmd := exec.Command(cmdGoPath, "install", "-p", strconv.Itoa(ncpus), "std") 219 cmd.Env = env 220 out, err := cmd.CombinedOutput() 221 if err != nil { 222 log.Fatalf("failed to run GOOS=%s GOARCH=%s 'go install std': %v\n%s", p.os, p.arch, err, out) 223 } 224 225 // 'go tool vet .' is considerably faster than 'go vet ./...' 226 // TODO: The unsafeptr checks are disabled for now, 227 // because there are so many false positives, 228 // and no clear way to improve vet to eliminate large chunks of them. 229 // And having them in the whitelists will just cause annoyance 230 // and churn when working on the runtime. 231 cmd = exec.Command(cmdGoPath, "tool", "vet", "-unsafeptr=false", ".") 232 cmd.Dir = filepath.Join(runtime.GOROOT(), "src") 233 cmd.Env = env 234 stderr, err := cmd.StderrPipe() 235 if err != nil { 236 log.Fatal(err) 237 } 238 if err := cmd.Start(); err != nil { 239 log.Fatal(err) 240 } 241 242 // Process vet output. 243 scan := bufio.NewScanner(stderr) 244 NextLine: 245 for scan.Scan() { 246 line := scan.Text() 247 if strings.HasPrefix(line, "vet: ") { 248 // Typecheck failure: Malformed syntax or multiple packages or the like. 249 // This will yield nicer error messages elsewhere, so ignore them here. 250 continue 251 } 252 253 fields := strings.SplitN(line, ":", 3) 254 var file, lineno, msg string 255 switch len(fields) { 256 case 2: 257 // vet message with no line number 258 file, msg = fields[0], fields[1] 259 case 3: 260 file, lineno, msg = fields[0], fields[1], fields[2] 261 default: 262 log.Fatalf("could not parse vet output line:\n%s", line) 263 } 264 msg = strings.TrimSpace(msg) 265 266 for _, ignore := range ignorePathPrefixes { 267 if strings.HasPrefix(file, filepath.FromSlash(ignore)) { 268 continue NextLine 269 } 270 } 271 272 // Temporarily ignore unrecognized printf verbs from cmd. 273 // The compiler now has several fancy verbs (CL 28339) 274 // used with types implementing fmt.Formatters, 275 // and I believe gri has plans to add many more. 276 // TODO: remove when issue 17057 is fixed. 277 if strings.HasPrefix(file, "cmd/") && strings.HasPrefix(msg, "unrecognized printf verb") { 278 continue 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 continue 290 } 291 w[key]-- 292 } 293 if scan.Err() != nil { 294 log.Fatalf("failed to scan vet output: %v", scan.Err()) 295 } 296 err = cmd.Wait() 297 // We expect vet to fail. 298 // Make sure it has failed appropriately, though (for example, not a PathError). 299 if _, ok := err.(*exec.ExitError); !ok { 300 log.Fatalf("unexpected go vet execution failure: %v", err) 301 } 302 printedHeader := false 303 if len(w) > 0 { 304 for k, v := range w { 305 if v != 0 { 306 if !printedHeader { 307 fmt.Fprintln(&buf, "unmatched whitelist entries:") 308 printedHeader = true 309 } 310 for i := 0; i < v; i++ { 311 fmt.Fprintln(&buf, k) 312 } 313 } 314 } 315 } 316 317 os.Stdout.Write(buf.Bytes()) 318 } 319 320 // nbits maps from architecture names to the number of bits in a pointer. 321 // TODO: figure out a clean way to avoid get this info rather than listing it here yet again. 322 var nbits = map[string]int{ 323 "386": 32, 324 "amd64": 64, 325 "amd64p32": 32, 326 "arm": 32, 327 "arm64": 64, 328 "mips64": 64, 329 "mips64le": 64, 330 "ppc64": 64, 331 "ppc64le": 64, 332 } 333 334 // archAsmX maps architectures to the suffix usually used for their assembly files, 335 // if different than the arch name itself. 336 var archAsmX = map[string]string{ 337 "android": "linux", 338 "mips64": "mips64x", 339 "mips64le": "mips64x", 340 "ppc64": "ppc64x", 341 "ppc64le": "ppc64x", 342 }