...

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

Documentation: cmd/vendor/golang.org/x/tools/go/analysis/unitchecker

     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  	"log"
    42  	"os"
    43  	"path/filepath"
    44  	"reflect"
    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/internal/facts"
    53  	"golang.org/x/tools/internal/versions"
    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".
    59  type Config struct {
    60  	ID                        string // e.g. "fmt [fmt.test]"
    61  	Compiler                  string // gc or gccgo, provided to MakeImporter
    62  	Dir                       string // (unused)
    63  	ImportPath                string // package path
    64  	GoVersion                 string // minimum required Go version, such as "go1.21.0"
    65  	GoFiles                   []string
    66  	NonGoFiles                []string
    67  	IgnoredFiles              []string
    68  	ImportMap                 map[string]string // maps import path to package path
    69  	PackageFile               map[string]string // maps package path to file of type information
    70  	Standard                  map[string]bool   // package belongs to standard library
    71  	PackageVetx               map[string]string // maps package path to file of fact information
    72  	VetxOnly                  bool              // run analysis only for facts, not diagnostics
    73  	VetxOutput                string            // where to write file of fact information
    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 := os.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  type factImporter = func(pkgPath string) ([]byte, error)
   187  
   188  // These four hook variables are a proof of concept of a future
   189  // parameterization of a unitchecker API that allows the client to
   190  // determine how and where facts and types are produced and consumed.
   191  // (Note that the eventual API will likely be quite different.)
   192  //
   193  // The defaults honor a Config in a manner compatible with 'go vet'.
   194  var (
   195  	makeTypesImporter = func(cfg *Config, fset *token.FileSet) types.Importer {
   196  		compilerImporter := importer.ForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
   197  			// path is a resolved package path, not an import path.
   198  			file, ok := cfg.PackageFile[path]
   199  			if !ok {
   200  				if cfg.Compiler == "gccgo" && cfg.Standard[path] {
   201  					return nil, nil // fall back to default gccgo lookup
   202  				}
   203  				return nil, fmt.Errorf("no package file for %q", path)
   204  			}
   205  			return os.Open(file)
   206  		})
   207  		return importerFunc(func(importPath string) (*types.Package, error) {
   208  			path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
   209  			if !ok {
   210  				return nil, fmt.Errorf("can't resolve import %q", path)
   211  			}
   212  			return compilerImporter.Import(path)
   213  		})
   214  	}
   215  
   216  	exportTypes = func(*Config, *token.FileSet, *types.Package) error {
   217  		// By default this is a no-op, because "go vet"
   218  		// makes the compiler produce type information.
   219  		return nil
   220  	}
   221  
   222  	makeFactImporter = func(cfg *Config) factImporter {
   223  		return func(pkgPath string) ([]byte, error) {
   224  			if vetx, ok := cfg.PackageVetx[pkgPath]; ok {
   225  				return os.ReadFile(vetx)
   226  			}
   227  			return nil, nil // no .vetx file, no facts
   228  		}
   229  	}
   230  
   231  	exportFacts = func(cfg *Config, data []byte) error {
   232  		return os.WriteFile(cfg.VetxOutput, data, 0666)
   233  	}
   234  )
   235  
   236  func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
   237  	// Load, parse, typecheck.
   238  	var files []*ast.File
   239  	for _, name := range cfg.GoFiles {
   240  		f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
   241  		if err != nil {
   242  			if cfg.SucceedOnTypecheckFailure {
   243  				// Silently succeed; let the compiler
   244  				// report parse errors.
   245  				err = nil
   246  			}
   247  			return nil, err
   248  		}
   249  		files = append(files, f)
   250  	}
   251  	tc := &types.Config{
   252  		Importer:  makeTypesImporter(cfg, fset),
   253  		Sizes:     types.SizesFor("gc", build.Default.GOARCH), // TODO(adonovan): use cfg.Compiler
   254  		GoVersion: cfg.GoVersion,
   255  	}
   256  	info := &types.Info{
   257  		Types:      make(map[ast.Expr]types.TypeAndValue),
   258  		Defs:       make(map[*ast.Ident]types.Object),
   259  		Uses:       make(map[*ast.Ident]types.Object),
   260  		Implicits:  make(map[ast.Node]types.Object),
   261  		Instances:  make(map[*ast.Ident]types.Instance),
   262  		Scopes:     make(map[ast.Node]*types.Scope),
   263  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   264  	}
   265  	versions.InitFileVersions(info)
   266  
   267  	pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
   268  	if err != nil {
   269  		if cfg.SucceedOnTypecheckFailure {
   270  			// Silently succeed; let the compiler
   271  			// report type errors.
   272  			err = nil
   273  		}
   274  		return nil, err
   275  	}
   276  
   277  	// Register fact types with gob.
   278  	// In VetxOnly mode, analyzers are only for their facts,
   279  	// so we can skip any analysis that neither produces facts
   280  	// nor depends on any analysis that produces facts.
   281  	//
   282  	// TODO(adonovan): fix: the command (and logic!) here are backwards.
   283  	// It should say "...nor is required by any...". (Issue 443099)
   284  	//
   285  	// Also build a map to hold working state and result.
   286  	type action struct {
   287  		once        sync.Once
   288  		result      interface{}
   289  		err         error
   290  		usesFacts   bool // (transitively uses)
   291  		diagnostics []analysis.Diagnostic
   292  	}
   293  	actions := make(map[*analysis.Analyzer]*action)
   294  	var registerFacts func(a *analysis.Analyzer) bool
   295  	registerFacts = func(a *analysis.Analyzer) bool {
   296  		act, ok := actions[a]
   297  		if !ok {
   298  			act = new(action)
   299  			var usesFacts bool
   300  			for _, f := range a.FactTypes {
   301  				usesFacts = true
   302  				gob.Register(f)
   303  			}
   304  			for _, req := range a.Requires {
   305  				if registerFacts(req) {
   306  					usesFacts = true
   307  				}
   308  			}
   309  			act.usesFacts = usesFacts
   310  			actions[a] = act
   311  		}
   312  		return act.usesFacts
   313  	}
   314  	var filtered []*analysis.Analyzer
   315  	for _, a := range analyzers {
   316  		if registerFacts(a) || !cfg.VetxOnly {
   317  			filtered = append(filtered, a)
   318  		}
   319  	}
   320  	analyzers = filtered
   321  
   322  	// Read facts from imported packages.
   323  	facts, err := facts.NewDecoder(pkg).Decode(makeFactImporter(cfg))
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  
   328  	// In parallel, execute the DAG of analyzers.
   329  	var exec func(a *analysis.Analyzer) *action
   330  	var execAll func(analyzers []*analysis.Analyzer)
   331  	exec = func(a *analysis.Analyzer) *action {
   332  		act := actions[a]
   333  		act.once.Do(func() {
   334  			execAll(a.Requires) // prefetch dependencies in parallel
   335  
   336  			// The inputs to this analysis are the
   337  			// results of its prerequisites.
   338  			inputs := make(map[*analysis.Analyzer]interface{})
   339  			var failed []string
   340  			for _, req := range a.Requires {
   341  				reqact := exec(req)
   342  				if reqact.err != nil {
   343  					failed = append(failed, req.String())
   344  					continue
   345  				}
   346  				inputs[req] = reqact.result
   347  			}
   348  
   349  			// Report an error if any dependency failed.
   350  			if failed != nil {
   351  				sort.Strings(failed)
   352  				act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
   353  				return
   354  			}
   355  
   356  			factFilter := make(map[reflect.Type]bool)
   357  			for _, f := range a.FactTypes {
   358  				factFilter[reflect.TypeOf(f)] = true
   359  			}
   360  
   361  			pass := &analysis.Pass{
   362  				Analyzer:          a,
   363  				Fset:              fset,
   364  				Files:             files,
   365  				OtherFiles:        cfg.NonGoFiles,
   366  				IgnoredFiles:      cfg.IgnoredFiles,
   367  				Pkg:               pkg,
   368  				TypesInfo:         info,
   369  				TypesSizes:        tc.Sizes,
   370  				TypeErrors:        nil, // unitchecker doesn't RunDespiteErrors
   371  				ResultOf:          inputs,
   372  				Report:            func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
   373  				ImportObjectFact:  facts.ImportObjectFact,
   374  				ExportObjectFact:  facts.ExportObjectFact,
   375  				AllObjectFacts:    func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
   376  				ImportPackageFact: facts.ImportPackageFact,
   377  				ExportPackageFact: facts.ExportPackageFact,
   378  				AllPackageFacts:   func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
   379  			}
   380  
   381  			t0 := time.Now()
   382  			act.result, act.err = a.Run(pass)
   383  
   384  			if act.err == nil { // resolve URLs on diagnostics.
   385  				for i := range act.diagnostics {
   386  					if url, uerr := analysisflags.ResolveURL(a, act.diagnostics[i]); uerr == nil {
   387  						act.diagnostics[i].URL = url
   388  					} else {
   389  						act.err = uerr // keep the last error
   390  					}
   391  				}
   392  			}
   393  			if false {
   394  				log.Printf("analysis %s = %s", pass, time.Since(t0))
   395  			}
   396  		})
   397  		return act
   398  	}
   399  	execAll = func(analyzers []*analysis.Analyzer) {
   400  		var wg sync.WaitGroup
   401  		for _, a := range analyzers {
   402  			wg.Add(1)
   403  			go func(a *analysis.Analyzer) {
   404  				_ = exec(a)
   405  				wg.Done()
   406  			}(a)
   407  		}
   408  		wg.Wait()
   409  	}
   410  
   411  	execAll(analyzers)
   412  
   413  	// Return diagnostics and errors from root analyzers.
   414  	results := make([]result, len(analyzers))
   415  	for i, a := range analyzers {
   416  		act := actions[a]
   417  		results[i].a = a
   418  		results[i].err = act.err
   419  		results[i].diagnostics = act.diagnostics
   420  	}
   421  
   422  	data := facts.Encode()
   423  	if err := exportFacts(cfg, data); err != nil {
   424  		return nil, fmt.Errorf("failed to export analysis facts: %v", err)
   425  	}
   426  	if err := exportTypes(cfg, fset, pkg); err != nil {
   427  		return nil, fmt.Errorf("failed to export type information: %v", err)
   428  	}
   429  
   430  	return results, nil
   431  }
   432  
   433  type result struct {
   434  	a           *analysis.Analyzer
   435  	diagnostics []analysis.Diagnostic
   436  	err         error
   437  }
   438  
   439  type importerFunc func(path string) (*types.Package, error)
   440  
   441  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
   442  

View as plain text