1// Copyright 2018 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// The unitchecker package defines the main function for an analysis 6// driver that analyzes a single compilation unit during a build. 7// It is invoked by a build system such as "go vet": 8// 9// $ go vet -vettool=$(which vet) 10// 11// It supports the following command-line protocol: 12// 13// -V=full describe executable (to the build tool) 14// -flags describe flags (to the build tool) 15// foo.cfg description of compilation unit (from the build tool) 16// 17// This package does not depend on go/packages. 18// If you need a standalone tool, use multichecker, 19// which supports this mode but can also load packages 20// from source using go/packages. 21package unitchecker 22 23// TODO(adonovan): 24// - with gccgo, go build does not build standard library, 25// so we will not get to analyze it. Yet we must in order 26// to create base facts for, say, the fmt package for the 27// printf checker. 28 29import ( 30 "encoding/gob" 31 "encoding/json" 32 "flag" 33 "fmt" 34 "go/ast" 35 "go/build" 36 "go/importer" 37 "go/parser" 38 "go/token" 39 "go/types" 40 "io" 41 "io/ioutil" 42 "log" 43 "os" 44 "path/filepath" 45 "reflect" 46 "sort" 47 "strings" 48 "sync" 49 "time" 50 51 "golang.org/x/tools/go/analysis" 52 "golang.org/x/tools/go/analysis/internal/analysisflags" 53 "golang.org/x/tools/go/analysis/internal/facts" 54) 55 56// A Config describes a compilation unit to be analyzed. 57// It is provided to the tool in a JSON-encoded file 58// whose name ends with ".cfg". 59type Config struct { 60 ID string // e.g. "fmt [fmt.test]" 61 Compiler string 62 Dir string 63 ImportPath string 64 GoFiles []string 65 NonGoFiles []string 66 ImportMap map[string]string 67 PackageFile map[string]string 68 Standard map[string]bool 69 PackageVetx map[string]string 70 VetxOnly bool 71 VetxOutput string 72 SucceedOnTypecheckFailure bool 73} 74 75// Main is the main function of a vet-like analysis tool that must be 76// invoked by a build system to analyze a single package. 77// 78// The protocol required by 'go vet -vettool=...' is that the tool must support: 79// 80// -flags describe flags in JSON 81// -V=full describe executable for build caching 82// foo.cfg perform separate modular analyze on the single 83// unit described by a JSON config file foo.cfg. 84// 85func Main(analyzers ...*analysis.Analyzer) { 86 progname := filepath.Base(os.Args[0]) 87 log.SetFlags(0) 88 log.SetPrefix(progname + ": ") 89 90 if err := analysis.Validate(analyzers); err != nil { 91 log.Fatal(err) 92 } 93 94 flag.Usage = func() { 95 fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs. 96 97Usage of %[1]s: 98 %.16[1]s unit.cfg # execute analysis specified by config file 99 %.16[1]s help # general help 100 %.16[1]s help name # help on specific analyzer and its flags 101`, progname) 102 os.Exit(1) 103 } 104 105 analyzers = analysisflags.Parse(analyzers, true) 106 107 args := flag.Args() 108 if len(args) == 0 { 109 flag.Usage() 110 } 111 if args[0] == "help" { 112 analysisflags.Help(progname, analyzers, args[1:]) 113 os.Exit(0) 114 } 115 if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") { 116 log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`) 117 } 118 Run(args[0], analyzers) 119} 120 121// Run reads the *.cfg file, runs the analysis, 122// and calls os.Exit with an appropriate error code. 123// It assumes flags have already been set. 124func Run(configFile string, analyzers []*analysis.Analyzer) { 125 cfg, err := readConfig(configFile) 126 if err != nil { 127 log.Fatal(err) 128 } 129 130 fset := token.NewFileSet() 131 results, err := run(fset, cfg, analyzers) 132 if err != nil { 133 log.Fatal(err) 134 } 135 136 // In VetxOnly mode, the analysis is run only for facts. 137 if !cfg.VetxOnly { 138 if analysisflags.JSON { 139 // JSON output 140 tree := make(analysisflags.JSONTree) 141 for _, res := range results { 142 tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err) 143 } 144 tree.Print() 145 } else { 146 // plain text 147 exit := 0 148 for _, res := range results { 149 if res.err != nil { 150 log.Println(res.err) 151 exit = 1 152 } 153 } 154 for _, res := range results { 155 for _, diag := range res.diagnostics { 156 analysisflags.PrintPlain(fset, diag) 157 exit = 1 158 } 159 } 160 os.Exit(exit) 161 } 162 } 163 164 os.Exit(0) 165} 166 167func readConfig(filename string) (*Config, error) { 168 data, err := ioutil.ReadFile(filename) 169 if err != nil { 170 return nil, err 171 } 172 cfg := new(Config) 173 if err := json.Unmarshal(data, cfg); err != nil { 174 return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err) 175 } 176 if len(cfg.GoFiles) == 0 { 177 // The go command disallows packages with no files. 178 // The only exception is unsafe, but the go command 179 // doesn't call vet on it. 180 return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath) 181 } 182 return cfg, nil 183} 184 185var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer { 186 // broken legacy implementation (https://golang.org/issue/28995) 187 return importer.For(compiler, lookup) 188} 189 190func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) { 191 // Load, parse, typecheck. 192 var files []*ast.File 193 for _, name := range cfg.GoFiles { 194 f, err := parser.ParseFile(fset, name, nil, parser.ParseComments) 195 if err != nil { 196 if cfg.SucceedOnTypecheckFailure { 197 // Silently succeed; let the compiler 198 // report parse errors. 199 err = nil 200 } 201 return nil, err 202 } 203 files = append(files, f) 204 } 205 compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) { 206 // path is a resolved package path, not an import path. 207 file, ok := cfg.PackageFile[path] 208 if !ok { 209 if cfg.Compiler == "gccgo" && cfg.Standard[path] { 210 return nil, nil // fall back to default gccgo lookup 211 } 212 return nil, fmt.Errorf("no package file for %q", path) 213 } 214 return os.Open(file) 215 }) 216 importer := importerFunc(func(importPath string) (*types.Package, error) { 217 path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc 218 if !ok { 219 return nil, fmt.Errorf("can't resolve import %q", path) 220 } 221 return compilerImporter.Import(path) 222 }) 223 tc := &types.Config{ 224 Importer: importer, 225 Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc? 226 } 227 info := &types.Info{ 228 Types: make(map[ast.Expr]types.TypeAndValue), 229 Defs: make(map[*ast.Ident]types.Object), 230 Uses: make(map[*ast.Ident]types.Object), 231 Implicits: make(map[ast.Node]types.Object), 232 Scopes: make(map[ast.Node]*types.Scope), 233 Selections: make(map[*ast.SelectorExpr]*types.Selection), 234 } 235 pkg, err := tc.Check(cfg.ImportPath, fset, files, info) 236 if err != nil { 237 if cfg.SucceedOnTypecheckFailure { 238 // Silently succeed; let the compiler 239 // report type errors. 240 err = nil 241 } 242 return nil, err 243 } 244 245 // Register fact types with gob. 246 // In VetxOnly mode, analyzers are only for their facts, 247 // so we can skip any analysis that neither produces facts 248 // nor depends on any analysis that produces facts. 249 // Also build a map to hold working state and result. 250 type action struct { 251 once sync.Once 252 result interface{} 253 err error 254 usesFacts bool // (transitively uses) 255 diagnostics []analysis.Diagnostic 256 } 257 actions := make(map[*analysis.Analyzer]*action) 258 var registerFacts func(a *analysis.Analyzer) bool 259 registerFacts = func(a *analysis.Analyzer) bool { 260 act, ok := actions[a] 261 if !ok { 262 act = new(action) 263 var usesFacts bool 264 for _, f := range a.FactTypes { 265 usesFacts = true 266 gob.Register(f) 267 } 268 for _, req := range a.Requires { 269 if registerFacts(req) { 270 usesFacts = true 271 } 272 } 273 act.usesFacts = usesFacts 274 actions[a] = act 275 } 276 return act.usesFacts 277 } 278 var filtered []*analysis.Analyzer 279 for _, a := range analyzers { 280 if registerFacts(a) || !cfg.VetxOnly { 281 filtered = append(filtered, a) 282 } 283 } 284 analyzers = filtered 285 286 // Read facts from imported packages. 287 read := func(path string) ([]byte, error) { 288 if vetx, ok := cfg.PackageVetx[path]; ok { 289 return ioutil.ReadFile(vetx) 290 } 291 return nil, nil // no .vetx file, no facts 292 } 293 facts, err := facts.Decode(pkg, read) 294 if err != nil { 295 return nil, err 296 } 297 298 // In parallel, execute the DAG of analyzers. 299 var exec func(a *analysis.Analyzer) *action 300 var execAll func(analyzers []*analysis.Analyzer) 301 exec = func(a *analysis.Analyzer) *action { 302 act := actions[a] 303 act.once.Do(func() { 304 execAll(a.Requires) // prefetch dependencies in parallel 305 306 // The inputs to this analysis are the 307 // results of its prerequisites. 308 inputs := make(map[*analysis.Analyzer]interface{}) 309 var failed []string 310 for _, req := range a.Requires { 311 reqact := exec(req) 312 if reqact.err != nil { 313 failed = append(failed, req.String()) 314 continue 315 } 316 inputs[req] = reqact.result 317 } 318 319 // Report an error if any dependency failed. 320 if failed != nil { 321 sort.Strings(failed) 322 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", ")) 323 return 324 } 325 326 factFilter := make(map[reflect.Type]bool) 327 for _, f := range a.FactTypes { 328 factFilter[reflect.TypeOf(f)] = true 329 } 330 331 pass := &analysis.Pass{ 332 Analyzer: a, 333 Fset: fset, 334 Files: files, 335 OtherFiles: cfg.NonGoFiles, 336 Pkg: pkg, 337 TypesInfo: info, 338 TypesSizes: tc.Sizes, 339 ResultOf: inputs, 340 Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) }, 341 ImportObjectFact: facts.ImportObjectFact, 342 ExportObjectFact: facts.ExportObjectFact, 343 AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) }, 344 ImportPackageFact: facts.ImportPackageFact, 345 ExportPackageFact: facts.ExportPackageFact, 346 AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) }, 347 } 348 349 t0 := time.Now() 350 act.result, act.err = a.Run(pass) 351 if false { 352 log.Printf("analysis %s = %s", pass, time.Since(t0)) 353 } 354 }) 355 return act 356 } 357 execAll = func(analyzers []*analysis.Analyzer) { 358 var wg sync.WaitGroup 359 for _, a := range analyzers { 360 wg.Add(1) 361 go func(a *analysis.Analyzer) { 362 _ = exec(a) 363 wg.Done() 364 }(a) 365 } 366 wg.Wait() 367 } 368 369 execAll(analyzers) 370 371 // Return diagnostics and errors from root analyzers. 372 results := make([]result, len(analyzers)) 373 for i, a := range analyzers { 374 act := actions[a] 375 results[i].a = a 376 results[i].err = act.err 377 results[i].diagnostics = act.diagnostics 378 } 379 380 data := facts.Encode() 381 if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil { 382 return nil, fmt.Errorf("failed to write analysis facts: %v", err) 383 } 384 385 return results, nil 386} 387 388type result struct { 389 a *analysis.Analyzer 390 diagnostics []analysis.Diagnostic 391 err error 392} 393 394type importerFunc func(path string) (*types.Package, error) 395 396func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } 397