github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/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 "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 }