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  	"archive/zip"
    31  	"encoding/gob"
    32  	"encoding/json"
    33  	"flag"
    34  	"fmt"
    35  	"go/ast"
    36  	"go/build"
    37  	"go/importer"
    38  	"go/parser"
    39  	"go/token"
    40  	"go/types"
    41  	"io"
    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/analysis/driverutil"
    54  	"golang.org/x/tools/internal/facts"
    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 // gc or gccgo, provided to MakeImporter
    63  	Dir                       string // (unused)
    64  	ImportPath                string // package path
    65  	GoVersion                 string // minimum required Go version, such as "go1.21.0"
    66  	GoFiles                   []string
    67  	NonGoFiles                []string
    68  	IgnoredFiles              []string
    69  	ModulePath                string            // module path
    70  	ModuleVersion             string            // module version
    71  	ImportMap                 map[string]string // maps import path to package path
    72  	PackageFile               map[string]string // maps package path to file of type information
    73  	Standard                  map[string]bool   // package belongs to standard library
    74  	PackageVetx               map[string]string // maps package path to file of fact information
    75  	VetxOnly                  bool              // run analysis only for facts, not diagnostics
    76  	VetxOutput                string            // where to write file of fact information
    77  	Stdout                    string            // write stdout (e.g. JSON, unified diff) to this file
    78  	FixArchive                string            // write fixed files to this zip archive, if non-empty
    79  	SucceedOnTypecheckFailure bool              // obsolete awful hack; see #18395 and below
    80  }
    81  
    82  // Main is the main function of a vet-like analysis tool that must be
    83  // invoked by a build system to analyze a single package.
    84  //
    85  // The protocol required by 'go vet -vettool=...' is that the tool must support:
    86  //
    87  //	-flags          describe flags in JSON
    88  //	-V=full         describe executable for build caching
    89  //	foo.cfg         perform separate modular analyze on the single
    90  //	                unit described by a JSON config file foo.cfg.
    91  //	-fix		don't print each diagnostic, apply its first fix
    92  //	-diff		don't apply a fix, print the diff (requires -fix)
    93  //	-json		print diagnostics and fixes in JSON form
    94  func Main(analyzers ...*analysis.Analyzer) {
    95  	progname := filepath.Base(os.Args[0])
    96  	log.SetFlags(0)
    97  	log.SetPrefix(progname + ": ")
    98  
    99  	if err := analysis.Validate(analyzers); err != nil {
   100  		log.Fatal(err)
   101  	}
   102  
   103  	flag.Usage = func() {
   104  		fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
   105  
   106  Usage of %[1]s:
   107  	%.16[1]s unit.cfg	# execute analysis specified by config file
   108  	%.16[1]s help    	# general help, including listing analyzers and flags
   109  	%.16[1]s help name	# help on specific analyzer and its flags
   110  `, progname)
   111  		os.Exit(1)
   112  	}
   113  
   114  	analyzers = analysisflags.Parse(analyzers, true)
   115  
   116  	args := flag.Args()
   117  	if len(args) == 0 {
   118  		flag.Usage()
   119  	}
   120  	if args[0] == "help" {
   121  		analysisflags.Help(progname, analyzers, args[1:])
   122  		os.Exit(0)
   123  	}
   124  	if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
   125  		log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
   126  	}
   127  	Run(args[0], analyzers)
   128  }
   129  
   130  // Run reads the *.cfg file, runs the analysis,
   131  // and calls os.Exit with an appropriate error code.
   132  // It assumes flags have already been set.
   133  func Run(configFile string, analyzers []*analysis.Analyzer) {
   134  	cfg, err := readConfig(configFile)
   135  	if err != nil {
   136  		log.Fatal(err)
   137  	}
   138  
   139  	// Redirect stdout to a file as requested.
   140  	if cfg.Stdout != "" {
   141  		f, err := os.Create(cfg.Stdout)
   142  		if err != nil {
   143  			log.Fatal(err)
   144  		}
   145  		os.Stdout = f
   146  	}
   147  
   148  	fset := token.NewFileSet()
   149  	results, err := run(fset, cfg, analyzers)
   150  	if err != nil {
   151  		log.Fatal(err)
   152  	}
   153  
   154  	code := 0
   155  
   156  	// In VetxOnly mode, the analysis is run only for facts.
   157  	if !cfg.VetxOnly {
   158  		code = processResults(fset, cfg.ID, cfg.FixArchive, results)
   159  	}
   160  
   161  	os.Exit(code)
   162  }
   163  
   164  func readConfig(filename string) (*Config, error) {
   165  	data, err := os.ReadFile(filename)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	cfg := new(Config)
   170  	if err := json.Unmarshal(data, cfg); err != nil {
   171  		return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
   172  	}
   173  	if len(cfg.GoFiles) == 0 {
   174  		// The go command disallows packages with no files.
   175  		// The only exception is unsafe, but the go command
   176  		// doesn't call vet on it.
   177  		return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
   178  	}
   179  	return cfg, nil
   180  }
   181  
   182  func processResults(fset *token.FileSet, id, fixArchive string, results []result) (exit int) {
   183  	if analysisflags.Fix {
   184  		// Don't print the diagnostics,
   185  		// but apply all fixes from the root actions.
   186  
   187  		// Convert results to form needed by ApplyFixes.
   188  		fixActions := make([]driverutil.FixAction, len(results))
   189  		for i, res := range results {
   190  			fixActions[i] = driverutil.FixAction{
   191  				Name:         res.a.Name,
   192  				Pkg:          res.pkg,
   193  				Files:        res.files,
   194  				FileSet:      fset,
   195  				ReadFileFunc: os.ReadFile, // TODO(adonovan): respect overlays
   196  				Diagnostics:  res.diagnostics,
   197  			}
   198  		}
   199  
   200  		// By default, fixes overwrite the original file.
   201  		// With the -diff flag, print the diffs to stdout.
   202  		// If "go fix" provides a fix archive, we write files
   203  		// into it so that mutations happen after the build.
   204  		write := func(filename string, content []byte) error {
   205  			return os.WriteFile(filename, content, 0644)
   206  		}
   207  		if fixArchive != "" {
   208  			f, err := os.Create(fixArchive)
   209  			if err != nil {
   210  				log.Fatalf("can't create -fix archive: %v", err)
   211  			}
   212  			zw := zip.NewWriter(f)
   213  			zw.SetComment(id) // ignore error
   214  			defer func() {
   215  				if err := zw.Close(); err != nil {
   216  					log.Fatalf("closing -fix archive zip writer: %v", err)
   217  				}
   218  				if err := f.Close(); err != nil {
   219  					log.Fatalf("closing -fix archive file: %v", err)
   220  				}
   221  			}()
   222  			write = func(filename string, content []byte) error {
   223  				f, err := zw.Create(filename)
   224  				if err != nil {
   225  					return err
   226  				}
   227  				_, err = f.Write(content)
   228  				return err
   229  			}
   230  		}
   231  
   232  		if err := driverutil.ApplyFixes(fixActions, write, analysisflags.Diff, false); err != nil {
   233  			// Fail when applying fixes failed.
   234  			log.Print(err)
   235  			exit = 1
   236  		}
   237  
   238  		// Don't proceed to print text/JSON,
   239  		// and don't report an error
   240  		// just because there were diagnostics.
   241  		return
   242  	}
   243  
   244  	// Keep consistent with analogous logic in
   245  	// printDiagnostics in ../internal/checker/checker.go.
   246  
   247  	if analysisflags.JSON {
   248  		// JSON output
   249  		tree := make(driverutil.JSONTree)
   250  		for _, res := range results {
   251  			tree.Add(fset, id, res.a.Name, res.diagnostics, res.err)
   252  		}
   253  		tree.Print(os.Stdout) // ignore error
   254  
   255  	} else {
   256  		// plain text
   257  		for _, res := range results {
   258  			if res.err != nil {
   259  				log.Println(res.err)
   260  				exit = 1
   261  			}
   262  		}
   263  		for _, res := range results {
   264  			for _, diag := range res.diagnostics {
   265  				driverutil.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
   266  				exit = 1
   267  			}
   268  		}
   269  	}
   270  
   271  	return
   272  }
   273  
   274  type factImporter = func(pkgPath string) ([]byte, error)
   275  
   276  // These four hook variables are a proof of concept of a future
   277  // parameterization of a unitchecker API that allows the client to
   278  // determine how and where facts and types are produced and consumed.
   279  // (Note that the eventual API will likely be quite different.)
   280  //
   281  // The defaults honor a Config in a manner compatible with 'go vet'.
   282  var (
   283  	makeTypesImporter = func(cfg *Config, fset *token.FileSet) types.Importer {
   284  		compilerImporter := importer.ForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
   285  			// path is a resolved package path, not an import path.
   286  			file, ok := cfg.PackageFile[path]
   287  			if !ok {
   288  				if cfg.Compiler == "gccgo" && cfg.Standard[path] {
   289  					return nil, nil // fall back to default gccgo lookup
   290  				}
   291  				return nil, fmt.Errorf("no package file for %q", path)
   292  			}
   293  			return os.Open(file)
   294  		})
   295  		return importerFunc(func(importPath string) (*types.Package, error) {
   296  			path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
   297  			if !ok {
   298  				return nil, fmt.Errorf("can't resolve import %q", path)
   299  			}
   300  			return compilerImporter.Import(path)
   301  		})
   302  	}
   303  
   304  	exportTypes = func(*Config, *token.FileSet, *types.Package) error {
   305  		// By default this is a no-op, because "go vet"
   306  		// makes the compiler produce type information.
   307  		return nil
   308  	}
   309  
   310  	makeFactImporter = func(cfg *Config) factImporter {
   311  		return func(pkgPath string) ([]byte, error) {
   312  			if vetx, ok := cfg.PackageVetx[pkgPath]; ok {
   313  				return os.ReadFile(vetx)
   314  			}
   315  			return nil, nil // no .vetx file, no facts
   316  		}
   317  	}
   318  
   319  	exportFacts = func(cfg *Config, data []byte) error {
   320  		return os.WriteFile(cfg.VetxOutput, data, 0666)
   321  	}
   322  )
   323  
   324  func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
   325  	// Load, parse, typecheck.
   326  	var files []*ast.File
   327  	for _, name := range cfg.GoFiles {
   328  		f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
   329  		if err != nil {
   330  			if cfg.SucceedOnTypecheckFailure {
   331  				// Silently succeed; let the compiler
   332  				// report parse errors.
   333  				err = nil
   334  			}
   335  			return nil, err
   336  		}
   337  		files = append(files, f)
   338  	}
   339  	tc := &types.Config{
   340  		Importer:  makeTypesImporter(cfg, fset),
   341  		Sizes:     types.SizesFor("gc", build.Default.GOARCH), // TODO(adonovan): use cfg.Compiler
   342  		GoVersion: cfg.GoVersion,
   343  	}
   344  	info := &types.Info{
   345  		Types:        make(map[ast.Expr]types.TypeAndValue),
   346  		Defs:         make(map[*ast.Ident]types.Object),
   347  		Uses:         make(map[*ast.Ident]types.Object),
   348  		Implicits:    make(map[ast.Node]types.Object),
   349  		Instances:    make(map[*ast.Ident]types.Instance),
   350  		Scopes:       make(map[ast.Node]*types.Scope),
   351  		Selections:   make(map[*ast.SelectorExpr]*types.Selection),
   352  		FileVersions: make(map[*ast.File]string),
   353  	}
   354  
   355  	pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
   356  	if err != nil {
   357  		if cfg.SucceedOnTypecheckFailure {
   358  			// Silently succeed; let the compiler
   359  			// report type errors.
   360  			err = nil
   361  		}
   362  		return nil, err
   363  	}
   364  
   365  	// Register fact types with gob.
   366  	// In VetxOnly mode, analyzers are only for their facts,
   367  	// so we can skip any analysis that neither produces facts
   368  	// nor depends on any analysis that produces facts.
   369  	//
   370  	// TODO(adonovan): fix: the command (and logic!) here are backwards.
   371  	// It should say "...nor is required by any...". (Issue 443099)
   372  	//
   373  	// Also build a map to hold working state and result.
   374  	type action struct {
   375  		once        sync.Once
   376  		result      any
   377  		err         error
   378  		usesFacts   bool // (transitively uses)
   379  		diagnostics []analysis.Diagnostic
   380  	}
   381  	actions := make(map[*analysis.Analyzer]*action)
   382  	var registerFacts func(a *analysis.Analyzer) bool
   383  	registerFacts = func(a *analysis.Analyzer) bool {
   384  		act, ok := actions[a]
   385  		if !ok {
   386  			act = new(action)
   387  			var usesFacts bool
   388  			for _, f := range a.FactTypes {
   389  				usesFacts = true
   390  				gob.Register(f)
   391  			}
   392  			for _, req := range a.Requires {
   393  				if registerFacts(req) {
   394  					usesFacts = true
   395  				}
   396  			}
   397  			act.usesFacts = usesFacts
   398  			actions[a] = act
   399  		}
   400  		return act.usesFacts
   401  	}
   402  	var filtered []*analysis.Analyzer
   403  	for _, a := range analyzers {
   404  		if registerFacts(a) || !cfg.VetxOnly {
   405  			filtered = append(filtered, a)
   406  		}
   407  	}
   408  	analyzers = filtered
   409  
   410  	// Read facts from imported packages.
   411  	facts, err := facts.NewDecoder(pkg).Decode(makeFactImporter(cfg))
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	// In parallel, execute the DAG of analyzers.
   417  	var exec func(a *analysis.Analyzer) *action
   418  	var execAll func(analyzers []*analysis.Analyzer)
   419  	exec = func(a *analysis.Analyzer) *action {
   420  		act := actions[a]
   421  		act.once.Do(func() {
   422  			execAll(a.Requires) // prefetch dependencies in parallel
   423  
   424  			// The inputs to this analysis are the
   425  			// results of its prerequisites.
   426  			inputs := make(map[*analysis.Analyzer]any)
   427  			var failed []string
   428  			for _, req := range a.Requires {
   429  				reqact := exec(req)
   430  				if reqact.err != nil {
   431  					failed = append(failed, req.String())
   432  					continue
   433  				}
   434  				inputs[req] = reqact.result
   435  			}
   436  
   437  			// Report an error if any dependency failed.
   438  			if failed != nil {
   439  				sort.Strings(failed)
   440  				act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
   441  				return
   442  			}
   443  
   444  			factFilter := make(map[reflect.Type]bool)
   445  			for _, f := range a.FactTypes {
   446  				factFilter[reflect.TypeOf(f)] = true
   447  			}
   448  
   449  			module := &analysis.Module{
   450  				Path:      cfg.ModulePath,
   451  				Version:   cfg.ModuleVersion,
   452  				GoVersion: cfg.GoVersion,
   453  			}
   454  
   455  			pass := &analysis.Pass{
   456  				Analyzer:     a,
   457  				Fset:         fset,
   458  				Files:        files,
   459  				OtherFiles:   cfg.NonGoFiles,
   460  				IgnoredFiles: cfg.IgnoredFiles,
   461  				Pkg:          pkg,
   462  				TypesInfo:    info,
   463  				TypesSizes:   tc.Sizes,
   464  				TypeErrors:   nil, // unitchecker doesn't RunDespiteErrors
   465  				ResultOf:     inputs,
   466  				Report: func(d analysis.Diagnostic) {
   467  					// Unitchecker doesn't apply fixes, but it does report them in the JSON output.
   468  					if err := driverutil.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
   469  						// Since we have diagnostics, the exit code will be nonzero,
   470  						// so logging these errors is sufficient.
   471  						log.Println(err)
   472  						d.SuggestedFixes = nil
   473  					}
   474  					act.diagnostics = append(act.diagnostics, d)
   475  				},
   476  				ImportObjectFact:  facts.ImportObjectFact,
   477  				ExportObjectFact:  facts.ExportObjectFact,
   478  				AllObjectFacts:    func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
   479  				ImportPackageFact: facts.ImportPackageFact,
   480  				ExportPackageFact: facts.ExportPackageFact,
   481  				AllPackageFacts:   func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
   482  				Module:            module,
   483  			}
   484  			pass.ReadFile = driverutil.CheckedReadFile(pass, os.ReadFile)
   485  
   486  			t0 := time.Now()
   487  			act.result, act.err = a.Run(pass)
   488  
   489  			if act.err == nil { // resolve URLs on diagnostics.
   490  				for i := range act.diagnostics {
   491  					if url, uerr := driverutil.ResolveURL(a, act.diagnostics[i]); uerr == nil {
   492  						act.diagnostics[i].URL = url
   493  					} else {
   494  						act.err = uerr // keep the last error
   495  					}
   496  				}
   497  			}
   498  			if false {
   499  				log.Printf("analysis %s = %s", pass, time.Since(t0))
   500  			}
   501  		})
   502  		return act
   503  	}
   504  	execAll = func(analyzers []*analysis.Analyzer) {
   505  		var wg sync.WaitGroup
   506  		for _, a := range analyzers {
   507  			wg.Add(1)
   508  			go func(a *analysis.Analyzer) {
   509  				_ = exec(a)
   510  				wg.Done()
   511  			}(a)
   512  		}
   513  		wg.Wait()
   514  	}
   515  
   516  	execAll(analyzers)
   517  
   518  	// Return diagnostics and errors from root analyzers.
   519  	results := make([]result, len(analyzers))
   520  	for i, a := range analyzers {
   521  		act := actions[a]
   522  		results[i] = result{pkg, files, a, act.diagnostics, act.err}
   523  	}
   524  
   525  	data := facts.Encode()
   526  	if err := exportFacts(cfg, data); err != nil {
   527  		return nil, fmt.Errorf("failed to export analysis facts: %v", err)
   528  	}
   529  	if err := exportTypes(cfg, fset, pkg); err != nil {
   530  		return nil, fmt.Errorf("failed to export type information: %v", err)
   531  	}
   532  
   533  	return results, nil
   534  }
   535  
   536  type result struct {
   537  	pkg         *types.Package
   538  	files       []*ast.File
   539  	a           *analysis.Analyzer
   540  	diagnostics []analysis.Diagnostic
   541  	err         error
   542  }
   543  
   544  type importerFunc func(path string) (*types.Package, error)
   545  
   546  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
   547  

View as plain text