github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/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.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 "riscv": 64, 323 } 324 325 // archAsmX maps architectures to the suffix usually used for their assembly files, 326 // if different than the arch name itself. 327 var archAsmX = map[string]string{ 328 "android": "linux", 329 "mips64": "mips64x", 330 "mips64le": "mips64x", 331 "ppc64": "ppc64x", 332 "ppc64le": "ppc64x", 333 }