xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/nogo_main.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1/* Copyright 2018 The Bazel Authors. All rights reserved.
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7   http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations 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
20package main
21
22import (
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/gcexportdata"
43	"golang.org/x/tools/internal/facts"
44)
45
46const nogoBaseConfigName = "_base"
47
48func init() {
49	if err := analysis.Validate(analyzers); err != nil {
50		log.Fatal(err)
51	}
52}
53
54var typesSizes = types.SizesFor("gc", os.Getenv("GOARCH"))
55
56func main() {
57	log.SetFlags(0) // no timestamp
58	log.SetPrefix("nogo: ")
59	if err := run(os.Args[1:]); err != nil {
60		log.Fatal(err)
61	}
62}
63
64// run returns an error if there is a problem loading the package or if any
65// analysis fails.
66func run(args []string) error {
67	args, _, err := expandParamsFiles(args)
68	if err != nil {
69		return fmt.Errorf("error reading paramfiles: %v", err)
70	}
71
72	factMap := factMultiFlag{}
73	flags := flag.NewFlagSet("nogo", flag.ExitOnError)
74	flags.Var(&factMap, "fact", "Import path and file containing facts for that library, separated by '=' (may be repeated)'")
75	importcfg := flags.String("importcfg", "", "The import configuration file")
76	packagePath := flags.String("p", "", "The package path (importmap) of the package being compiled")
77	xPath := flags.String("x", "", "The archive file where serialized facts should be written")
78	flags.Parse(args)
79	srcs := flags.Args()
80
81	packageFile, importMap, err := readImportCfg(*importcfg)
82	if err != nil {
83		return fmt.Errorf("error parsing importcfg: %v", err)
84	}
85
86	diagnostics, facts, err := checkPackage(analyzers, *packagePath, packageFile, importMap, factMap, srcs)
87	if err != nil {
88		return fmt.Errorf("error running analyzers: %v", err)
89	}
90	if diagnostics != "" {
91		return fmt.Errorf("errors found by nogo during build-time code analysis:\n%s\n", diagnostics)
92	}
93	if *xPath != "" {
94		if err := ioutil.WriteFile(abs(*xPath), facts, 0o666); err != nil {
95			return fmt.Errorf("error writing facts: %v", err)
96		}
97	}
98
99	return nil
100}
101
102// Adapted from go/src/cmd/compile/internal/gc/main.go. Keep in sync.
103func readImportCfg(file string) (packageFile map[string]string, importMap map[string]string, err error) {
104	packageFile, importMap = make(map[string]string), make(map[string]string)
105	data, err := ioutil.ReadFile(file)
106	if err != nil {
107		return nil, nil, fmt.Errorf("-importcfg: %v", err)
108	}
109
110	for lineNum, line := range strings.Split(string(data), "\n") {
111		lineNum++ // 1-based
112		line = strings.TrimSpace(line)
113		if line == "" || strings.HasPrefix(line, "#") {
114			continue
115		}
116
117		var verb, args string
118		if i := strings.Index(line, " "); i < 0 {
119			verb = line
120		} else {
121			verb, args = line[:i], strings.TrimSpace(line[i+1:])
122		}
123		var before, after string
124		if i := strings.Index(args, "="); i >= 0 {
125			before, after = args[:i], args[i+1:]
126		}
127		switch verb {
128		default:
129			return nil, nil, fmt.Errorf("%s:%d: unknown directive %q", file, lineNum, verb)
130		case "importmap":
131			if before == "" || after == "" {
132				return nil, nil, fmt.Errorf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum)
133			}
134			importMap[before] = after
135		case "packagefile":
136			if before == "" || after == "" {
137				return nil, nil, fmt.Errorf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum)
138			}
139			packageFile[before] = after
140		}
141	}
142	return packageFile, importMap, nil
143}
144
145// checkPackage runs all the given analyzers on the specified package and
146// returns the source code diagnostics that the must be printed in the build log.
147// It returns an empty string if no source code diagnostics need to be printed.
148//
149// This implementation was adapted from that of golang.org/x/tools/go/checker/internal/checker.
150func checkPackage(analyzers []*analysis.Analyzer, packagePath string, packageFile, importMap map[string]string, factMap map[string]string, filenames []string) (string, []byte, error) {
151	// Register fact types and establish dependencies between analyzers.
152	actions := make(map[*analysis.Analyzer]*action)
153	var visit func(a *analysis.Analyzer) *action
154	visit = func(a *analysis.Analyzer) *action {
155		act, ok := actions[a]
156		if !ok {
157			act = &action{a: a}
158			actions[a] = act
159			for _, f := range a.FactTypes {
160				act.usesFacts = true
161				gob.Register(f)
162			}
163			act.deps = make([]*action, len(a.Requires))
164			for i, req := range a.Requires {
165				dep := visit(req)
166				if dep.usesFacts {
167					act.usesFacts = true
168				}
169				act.deps[i] = dep
170			}
171		}
172		return act
173	}
174
175	roots := make([]*action, 0, len(analyzers))
176	for _, a := range analyzers {
177		if cfg, ok := configs[a.Name]; ok {
178			for flagKey, flagVal := range cfg.analyzerFlags {
179				if strings.HasPrefix(flagKey, "-") {
180					return "", nil, fmt.Errorf(
181						"%s: flag should not begin with '-': %s", a.Name, flagKey)
182				}
183				if flag := a.Flags.Lookup(flagKey); flag == nil {
184					return "", nil, fmt.Errorf("%s: unrecognized flag: %s", a.Name, flagKey)
185				}
186				if err := a.Flags.Set(flagKey, flagVal); err != nil {
187					return "", nil, fmt.Errorf(
188						"%s: invalid value for flag: %s=%s: %w", a.Name, flagKey, flagVal, err)
189				}
190			}
191		}
192		roots = append(roots, visit(a))
193	}
194
195	// Load the package, including AST, types, and facts.
196	imp := newImporter(importMap, packageFile, factMap)
197	pkg, err := load(packagePath, imp, filenames)
198	if err != nil {
199		return "", nil, fmt.Errorf("error loading package: %v", err)
200	}
201	for _, act := range actions {
202		act.pkg = pkg
203	}
204
205	// Process nolint directives similar to golangci-lint.
206	for _, f := range pkg.syntax {
207		// CommentMap will correctly associate comments to the largest node group
208		// applicable. This handles inline comments that might trail a large
209		// assignment and will apply the comment to the entire assignment.
210		commentMap := ast.NewCommentMap(pkg.fset, f, f.Comments)
211		for node, groups := range commentMap {
212			rng := &Range{
213				from: pkg.fset.Position(node.Pos()),
214				to:   pkg.fset.Position(node.End()).Line,
215			}
216			for _, group := range groups {
217				for _, comm := range group.List {
218					linters, ok := parseNolint(comm.Text)
219					if !ok {
220						continue
221					}
222					for analyzer, act := range actions {
223						if linters == nil || linters[analyzer.Name] {
224							act.nolint = append(act.nolint, rng)
225						}
226					}
227				}
228			}
229		}
230	}
231
232	// Execute the analyzers.
233	execAll(roots)
234
235	// Process diagnostics and encode facts for importers of this package.
236	diagnostics := checkAnalysisResults(roots, pkg)
237	facts := pkg.facts.Encode()
238	return diagnostics, facts, nil
239}
240
241type Range struct {
242	from token.Position
243	to   int
244}
245
246// An action represents one unit of analysis work: the application of
247// one analysis to one package. Actions form a DAG within a
248// package (as different analyzers are applied, either in sequence or
249// parallel).
250type action struct {
251	once        sync.Once
252	a           *analysis.Analyzer
253	pass        *analysis.Pass
254	pkg         *goPackage
255	deps        []*action
256	inputs      map[*analysis.Analyzer]interface{}
257	result      interface{}
258	diagnostics []analysis.Diagnostic
259	usesFacts   bool
260	err         error
261	nolint      []*Range
262}
263
264func (act *action) String() string {
265	return fmt.Sprintf("%s@%s", act.a, act.pkg)
266}
267
268func execAll(actions []*action) {
269	var wg sync.WaitGroup
270	wg.Add(len(actions))
271	for _, act := range actions {
272		go func(act *action) {
273			defer wg.Done()
274			act.exec()
275		}(act)
276	}
277	wg.Wait()
278}
279
280func (act *action) exec() { act.once.Do(act.execOnce) }
281
282func (act *action) execOnce() {
283	// Analyze dependencies.
284	execAll(act.deps)
285
286	// Report an error if any dependency failed.
287	var failed []string
288	for _, dep := range act.deps {
289		if dep.err != nil {
290			failed = append(failed, dep.String())
291		}
292	}
293	if failed != nil {
294		sort.Strings(failed)
295		act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
296		return
297	}
298
299	// Plumb the output values of the dependencies
300	// into the inputs of this action.
301	inputs := make(map[*analysis.Analyzer]interface{})
302	for _, dep := range act.deps {
303		// Same package, different analysis (horizontal edge):
304		// in-memory outputs of prerequisite analyzers
305		// become inputs to this analysis pass.
306		inputs[dep.a] = dep.result
307	}
308
309	ignoreNolintReporter := func(d analysis.Diagnostic) {
310		pos := act.pkg.fset.Position(d.Pos)
311		for _, rng := range act.nolint {
312			// The list of nolint ranges is built for the entire package. Make sure we
313			// only apply ranges to the correct file.
314			if pos.Filename != rng.from.Filename {
315				continue
316			}
317			if pos.Line < rng.from.Line || pos.Line > rng.to {
318				continue
319			}
320			// Found a nolint range. Ignore the issue.
321			return
322		}
323		act.diagnostics = append(act.diagnostics, d)
324	}
325
326	// Run the analysis.
327	factFilter := make(map[reflect.Type]bool)
328	for _, f := range act.a.FactTypes {
329		factFilter[reflect.TypeOf(f)] = true
330	}
331	pass := &analysis.Pass{
332		Analyzer:          act.a,
333		Fset:              act.pkg.fset,
334		Files:             act.pkg.syntax,
335		Pkg:               act.pkg.types,
336		TypesInfo:         act.pkg.typesInfo,
337		ResultOf:          inputs,
338		Report:            ignoreNolintReporter,
339		ImportPackageFact: act.pkg.facts.ImportPackageFact,
340		ExportPackageFact: act.pkg.facts.ExportPackageFact,
341		ImportObjectFact:  act.pkg.facts.ImportObjectFact,
342		ExportObjectFact:  act.pkg.facts.ExportObjectFact,
343		AllPackageFacts:   func() []analysis.PackageFact { return act.pkg.facts.AllPackageFacts(factFilter) },
344		AllObjectFacts:    func() []analysis.ObjectFact { return act.pkg.facts.AllObjectFacts(factFilter) },
345		TypesSizes:        typesSizes,
346	}
347	act.pass = pass
348
349	var err error
350	if act.pkg.illTyped && !pass.Analyzer.RunDespiteErrors {
351		err = fmt.Errorf("analysis skipped due to type-checking error: %v", act.pkg.typeCheckError)
352	} else {
353		act.result, err = pass.Analyzer.Run(pass)
354		if err == nil {
355			if got, want := reflect.TypeOf(act.result), pass.Analyzer.ResultType; got != want {
356				err = fmt.Errorf(
357					"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
358					pass.Pkg.Path(), pass.Analyzer, got, want)
359			}
360		}
361	}
362	act.err = err
363}
364
365// load parses and type checks the source code in each file in filenames.
366// load also deserializes facts stored for imported packages.
367func load(packagePath string, imp *importer, filenames []string) (*goPackage, error) {
368	if len(filenames) == 0 {
369		return nil, errors.New("no filenames")
370	}
371	var syntax []*ast.File
372	for _, file := range filenames {
373		s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments)
374		if err != nil {
375			return nil, err
376		}
377		syntax = append(syntax, s)
378	}
379	pkg := &goPackage{fset: imp.fset, syntax: syntax}
380
381	config := types.Config{Importer: imp}
382	info := &types.Info{
383		Types:      make(map[ast.Expr]types.TypeAndValue),
384		Uses:       make(map[*ast.Ident]types.Object),
385		Defs:       make(map[*ast.Ident]types.Object),
386		Implicits:  make(map[ast.Node]types.Object),
387		Scopes:     make(map[ast.Node]*types.Scope),
388		Selections: make(map[*ast.SelectorExpr]*types.Selection),
389	}
390
391	initInstanceInfo(info)
392
393	types, err := config.Check(packagePath, pkg.fset, syntax, info)
394	if err != nil {
395		pkg.illTyped, pkg.typeCheckError = true, err
396	}
397	pkg.types, pkg.typesInfo = types, info
398
399	pkg.facts, err = facts.NewDecoder(pkg.types).Decode(imp.readFacts)
400	if err != nil {
401		return nil, fmt.Errorf("internal error decoding facts: %v", err)
402	}
403
404	return pkg, nil
405}
406
407// A goPackage describes a loaded Go package.
408type goPackage struct {
409	// fset provides position information for types, typesInfo, and syntax.
410	// It is set only when types is set.
411	fset *token.FileSet
412	// syntax is the package's syntax trees.
413	syntax []*ast.File
414	// types provides type information for the package.
415	types *types.Package
416	// facts contains information saved by the analysis framework. Passes may
417	// import facts for imported packages and may also export facts for this
418	// package to be consumed by analyses in downstream packages.
419	facts *facts.Set
420	// illTyped indicates whether the package or any dependency contains errors.
421	// It is set only when types is set.
422	illTyped bool
423	// typeCheckError contains any error encountered during type-checking. It is
424	// only set when illTyped is true.
425	typeCheckError error
426	// typesInfo provides type information about the package's syntax trees.
427	// It is set only when syntax is set.
428	typesInfo *types.Info
429}
430
431func (g *goPackage) String() string {
432	return g.types.Path()
433}
434
435// checkAnalysisResults checks the analysis diagnostics in the given actions
436// and returns a string containing all the diagnostics that should be printed
437// to the build log.
438func checkAnalysisResults(actions []*action, pkg *goPackage) string {
439	type entry struct {
440		analysis.Diagnostic
441		*analysis.Analyzer
442	}
443	var diagnostics []entry
444	var errs []error
445	for _, act := range actions {
446		if act.err != nil {
447			// Analyzer failed.
448			errs = append(errs, fmt.Errorf("analyzer %q failed: %v", act.a.Name, act.err))
449			continue
450		}
451		if len(act.diagnostics) == 0 {
452			continue
453		}
454		var currentConfig config
455		// Use the base config if it exists.
456		if baseConfig, ok := configs[nogoBaseConfigName]; ok {
457			currentConfig = baseConfig
458		}
459		// Overwrite the config with the desired config. Any unset fields
460		// in the config will default to the base config.
461		if actionConfig, ok := configs[act.a.Name]; ok {
462			if actionConfig.analyzerFlags != nil {
463				currentConfig.analyzerFlags = actionConfig.analyzerFlags
464			}
465			if actionConfig.onlyFiles != nil {
466				currentConfig.onlyFiles = actionConfig.onlyFiles
467			}
468			if actionConfig.excludeFiles != nil {
469				currentConfig.excludeFiles = actionConfig.excludeFiles
470			}
471		}
472
473		if currentConfig.onlyFiles == nil && currentConfig.excludeFiles == nil {
474			for _, diag := range act.diagnostics {
475				diagnostics = append(diagnostics, entry{Diagnostic: diag, Analyzer: act.a})
476			}
477			continue
478		}
479		// Discard diagnostics based on the analyzer configuration.
480		for _, d := range act.diagnostics {
481			// NOTE(golang.org/issue/31008): nilness does not set positions,
482			// so don't assume the position is valid.
483			p := pkg.fset.Position(d.Pos)
484			filename := "-"
485			if p.IsValid() {
486				filename = p.Filename
487			}
488			include := true
489			if len(currentConfig.onlyFiles) > 0 {
490				// This analyzer emits diagnostics for only a set of files.
491				include = false
492				for _, pattern := range currentConfig.onlyFiles {
493					if pattern.MatchString(filename) {
494						include = true
495						break
496					}
497				}
498			}
499			if include {
500				for _, pattern := range currentConfig.excludeFiles {
501					if pattern.MatchString(filename) {
502						include = false
503						break
504					}
505				}
506			}
507			if include {
508				diagnostics = append(diagnostics, entry{Diagnostic: d, Analyzer: act.a})
509			}
510		}
511	}
512	if len(diagnostics) == 0 && len(errs) == 0 {
513		return ""
514	}
515
516	sort.Slice(diagnostics, func(i, j int) bool {
517		return diagnostics[i].Pos < diagnostics[j].Pos
518	})
519	errMsg := &bytes.Buffer{}
520	sep := ""
521	for _, err := range errs {
522		errMsg.WriteString(sep)
523		sep = "\n"
524		errMsg.WriteString(err.Error())
525	}
526	for _, d := range diagnostics {
527		errMsg.WriteString(sep)
528		sep = "\n"
529		fmt.Fprintf(errMsg, "%s: %s (%s)", pkg.fset.Position(d.Pos), d.Message, d.Name)
530	}
531	return errMsg.String()
532}
533
534// config determines which source files an analyzer will emit diagnostics for.
535// config values are generated in another file that is compiled with
536// nogo_main.go by the nogo rule.
537type config struct {
538	// onlyFiles is a list of regular expressions that match files an analyzer
539	// will emit diagnostics for. When empty, the analyzer will emit diagnostics
540	// for all files.
541	onlyFiles []*regexp.Regexp
542
543	// excludeFiles is a list of regular expressions that match files that an
544	// analyzer will not emit diagnostics for.
545	excludeFiles []*regexp.Regexp
546
547	// analyzerFlags is a map of flag names to flag values which will be passed
548	// to Analyzer.Flags. Note that no leading '-' should be present in a flag
549	// name
550	analyzerFlags map[string]string
551}
552
553// importer is an implementation of go/types.Importer that imports type
554// information from the export data in compiled .a files.
555type importer struct {
556	fset         *token.FileSet
557	importMap    map[string]string         // map import path in source code to package path
558	packageCache map[string]*types.Package // cache of previously imported packages
559	packageFile  map[string]string         // map package path to .a file with export data
560	factMap      map[string]string         // map import path in source code to file containing serialized facts
561}
562
563func newImporter(importMap, packageFile map[string]string, factMap map[string]string) *importer {
564	return &importer{
565		fset:         token.NewFileSet(),
566		importMap:    importMap,
567		packageCache: make(map[string]*types.Package),
568		packageFile:  packageFile,
569		factMap:      factMap,
570	}
571}
572
573func (i *importer) Import(path string) (*types.Package, error) {
574	if imp, ok := i.importMap[path]; ok {
575		// Translate import path if necessary.
576		path = imp
577	}
578	if path == "unsafe" {
579		// Special case: go/types has pre-defined type information for unsafe.
580		// See https://github.com/golang/go/issues/13882.
581		return types.Unsafe, nil
582	}
583	if pkg, ok := i.packageCache[path]; ok && pkg.Complete() {
584		return pkg, nil // cache hit
585	}
586
587	archive, ok := i.packageFile[path]
588	if !ok {
589		return nil, fmt.Errorf("could not import %q", path)
590	}
591	// open file
592	f, err := os.Open(archive)
593	if err != nil {
594		return nil, err
595	}
596	defer func() {
597		f.Close()
598		if err != nil {
599			// add file name to error
600			err = fmt.Errorf("reading export data: %s: %v", archive, err)
601		}
602	}()
603
604	r, err := gcexportdata.NewReader(f)
605	if err != nil {
606		return nil, err
607	}
608
609	return gcexportdata.Read(r, i.fset, i.packageCache, path)
610}
611
612func (i *importer) readFacts(pkg *types.Package) ([]byte, error) {
613	archive := i.factMap[pkg.Path()]
614	if archive == "" {
615		// Packages that were not built with the nogo toolchain will not be
616		// analyzed, so there's no opportunity to store facts. This includes
617		// packages in the standard library and packages built with go_tool_library,
618		// such as coverdata. Analyzers are expected to hard code information
619		// about standard library definitions and must gracefully handle packages
620		// that don't have facts. For example, the "printf" analyzer must know
621		// fmt.Printf accepts a format string.
622		return nil, nil
623	}
624	factReader, err := readFileInArchive(nogoFact, archive)
625	if os.IsNotExist(err) {
626		// Packages that were not built with the nogo toolchain will not be
627		// analyzed, so there's no opportunity to store facts. This includes
628		// packages in the standard library and packages built with go_tool_library,
629		// such as coverdata.
630		return nil, nil
631	} else if err != nil {
632		return nil, err
633	}
634	defer factReader.Close()
635	return ioutil.ReadAll(factReader)
636}
637
638type factMultiFlag map[string]string
639
640func (m *factMultiFlag) String() string {
641	if m == nil || len(*m) == 0 {
642		return ""
643	}
644	return fmt.Sprintf("%v", *m)
645}
646
647func (m *factMultiFlag) Set(v string) error {
648	parts := strings.Split(v, "=")
649	if len(parts) != 2 {
650		return fmt.Errorf("badly formatted -fact flag: %s", v)
651	}
652	(*m)[parts[0]] = parts[1]
653	return nil
654}
655