1// Copyright 2010 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// Vet is a simple checker for static errors in Go source code.
6// See doc.go for more information.
7package main
8
9import (
10	"bytes"
11	"encoding/json"
12	"flag"
13	"fmt"
14	"go/ast"
15	"go/build"
16	"go/importer"
17	"go/parser"
18	"go/printer"
19	"go/token"
20	"go/types"
21	"io"
22	"io/ioutil"
23	"os"
24	"path/filepath"
25	"strconv"
26	"strings"
27)
28
29// Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go.
30
31var (
32	verbose = flag.Bool("v", false, "verbose")
33	source  = flag.Bool("source", false, "import from source instead of compiled object files")
34	tags    = flag.String("tags", "", "space-separated list of build tags to apply when parsing")
35	tagList = []string{} // exploded version of tags flag; set in main
36
37	vcfg          vetConfig
38	mustTypecheck bool
39)
40
41var exitCode = 0
42
43// "-all" flag enables all non-experimental checks
44var all = triStateFlag("all", unset, "enable all non-experimental checks")
45
46// Flags to control which individual checks to perform.
47var report = map[string]*triState{
48	// Only unusual checks are written here.
49	// Most checks that operate during the AST walk are added by register.
50	"asmdecl":   triStateFlag("asmdecl", unset, "check assembly against Go declarations"),
51	"buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"),
52}
53
54// experimental records the flags enabling experimental features. These must be
55// requested explicitly; they are not enabled by -all.
56var experimental = map[string]bool{}
57
58// setTrueCount record how many flags are explicitly set to true.
59var setTrueCount int
60
61// dirsRun and filesRun indicate whether the vet is applied to directory or
62// file targets. The distinction affects which checks are run.
63var dirsRun, filesRun bool
64
65// includesNonTest indicates whether the vet is applied to non-test targets.
66// Certain checks are relevant only if they touch both test and non-test files.
67var includesNonTest bool
68
69// A triState is a boolean that knows whether it has been set to either true or false.
70// It is used to identify if a flag appears; the standard boolean flag cannot
71// distinguish missing from unset. It also satisfies flag.Value.
72type triState int
73
74const (
75	unset triState = iota
76	setTrue
77	setFalse
78)
79
80func triStateFlag(name string, value triState, usage string) *triState {
81	flag.Var(&value, name, usage)
82	return &value
83}
84
85// triState implements flag.Value, flag.Getter, and flag.boolFlag.
86// They work like boolean flags: we can say vet -printf as well as vet -printf=true
87func (ts *triState) Get() interface{} {
88	return *ts == setTrue
89}
90
91func (ts triState) isTrue() bool {
92	return ts == setTrue
93}
94
95func (ts *triState) Set(value string) error {
96	b, err := strconv.ParseBool(value)
97	if err != nil {
98		return err
99	}
100	if b {
101		*ts = setTrue
102		setTrueCount++
103	} else {
104		*ts = setFalse
105	}
106	return nil
107}
108
109func (ts *triState) String() string {
110	switch *ts {
111	case unset:
112		return "true" // An unset flag will be set by -all, so defaults to true.
113	case setTrue:
114		return "true"
115	case setFalse:
116		return "false"
117	}
118	panic("not reached")
119}
120
121func (ts triState) IsBoolFlag() bool {
122	return true
123}
124
125// vet tells whether to report errors for the named check, a flag name.
126func vet(name string) bool {
127	return report[name].isTrue()
128}
129
130// setExit sets the value for os.Exit when it is called, later. It
131// remembers the highest value.
132func setExit(err int) {
133	if err > exitCode {
134		exitCode = err
135	}
136}
137
138var (
139	// Each of these vars has a corresponding case in (*File).Visit.
140	assignStmt    *ast.AssignStmt
141	binaryExpr    *ast.BinaryExpr
142	callExpr      *ast.CallExpr
143	compositeLit  *ast.CompositeLit
144	exprStmt      *ast.ExprStmt
145	forStmt       *ast.ForStmt
146	funcDecl      *ast.FuncDecl
147	funcLit       *ast.FuncLit
148	genDecl       *ast.GenDecl
149	interfaceType *ast.InterfaceType
150	rangeStmt     *ast.RangeStmt
151	returnStmt    *ast.ReturnStmt
152	structType    *ast.StructType
153
154	// checkers is a two-level map.
155	// The outer level is keyed by a nil pointer, one of the AST vars above.
156	// The inner level is keyed by checker name.
157	checkers = make(map[ast.Node]map[string]func(*File, ast.Node))
158)
159
160func register(name, usage string, fn func(*File, ast.Node), types ...ast.Node) {
161	report[name] = triStateFlag(name, unset, usage)
162	for _, typ := range types {
163		m := checkers[typ]
164		if m == nil {
165			m = make(map[string]func(*File, ast.Node))
166			checkers[typ] = m
167		}
168		m[name] = fn
169	}
170}
171
172// Usage is a replacement usage function for the flags package.
173func Usage() {
174	fmt.Fprintf(os.Stderr, "Usage of vet:\n")
175	fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n")
176	fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n")
177	fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n")
178	fmt.Fprintf(os.Stderr, "For more information run\n")
179	fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n")
180	fmt.Fprintf(os.Stderr, "Flags:\n")
181	flag.PrintDefaults()
182	os.Exit(2)
183}
184
185// File is a wrapper for the state of a file used in the parser.
186// The parse tree walkers are all methods of this type.
187type File struct {
188	pkg     *Package
189	fset    *token.FileSet
190	name    string
191	content []byte
192	file    *ast.File
193	b       bytes.Buffer // for use by methods
194
195	// Parsed package "foo" when checking package "foo_test"
196	basePkg *Package
197
198	// The objects that are receivers of a "String() string" method.
199	// This is used by the recursiveStringer method in print.go.
200	stringers map[*ast.Object]bool
201
202	// Registered checkers to run.
203	checkers map[ast.Node][]func(*File, ast.Node)
204
205	// Unreachable nodes; can be ignored in shift check.
206	dead map[ast.Node]bool
207}
208
209func main() {
210	flag.Usage = Usage
211	flag.Parse()
212
213	// If any flag is set, we run only those checks requested.
214	// If all flag is set true or if no flags are set true, set all the non-experimental ones
215	// not explicitly set (in effect, set the "-all" flag).
216	if setTrueCount == 0 || *all == setTrue {
217		for name, setting := range report {
218			if *setting == unset && !experimental[name] {
219				*setting = setTrue
220			}
221		}
222	}
223
224	// Accept space-separated tags because that matches
225	// the go command's other subcommands.
226	// Accept commas because go tool vet traditionally has.
227	tagList = strings.Fields(strings.Replace(*tags, ",", " ", -1))
228
229	initPrintFlags()
230	initUnusedFlags()
231
232	if flag.NArg() == 0 {
233		Usage()
234	}
235
236	// Special case for "go vet" passing an explicit configuration:
237	// single argument ending in vet.cfg.
238	// Once we have a more general mechanism for obtaining this
239	// information from build tools like the go command,
240	// vet should be changed to use it. This vet.cfg hack is an
241	// experiment to learn about what form that information should take.
242	if flag.NArg() == 1 && strings.HasSuffix(flag.Arg(0), "vet.cfg") {
243		doPackageCfg(flag.Arg(0))
244		os.Exit(exitCode)
245	}
246
247	for _, name := range flag.Args() {
248		// Is it a directory?
249		fi, err := os.Stat(name)
250		if err != nil {
251			warnf("error walking tree: %s", err)
252			continue
253		}
254		if fi.IsDir() {
255			dirsRun = true
256		} else {
257			filesRun = true
258			if !strings.HasSuffix(name, "_test.go") {
259				includesNonTest = true
260			}
261		}
262	}
263	if dirsRun && filesRun {
264		Usage()
265	}
266	if dirsRun {
267		for _, name := range flag.Args() {
268			walkDir(name)
269		}
270		os.Exit(exitCode)
271	}
272	if doPackage(flag.Args(), nil) == nil {
273		warnf("no files checked")
274	}
275	os.Exit(exitCode)
276}
277
278// prefixDirectory places the directory name on the beginning of each name in the list.
279func prefixDirectory(directory string, names []string) {
280	if directory != "." {
281		for i, name := range names {
282			names[i] = filepath.Join(directory, name)
283		}
284	}
285}
286
287// vetConfig is the JSON config struct prepared by the Go command.
288type vetConfig struct {
289	Compiler    string
290	Dir         string
291	ImportPath  string
292	GoFiles     []string
293	ImportMap   map[string]string
294	PackageFile map[string]string
295	Standard    map[string]bool
296
297	SucceedOnTypecheckFailure bool
298
299	imp types.Importer
300}
301
302func (v *vetConfig) Import(path string) (*types.Package, error) {
303	if v.imp == nil {
304		v.imp = importer.For(v.Compiler, v.openPackageFile)
305	}
306	if path == "unsafe" {
307		return v.imp.Import("unsafe")
308	}
309	p := v.ImportMap[path]
310	if p == "" {
311		return nil, fmt.Errorf("unknown import path %q", path)
312	}
313	if v.PackageFile[p] == "" {
314		if v.Compiler == "gccgo" && v.Standard[path] {
315			// gccgo doesn't have sources for standard library packages,
316			// but the importer will do the right thing.
317			return v.imp.Import(path)
318		}
319		return nil, fmt.Errorf("unknown package file for import %q", path)
320	}
321	return v.imp.Import(p)
322}
323
324func (v *vetConfig) openPackageFile(path string) (io.ReadCloser, error) {
325	file := v.PackageFile[path]
326	if file == "" {
327		if v.Compiler == "gccgo" && v.Standard[path] {
328			// The importer knows how to handle this.
329			return nil, nil
330		}
331		// Note that path here has been translated via v.ImportMap,
332		// unlike in the error in Import above. We prefer the error in
333		// Import, but it's worth diagnosing this one too, just in case.
334		return nil, fmt.Errorf("unknown package file for %q", path)
335	}
336	f, err := os.Open(file)
337	if err != nil {
338		return nil, err
339	}
340	return f, nil
341}
342
343// doPackageCfg analyzes a single package described in a config file.
344func doPackageCfg(cfgFile string) {
345	js, err := ioutil.ReadFile(cfgFile)
346	if err != nil {
347		errorf("%v", err)
348	}
349	if err := json.Unmarshal(js, &vcfg); err != nil {
350		errorf("parsing vet config %s: %v", cfgFile, err)
351	}
352	stdImporter = &vcfg
353	inittypes()
354	mustTypecheck = true
355	doPackage(vcfg.GoFiles, nil)
356}
357
358// doPackageDir analyzes the single package found in the directory, if there is one,
359// plus a test package, if there is one.
360func doPackageDir(directory string) {
361	context := build.Default
362	if len(context.BuildTags) != 0 {
363		warnf("build tags %s previously set", context.BuildTags)
364	}
365	context.BuildTags = append(tagList, context.BuildTags...)
366
367	pkg, err := context.ImportDir(directory, 0)
368	if err != nil {
369		// If it's just that there are no go source files, that's fine.
370		if _, nogo := err.(*build.NoGoError); nogo {
371			return
372		}
373		// Non-fatal: we are doing a recursive walk and there may be other directories.
374		warnf("cannot process directory %s: %s", directory, err)
375		return
376	}
377	var names []string
378	names = append(names, pkg.GoFiles...)
379	names = append(names, pkg.CgoFiles...)
380	names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
381	names = append(names, pkg.SFiles...)
382	prefixDirectory(directory, names)
383	basePkg := doPackage(names, nil)
384	// Is there also a "foo_test" package? If so, do that one as well.
385	if len(pkg.XTestGoFiles) > 0 {
386		names = pkg.XTestGoFiles
387		prefixDirectory(directory, names)
388		doPackage(names, basePkg)
389	}
390}
391
392type Package struct {
393	path      string
394	defs      map[*ast.Ident]types.Object
395	uses      map[*ast.Ident]types.Object
396	selectors map[*ast.SelectorExpr]*types.Selection
397	types     map[ast.Expr]types.TypeAndValue
398	spans     map[types.Object]Span
399	files     []*File
400	typesPkg  *types.Package
401}
402
403// doPackage analyzes the single package constructed from the named files.
404// It returns the parsed Package or nil if none of the files have been checked.
405func doPackage(names []string, basePkg *Package) *Package {
406	var files []*File
407	var astFiles []*ast.File
408	fs := token.NewFileSet()
409	for _, name := range names {
410		data, err := ioutil.ReadFile(name)
411		if err != nil {
412			// Warn but continue to next package.
413			warnf("%s: %s", name, err)
414			return nil
415		}
416		checkBuildTag(name, data)
417		var parsedFile *ast.File
418		if strings.HasSuffix(name, ".go") {
419			parsedFile, err = parser.ParseFile(fs, name, data, 0)
420			if err != nil {
421				warnf("%s: %s", name, err)
422				return nil
423			}
424			astFiles = append(astFiles, parsedFile)
425		}
426		files = append(files, &File{
427			fset:    fs,
428			content: data,
429			name:    name,
430			file:    parsedFile,
431			dead:    make(map[ast.Node]bool),
432		})
433	}
434	if len(astFiles) == 0 {
435		return nil
436	}
437	pkg := new(Package)
438	pkg.path = astFiles[0].Name.Name
439	pkg.files = files
440	// Type check the package.
441	errs := pkg.check(fs, astFiles)
442	if errs != nil {
443		if vcfg.SucceedOnTypecheckFailure {
444			os.Exit(0)
445		}
446		if *verbose || mustTypecheck {
447			for _, err := range errs {
448				fmt.Fprintf(os.Stderr, "%v\n", err)
449			}
450			if mustTypecheck {
451				// This message could be silenced, and we could just exit,
452				// but it might be helpful at least at first to make clear that the
453				// above errors are coming from vet and not the compiler
454				// (they often look like compiler errors, such as "declared but not used").
455				errorf("typecheck failures")
456			}
457		}
458	}
459
460	// Check.
461	chk := make(map[ast.Node][]func(*File, ast.Node))
462	for typ, set := range checkers {
463		for name, fn := range set {
464			if vet(name) {
465				chk[typ] = append(chk[typ], fn)
466			}
467		}
468	}
469	for _, file := range files {
470		file.pkg = pkg
471		file.basePkg = basePkg
472		file.checkers = chk
473		if file.file != nil {
474			file.walkFile(file.name, file.file)
475		}
476	}
477	asmCheck(pkg)
478	return pkg
479}
480
481func visit(path string, f os.FileInfo, err error) error {
482	if err != nil {
483		warnf("walk error: %s", err)
484		return err
485	}
486	// One package per directory. Ignore the files themselves.
487	if !f.IsDir() {
488		return nil
489	}
490	doPackageDir(path)
491	return nil
492}
493
494func (pkg *Package) hasFileWithSuffix(suffix string) bool {
495	for _, f := range pkg.files {
496		if strings.HasSuffix(f.name, suffix) {
497			return true
498		}
499	}
500	return false
501}
502
503// walkDir recursively walks the tree looking for Go packages.
504func walkDir(root string) {
505	filepath.Walk(root, visit)
506}
507
508// errorf formats the error to standard error, adding program
509// identification and a newline, and exits.
510func errorf(format string, args ...interface{}) {
511	fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
512	os.Exit(2)
513}
514
515// warnf formats the error to standard error, adding program
516// identification and a newline, but does not exit.
517func warnf(format string, args ...interface{}) {
518	fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
519	setExit(1)
520}
521
522// Println is fmt.Println guarded by -v.
523func Println(args ...interface{}) {
524	if !*verbose {
525		return
526	}
527	fmt.Println(args...)
528}
529
530// Printf is fmt.Printf guarded by -v.
531func Printf(format string, args ...interface{}) {
532	if !*verbose {
533		return
534	}
535	fmt.Printf(format+"\n", args...)
536}
537
538// Bad reports an error and sets the exit code..
539func (f *File) Bad(pos token.Pos, args ...interface{}) {
540	f.Warn(pos, args...)
541	setExit(1)
542}
543
544// Badf reports a formatted error and sets the exit code.
545func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
546	f.Warnf(pos, format, args...)
547	setExit(1)
548}
549
550// loc returns a formatted representation of the position.
551func (f *File) loc(pos token.Pos) string {
552	if pos == token.NoPos {
553		return ""
554	}
555	// Do not print columns. Because the pos often points to the start of an
556	// expression instead of the inner part with the actual error, the
557	// precision can mislead.
558	posn := f.fset.Position(pos)
559	return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
560}
561
562// locPrefix returns a formatted representation of the position for use as a line prefix.
563func (f *File) locPrefix(pos token.Pos) string {
564	if pos == token.NoPos {
565		return ""
566	}
567	return fmt.Sprintf("%s: ", f.loc(pos))
568}
569
570// Warn reports an error but does not set the exit code.
571func (f *File) Warn(pos token.Pos, args ...interface{}) {
572	fmt.Fprintf(os.Stderr, "%s%s", f.locPrefix(pos), fmt.Sprintln(args...))
573}
574
575// Warnf reports a formatted error but does not set the exit code.
576func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
577	fmt.Fprintf(os.Stderr, "%s%s\n", f.locPrefix(pos), fmt.Sprintf(format, args...))
578}
579
580// walkFile walks the file's tree.
581func (f *File) walkFile(name string, file *ast.File) {
582	Println("Checking file", name)
583	ast.Walk(f, file)
584}
585
586// Visit implements the ast.Visitor interface.
587func (f *File) Visit(node ast.Node) ast.Visitor {
588	f.updateDead(node)
589	var key ast.Node
590	switch node.(type) {
591	case *ast.AssignStmt:
592		key = assignStmt
593	case *ast.BinaryExpr:
594		key = binaryExpr
595	case *ast.CallExpr:
596		key = callExpr
597	case *ast.CompositeLit:
598		key = compositeLit
599	case *ast.ExprStmt:
600		key = exprStmt
601	case *ast.ForStmt:
602		key = forStmt
603	case *ast.FuncDecl:
604		key = funcDecl
605	case *ast.FuncLit:
606		key = funcLit
607	case *ast.GenDecl:
608		key = genDecl
609	case *ast.InterfaceType:
610		key = interfaceType
611	case *ast.RangeStmt:
612		key = rangeStmt
613	case *ast.ReturnStmt:
614		key = returnStmt
615	case *ast.StructType:
616		key = structType
617	}
618	for _, fn := range f.checkers[key] {
619		fn(f, node)
620	}
621	return f
622}
623
624// gofmt returns a string representation of the expression.
625func (f *File) gofmt(x ast.Expr) string {
626	f.b.Reset()
627	printer.Fprint(&f.b, f.fset, x)
628	return f.b.String()
629}
630