gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/nogo/check/check.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package check implements binary analysis similar to bazel's nogo, or the
    16  // unitchecker package. It exists in order to provide additional facilities for
    17  // analysis, namely plumbing through the output from dumping the generated
    18  // binary (to analyze actual produced code).
    19  package check
    20  
    21  import (
    22  	"errors"
    23  	"fmt"
    24  	"go/ast"
    25  	"go/build"
    26  	"go/parser"
    27  	"go/token"
    28  	"go/types"
    29  	"io"
    30  	"os"
    31  	"path"
    32  	"path/filepath"
    33  	"reflect"
    34  	"regexp"
    35  	"runtime/debug"
    36  	"strings"
    37  	"sync"
    38  
    39  	"golang.org/x/tools/go/analysis"
    40  	"golang.org/x/tools/go/gcexportdata"
    41  	"gvisor.dev/gvisor/runsc/flag"
    42  	"gvisor.dev/gvisor/tools/nogo/facts"
    43  	"gvisor.dev/gvisor/tools/nogo/flags"
    44  )
    45  
    46  var (
    47  	// ErrSkip indicates the package should be skipped.
    48  	ErrSkip = errors.New("skipped")
    49  
    50  	// showTimes indicates we should show analyzer times.
    51  	showTimes = flag.Bool("show_times", false, "show all analyzer times")
    52  )
    53  
    54  var (
    55  	tagsOnce       sync.Once
    56  	buildTags      []string
    57  	releaseTagsVal []string
    58  	releaseTagsErr error
    59  )
    60  
    61  // Hack! factFacts only provides facts loaded from directly imported packages
    62  // for efficiency (see importer.cache). In general, if you need a fact from a
    63  // package that isn't otherwise imported, the expectation is that you will add
    64  // a dummy import/use of the desired package to ensure it is a dependency.
    65  //
    66  // Unfortunately, some packages need facts from internal packages. Since
    67  // internal packages cannot be imported we explicitly import in this tool to
    68  // ensure the facts are available to ImportPackageFact.
    69  var internalPackages = []string{
    70  	// Required by pkg/sync for internal/abi.MapType.
    71  	"internal/abi",
    72  }
    73  
    74  // shouldInclude indicates whether the file should be included.
    75  func shouldInclude(path string) (bool, error) {
    76  	tagsOnce.Do(func() {
    77  		if len(flags.BuildTags) > 0 {
    78  			buildTags = strings.Split(flags.BuildTags, ",")
    79  		}
    80  		releaseTagsVal, releaseTagsErr = releaseTags()
    81  	})
    82  	if releaseTagsErr != nil {
    83  		return false, releaseTagsErr
    84  	}
    85  	ctx := build.Default
    86  	ctx.GOOS = flags.GOOS
    87  	ctx.GOARCH = flags.GOARCH
    88  	ctx.BuildTags = buildTags
    89  	ctx.ReleaseTags = releaseTagsVal
    90  	return ctx.MatchFile(filepath.Dir(path), filepath.Base(path))
    91  }
    92  
    93  // sortSrcs sorts a set of src files into Go files and non-Go files.
    94  func sortSrcs(srcs []string) (goFiles []string, nonGoFiles []string) {
    95  	for _, filename := range srcs {
    96  		if strings.HasSuffix(filename, ".go") {
    97  			goFiles = append(goFiles, filename)
    98  		} else {
    99  			nonGoFiles = append(nonGoFiles, filename)
   100  		}
   101  	}
   102  	return
   103  }
   104  
   105  // importerEntry is a single entry in the importer.
   106  type importerEntry struct {
   107  	ready    sync.WaitGroup
   108  	pkg      *types.Package
   109  	findings FindingSet
   110  	err      error
   111  	factsMu  sync.Mutex
   112  	facts    *facts.Package
   113  }
   114  
   115  // importer is an almost-implementation of go/types.Importer.
   116  //
   117  // This wraps a configuration, which provides the map of package names to
   118  // files, and the facts. Note that this importer implementation will always
   119  // pass when a given package is not available.
   120  type importer struct {
   121  	fset    *token.FileSet
   122  	sources map[string][]string
   123  
   124  	// mu protects cache & bundles (see below).
   125  	mu    sync.Mutex
   126  	cache map[string]*importerEntry
   127  
   128  	// bundles is protected by mu, but once set is immutable.
   129  	bundles []*facts.Bundle
   130  
   131  	// importsMu protects imports.
   132  	importsMu sync.Mutex
   133  	imports   map[string]*types.Package
   134  }
   135  
   136  // loadBundles loads all bundle files.
   137  //
   138  // This should only be called from loadFacts, below. After calling this
   139  // function, i.bundles may be read freely without holding a lock.
   140  func (i *importer) loadBundles() error {
   141  	i.mu.Lock()
   142  	defer i.mu.Unlock()
   143  
   144  	// Are bundles already available?
   145  	if i.bundles != nil {
   146  		return nil
   147  	}
   148  
   149  	// Scan all bundle files.
   150  	for _, filename := range flags.Bundles {
   151  		// Open the given filename as a bundle.
   152  		loadedFacts, err := facts.BundleFrom(filename)
   153  		if err != nil {
   154  			return fmt.Errorf("error loading bundled facts: %w", err)
   155  		}
   156  
   157  		// Add to the set of available bundles.
   158  		i.bundles = append(i.bundles, loadedFacts)
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  // loadFacts returns all package facts for the given name.
   165  //
   166  // This should be called only from importPackage, as this may deserialize a
   167  // facts file (which is an expensive operation). Callers should generally rely
   168  // on fastFacts to access facts for packages that have already been imported.
   169  func (i *importer) loadFacts(pkg *types.Package) (*facts.Package, error) {
   170  	// Attempt to load from the fact map.
   171  	filename, ok := flags.FactMap[pkg.Path()]
   172  	if ok {
   173  		r, openErr := os.Open(filename)
   174  		if openErr != nil {
   175  			return nil, fmt.Errorf("error loading facts from %q: %w", filename, openErr)
   176  		}
   177  		defer r.Close()
   178  		loadedFacts := facts.NewPackage()
   179  		if readErr := loadedFacts.ReadFrom(pkg, r); readErr != nil {
   180  			return nil, fmt.Errorf("error loading facts: %w", readErr)
   181  		}
   182  		return loadedFacts, nil
   183  	}
   184  
   185  	// Attempt to load any bundles.
   186  	if err := i.loadBundles(); err != nil {
   187  		return nil, fmt.Errorf("error loading bundles: %w", err)
   188  	}
   189  
   190  	// Try to import from the bundle.
   191  	for _, bundleFacts := range i.bundles {
   192  		localFacts, err := bundleFacts.Package(pkg)
   193  		if err != nil {
   194  			return nil, fmt.Errorf("error loading from a bundle: %w", err)
   195  		}
   196  		if localFacts != nil {
   197  			return localFacts, nil
   198  		}
   199  	}
   200  
   201  	// Nothing available for this package?
   202  	return nil, nil
   203  }
   204  
   205  // fastFacts returns facts for the given package.
   206  //
   207  // This relies exclusively on loaded packages, as the parameter is
   208  // *types.Package and therefore the package data must already be available.
   209  func (i *importer) fastFacts(pkg *types.Package) *facts.Package {
   210  	i.mu.Lock()
   211  	e, ok := i.cache[pkg.Path()]
   212  	i.mu.Unlock()
   213  	if !ok {
   214  		return nil
   215  	}
   216  
   217  	e.factsMu.Lock()
   218  	defer e.factsMu.Unlock()
   219  
   220  	// Do we have them already?
   221  	if e.facts != nil {
   222  		return e.facts
   223  	}
   224  
   225  	// Load the facts.
   226  	facts, err := i.loadFacts(pkg)
   227  	if err != nil {
   228  		// There are no facts available, but no good way to propagate
   229  		// this minor error. It may be intentional that no analysis was
   230  		// performed on some part of the standard library, for example.
   231  		return nil
   232  	}
   233  	e.facts = facts // Cache the result.
   234  	return facts
   235  }
   236  
   237  // findArchive finds the archive for the given package.
   238  func (i *importer) findArchive(path string) (rc io.ReadCloser, err error) {
   239  	realPath, ok := flags.ArchiveMap[path]
   240  	if !ok {
   241  		return i.findBinary(path)
   242  	}
   243  	return os.Open(realPath)
   244  }
   245  
   246  // findBinary finds the binary for the given package.
   247  func (i *importer) findBinary(path string) (rc io.ReadCloser, err error) {
   248  	realPath, ok := flags.ImportMap[path]
   249  	if !ok {
   250  		// Not found in the import path. Attempt to find the package
   251  		// via the standard library.
   252  		rc, err = findStdPkg(path)
   253  	} else {
   254  		// Open the file.
   255  		rc, err = os.Open(realPath)
   256  	}
   257  	return rc, err
   258  }
   259  
   260  // importPackage almost-implements types.Importer.Import.
   261  //
   262  // This must be called by other methods directly.
   263  func (i *importer) importPackage(path string) (*types.Package, error) {
   264  	if path == "unsafe" {
   265  		// Special case: go/types has pre-defined type information for
   266  		// unsafe. We ensure that this package is correct, in case any
   267  		// analyzers are specifically looking for this.
   268  		return types.Unsafe, nil
   269  	}
   270  
   271  	// Pull the internal entry.
   272  	i.mu.Lock()
   273  	entry, ok := i.cache[path]
   274  	if ok && entry.pkg != nil {
   275  		i.mu.Unlock()
   276  		entry.ready.Wait()
   277  		return entry.pkg, entry.err
   278  	}
   279  
   280  	// Start preparing this entry.
   281  	entry = new(importerEntry)
   282  	entry.ready.Add(1)
   283  	defer entry.ready.Done()
   284  	i.cache[path] = entry
   285  	i.mu.Unlock()
   286  
   287  	// If we have the srcs for this package, then we can actually do an
   288  	// analysis from first principles to validate the package and derive
   289  	// the types. We strictly prefer this to the gcexportdata.
   290  	if srcs, ok := i.sources[path]; ok && len(srcs) > 0 {
   291  		entry.pkg, entry.findings, entry.facts, entry.err = i.checkPackage(path, srcs)
   292  		if entry.err != nil {
   293  			return nil, entry.err
   294  		}
   295  		i.importsMu.Lock()
   296  		defer i.importsMu.Unlock()
   297  		i.imports[path] = entry.pkg
   298  		return entry.pkg, entry.err
   299  	}
   300  
   301  	// Load all exported data. Unfortunately, we will have to hold the lock
   302  	// during this time. The imported may access imports directly.
   303  	rc, err := i.findBinary(path)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	defer rc.Close()
   308  	r, err := gcexportdata.NewReader(rc)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  	i.importsMu.Lock()
   313  	defer i.importsMu.Unlock()
   314  	entry.pkg, entry.err = gcexportdata.Read(r, i.fset, i.imports, path)
   315  	return entry.pkg, entry.err
   316  }
   317  
   318  // Import implements types.Importer.Import.
   319  func (i *importer) Import(path string) (*types.Package, error) {
   320  	return i.importPackage(path)
   321  }
   322  
   323  // errorImporter tracks the last error.
   324  type errorImporter struct {
   325  	*importer
   326  	lastErr error
   327  }
   328  
   329  // Import implements types.Importer.Import.
   330  func (i *errorImporter) Import(path string) (*types.Package, error) {
   331  	pkg, err := i.importer.importPackage(path)
   332  	if err != nil {
   333  		i.lastErr = err
   334  	}
   335  	return pkg, err
   336  }
   337  
   338  // checkPackage is the backing implementation for CheckPackage and others.
   339  //
   340  // The implementation was adapted from [1], which was in turn adpated from [2].
   341  // This returns a list of matching analysis issues, or an error if the analysis
   342  // could not be completed.
   343  //
   344  // Note that a partial result may be returned if an error occurred on at least
   345  // one analyzer. This may be expected if e.g. a binary is not provided but a
   346  // binaryAnalyzer is used.
   347  //
   348  // [1] bazelbuid/rules_go/tools/builders/nogo_main.go
   349  // [2] golang.org/x/tools/go/checker/internal/checker
   350  func (i *importer) checkPackage(path string, srcs []string) (*types.Package, FindingSet, *facts.Package, error) {
   351  	// Load all source files.
   352  	goFiles, nonGoFiles := sortSrcs(srcs)
   353  	syntax := make([]*ast.File, 0, len(goFiles))
   354  	for _, file := range goFiles {
   355  		include, err := shouldInclude(file)
   356  		if err != nil {
   357  			return nil, nil, nil, fmt.Errorf("error evaluating file %q: %w", file, err)
   358  		}
   359  		if !include {
   360  			continue
   361  		}
   362  		s, err := parser.ParseFile(i.fset, file, nil, parser.ParseComments)
   363  		if err != nil {
   364  			return nil, nil, nil, fmt.Errorf("error parsing file %q: %w", file, err)
   365  		}
   366  		syntax = append(syntax, s)
   367  	}
   368  	otherFiles := make([]string, 0, len(nonGoFiles))
   369  	for _, file := range nonGoFiles {
   370  		include, err := shouldInclude(file)
   371  		if err != nil {
   372  			return nil, nil, nil, fmt.Errorf("error evaluating non-Go file %q: %w", file, err)
   373  		}
   374  		if !include {
   375  			continue
   376  		}
   377  		otherFiles = append(otherFiles, file)
   378  	}
   379  
   380  	// Check type information.
   381  	ei := &errorImporter{
   382  		importer: i,
   383  	}
   384  	typesSizes := types.SizesFor("gc", flags.GOARCH)
   385  	typeConfig := types.Config{
   386  		Importer: ei,
   387  		Error:    func(error) {},
   388  	}
   389  	typesInfo := &types.Info{
   390  		Types:      make(map[ast.Expr]types.TypeAndValue),
   391  		Instances:  make(map[*ast.Ident]types.Instance),
   392  		Uses:       make(map[*ast.Ident]types.Object),
   393  		Defs:       make(map[*ast.Ident]types.Object),
   394  		Implicits:  make(map[ast.Node]types.Object),
   395  		Scopes:     make(map[ast.Node]*types.Scope),
   396  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   397  	}
   398  	astPackage, err := typeConfig.Check(path, i.fset, syntax, typesInfo)
   399  	if err != nil && ei.lastErr != ErrSkip {
   400  		return nil, nil, nil, fmt.Errorf("error checking types: %w", err)
   401  	}
   402  
   403  	// Note that facts should be reconcilable between types as of go/tools
   404  	// commit ee04797aa0b6be5ce3d5f7ac0f91e34716b3acdf. We previously used
   405  	// to do a sanity check to ensure that binary import data was
   406  	// compatible with ast-derived data, but this is no longer necessary.
   407  	// If packages are available locally, we can refer to those directly.
   408  	astFacts := facts.NewPackage()
   409  
   410  	// Recursively visit all analyzers.
   411  	var (
   412  		resultsMu sync.RWMutex // protects results & errs, findings.
   413  		factsMu   sync.RWMutex // protects facts.
   414  		ready     = make(map[*analysis.Analyzer]*sync.WaitGroup)
   415  		results   = make(map[*analysis.Analyzer]any)
   416  		errs      = make(map[*analysis.Analyzer]error)
   417  		findings  = make(FindingSet, 0)
   418  	)
   419  	for a := range allAnalyzers {
   420  		wg := new(sync.WaitGroup)
   421  		wg.Add(1) // For analysis.
   422  		ready[a] = wg
   423  	}
   424  	limit := make(chan struct{}, 1)
   425  	for a, wg := range ready {
   426  		go func(a *analysis.Analyzer, wg *sync.WaitGroup) {
   427  			defer wg.Done()
   428  
   429  			// Wait for all requirements.
   430  			for _, orig := range a.Requires {
   431  				ready[orig].Wait()
   432  
   433  				// Should we bail early?
   434  				resultsMu.RLock()
   435  				if err := errs[orig]; err != nil {
   436  					resultsMu.RUnlock()
   437  					resultsMu.Lock()
   438  					defer resultsMu.Unlock()
   439  					errs[a] = err
   440  					return
   441  				}
   442  				resultsMu.RUnlock()
   443  			}
   444  
   445  			limit <- struct{}{}
   446  			defer func() { <-limit }()
   447  
   448  			// Collect local fact types.
   449  			localFactTypes := make(map[reflect.Type]bool)
   450  			for _, ft := range a.FactTypes {
   451  				localFactTypes[reflect.TypeOf(ft)] = true
   452  			}
   453  
   454  			// Run the analysis.
   455  			var localFindings FindingSet
   456  			p := &analysis.Pass{
   457  				Analyzer:   a,
   458  				Fset:       i.fset,
   459  				Files:      syntax,
   460  				OtherFiles: otherFiles,
   461  				Pkg:        astPackage,
   462  				TypesInfo:  typesInfo,
   463  				ResultOf:   results, // All results.
   464  				Report: func(d analysis.Diagnostic) {
   465  					localFindings = append(localFindings, Finding{
   466  						Category: a.Name,
   467  						Position: i.fset.Position(d.Pos),
   468  						Message:  d.Message,
   469  						GOOS:     flags.GOOS,
   470  						GOARCH:   flags.GOARCH,
   471  					})
   472  				},
   473  				ImportPackageFact: func(pkg *types.Package, ptr analysis.Fact) bool {
   474  					if pkg != astPackage {
   475  						if f := i.fastFacts(pkg); f != nil {
   476  							return f.ImportFact(nil, ptr)
   477  						}
   478  						return false
   479  					}
   480  					factsMu.RLock()
   481  					defer factsMu.RUnlock()
   482  					return astFacts.ImportFact(nil, ptr)
   483  				},
   484  				ExportPackageFact: func(fact analysis.Fact) {
   485  					factsMu.Lock()
   486  					defer factsMu.Unlock()
   487  					astFacts.ExportFact(nil, fact)
   488  				},
   489  				ImportObjectFact: func(obj types.Object, ptr analysis.Fact) bool {
   490  					if pkg := obj.Pkg(); pkg != nil && pkg != astPackage {
   491  						if f := i.fastFacts(pkg); f != nil {
   492  							return f.ImportFact(obj, ptr)
   493  						}
   494  						return false
   495  					}
   496  					factsMu.RLock()
   497  					defer factsMu.RUnlock()
   498  					return astFacts.ImportFact(obj, ptr)
   499  				},
   500  				ExportObjectFact: func(obj types.Object, fact analysis.Fact) {
   501  					if obj == nil {
   502  						// Tried to export nil object?
   503  						return
   504  					}
   505  					if obj.Pkg() != astPackage {
   506  						// This is not allowed: the
   507  						// built-in facts library will
   508  						// also panic in this case.
   509  						return
   510  					}
   511  					factsMu.Lock()
   512  					defer factsMu.Unlock()
   513  					astFacts.ExportFact(obj, fact)
   514  				},
   515  				AllPackageFacts: func() (rv []analysis.PackageFact) {
   516  					factsMu.RLock()
   517  					defer factsMu.RUnlock()
   518  					// Pull all dependencies.
   519  					for _, importedPkg := range astPackage.Imports() {
   520  						otherFacts := i.fastFacts(importedPkg)
   521  						if otherFacts == nil {
   522  							continue
   523  						}
   524  						for typ := range localFactTypes {
   525  							v := reflect.New(typ.Elem())
   526  							if otherFacts.ImportFact(nil, v.Interface().(analysis.Fact)) {
   527  								rv = append(rv, analysis.PackageFact{
   528  									Package: importedPkg,
   529  									Fact:    v.Interface().(analysis.Fact),
   530  								})
   531  							}
   532  						}
   533  					}
   534  					// Pull all local facts.
   535  					for typ := range localFactTypes {
   536  						v := reflect.New(typ.Elem())
   537  						if astFacts.ImportFact(nil, v.Interface().(analysis.Fact)) {
   538  							rv = append(rv, analysis.PackageFact{
   539  								Package: astPackage,
   540  								Fact:    v.Interface().(analysis.Fact),
   541  							})
   542  						}
   543  					}
   544  					return
   545  				},
   546  				AllObjectFacts: func() (rv []analysis.ObjectFact) {
   547  					factsMu.RLock()
   548  					defer factsMu.RUnlock()
   549  					// Pull all local facts.
   550  					for obj := range astFacts.Objects {
   551  						for typ := range localFactTypes {
   552  							v := reflect.New(typ.Elem())
   553  							if astFacts.ImportFact(obj, v.Interface().(analysis.Fact)) {
   554  								rv = append(rv, analysis.ObjectFact{
   555  									Object: obj,
   556  									Fact:   v.Interface().(analysis.Fact),
   557  								})
   558  							}
   559  						}
   560  					}
   561  					return
   562  				},
   563  				TypesSizes: typesSizes,
   564  			}
   565  
   566  			// Ensure any analyzer panics are captured. This may
   567  			// happen for packages that are not supported by
   568  			// specific analyzers. The only panic that can happen
   569  			// is while resultsMu is held as a read-only lock.
   570  			var (
   571  				result any
   572  				err    error
   573  			)
   574  			defer func() {
   575  				if r := recover(); r != nil {
   576  					// In order to make the multiple
   577  					// analyzers running concurrently
   578  					// debuggable, capture panic exceptions
   579  					// and propagate as an analyzer error.
   580  					err = fmt.Errorf("panic recovered: %s (%s)", r, debug.Stack())
   581  					resultsMu.RUnlock() // +checklocksignore
   582  				}
   583  				resultsMu.Lock()
   584  				findings = append(findings, localFindings...)
   585  				results[a] = result
   586  				errs[a] = err
   587  				resultsMu.Unlock()
   588  			}()
   589  			found := findAnalyzer(a)
   590  			resultsMu.RLock()
   591  			if ba, ok := found.(binaryAnalyzer); ok {
   592  				// Load the binary and analyze.
   593  				rc, loadErr := i.findArchive(path)
   594  				if loadErr != nil {
   595  					if loadErr != ErrSkip {
   596  						err = loadErr
   597  					} else {
   598  						err = nil // Ignore.
   599  					}
   600  				} else {
   601  					result, err = ba.Run(p, rc)
   602  					rc.Close()
   603  				}
   604  			} else {
   605  				result, err = a.Run(p)
   606  			}
   607  			resultsMu.RUnlock()
   608  		}(a, wg)
   609  	}
   610  	for _, wg := range ready {
   611  		// Wait for completion.
   612  		wg.Wait()
   613  	}
   614  	for a := range ready {
   615  		// Check the error. If we generate an error here, we report
   616  		// this as a finding that can be suppressed. Some analyzers
   617  		// will fail on some packages.
   618  		if errs[a] != nil {
   619  			filename := ""
   620  			if len(srcs) > 0 {
   621  				filename = srcs[0]
   622  			}
   623  			findings = append(findings, Finding{
   624  				Category: a.Name,
   625  				Position: token.Position{Filename: filename},
   626  				Message:  errs[a].Error(),
   627  				GOOS:     flags.GOOS,
   628  				GOARCH:   flags.GOARCH,
   629  			})
   630  			continue
   631  		}
   632  
   633  		// Check the result. Per above, we check that the type is what
   634  		// we expected and that an error did not occur during analysis.
   635  		if got, want := reflect.TypeOf(results[a]), a.ResultType; got != want {
   636  			return astPackage, findings, astFacts, fmt.Errorf("error: analyzer %s returned %v (expected type %v)", a.Name, results[a], want)
   637  		}
   638  	}
   639  
   640  	// Return all findings.
   641  	return astPackage, findings, astFacts, nil
   642  }
   643  
   644  // Package runs all analyzer on a single package.
   645  func Package(path string, srcs []string) (FindingSet, facts.Serializer, error) {
   646  	i := &importer{
   647  		fset:    token.NewFileSet(),
   648  		cache:   make(map[string]*importerEntry),
   649  		imports: make(map[string]*types.Package),
   650  	}
   651  
   652  	// See comment on internalPackages.
   653  	for _, pkg := range internalPackages {
   654  		if _, err := i.Import(pkg); err != nil {
   655  			return nil, nil, fmt.Errorf("error importing %s: %w", pkg, err)
   656  		}
   657  	}
   658  
   659  	_, findings, facts, err := i.checkPackage(path, srcs)
   660  	if err != nil {
   661  		return nil, nil, err
   662  	}
   663  	return findings, facts, nil
   664  }
   665  
   666  // allFactsAndFindings returns all factsAndFindings from an importer.
   667  func (i *importer) allFactsAndFindings() (FindingSet, *facts.Bundle) {
   668  	var (
   669  		findings = make(FindingSet, 0)
   670  		allFacts = facts.NewBundle()
   671  	)
   672  	for path, entry := range i.cache {
   673  		findings = append(findings, entry.findings...)
   674  		allFacts.Add(path, entry.facts)
   675  	}
   676  	return findings, allFacts
   677  }
   678  
   679  // FindRoot finds a package root.
   680  func FindRoot(srcs []string, srcRootRegex string) (string, error) {
   681  	if srcRootRegex == "" {
   682  		return "", nil
   683  	}
   684  
   685  	// Calculate the root source directory. This is always a directory
   686  	// named 'src', of which we simply take the first we find. This is a
   687  	// bit fragile, but works for all currently known Go source
   688  	// configurations.
   689  	//
   690  	// Note that there may be extra files outside of the root source
   691  	// directory; we simply ignore those.
   692  	re, err := regexp.Compile(srcRootRegex)
   693  	if err != nil {
   694  		return "", fmt.Errorf("srcRootRegex is not valid: %w", err)
   695  	}
   696  	srcRootPrefix := ""
   697  	for _, filename := range srcs {
   698  		if s := re.FindString(filename); len(s) > len(srcRootPrefix) {
   699  			srcRootPrefix = s
   700  		}
   701  	}
   702  	if srcRootPrefix == "" {
   703  		// For whatever reason, we didn't identify a good common prefix to use here.
   704  		return "", fmt.Errorf("unable to identify src prefix for %v with regex %s", srcs, srcRootRegex)
   705  	}
   706  	return srcRootPrefix, nil
   707  }
   708  
   709  // SplitPackages splits a typical package structure into packages.
   710  func SplitPackages(srcs []string, srcRootPrefix string) map[string][]string {
   711  	sources := make(map[string][]string)
   712  	for _, filename := range srcs {
   713  		if !strings.HasPrefix(filename, srcRootPrefix) {
   714  			continue // Superflouous file.
   715  		}
   716  		d := path.Dir(filename)
   717  		if len(srcRootPrefix) >= len(d) {
   718  			continue // Not a file.
   719  		}
   720  		pkg := d[len(srcRootPrefix):]
   721  		for len(pkg) > 0 && pkg[0] == '/' {
   722  			pkg = pkg[1:]
   723  		}
   724  		if len(pkg) == 0 {
   725  			continue // Also not a file.
   726  		}
   727  
   728  		// Ignore any files with /testdata/ in the path.
   729  		if strings.Contains(filename, "/testdata/") {
   730  			continue
   731  		}
   732  
   733  		// Ignore all test files since they *may* be in a different
   734  		// package than the rest of the sources.
   735  		if strings.HasSuffix(filename, "_test.go") {
   736  			continue
   737  		}
   738  
   739  		// Skip the "builtin" package, which is only for docs and not a
   740  		// real package. Attempting type checking goes crazy.
   741  		if pkg == "builtin" {
   742  			continue
   743  		}
   744  
   745  		// In Go's sources, vendored packages under cmd/vendor are imported via
   746  		// paths not containing cmd/vendor.
   747  		pkg = strings.TrimPrefix(pkg, "cmd/vendor/")
   748  
   749  		// Place the special runtime package (functions emitted by the
   750  		// compiler itself) into the runtime packages.
   751  		if strings.Contains(filename, "cmd/compile/internal/typecheck/_builtin/runtime.go") {
   752  			pkg = "runtime"
   753  		}
   754  
   755  		// Add to the package.
   756  		sources[pkg] = append(sources[pkg], filename)
   757  	}
   758  
   759  	return sources
   760  }
   761  
   762  // Bundle checks a bundle of files (typically the standard library).
   763  func Bundle(sources map[string][]string) (FindingSet, facts.Serializer, error) {
   764  	// Process all packages.
   765  	i := &importer{
   766  		fset:    token.NewFileSet(),
   767  		sources: sources,
   768  		cache:   make(map[string]*importerEntry),
   769  		imports: make(map[string]*types.Package),
   770  	}
   771  	for pkg := range sources {
   772  		// Was there an error processing this package?
   773  		if _, err := i.importPackage(pkg); err != nil && err != ErrSkip {
   774  			return nil, nil, err
   775  		}
   776  	}
   777  
   778  	findings, facts := i.allFactsAndFindings()
   779  	return findings, facts, nil
   780  }