1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// The unitchecker package defines the main function for an analysis
6// driver that analyzes a single compilation unit during a build.
7// It is invoked by a build system such as "go vet":
8//
9//   $ go vet -vettool=$(which vet)
10//
11// It supports the following command-line protocol:
12//
13//      -V=full         describe executable               (to the build tool)
14//      -flags          describe flags                    (to the build tool)
15//      foo.cfg         description of compilation unit (from the build tool)
16//
17// This package does not depend on go/packages.
18// If you need a standalone tool, use multichecker,
19// which supports this mode but can also load packages
20// from source using go/packages.
21package unitchecker
22
23// TODO(adonovan):
24// - with gccgo, go build does not build standard library,
25//   so we will not get to analyze it. Yet we must in order
26//   to create base facts for, say, the fmt package for the
27//   printf checker.
28
29import (
30	"encoding/gob"
31	"encoding/json"
32	"flag"
33	"fmt"
34	"go/ast"
35	"go/build"
36	"go/importer"
37	"go/parser"
38	"go/token"
39	"go/types"
40	"io"
41	"io/ioutil"
42	"log"
43	"os"
44	"path/filepath"
45	"reflect"
46	"sort"
47	"strings"
48	"sync"
49	"time"
50
51	"golang.org/x/tools/go/analysis"
52	"golang.org/x/tools/go/analysis/internal/analysisflags"
53	"golang.org/x/tools/go/analysis/internal/facts"
54)
55
56// A Config describes a compilation unit to be analyzed.
57// It is provided to the tool in a JSON-encoded file
58// whose name ends with ".cfg".
59type Config struct {
60	ID                        string // e.g. "fmt [fmt.test]"
61	Compiler                  string
62	Dir                       string
63	ImportPath                string
64	GoFiles                   []string
65	NonGoFiles                []string
66	ImportMap                 map[string]string
67	PackageFile               map[string]string
68	Standard                  map[string]bool
69	PackageVetx               map[string]string
70	VetxOnly                  bool
71	VetxOutput                string
72	SucceedOnTypecheckFailure bool
73}
74
75// Main is the main function of a vet-like analysis tool that must be
76// invoked by a build system to analyze a single package.
77//
78// The protocol required by 'go vet -vettool=...' is that the tool must support:
79//
80//      -flags          describe flags in JSON
81//      -V=full         describe executable for build caching
82//      foo.cfg         perform separate modular analyze on the single
83//                      unit described by a JSON config file foo.cfg.
84//
85func Main(analyzers ...*analysis.Analyzer) {
86	progname := filepath.Base(os.Args[0])
87	log.SetFlags(0)
88	log.SetPrefix(progname + ": ")
89
90	if err := analysis.Validate(analyzers); err != nil {
91		log.Fatal(err)
92	}
93
94	flag.Usage = func() {
95		fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
96
97Usage of %[1]s:
98	%.16[1]s unit.cfg	# execute analysis specified by config file
99	%.16[1]s help    	# general help
100	%.16[1]s help name	# help on specific analyzer and its flags
101`, progname)
102		os.Exit(1)
103	}
104
105	analyzers = analysisflags.Parse(analyzers, true)
106
107	args := flag.Args()
108	if len(args) == 0 {
109		flag.Usage()
110	}
111	if args[0] == "help" {
112		analysisflags.Help(progname, analyzers, args[1:])
113		os.Exit(0)
114	}
115	if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
116		log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
117	}
118	Run(args[0], analyzers)
119}
120
121// Run reads the *.cfg file, runs the analysis,
122// and calls os.Exit with an appropriate error code.
123// It assumes flags have already been set.
124func Run(configFile string, analyzers []*analysis.Analyzer) {
125	cfg, err := readConfig(configFile)
126	if err != nil {
127		log.Fatal(err)
128	}
129
130	fset := token.NewFileSet()
131	results, err := run(fset, cfg, analyzers)
132	if err != nil {
133		log.Fatal(err)
134	}
135
136	// In VetxOnly mode, the analysis is run only for facts.
137	if !cfg.VetxOnly {
138		if analysisflags.JSON {
139			// JSON output
140			tree := make(analysisflags.JSONTree)
141			for _, res := range results {
142				tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err)
143			}
144			tree.Print()
145		} else {
146			// plain text
147			exit := 0
148			for _, res := range results {
149				if res.err != nil {
150					log.Println(res.err)
151					exit = 1
152				}
153			}
154			for _, res := range results {
155				for _, diag := range res.diagnostics {
156					analysisflags.PrintPlain(fset, diag)
157					exit = 1
158				}
159			}
160			os.Exit(exit)
161		}
162	}
163
164	os.Exit(0)
165}
166
167func readConfig(filename string) (*Config, error) {
168	data, err := ioutil.ReadFile(filename)
169	if err != nil {
170		return nil, err
171	}
172	cfg := new(Config)
173	if err := json.Unmarshal(data, cfg); err != nil {
174		return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
175	}
176	if len(cfg.GoFiles) == 0 {
177		// The go command disallows packages with no files.
178		// The only exception is unsafe, but the go command
179		// doesn't call vet on it.
180		return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
181	}
182	return cfg, nil
183}
184
185var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer {
186	// broken legacy implementation (https://golang.org/issue/28995)
187	return importer.For(compiler, lookup)
188}
189
190func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
191	// Load, parse, typecheck.
192	var files []*ast.File
193	for _, name := range cfg.GoFiles {
194		f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
195		if err != nil {
196			if cfg.SucceedOnTypecheckFailure {
197				// Silently succeed; let the compiler
198				// report parse errors.
199				err = nil
200			}
201			return nil, err
202		}
203		files = append(files, f)
204	}
205	compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
206		// path is a resolved package path, not an import path.
207		file, ok := cfg.PackageFile[path]
208		if !ok {
209			if cfg.Compiler == "gccgo" && cfg.Standard[path] {
210				return nil, nil // fall back to default gccgo lookup
211			}
212			return nil, fmt.Errorf("no package file for %q", path)
213		}
214		return os.Open(file)
215	})
216	importer := importerFunc(func(importPath string) (*types.Package, error) {
217		path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
218		if !ok {
219			return nil, fmt.Errorf("can't resolve import %q", path)
220		}
221		return compilerImporter.Import(path)
222	})
223	tc := &types.Config{
224		Importer: importer,
225		Sizes:    types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
226	}
227	info := &types.Info{
228		Types:      make(map[ast.Expr]types.TypeAndValue),
229		Defs:       make(map[*ast.Ident]types.Object),
230		Uses:       make(map[*ast.Ident]types.Object),
231		Implicits:  make(map[ast.Node]types.Object),
232		Scopes:     make(map[ast.Node]*types.Scope),
233		Selections: make(map[*ast.SelectorExpr]*types.Selection),
234	}
235	pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
236	if err != nil {
237		if cfg.SucceedOnTypecheckFailure {
238			// Silently succeed; let the compiler
239			// report type errors.
240			err = nil
241		}
242		return nil, err
243	}
244
245	// Register fact types with gob.
246	// In VetxOnly mode, analyzers are only for their facts,
247	// so we can skip any analysis that neither produces facts
248	// nor depends on any analysis that produces facts.
249	// Also build a map to hold working state and result.
250	type action struct {
251		once        sync.Once
252		result      interface{}
253		err         error
254		usesFacts   bool // (transitively uses)
255		diagnostics []analysis.Diagnostic
256	}
257	actions := make(map[*analysis.Analyzer]*action)
258	var registerFacts func(a *analysis.Analyzer) bool
259	registerFacts = func(a *analysis.Analyzer) bool {
260		act, ok := actions[a]
261		if !ok {
262			act = new(action)
263			var usesFacts bool
264			for _, f := range a.FactTypes {
265				usesFacts = true
266				gob.Register(f)
267			}
268			for _, req := range a.Requires {
269				if registerFacts(req) {
270					usesFacts = true
271				}
272			}
273			act.usesFacts = usesFacts
274			actions[a] = act
275		}
276		return act.usesFacts
277	}
278	var filtered []*analysis.Analyzer
279	for _, a := range analyzers {
280		if registerFacts(a) || !cfg.VetxOnly {
281			filtered = append(filtered, a)
282		}
283	}
284	analyzers = filtered
285
286	// Read facts from imported packages.
287	read := func(path string) ([]byte, error) {
288		if vetx, ok := cfg.PackageVetx[path]; ok {
289			return ioutil.ReadFile(vetx)
290		}
291		return nil, nil // no .vetx file, no facts
292	}
293	facts, err := facts.Decode(pkg, read)
294	if err != nil {
295		return nil, err
296	}
297
298	// In parallel, execute the DAG of analyzers.
299	var exec func(a *analysis.Analyzer) *action
300	var execAll func(analyzers []*analysis.Analyzer)
301	exec = func(a *analysis.Analyzer) *action {
302		act := actions[a]
303		act.once.Do(func() {
304			execAll(a.Requires) // prefetch dependencies in parallel
305
306			// The inputs to this analysis are the
307			// results of its prerequisites.
308			inputs := make(map[*analysis.Analyzer]interface{})
309			var failed []string
310			for _, req := range a.Requires {
311				reqact := exec(req)
312				if reqact.err != nil {
313					failed = append(failed, req.String())
314					continue
315				}
316				inputs[req] = reqact.result
317			}
318
319			// Report an error if any dependency failed.
320			if failed != nil {
321				sort.Strings(failed)
322				act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
323				return
324			}
325
326			factFilter := make(map[reflect.Type]bool)
327			for _, f := range a.FactTypes {
328				factFilter[reflect.TypeOf(f)] = true
329			}
330
331			pass := &analysis.Pass{
332				Analyzer:          a,
333				Fset:              fset,
334				Files:             files,
335				OtherFiles:        cfg.NonGoFiles,
336				Pkg:               pkg,
337				TypesInfo:         info,
338				TypesSizes:        tc.Sizes,
339				ResultOf:          inputs,
340				Report:            func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
341				ImportObjectFact:  facts.ImportObjectFact,
342				ExportObjectFact:  facts.ExportObjectFact,
343				AllObjectFacts:    func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
344				ImportPackageFact: facts.ImportPackageFact,
345				ExportPackageFact: facts.ExportPackageFact,
346				AllPackageFacts:   func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
347			}
348
349			t0 := time.Now()
350			act.result, act.err = a.Run(pass)
351			if false {
352				log.Printf("analysis %s = %s", pass, time.Since(t0))
353			}
354		})
355		return act
356	}
357	execAll = func(analyzers []*analysis.Analyzer) {
358		var wg sync.WaitGroup
359		for _, a := range analyzers {
360			wg.Add(1)
361			go func(a *analysis.Analyzer) {
362				_ = exec(a)
363				wg.Done()
364			}(a)
365		}
366		wg.Wait()
367	}
368
369	execAll(analyzers)
370
371	// Return diagnostics and errors from root analyzers.
372	results := make([]result, len(analyzers))
373	for i, a := range analyzers {
374		act := actions[a]
375		results[i].a = a
376		results[i].err = act.err
377		results[i].diagnostics = act.diagnostics
378	}
379
380	data := facts.Encode()
381	if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil {
382		return nil, fmt.Errorf("failed to write analysis facts: %v", err)
383	}
384
385	return results, nil
386}
387
388type result struct {
389	a           *analysis.Analyzer
390	diagnostics []analysis.Diagnostic
391	err         error
392}
393
394type importerFunc func(path string) (*types.Package, error)
395
396func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
397