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

View as plain text