github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/unitchecker/unitchecker.go (about)

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