github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/nogo_main.go (about)

     1  /* Copyright 2018 The Bazel Authors. All rights reserved.
     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  
    16  // Loads and runs registered analyses on a well-typed Go package.
    17  // The code in this file is combined with the code generated by
    18  // generate_nogo_main.go.
    19  
    20  package main
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/gob"
    25  	"errors"
    26  	"flag"
    27  	"fmt"
    28  	"go/ast"
    29  	"go/parser"
    30  	"go/token"
    31  	"go/types"
    32  	"io/ioutil"
    33  	"log"
    34  	"os"
    35  	"reflect"
    36  	"regexp"
    37  	"sort"
    38  	"strings"
    39  	"sync"
    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  )
    45  
    46  func init() {
    47  	if err := analysis.Validate(analyzers); err != nil {
    48  		log.Fatal(err)
    49  	}
    50  }
    51  
    52  var typesSizes = types.SizesFor("gc", os.Getenv("GOARCH"))
    53  
    54  func main() {
    55  	log.SetFlags(0) // no timestamp
    56  	log.SetPrefix("nogo: ")
    57  	if err := run(os.Args[1:]); err != nil {
    58  		log.Fatal(err)
    59  	}
    60  }
    61  
    62  // run returns an error if there is a problem loading the package or if any
    63  // analysis fails.
    64  func run(args []string) error {
    65  	args, err := expandParamsFiles(args)
    66  	if err != nil {
    67  		return fmt.Errorf("error reading paramfiles: %v", err)
    68  	}
    69  
    70  	factMap := factMultiFlag{}
    71  	flags := flag.NewFlagSet("nogo", flag.ExitOnError)
    72  	flags.Var(&factMap, "fact", "Import path and file containing facts for that library, separated by '=' (may be repeated)'")
    73  	importcfg := flags.String("importcfg", "", "The import configuration file")
    74  	packagePath := flags.String("p", "", "The package path (importmap) of the package being compiled")
    75  	xPath := flags.String("x", "", "The archive file where serialized facts should be written")
    76  	flags.Parse(args)
    77  	srcs := flags.Args()
    78  
    79  	packageFile, importMap, err := readImportCfg(*importcfg)
    80  	if err != nil {
    81  		return fmt.Errorf("error parsing importcfg: %v", err)
    82  	}
    83  
    84  	diagnostics, facts, err := checkPackage(analyzers, *packagePath, packageFile, importMap, factMap, srcs)
    85  	if err != nil {
    86  		return fmt.Errorf("error running analyzers: %v", err)
    87  	}
    88  	if diagnostics != "" {
    89  		return fmt.Errorf("errors found by nogo during build-time code analysis:\n%s\n", diagnostics)
    90  	}
    91  	if *xPath != "" {
    92  		if err := ioutil.WriteFile(abs(*xPath), facts, 0666); err != nil {
    93  			return fmt.Errorf("error writing facts: %v", err)
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // Adapted from go/src/cmd/compile/internal/gc/main.go. Keep in sync.
   101  func readImportCfg(file string) (packageFile map[string]string, importMap map[string]string, err error) {
   102  	packageFile, importMap = make(map[string]string), make(map[string]string)
   103  	data, err := ioutil.ReadFile(file)
   104  	if err != nil {
   105  		return nil, nil, fmt.Errorf("-importcfg: %v", err)
   106  	}
   107  
   108  	for lineNum, line := range strings.Split(string(data), "\n") {
   109  		lineNum++ // 1-based
   110  		line = strings.TrimSpace(line)
   111  		if line == "" || strings.HasPrefix(line, "#") {
   112  			continue
   113  		}
   114  
   115  		var verb, args string
   116  		if i := strings.Index(line, " "); i < 0 {
   117  			verb = line
   118  		} else {
   119  			verb, args = line[:i], strings.TrimSpace(line[i+1:])
   120  		}
   121  		var before, after string
   122  		if i := strings.Index(args, "="); i >= 0 {
   123  			before, after = args[:i], args[i+1:]
   124  		}
   125  		switch verb {
   126  		default:
   127  			return nil, nil, fmt.Errorf("%s:%d: unknown directive %q", file, lineNum, verb)
   128  		case "importmap":
   129  			if before == "" || after == "" {
   130  				return nil, nil, fmt.Errorf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum)
   131  			}
   132  			importMap[before] = after
   133  		case "packagefile":
   134  			if before == "" || after == "" {
   135  				return nil, nil, fmt.Errorf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum)
   136  			}
   137  			packageFile[before] = after
   138  		}
   139  	}
   140  	return packageFile, importMap, nil
   141  }
   142  
   143  // checkPackage runs all the given analyzers on the specified package and
   144  // returns the source code diagnostics that the must be printed in the build log.
   145  // It returns an empty string if no source code diagnostics need to be printed.
   146  //
   147  // This implementation was adapted from that of golang.org/x/tools/go/checker/internal/checker.
   148  func checkPackage(analyzers []*analysis.Analyzer, packagePath string, packageFile, importMap map[string]string, factMap map[string]string, filenames []string) (string, []byte, error) {
   149  	// Register fact types and establish dependencies between analyzers.
   150  	actions := make(map[*analysis.Analyzer]*action)
   151  	var visit func(a *analysis.Analyzer) *action
   152  	visit = func(a *analysis.Analyzer) *action {
   153  		act, ok := actions[a]
   154  		if !ok {
   155  			act = &action{a: a}
   156  			actions[a] = act
   157  			for _, f := range a.FactTypes {
   158  				act.usesFacts = true
   159  				gob.Register(f)
   160  			}
   161  			act.deps = make([]*action, len(a.Requires))
   162  			for i, req := range a.Requires {
   163  				dep := visit(req)
   164  				if dep.usesFacts {
   165  					act.usesFacts = true
   166  				}
   167  				act.deps[i] = dep
   168  			}
   169  		}
   170  		return act
   171  	}
   172  
   173  	roots := make([]*action, 0, len(analyzers))
   174  	for _, a := range analyzers {
   175  		roots = append(roots, visit(a))
   176  	}
   177  
   178  	// Load the package, including AST, types, and facts.
   179  	imp := newImporter(importMap, packageFile, factMap)
   180  	pkg, err := load(packagePath, imp, filenames)
   181  	if err != nil {
   182  		return "", nil, fmt.Errorf("error loading package: %v", err)
   183  	}
   184  	for _, act := range actions {
   185  		act.pkg = pkg
   186  	}
   187  
   188  	// Execute the analyzers.
   189  	execAll(roots)
   190  
   191  	// Process diagnostics and encode facts for importers of this package.
   192  	diagnostics := checkAnalysisResults(roots, pkg)
   193  	facts := pkg.facts.Encode()
   194  	return diagnostics, facts, nil
   195  }
   196  
   197  // An action represents one unit of analysis work: the application of
   198  // one analysis to one package. Actions form a DAG within a
   199  // package (as different analyzers are applied, either in sequence or
   200  // parallel).
   201  type action struct {
   202  	once        sync.Once
   203  	a           *analysis.Analyzer
   204  	pass        *analysis.Pass
   205  	pkg         *goPackage
   206  	deps        []*action
   207  	inputs      map[*analysis.Analyzer]interface{}
   208  	result      interface{}
   209  	diagnostics []analysis.Diagnostic
   210  	usesFacts   bool
   211  	err         error
   212  }
   213  
   214  func (act *action) String() string {
   215  	return fmt.Sprintf("%s@%s", act.a, act.pkg)
   216  }
   217  
   218  func execAll(actions []*action) {
   219  	var wg sync.WaitGroup
   220  	wg.Add(len(actions))
   221  	for _, act := range actions {
   222  		go func(act *action) {
   223  			defer wg.Done()
   224  			act.exec()
   225  		}(act)
   226  	}
   227  	wg.Wait()
   228  }
   229  
   230  func (act *action) exec() { act.once.Do(act.execOnce) }
   231  
   232  func (act *action) execOnce() {
   233  	// Analyze dependencies.
   234  	execAll(act.deps)
   235  
   236  	// Report an error if any dependency failed.
   237  	var failed []string
   238  	for _, dep := range act.deps {
   239  		if dep.err != nil {
   240  			failed = append(failed, dep.String())
   241  		}
   242  	}
   243  	if failed != nil {
   244  		sort.Strings(failed)
   245  		act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
   246  		return
   247  	}
   248  
   249  	// Plumb the output values of the dependencies
   250  	// into the inputs of this action.
   251  	inputs := make(map[*analysis.Analyzer]interface{})
   252  	for _, dep := range act.deps {
   253  		// Same package, different analysis (horizontal edge):
   254  		// in-memory outputs of prerequisite analyzers
   255  		// become inputs to this analysis pass.
   256  		inputs[dep.a] = dep.result
   257  	}
   258  
   259  	// Run the analysis.
   260  	factFilter := make(map[reflect.Type]bool)
   261  	for _, f := range act.a.FactTypes {
   262  		factFilter[reflect.TypeOf(f)] = true
   263  	}
   264  	pass := &analysis.Pass{
   265  		Analyzer:          act.a,
   266  		Fset:              act.pkg.fset,
   267  		Files:             act.pkg.syntax,
   268  		Pkg:               act.pkg.types,
   269  		TypesInfo:         act.pkg.typesInfo,
   270  		ResultOf:          inputs,
   271  		Report:            func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
   272  		ImportPackageFact: act.pkg.facts.ImportPackageFact,
   273  		ExportPackageFact: act.pkg.facts.ExportPackageFact,
   274  		ImportObjectFact:  act.pkg.facts.ImportObjectFact,
   275  		ExportObjectFact:  act.pkg.facts.ExportObjectFact,
   276  		AllPackageFacts:   func() []analysis.PackageFact { return act.pkg.facts.AllPackageFacts(factFilter) },
   277  		AllObjectFacts:    func() []analysis.ObjectFact { return act.pkg.facts.AllObjectFacts(factFilter) },
   278  		TypesSizes:        typesSizes,
   279  	}
   280  	act.pass = pass
   281  
   282  	var err error
   283  	if act.pkg.illTyped && !pass.Analyzer.RunDespiteErrors {
   284  		err = fmt.Errorf("analysis skipped due to type-checking error: %v", act.pkg.typeCheckError)
   285  	} else {
   286  		act.result, err = pass.Analyzer.Run(pass)
   287  		if err == nil {
   288  			if got, want := reflect.TypeOf(act.result), pass.Analyzer.ResultType; got != want {
   289  				err = fmt.Errorf(
   290  					"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
   291  					pass.Pkg.Path(), pass.Analyzer, got, want)
   292  			}
   293  		}
   294  	}
   295  	act.err = err
   296  }
   297  
   298  // load parses and type checks the source code in each file in filenames.
   299  // load also deserializes facts stored for imported packages.
   300  func load(packagePath string, imp *importer, filenames []string) (*goPackage, error) {
   301  	if len(filenames) == 0 {
   302  		return nil, errors.New("no filenames")
   303  	}
   304  	var syntax []*ast.File
   305  	for _, file := range filenames {
   306  		s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments)
   307  		if err != nil {
   308  			return nil, err
   309  		}
   310  		syntax = append(syntax, s)
   311  	}
   312  	pkg := &goPackage{fset: imp.fset, syntax: syntax}
   313  
   314  	config := types.Config{Importer: imp}
   315  	info := &types.Info{
   316  		Types:      make(map[ast.Expr]types.TypeAndValue),
   317  		Uses:       make(map[*ast.Ident]types.Object),
   318  		Defs:       make(map[*ast.Ident]types.Object),
   319  		Implicits:  make(map[ast.Node]types.Object),
   320  		Scopes:     make(map[ast.Node]*types.Scope),
   321  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   322  	}
   323  	types, err := config.Check(packagePath, pkg.fset, syntax, info)
   324  	if err != nil {
   325  		pkg.illTyped, pkg.typeCheckError = true, err
   326  	}
   327  	pkg.types, pkg.typesInfo = types, info
   328  
   329  	pkg.facts, err = facts.Decode(pkg.types, imp.readFacts)
   330  	if err != nil {
   331  		return nil, fmt.Errorf("internal error decoding facts: %v", err)
   332  	}
   333  
   334  	return pkg, nil
   335  }
   336  
   337  // A goPackage describes a loaded Go package.
   338  type goPackage struct {
   339  	// fset provides position information for types, typesInfo, and syntax.
   340  	// It is set only when types is set.
   341  	fset *token.FileSet
   342  	// syntax is the package's syntax trees.
   343  	syntax []*ast.File
   344  	// types provides type information for the package.
   345  	types *types.Package
   346  	// facts contains information saved by the analysis framework. Passes may
   347  	// import facts for imported packages and may also export facts for this
   348  	// package to be consumed by analyses in downstream packages.
   349  	facts *facts.Set
   350  	// illTyped indicates whether the package or any dependency contains errors.
   351  	// It is set only when types is set.
   352  	illTyped bool
   353  	// typeCheckError contains any error encountered during type-checking. It is
   354  	// only set when illTyped is true.
   355  	typeCheckError error
   356  	// typesInfo provides type information about the package's syntax trees.
   357  	// It is set only when syntax is set.
   358  	typesInfo *types.Info
   359  }
   360  
   361  func (g *goPackage) String() string {
   362  	return g.types.Path()
   363  }
   364  
   365  // checkAnalysisResults checks the analysis diagnostics in the given actions
   366  // and returns a string containing all the diagnostics that should be printed
   367  // to the build log.
   368  func checkAnalysisResults(actions []*action, pkg *goPackage) string {
   369  	var diagnostics []analysis.Diagnostic
   370  	var errs []error
   371  	for _, act := range actions {
   372  		if act.err != nil {
   373  			// Analyzer failed.
   374  			errs = append(errs, fmt.Errorf("analyzer %q failed: %v", act.a.Name, act.err))
   375  			continue
   376  		}
   377  		if len(act.diagnostics) == 0 {
   378  			continue
   379  		}
   380  		config, ok := configs[act.a.Name]
   381  		if !ok {
   382  			// If the analyzer is not explicitly configured, it emits diagnostics for
   383  			// all files.
   384  			diagnostics = append(diagnostics, act.diagnostics...)
   385  			continue
   386  		}
   387  		// Discard diagnostics based on the analyzer configuration.
   388  		for _, d := range act.diagnostics {
   389  			// NOTE(golang.org/issue/31008): nilness does not set positions,
   390  			// so don't assume the position is valid.
   391  			f := pkg.fset.File(d.Pos)
   392  			filename := "-"
   393  			if f != nil {
   394  				filename = f.Name()
   395  			}
   396  			include := true
   397  			if len(config.onlyFiles) > 0 {
   398  				// This analyzer emits diagnostics for only a set of files.
   399  				include = false
   400  				for _, pattern := range config.onlyFiles {
   401  					if pattern.MatchString(filename) {
   402  						include = true
   403  						break
   404  					}
   405  				}
   406  			}
   407  			if include {
   408  				for _, pattern := range config.excludeFiles {
   409  					if pattern.MatchString(filename) {
   410  						include = false
   411  						break
   412  					}
   413  				}
   414  			}
   415  			if include {
   416  				diagnostics = append(diagnostics, d)
   417  			}
   418  		}
   419  	}
   420  	if len(diagnostics) == 0 && len(errs) == 0 {
   421  		return ""
   422  	}
   423  
   424  	sort.Slice(diagnostics, func(i, j int) bool {
   425  		return diagnostics[i].Pos < diagnostics[j].Pos
   426  	})
   427  	errMsg := &bytes.Buffer{}
   428  	sep := ""
   429  	for _, err := range errs {
   430  		errMsg.WriteString(sep)
   431  		sep = "\n"
   432  		errMsg.WriteString(err.Error())
   433  	}
   434  	for _, d := range diagnostics {
   435  		errMsg.WriteString(sep)
   436  		sep = "\n"
   437  		fmt.Fprintf(errMsg, "%s: %s", pkg.fset.Position(d.Pos), d.Message)
   438  	}
   439  	return errMsg.String()
   440  }
   441  
   442  // config determines which source files an analyzer will emit diagnostics for.
   443  // config values are generated in another file that is compiled with
   444  // nogo_main.go by the nogo rule.
   445  type config struct {
   446  	// onlyFiles is a list of regular expressions that match files an analyzer
   447  	// will emit diagnostics for. When empty, the analyzer will emit diagnostics
   448  	// for all files.
   449  	onlyFiles []*regexp.Regexp
   450  
   451  	// excludeFiles is a list of regular expressions that match files that an
   452  	// analyzer will not emit diagnostics for.
   453  	excludeFiles []*regexp.Regexp
   454  }
   455  
   456  // importer is an implementation of go/types.Importer that imports type
   457  // information from the export data in compiled .a files.
   458  type importer struct {
   459  	fset         *token.FileSet
   460  	importMap    map[string]string         // map import path in source code to package path
   461  	packageCache map[string]*types.Package // cache of previously imported packages
   462  	packageFile  map[string]string         // map package path to .a file with export data
   463  	factMap      map[string]string         // map import path in source code to file containing serialized facts
   464  }
   465  
   466  func newImporter(importMap, packageFile map[string]string, factMap map[string]string) *importer {
   467  	return &importer{
   468  		fset:         token.NewFileSet(),
   469  		importMap:    importMap,
   470  		packageCache: make(map[string]*types.Package),
   471  		packageFile:  packageFile,
   472  		factMap:      factMap,
   473  	}
   474  }
   475  
   476  func (i *importer) Import(path string) (*types.Package, error) {
   477  	if imp, ok := i.importMap[path]; ok {
   478  		// Translate import path if necessary.
   479  		path = imp
   480  	}
   481  	if path == "unsafe" {
   482  		// Special case: go/types has pre-defined type information for unsafe.
   483  		// See https://github.com/golang/go/issues/13882.
   484  		return types.Unsafe, nil
   485  	}
   486  	if pkg, ok := i.packageCache[path]; ok && pkg.Complete() {
   487  		return pkg, nil // cache hit
   488  	}
   489  
   490  	archive, ok := i.packageFile[path]
   491  	if !ok {
   492  		return nil, fmt.Errorf("could not import %q", path)
   493  	}
   494  	// open file
   495  	f, err := os.Open(archive)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  	defer func() {
   500  		f.Close()
   501  		if err != nil {
   502  			// add file name to error
   503  			err = fmt.Errorf("reading export data: %s: %v", archive, err)
   504  		}
   505  	}()
   506  
   507  	r, err := gcexportdata.NewReader(f)
   508  	if err != nil {
   509  		return nil, err
   510  	}
   511  
   512  	return gcexportdata.Read(r, i.fset, i.packageCache, path)
   513  }
   514  
   515  func (i *importer) readFacts(path string) ([]byte, error) {
   516  	archive := i.factMap[path]
   517  	if archive == "" {
   518  		// Packages that were not built with the nogo toolchain will not be
   519  		// analyzed, so there's no opportunity to store facts. This includes
   520  		// packages in the standard library and packages built with go_tool_library,
   521  		// such as coverdata. Analyzers are expected to hard code information
   522  		// about standard library definitions and must gracefully handle packages
   523  		// that don't have facts. For example, the "printf" analyzer must know
   524  		// fmt.Printf accepts a format string.
   525  		return nil, nil
   526  	}
   527  	factReader, err := readFileInArchive(nogoFact, archive)
   528  	if os.IsNotExist(err) {
   529  		// Packages that were not built with the nogo toolchain will not be
   530  		// analyzed, so there's no opportunity to store facts. This includes
   531  		// packages in the standard library and packages built with go_tool_library,
   532  		// such as coverdata.
   533  		return nil, nil
   534  	} else if err != nil {
   535  		return nil, err
   536  	}
   537  	defer factReader.Close()
   538  	return ioutil.ReadAll(factReader)
   539  }
   540  
   541  type factMultiFlag map[string]string
   542  
   543  func (m *factMultiFlag) String() string {
   544  	if m == nil || len(*m) == 0 {
   545  		return ""
   546  	}
   547  	return fmt.Sprintf("%v", *m)
   548  }
   549  
   550  func (m *factMultiFlag) Set(v string) error {
   551  	parts := strings.Split(v, "=")
   552  	if len(parts) != 2 {
   553  		return fmt.Errorf("badly formatted -fact flag: %s", v)
   554  	}
   555  	(*m)[parts[0]] = parts[1]
   556  	return nil
   557  }