...

Source file src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go

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

View as plain text