github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/nogo/nogo.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 nogo implements binary analysis similar to bazel's nogo,
    16  // or the unitchecker package. It exists in order to provide additional
    17  // facilities for analysis, namely plumbing through the output from
    18  // dumping the generated binary (to analyze actual produced code).
    19  package nogo
    20  
    21  import (
    22  	"bytes"
    23  	"encoding/gob"
    24  	"errors"
    25  	"fmt"
    26  	"go/ast"
    27  	"go/build"
    28  	"go/parser"
    29  	"go/token"
    30  	"go/types"
    31  	"io"
    32  	"io/ioutil"
    33  	"log"
    34  	"os"
    35  	"path"
    36  	"path/filepath"
    37  	"reflect"
    38  	"sort"
    39  	"strings"
    40  
    41  	"golang.org/x/tools/go/analysis"
    42  	"golang.org/x/tools/go/analysis/internal/facts"
    43  	"golang.org/x/tools/go/gcexportdata"
    44  	"golang.org/x/tools/go/types/objectpath"
    45  
    46  	// Special case: flags live here and change overall behavior.
    47  	"github.com/SagerNet/gvisor/tools/nogo/objdump"
    48  	"github.com/SagerNet/gvisor/tools/worker"
    49  )
    50  
    51  // StdlibConfig is serialized as the configuration.
    52  //
    53  // This contains everything required for stdlib analysis.
    54  type StdlibConfig struct {
    55  	Srcs   []string
    56  	GOOS   string
    57  	GOARCH string
    58  	Tags   []string
    59  }
    60  
    61  // PackageConfig is serialized as the configuration.
    62  //
    63  // This contains everything required for single package analysis.
    64  type PackageConfig struct {
    65  	ImportPath  string
    66  	GoFiles     []string
    67  	NonGoFiles  []string
    68  	Tags        []string
    69  	GOOS        string
    70  	GOARCH      string
    71  	ImportMap   map[string]string
    72  	FactMap     map[string]string
    73  	StdlibFacts string
    74  }
    75  
    76  // loader is a fact-loader function.
    77  type loader func(string) ([]byte, error)
    78  
    79  // saver is a fact-saver function.
    80  type saver func([]byte) error
    81  
    82  // stdlibFact is used for serialiation.
    83  type stdlibFact struct {
    84  	Package string
    85  	Facts   []byte
    86  }
    87  
    88  // stdlibFacts is a set of standard library facts.
    89  type stdlibFacts map[string][]byte
    90  
    91  // Size implements worker.Sizer.Size.
    92  func (sf stdlibFacts) Size() int64 {
    93  	size := int64(0)
    94  	for filename, data := range sf {
    95  		size += int64(len(filename))
    96  		size += int64(len(data))
    97  	}
    98  	return size
    99  }
   100  
   101  // EncodeTo serializes stdlibFacts.
   102  func (sf stdlibFacts) EncodeTo(w io.Writer) error {
   103  	stdlibFactsSorted := make([]stdlibFact, 0, len(sf))
   104  	for pkg, facts := range sf {
   105  		stdlibFactsSorted = append(stdlibFactsSorted, stdlibFact{
   106  			Package: pkg,
   107  			Facts:   facts,
   108  		})
   109  	}
   110  	sort.Slice(stdlibFactsSorted, func(i, j int) bool {
   111  		return stdlibFactsSorted[i].Package < stdlibFactsSorted[j].Package
   112  	})
   113  	enc := gob.NewEncoder(w)
   114  	if err := enc.Encode(stdlibFactsSorted); err != nil {
   115  		return err
   116  	}
   117  	return nil
   118  }
   119  
   120  // DecodeFrom deserializes stdlibFacts.
   121  func (sf stdlibFacts) DecodeFrom(r io.Reader) error {
   122  	var stdlibFactsSorted []stdlibFact
   123  	dec := gob.NewDecoder(r)
   124  	if err := dec.Decode(&stdlibFactsSorted); err != nil {
   125  		return err
   126  	}
   127  	for _, stdlibFact := range stdlibFactsSorted {
   128  		sf[stdlibFact.Package] = stdlibFact.Facts
   129  	}
   130  	return nil
   131  }
   132  
   133  var (
   134  	// cachedFacts caches by file (just byte data).
   135  	cachedFacts = worker.NewCache("facts")
   136  
   137  	// stdlibCachedFacts caches the standard library (stdlibFacts).
   138  	stdlibCachedFacts = worker.NewCache("stdlib")
   139  )
   140  
   141  // factLoader loads facts.
   142  func (c *PackageConfig) factLoader(path string) (data []byte, err error) {
   143  	filename, ok := c.FactMap[path]
   144  	if ok {
   145  		cb := cachedFacts.Lookup([]string{filename}, func() worker.Sizer {
   146  			data, readErr := ioutil.ReadFile(filename)
   147  			if readErr != nil {
   148  				err = fmt.Errorf("error loading %q: %w", filename, readErr)
   149  				return nil
   150  			}
   151  			return worker.CacheBytes(data)
   152  		})
   153  		if cb != nil {
   154  			return []byte(cb.(worker.CacheBytes)), err
   155  		}
   156  		return nil, err
   157  	}
   158  	cb := stdlibCachedFacts.Lookup([]string{c.StdlibFacts}, func() worker.Sizer {
   159  		r, openErr := os.Open(c.StdlibFacts)
   160  		if openErr != nil {
   161  			err = fmt.Errorf("error loading stdlib facts from %q: %w", c.StdlibFacts, openErr)
   162  			return nil
   163  		}
   164  		defer r.Close()
   165  		sf := make(stdlibFacts)
   166  		if readErr := sf.DecodeFrom(r); readErr != nil {
   167  			err = fmt.Errorf("error loading stdlib facts: %w", readErr)
   168  			return nil
   169  		}
   170  		return sf
   171  	})
   172  	if cb != nil {
   173  		return (cb.(stdlibFacts))[path], err
   174  	}
   175  	return nil, err
   176  }
   177  
   178  // shouldInclude indicates whether the file should be included.
   179  //
   180  // NOTE: This does only basic parsing of tags.
   181  func (c *PackageConfig) shouldInclude(path string) (bool, error) {
   182  	ctx := build.Default
   183  	ctx.GOOS = c.GOOS
   184  	ctx.GOARCH = c.GOARCH
   185  	ctx.BuildTags = c.Tags
   186  	return ctx.MatchFile(filepath.Dir(path), filepath.Base(path))
   187  }
   188  
   189  // importer is an implementation of go/types.Importer.
   190  //
   191  // This wraps a configuration, which provides the map of package names to
   192  // files, and the facts. Note that this importer implementation will always
   193  // pass when a given package is not available.
   194  type importer struct {
   195  	*PackageConfig
   196  	fset     *token.FileSet
   197  	cache    map[string]*types.Package
   198  	lastErr  error
   199  	callback func(string) error
   200  }
   201  
   202  // Import implements types.Importer.Import.
   203  func (i *importer) Import(path string) (*types.Package, error) {
   204  	if path == "unsafe" {
   205  		// Special case: go/types has pre-defined type information for
   206  		// unsafe. We ensure that this package is correct, in case any
   207  		// analyzers are specifically looking for this.
   208  		return types.Unsafe, nil
   209  	}
   210  
   211  	// Call the internal callback. This is used to resolve loading order
   212  	// for the standard library. See checkStdlib.
   213  	if i.callback != nil {
   214  		if err := i.callback(path); err != nil {
   215  			i.lastErr = err
   216  			return nil, err
   217  		}
   218  	}
   219  
   220  	// Check the cache.
   221  	if pkg, ok := i.cache[path]; ok && pkg.Complete() {
   222  		return pkg, nil
   223  	}
   224  
   225  	// Actually load the data.
   226  	realPath, ok := i.ImportMap[path]
   227  	var (
   228  		rc  io.ReadCloser
   229  		err error
   230  	)
   231  	if !ok {
   232  		// Not found in the import path. Attempt to find the package
   233  		// via the standard library.
   234  		rc, err = findStdPkg(i.GOOS, i.GOARCH, path)
   235  	} else {
   236  		// Open the file.
   237  		rc, err = os.Open(realPath)
   238  	}
   239  	if err != nil {
   240  		i.lastErr = err
   241  		return nil, err
   242  	}
   243  	defer rc.Close()
   244  
   245  	// Load all exported data.
   246  	r, err := gcexportdata.NewReader(rc)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	return gcexportdata.Read(r, i.fset, i.cache, path)
   252  }
   253  
   254  // ErrSkip indicates the package should be skipped.
   255  var ErrSkip = errors.New("skipped")
   256  
   257  // CheckStdlib checks the standard library.
   258  //
   259  // This constructs a synthetic package configuration for each library in the
   260  // standard library sources, and call CheckPackage repeatedly.
   261  //
   262  // Note that not all parts of the source are expected to build. We skip obvious
   263  // test files, and cmd files, which should not be dependencies.
   264  func CheckStdlib(config *StdlibConfig, analyzers []*analysis.Analyzer) (allFindings FindingSet, facts []byte, err error) {
   265  	if len(config.Srcs) == 0 {
   266  		return nil, nil, nil
   267  	}
   268  
   269  	// Ensure all paths are normalized.
   270  	for i := 0; i < len(config.Srcs); i++ {
   271  		config.Srcs[i] = path.Clean(config.Srcs[i])
   272  	}
   273  
   274  	// Calculate the root source directory. This is always a directory
   275  	// named 'src', of which we simply take the first we find. This is a
   276  	// bit fragile, but works for all currently known Go source
   277  	// configurations.
   278  	//
   279  	// Note that there may be extra files outside of the root source
   280  	// directory; we simply ignore those.
   281  	rootSrcPrefix := ""
   282  	for _, file := range config.Srcs {
   283  		const src = "/src/"
   284  		i := strings.Index(file, src)
   285  		if i == -1 {
   286  			// Superfluous file.
   287  			continue
   288  		}
   289  
   290  		// Index of first character after /src/.
   291  		i += len(src)
   292  		rootSrcPrefix = file[:i]
   293  		break
   294  	}
   295  
   296  	// Aggregate all files by directory.
   297  	packages := make(map[string]*PackageConfig)
   298  	for _, file := range config.Srcs {
   299  		if !strings.HasPrefix(file, rootSrcPrefix) {
   300  			// Superflouous file.
   301  			continue
   302  		}
   303  
   304  		d := path.Dir(file)
   305  		if len(rootSrcPrefix) >= len(d) {
   306  			continue // Not a file.
   307  		}
   308  		pkg := d[len(rootSrcPrefix):]
   309  		// Skip cmd packages and obvious test files: see above.
   310  		if strings.HasPrefix(pkg, "cmd/") || strings.HasSuffix(file, "_test.go") {
   311  			continue
   312  		}
   313  		c, ok := packages[pkg]
   314  		if !ok {
   315  			c = &PackageConfig{
   316  				ImportPath: pkg,
   317  				GOOS:       config.GOOS,
   318  				GOARCH:     config.GOARCH,
   319  				Tags:       config.Tags,
   320  			}
   321  			packages[pkg] = c
   322  		}
   323  		// Add the files appropriately. Note that they will be further
   324  		// filtered by architecture and build tags below, so this need
   325  		// not be done immediately.
   326  		if strings.HasSuffix(file, ".go") {
   327  			c.GoFiles = append(c.GoFiles, file)
   328  		} else {
   329  			c.NonGoFiles = append(c.NonGoFiles, file)
   330  		}
   331  	}
   332  
   333  	// Closure to check a single package.
   334  	localStdlibFacts := make(stdlibFacts)
   335  	localStdlibErrs := make(map[string]error)
   336  	stdlibCachedFacts.Lookup([]string{""}, func() worker.Sizer {
   337  		return localStdlibFacts
   338  	})
   339  	var checkOne func(pkg string) error // Recursive.
   340  	checkOne = func(pkg string) error {
   341  		// Is this already done?
   342  		if _, ok := localStdlibFacts[pkg]; ok {
   343  			return nil
   344  		}
   345  		// Did this fail previously?
   346  		if _, ok := localStdlibErrs[pkg]; ok {
   347  			return nil
   348  		}
   349  
   350  		// Lookup the configuration.
   351  		config, ok := packages[pkg]
   352  		if !ok {
   353  			return nil // Not known.
   354  		}
   355  
   356  		// Find the binary package, and provide to objdump.
   357  		rc, err := findStdPkg(config.GOOS, config.GOARCH, pkg)
   358  		if err != nil {
   359  			// If there's no binary for this package, it is likely
   360  			// not built with the distribution. That's fine, we can
   361  			// just skip analysis.
   362  			localStdlibErrs[pkg] = err
   363  			return nil
   364  		}
   365  
   366  		// Provide the input.
   367  		oldReader := objdump.Reader
   368  		objdump.Reader = rc // For analysis.
   369  		defer func() {
   370  			rc.Close()
   371  			objdump.Reader = oldReader // Restore.
   372  		}()
   373  
   374  		// Run the analysis.
   375  		findings, factData, err := CheckPackage(config, analyzers, checkOne)
   376  		if err != nil {
   377  			// If we can't analyze a package from the standard library,
   378  			// then we skip it. It will simply not have any findings.
   379  			localStdlibErrs[pkg] = err
   380  			return nil
   381  		}
   382  		localStdlibFacts[pkg] = factData
   383  		allFindings = append(allFindings, findings...)
   384  		return nil
   385  	}
   386  
   387  	// Check all packages.
   388  	//
   389  	// Note that this may call checkOne recursively, so it's not guaranteed
   390  	// to evaluate in the order provided here. We do ensure however, that
   391  	// all packages are evaluated.
   392  	for pkg := range packages {
   393  		if err := checkOne(pkg); err != nil {
   394  			return nil, nil, err
   395  		}
   396  	}
   397  
   398  	// Sanity check.
   399  	if len(localStdlibFacts) == 0 {
   400  		return nil, nil, fmt.Errorf("no stdlib facts found: misconfiguration?")
   401  	}
   402  
   403  	// Write out all findings.
   404  	buf := bytes.NewBuffer(nil)
   405  	if err := localStdlibFacts.EncodeTo(buf); err != nil {
   406  		return nil, nil, fmt.Errorf("error serialized stdlib facts: %v", err)
   407  	}
   408  
   409  	// Write out all errors.
   410  	for pkg, err := range localStdlibErrs {
   411  		log.Printf("WARNING: error while processing %v: %v", pkg, err)
   412  	}
   413  
   414  	// Return all findings.
   415  	return allFindings, buf.Bytes(), nil
   416  }
   417  
   418  // sanityCheckScope checks that all object in astTypes map to the correct
   419  // objects in binaryTypes. Note that we don't check whether the sets are the
   420  // same, we only care about the fidelity of objects in astTypes.
   421  //
   422  // When an inconsistency is identified, we record it in the astToBinaryMap.
   423  // This allows us to dynamically replace facts and correct for the issue. The
   424  // total number of mismatches is returned.
   425  func sanityCheckScope(astScope *types.Scope, binaryTypes *types.Package, binaryScope *types.Scope, astToBinary map[types.Object]types.Object) error {
   426  	for _, x := range astScope.Names() {
   427  		fe := astScope.Lookup(x)
   428  		path, err := objectpath.For(fe)
   429  		if err != nil {
   430  			continue // Not an encoded object.
   431  		}
   432  		se, err := objectpath.Object(binaryTypes, path)
   433  		if err != nil {
   434  			continue // May be unused, see below.
   435  		}
   436  		if fe.Id() != se.Id() {
   437  			// These types are incompatible. This means that when
   438  			// this objectpath is loading from the binaryTypes (for
   439  			// dependencies) it will resolve to a fact for that
   440  			// type. We don't actually care about this error since
   441  			// we do the rewritten, but may as well alert.
   442  			log.Printf("WARNING: Object %s is a victim of go/issues/44195.", fe.Id())
   443  		}
   444  		se = binaryScope.Lookup(x)
   445  		if se == nil {
   446  			// The fact may not be exported in the objectdata, if
   447  			// it is package internal. This is fine, as nothing out
   448  			// of this package can use these symbols.
   449  			continue
   450  		}
   451  		// Save the translation.
   452  		astToBinary[fe] = se
   453  	}
   454  	for i := 0; i < astScope.NumChildren(); i++ {
   455  		if err := sanityCheckScope(astScope.Child(i), binaryTypes, binaryScope, astToBinary); err != nil {
   456  			return err
   457  		}
   458  	}
   459  	return nil
   460  }
   461  
   462  // sanityCheckTypes checks that two types are sane. The total number of
   463  // mismatches is returned.
   464  func sanityCheckTypes(astTypes, binaryTypes *types.Package, astToBinary map[types.Object]types.Object) error {
   465  	return sanityCheckScope(astTypes.Scope(), binaryTypes, binaryTypes.Scope(), astToBinary)
   466  }
   467  
   468  // CheckPackage runs all given analyzers.
   469  //
   470  // The implementation was adapted from [1], which was in turn adpated from [2].
   471  // This returns a list of matching analysis issues, or an error if the analysis
   472  // could not be completed.
   473  //
   474  // [1] bazelbuid/rules_go/tools/builders/nogo_main.go
   475  // [2] golang.org/x/tools/go/checker/internal/checker
   476  func CheckPackage(config *PackageConfig, analyzers []*analysis.Analyzer, importCallback func(string) error) (findings []Finding, factData []byte, err error) {
   477  	imp := &importer{
   478  		PackageConfig: config,
   479  		fset:          token.NewFileSet(),
   480  		cache:         make(map[string]*types.Package),
   481  		callback:      importCallback,
   482  	}
   483  
   484  	// Load all source files.
   485  	var syntax []*ast.File
   486  	for _, file := range config.GoFiles {
   487  		include, err := config.shouldInclude(file)
   488  		if err != nil {
   489  			return nil, nil, fmt.Errorf("error evaluating file %q: %v", file, err)
   490  		}
   491  		if !include {
   492  			continue
   493  		}
   494  		s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments)
   495  		if err != nil {
   496  			return nil, nil, fmt.Errorf("error parsing file %q: %v", file, err)
   497  		}
   498  		syntax = append(syntax, s)
   499  	}
   500  
   501  	// Check type information.
   502  	typesSizes := types.SizesFor("gc", config.GOARCH)
   503  	typeConfig := types.Config{Importer: imp}
   504  	typesInfo := &types.Info{
   505  		Types:      make(map[ast.Expr]types.TypeAndValue),
   506  		Uses:       make(map[*ast.Ident]types.Object),
   507  		Defs:       make(map[*ast.Ident]types.Object),
   508  		Implicits:  make(map[ast.Node]types.Object),
   509  		Scopes:     make(map[ast.Node]*types.Scope),
   510  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   511  	}
   512  	astTypes, err := typeConfig.Check(config.ImportPath, imp.fset, syntax, typesInfo)
   513  	if err != nil && imp.lastErr != ErrSkip {
   514  		return nil, nil, fmt.Errorf("error checking types: %w", err)
   515  	}
   516  
   517  	// Load all facts using the astTypes, although it may need reconciling
   518  	// later on. See the fact functions below.
   519  	astFacts, err := facts.Decode(astTypes, config.factLoader)
   520  	if err != nil {
   521  		return nil, nil, fmt.Errorf("error decoding facts: %w", err)
   522  	}
   523  
   524  	// Sanity check all types and record metadata to prevent
   525  	// https://github.com/golang/go/issues/44195.
   526  	//
   527  	// This block loads the binary types, whose encoding will be well
   528  	// defined and aligned with any downstream consumers. Below in the fact
   529  	// functions for the analysis, we serialize types to both the astFacts
   530  	// and the binaryFacts if available. The binaryFacts are the final
   531  	// encoded facts in order to ensure compatibility. We keep the
   532  	// intermediate astTypes in order to allow exporting and importing
   533  	// within the local package under analysis.
   534  	var (
   535  		astToBinary = make(map[types.Object]types.Object)
   536  		binaryFacts *facts.Set
   537  	)
   538  	if _, ok := config.ImportMap[config.ImportPath]; ok {
   539  		binaryTypes, err := imp.Import(config.ImportPath)
   540  		if err != nil {
   541  			return nil, nil, fmt.Errorf("error loading self: %w", err)
   542  		}
   543  		if err := sanityCheckTypes(astTypes, binaryTypes, astToBinary); err != nil {
   544  			return nil, nil, fmt.Errorf("error sanity checking types: %w", err)
   545  		}
   546  		binaryFacts, err = facts.Decode(binaryTypes, config.factLoader)
   547  		if err != nil {
   548  			return nil, nil, fmt.Errorf("error decoding facts: %w", err)
   549  		}
   550  	}
   551  
   552  	// Register fact types and establish dependencies between analyzers.
   553  	// The visit closure will execute recursively, and populate results
   554  	// will all required analysis results.
   555  	results := make(map[*analysis.Analyzer]interface{})
   556  	var visit func(*analysis.Analyzer) error // For recursion.
   557  	visit = func(a *analysis.Analyzer) error {
   558  		if _, ok := results[a]; ok {
   559  			return nil
   560  		}
   561  
   562  		// Run recursively for all dependencies.
   563  		for _, req := range a.Requires {
   564  			if err := visit(req); err != nil {
   565  				return err
   566  			}
   567  		}
   568  
   569  		// Run the analysis.
   570  		localFactsFilter := make(map[reflect.Type]bool)
   571  		for _, f := range a.FactTypes {
   572  			localFactsFilter[reflect.TypeOf(f)] = true
   573  		}
   574  		p := &analysis.Pass{
   575  			Analyzer:  a,
   576  			Fset:      imp.fset,
   577  			Files:     syntax,
   578  			Pkg:       astTypes,
   579  			TypesInfo: typesInfo,
   580  			ResultOf:  results, // All results.
   581  			Report: func(d analysis.Diagnostic) {
   582  				findings = append(findings, Finding{
   583  					Category: AnalyzerName(a.Name),
   584  					Position: imp.fset.Position(d.Pos),
   585  					Message:  d.Message,
   586  				})
   587  			},
   588  			ImportPackageFact: astFacts.ImportPackageFact,
   589  			ExportPackageFact: func(fact analysis.Fact) {
   590  				astFacts.ExportPackageFact(fact)
   591  				if binaryFacts != nil {
   592  					binaryFacts.ExportPackageFact(fact)
   593  				}
   594  			},
   595  			ImportObjectFact: astFacts.ImportObjectFact,
   596  			ExportObjectFact: func(obj types.Object, fact analysis.Fact) {
   597  				astFacts.ExportObjectFact(obj, fact)
   598  				// Note that if no object is recorded in
   599  				// astToBinary and binaryFacts != nil, then the
   600  				// object doesn't appear in the exported data.
   601  				// It was likely an internal object to the
   602  				// package, and there is no meaningful
   603  				// downstream consumer of the fact.
   604  				if binaryObj, ok := astToBinary[obj]; ok && binaryFacts != nil {
   605  					binaryFacts.ExportObjectFact(binaryObj, fact)
   606  				}
   607  			},
   608  			AllPackageFacts: func() []analysis.PackageFact { return astFacts.AllPackageFacts(localFactsFilter) },
   609  			AllObjectFacts:  func() []analysis.ObjectFact { return astFacts.AllObjectFacts(localFactsFilter) },
   610  			TypesSizes:      typesSizes,
   611  		}
   612  		result, err := a.Run(p)
   613  		if err != nil {
   614  			return fmt.Errorf("error running analysis %s: %v", a, err)
   615  		}
   616  
   617  		// Sanity check & save the result.
   618  		if got, want := reflect.TypeOf(result), a.ResultType; got != want {
   619  			return fmt.Errorf("error: analyzer %s returned a result of type %v, but declared ResultType %v", a, got, want)
   620  		}
   621  		results[a] = result
   622  		return nil // Success.
   623  	}
   624  
   625  	// Visit all analyzers recursively.
   626  	for _, a := range analyzers {
   627  		if imp.lastErr == ErrSkip {
   628  			continue // No local analysis.
   629  		}
   630  		if err := visit(a); err != nil {
   631  			return nil, nil, err // Already has context.
   632  		}
   633  	}
   634  
   635  	// Return all findings. Note that we have a preference to returning the
   636  	// binary facts if available, so that downstream consumers of these
   637  	// facts will find the export aligns with the internal type details.
   638  	// See the block above with the call to sanityCheckTypes.
   639  	if binaryFacts != nil {
   640  		return findings, binaryFacts.Encode(), nil
   641  	}
   642  	return findings, astFacts.Encode(), nil
   643  }
   644  
   645  func init() {
   646  	gob.Register((*stdlibFact)(nil))
   647  }