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

View as plain text