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