1// Copyright 2017 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
5package vet
6
7import (
8	"bytes"
9	"encoding/json"
10	"errors"
11	"flag"
12	"fmt"
13	"log"
14	"os"
15	"os/exec"
16	"path/filepath"
17	"strings"
18
19	"cmd/go/internal/base"
20	"cmd/go/internal/cmdflag"
21	"cmd/go/internal/work"
22)
23
24// go vet flag processing
25//
26// We query the flags of the tool specified by -vettool and accept any
27// of those flags plus any flag valid for 'go build'. The tool must
28// support -flags, which prints a description of its flags in JSON to
29// stdout.
30
31// vetTool specifies the vet command to run.
32// Any tool that supports the (still unpublished) vet
33// command-line protocol may be supplied; see
34// golang.org/x/tools/go/analysis/unitchecker for one
35// implementation. It is also used by tests.
36//
37// The default behavior (vetTool=="") runs 'go tool vet'.
38var vetTool string // -vettool
39
40func init() {
41	work.AddBuildFlags(CmdVet, work.DefaultBuildFlags)
42	CmdVet.Flag.StringVar(&vetTool, "vettool", "", "")
43}
44
45func parseVettoolFlag(args []string) {
46	// Extract -vettool by ad hoc flag processing:
47	// its value is needed even before we can declare
48	// the flags available during main flag processing.
49	for i, arg := range args {
50		if arg == "-vettool" || arg == "--vettool" {
51			if i+1 >= len(args) {
52				log.Fatalf("%s requires a filename", arg)
53			}
54			vetTool = args[i+1]
55			return
56		} else if strings.HasPrefix(arg, "-vettool=") ||
57			strings.HasPrefix(arg, "--vettool=") {
58			vetTool = arg[strings.IndexByte(arg, '=')+1:]
59			return
60		}
61	}
62}
63
64// vetFlags processes the command line, splitting it at the first non-flag
65// into the list of flags and list of packages.
66func vetFlags(args []string) (passToVet, packageNames []string) {
67	parseVettoolFlag(args)
68
69	// Query the vet command for its flags.
70	var tool string
71	if vetTool == "" {
72		tool = base.Tool("vet")
73	} else {
74		var err error
75		tool, err = filepath.Abs(vetTool)
76		if err != nil {
77			log.Fatal(err)
78		}
79	}
80	out := new(bytes.Buffer)
81	vetcmd := exec.Command(tool, "-flags")
82	vetcmd.Stdout = out
83	if err := vetcmd.Run(); err != nil {
84		fmt.Fprintf(os.Stderr, "go: can't execute %s -flags: %v\n", tool, err)
85		base.SetExitStatus(2)
86		base.Exit()
87	}
88	var analysisFlags []struct {
89		Name  string
90		Bool  bool
91		Usage string
92	}
93	if err := json.Unmarshal(out.Bytes(), &analysisFlags); err != nil {
94		fmt.Fprintf(os.Stderr, "go: can't unmarshal JSON from %s -flags: %v", tool, err)
95		base.SetExitStatus(2)
96		base.Exit()
97	}
98
99	// Add vet's flags to CmdVet.Flag.
100	//
101	// Some flags, in particular -tags and -v, are known to vet but
102	// also defined as build flags. This works fine, so we omit duplicates here.
103	// However some, like -x, are known to the build but not to vet.
104	isVetFlag := make(map[string]bool, len(analysisFlags))
105	cf := CmdVet.Flag
106	for _, f := range analysisFlags {
107		isVetFlag[f.Name] = true
108		if cf.Lookup(f.Name) == nil {
109			if f.Bool {
110				cf.Bool(f.Name, false, "")
111			} else {
112				cf.String(f.Name, "", "")
113			}
114		}
115	}
116
117	// Record the set of vet tool flags set by GOFLAGS. We want to pass them to
118	// the vet tool, but only if they aren't overridden by an explicit argument.
119	base.SetFromGOFLAGS(&CmdVet.Flag)
120	addFromGOFLAGS := map[string]bool{}
121	CmdVet.Flag.Visit(func(f *flag.Flag) {
122		if isVetFlag[f.Name] {
123			addFromGOFLAGS[f.Name] = true
124		}
125	})
126
127	explicitFlags := make([]string, 0, len(args))
128	for len(args) > 0 {
129		f, remainingArgs, err := cmdflag.ParseOne(&CmdVet.Flag, args)
130
131		if errors.Is(err, flag.ErrHelp) {
132			exitWithUsage()
133		}
134
135		if errors.Is(err, cmdflag.ErrFlagTerminator) {
136			// All remaining args must be package names, but the flag terminator is
137			// not included.
138			packageNames = remainingArgs
139			break
140		}
141
142		if nf := (cmdflag.NonFlagError{}); errors.As(err, &nf) {
143			// Everything from here on out — including the argument we just consumed —
144			// must be a package name.
145			packageNames = args
146			break
147		}
148
149		if err != nil {
150			fmt.Fprintln(os.Stderr, err)
151			exitWithUsage()
152		}
153
154		if isVetFlag[f.Name] {
155			// Forward the raw arguments rather than cleaned equivalents, just in
156			// case the vet tool parses them idiosyncratically.
157			explicitFlags = append(explicitFlags, args[:len(args)-len(remainingArgs)]...)
158
159			// This flag has been overridden explicitly, so don't forward its implicit
160			// value from GOFLAGS.
161			delete(addFromGOFLAGS, f.Name)
162		}
163
164		args = remainingArgs
165	}
166
167	// Prepend arguments from GOFLAGS before other arguments.
168	CmdVet.Flag.Visit(func(f *flag.Flag) {
169		if addFromGOFLAGS[f.Name] {
170			passToVet = append(passToVet, fmt.Sprintf("-%s=%s", f.Name, f.Value))
171		}
172	})
173	passToVet = append(passToVet, explicitFlags...)
174	return passToVet, packageNames
175}
176
177func exitWithUsage() {
178	fmt.Fprintf(os.Stderr, "usage: %s\n", CmdVet.UsageLine)
179	fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", CmdVet.LongName())
180
181	// This part is additional to what (*Command).Usage does:
182	cmd := "go tool vet"
183	if vetTool != "" {
184		cmd = vetTool
185	}
186	fmt.Fprintf(os.Stderr, "Run '%s help' for a full list of flags and analyzers.\n", cmd)
187	fmt.Fprintf(os.Stderr, "Run '%s -help' for an overview.\n", cmd)
188
189	base.SetExitStatus(2)
190	base.Exit()
191}
192