123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- // Copyright 2018 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- // Package analysisflags defines helpers for processing flags of
- // analysis driver tools.
- package analysisflags
-
- import (
- "crypto/sha256"
- "encoding/gob"
- "encoding/json"
- "flag"
- "fmt"
- "go/token"
- "io"
- "io/ioutil"
- "log"
- "os"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- )
-
- // flags common to all {single,multi,unit}checkers.
- var (
- JSON = false // -json
- Context = -1 // -c=N: if N>0, display offending line plus N lines of context
- )
-
- // Parse creates a flag for each of the analyzer's flags,
- // including (in multi mode) a flag named after the analyzer,
- // parses the flags, then filters and returns the list of
- // analyzers enabled by flags.
- //
- // The result is intended to be passed to unitchecker.Run or checker.Run.
- // Use in unitchecker.Run will gob.Register all fact types for the returned
- // graph of analyzers but of course not the ones only reachable from
- // dropped analyzers. To avoid inconsistency about which gob types are
- // registered from run to run, Parse itself gob.Registers all the facts
- // only reachable from dropped analyzers.
- // This is not a particularly elegant API, but this is an internal package.
- func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
- // Connect each analysis flag to the command line as -analysis.flag.
- enabled := make(map[*analysis.Analyzer]*triState)
- for _, a := range analyzers {
- var prefix string
-
- // Add -NAME flag to enable it.
- if multi {
- prefix = a.Name + "."
-
- enable := new(triState)
- enableUsage := "enable " + a.Name + " analysis"
- flag.Var(enable, a.Name, enableUsage)
- enabled[a] = enable
- }
-
- a.Flags.VisitAll(func(f *flag.Flag) {
- if !multi && flag.Lookup(f.Name) != nil {
- log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
- return
- }
-
- name := prefix + f.Name
- flag.Var(f.Value, name, f.Usage)
- })
- }
-
- // standard flags: -flags, -V.
- printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
- addVersionFlag()
-
- // flags common to all checkers
- flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
- flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
-
- // Add shims for legacy vet flags to enable existing
- // scripts that run vet to continue to work.
- _ = flag.Bool("source", false, "no effect (deprecated)")
- _ = flag.Bool("v", false, "no effect (deprecated)")
- _ = flag.Bool("all", false, "no effect (deprecated)")
- _ = flag.String("tags", "", "no effect (deprecated)")
- for old, new := range vetLegacyFlags {
- newFlag := flag.Lookup(new)
- if newFlag != nil && flag.Lookup(old) == nil {
- flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
- }
- }
-
- flag.Parse() // (ExitOnError)
-
- // -flags: print flags so that go vet knows which ones are legitimate.
- if *printflags {
- printFlags()
- os.Exit(0)
- }
-
- everything := expand(analyzers)
-
- // If any -NAME flag is true, run only those analyzers. Otherwise,
- // if any -NAME flag is false, run all but those analyzers.
- if multi {
- var hasTrue, hasFalse bool
- for _, ts := range enabled {
- switch *ts {
- case setTrue:
- hasTrue = true
- case setFalse:
- hasFalse = true
- }
- }
-
- var keep []*analysis.Analyzer
- if hasTrue {
- for _, a := range analyzers {
- if *enabled[a] == setTrue {
- keep = append(keep, a)
- }
- }
- analyzers = keep
- } else if hasFalse {
- for _, a := range analyzers {
- if *enabled[a] != setFalse {
- keep = append(keep, a)
- }
- }
- analyzers = keep
- }
- }
-
- // Register fact types of skipped analyzers
- // in case we encounter them in imported files.
- kept := expand(analyzers)
- for a := range everything {
- if !kept[a] {
- for _, f := range a.FactTypes {
- gob.Register(f)
- }
- }
- }
-
- return analyzers
- }
-
- func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
- seen := make(map[*analysis.Analyzer]bool)
- var visitAll func([]*analysis.Analyzer)
- visitAll = func(analyzers []*analysis.Analyzer) {
- for _, a := range analyzers {
- if !seen[a] {
- seen[a] = true
- visitAll(a.Requires)
- }
- }
- }
- visitAll(analyzers)
- return seen
- }
-
- func printFlags() {
- type jsonFlag struct {
- Name string
- Bool bool
- Usage string
- }
- var flags []jsonFlag = nil
- flag.VisitAll(func(f *flag.Flag) {
- // Don't report {single,multi}checker debugging
- // flags or fix as these have no effect on unitchecker
- // (as invoked by 'go vet').
- switch f.Name {
- case "debug", "cpuprofile", "memprofile", "trace", "fix":
- return
- }
-
- b, ok := f.Value.(interface{ IsBoolFlag() bool })
- isBool := ok && b.IsBoolFlag()
- flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
- })
- data, err := json.MarshalIndent(flags, "", "\t")
- if err != nil {
- log.Fatal(err)
- }
- os.Stdout.Write(data)
- }
-
- // addVersionFlag registers a -V flag that, if set,
- // prints the executable version and exits 0.
- //
- // If the -V flag already exists — for example, because it was already
- // registered by a call to cmd/internal/objabi.AddVersionFlag — then
- // addVersionFlag does nothing.
- func addVersionFlag() {
- if flag.Lookup("V") == nil {
- flag.Var(versionFlag{}, "V", "print version and exit")
- }
- }
-
- // versionFlag minimally complies with the -V protocol required by "go vet".
- type versionFlag struct{}
-
- func (versionFlag) IsBoolFlag() bool { return true }
- func (versionFlag) Get() interface{} { return nil }
- func (versionFlag) String() string { return "" }
- func (versionFlag) Set(s string) error {
- if s != "full" {
- log.Fatalf("unsupported flag value: -V=%s", s)
- }
-
- // This replicates the minimal subset of
- // cmd/internal/objabi.AddVersionFlag, which is private to the
- // go tool yet forms part of our command-line interface.
- // TODO(adonovan): clarify the contract.
-
- // Print the tool version so the build system can track changes.
- // Formats:
- // $progname version devel ... buildID=...
- // $progname version go1.9.1
- progname := os.Args[0]
- f, err := os.Open(progname)
- if err != nil {
- log.Fatal(err)
- }
- h := sha256.New()
- if _, err := io.Copy(h, f); err != nil {
- log.Fatal(err)
- }
- f.Close()
- fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
- progname, string(h.Sum(nil)))
- os.Exit(0)
- return nil
- }
-
- // A triState is a boolean that knows whether
- // it has been set to either true or false.
- // It is used to identify whether a flag appears;
- // the standard boolean flag cannot
- // distinguish missing from unset.
- // It also satisfies flag.Value.
- type triState int
-
- const (
- unset triState = iota
- setTrue
- setFalse
- )
-
- func triStateFlag(name string, value triState, usage string) *triState {
- flag.Var(&value, name, usage)
- return &value
- }
-
- // triState implements flag.Value, flag.Getter, and flag.boolFlag.
- // They work like boolean flags: we can say vet -printf as well as vet -printf=true
- func (ts *triState) Get() interface{} {
- return *ts == setTrue
- }
-
- func (ts triState) isTrue() bool {
- return ts == setTrue
- }
-
- func (ts *triState) Set(value string) error {
- b, err := strconv.ParseBool(value)
- if err != nil {
- // This error message looks poor but package "flag" adds
- // "invalid boolean value %q for -NAME: %s"
- return fmt.Errorf("want true or false")
- }
- if b {
- *ts = setTrue
- } else {
- *ts = setFalse
- }
- return nil
- }
-
- func (ts *triState) String() string {
- switch *ts {
- case unset:
- return "true"
- case setTrue:
- return "true"
- case setFalse:
- return "false"
- }
- panic("not reached")
- }
-
- func (ts triState) IsBoolFlag() bool {
- return true
- }
-
- // Legacy flag support
-
- // vetLegacyFlags maps flags used by legacy vet to their corresponding
- // new names. The old names will continue to work.
- var vetLegacyFlags = map[string]string{
- // Analyzer name changes
- "bool": "bools",
- "buildtags": "buildtag",
- "methods": "stdmethods",
- "rangeloops": "loopclosure",
-
- // Analyzer flags
- "compositewhitelist": "composites.whitelist",
- "printfuncs": "printf.funcs",
- "shadowstrict": "shadow.strict",
- "unusedfuncs": "unusedresult.funcs",
- "unusedstringmethods": "unusedresult.stringmethods",
- }
-
- // ---- output helpers common to all drivers ----
-
- // PrintPlain prints a diagnostic in plain text form,
- // with context specified by the -c flag.
- func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
- posn := fset.Position(diag.Pos)
- fmt.Fprintf(os.Stderr, "%s: %s\n", posn, diag.Message)
-
- // -c=N: show offending line plus N lines of context.
- if Context >= 0 {
- posn := fset.Position(diag.Pos)
- end := fset.Position(diag.End)
- if !end.IsValid() {
- end = posn
- }
- data, _ := ioutil.ReadFile(posn.Filename)
- lines := strings.Split(string(data), "\n")
- for i := posn.Line - Context; i <= end.Line+Context; i++ {
- if 1 <= i && i <= len(lines) {
- fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
- }
- }
- }
- }
-
- // A JSONTree is a mapping from package ID to analysis name to result.
- // Each result is either a jsonError or a list of jsonDiagnostic.
- type JSONTree map[string]map[string]interface{}
-
- // Add adds the result of analysis 'name' on package 'id'.
- // The result is either a list of diagnostics or an error.
- func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
- var v interface{}
- if err != nil {
- type jsonError struct {
- Err string `json:"error"`
- }
- v = jsonError{err.Error()}
- } else if len(diags) > 0 {
- type jsonDiagnostic struct {
- Category string `json:"category,omitempty"`
- Posn string `json:"posn"`
- Message string `json:"message"`
- }
- var diagnostics []jsonDiagnostic
- // TODO(matloob): Should the JSON diagnostics contain ranges?
- // If so, how should they be formatted?
- for _, f := range diags {
- diagnostics = append(diagnostics, jsonDiagnostic{
- Category: f.Category,
- Posn: fset.Position(f.Pos).String(),
- Message: f.Message,
- })
- }
- v = diagnostics
- }
- if v != nil {
- m, ok := tree[id]
- if !ok {
- m = make(map[string]interface{})
- tree[id] = m
- }
- m[name] = v
- }
- }
-
- func (tree JSONTree) Print() {
- data, err := json.MarshalIndent(tree, "", "\t")
- if err != nil {
- log.Panicf("internal error: JSON marshalling failed: %v", err)
- }
- fmt.Printf("%s\n", data)
- }
|