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