You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

flags.go 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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. // Package analysisflags defines helpers for processing flags of
  5. // analysis driver tools.
  6. package analysisflags
  7. import (
  8. "crypto/sha256"
  9. "encoding/gob"
  10. "encoding/json"
  11. "flag"
  12. "fmt"
  13. "go/token"
  14. "io"
  15. "io/ioutil"
  16. "log"
  17. "os"
  18. "strconv"
  19. "strings"
  20. "golang.org/x/tools/go/analysis"
  21. )
  22. // flags common to all {single,multi,unit}checkers.
  23. var (
  24. JSON = false // -json
  25. Context = -1 // -c=N: if N>0, display offending line plus N lines of context
  26. )
  27. // Parse creates a flag for each of the analyzer's flags,
  28. // including (in multi mode) a flag named after the analyzer,
  29. // parses the flags, then filters and returns the list of
  30. // analyzers enabled by flags.
  31. //
  32. // The result is intended to be passed to unitchecker.Run or checker.Run.
  33. // Use in unitchecker.Run will gob.Register all fact types for the returned
  34. // graph of analyzers but of course not the ones only reachable from
  35. // dropped analyzers. To avoid inconsistency about which gob types are
  36. // registered from run to run, Parse itself gob.Registers all the facts
  37. // only reachable from dropped analyzers.
  38. // This is not a particularly elegant API, but this is an internal package.
  39. func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
  40. // Connect each analysis flag to the command line as -analysis.flag.
  41. enabled := make(map[*analysis.Analyzer]*triState)
  42. for _, a := range analyzers {
  43. var prefix string
  44. // Add -NAME flag to enable it.
  45. if multi {
  46. prefix = a.Name + "."
  47. enable := new(triState)
  48. enableUsage := "enable " + a.Name + " analysis"
  49. flag.Var(enable, a.Name, enableUsage)
  50. enabled[a] = enable
  51. }
  52. a.Flags.VisitAll(func(f *flag.Flag) {
  53. if !multi && flag.Lookup(f.Name) != nil {
  54. log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
  55. return
  56. }
  57. name := prefix + f.Name
  58. flag.Var(f.Value, name, f.Usage)
  59. })
  60. }
  61. // standard flags: -flags, -V.
  62. printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
  63. addVersionFlag()
  64. // flags common to all checkers
  65. flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
  66. flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
  67. // Add shims for legacy vet flags to enable existing
  68. // scripts that run vet to continue to work.
  69. _ = flag.Bool("source", false, "no effect (deprecated)")
  70. _ = flag.Bool("v", false, "no effect (deprecated)")
  71. _ = flag.Bool("all", false, "no effect (deprecated)")
  72. _ = flag.String("tags", "", "no effect (deprecated)")
  73. for old, new := range vetLegacyFlags {
  74. newFlag := flag.Lookup(new)
  75. if newFlag != nil && flag.Lookup(old) == nil {
  76. flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
  77. }
  78. }
  79. flag.Parse() // (ExitOnError)
  80. // -flags: print flags so that go vet knows which ones are legitimate.
  81. if *printflags {
  82. printFlags()
  83. os.Exit(0)
  84. }
  85. everything := expand(analyzers)
  86. // If any -NAME flag is true, run only those analyzers. Otherwise,
  87. // if any -NAME flag is false, run all but those analyzers.
  88. if multi {
  89. var hasTrue, hasFalse bool
  90. for _, ts := range enabled {
  91. switch *ts {
  92. case setTrue:
  93. hasTrue = true
  94. case setFalse:
  95. hasFalse = true
  96. }
  97. }
  98. var keep []*analysis.Analyzer
  99. if hasTrue {
  100. for _, a := range analyzers {
  101. if *enabled[a] == setTrue {
  102. keep = append(keep, a)
  103. }
  104. }
  105. analyzers = keep
  106. } else if hasFalse {
  107. for _, a := range analyzers {
  108. if *enabled[a] != setFalse {
  109. keep = append(keep, a)
  110. }
  111. }
  112. analyzers = keep
  113. }
  114. }
  115. // Register fact types of skipped analyzers
  116. // in case we encounter them in imported files.
  117. kept := expand(analyzers)
  118. for a := range everything {
  119. if !kept[a] {
  120. for _, f := range a.FactTypes {
  121. gob.Register(f)
  122. }
  123. }
  124. }
  125. return analyzers
  126. }
  127. func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
  128. seen := make(map[*analysis.Analyzer]bool)
  129. var visitAll func([]*analysis.Analyzer)
  130. visitAll = func(analyzers []*analysis.Analyzer) {
  131. for _, a := range analyzers {
  132. if !seen[a] {
  133. seen[a] = true
  134. visitAll(a.Requires)
  135. }
  136. }
  137. }
  138. visitAll(analyzers)
  139. return seen
  140. }
  141. func printFlags() {
  142. type jsonFlag struct {
  143. Name string
  144. Bool bool
  145. Usage string
  146. }
  147. var flags []jsonFlag = nil
  148. flag.VisitAll(func(f *flag.Flag) {
  149. // Don't report {single,multi}checker debugging
  150. // flags or fix as these have no effect on unitchecker
  151. // (as invoked by 'go vet').
  152. switch f.Name {
  153. case "debug", "cpuprofile", "memprofile", "trace", "fix":
  154. return
  155. }
  156. b, ok := f.Value.(interface{ IsBoolFlag() bool })
  157. isBool := ok && b.IsBoolFlag()
  158. flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
  159. })
  160. data, err := json.MarshalIndent(flags, "", "\t")
  161. if err != nil {
  162. log.Fatal(err)
  163. }
  164. os.Stdout.Write(data)
  165. }
  166. // addVersionFlag registers a -V flag that, if set,
  167. // prints the executable version and exits 0.
  168. //
  169. // If the -V flag already exists — for example, because it was already
  170. // registered by a call to cmd/internal/objabi.AddVersionFlag — then
  171. // addVersionFlag does nothing.
  172. func addVersionFlag() {
  173. if flag.Lookup("V") == nil {
  174. flag.Var(versionFlag{}, "V", "print version and exit")
  175. }
  176. }
  177. // versionFlag minimally complies with the -V protocol required by "go vet".
  178. type versionFlag struct{}
  179. func (versionFlag) IsBoolFlag() bool { return true }
  180. func (versionFlag) Get() interface{} { return nil }
  181. func (versionFlag) String() string { return "" }
  182. func (versionFlag) Set(s string) error {
  183. if s != "full" {
  184. log.Fatalf("unsupported flag value: -V=%s", s)
  185. }
  186. // This replicates the minimal subset of
  187. // cmd/internal/objabi.AddVersionFlag, which is private to the
  188. // go tool yet forms part of our command-line interface.
  189. // TODO(adonovan): clarify the contract.
  190. // Print the tool version so the build system can track changes.
  191. // Formats:
  192. // $progname version devel ... buildID=...
  193. // $progname version go1.9.1
  194. progname := os.Args[0]
  195. f, err := os.Open(progname)
  196. if err != nil {
  197. log.Fatal(err)
  198. }
  199. h := sha256.New()
  200. if _, err := io.Copy(h, f); err != nil {
  201. log.Fatal(err)
  202. }
  203. f.Close()
  204. fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
  205. progname, string(h.Sum(nil)))
  206. os.Exit(0)
  207. return nil
  208. }
  209. // A triState is a boolean that knows whether
  210. // it has been set to either true or false.
  211. // It is used to identify whether a flag appears;
  212. // the standard boolean flag cannot
  213. // distinguish missing from unset.
  214. // It also satisfies flag.Value.
  215. type triState int
  216. const (
  217. unset triState = iota
  218. setTrue
  219. setFalse
  220. )
  221. func triStateFlag(name string, value triState, usage string) *triState {
  222. flag.Var(&value, name, usage)
  223. return &value
  224. }
  225. // triState implements flag.Value, flag.Getter, and flag.boolFlag.
  226. // They work like boolean flags: we can say vet -printf as well as vet -printf=true
  227. func (ts *triState) Get() interface{} {
  228. return *ts == setTrue
  229. }
  230. func (ts triState) isTrue() bool {
  231. return ts == setTrue
  232. }
  233. func (ts *triState) Set(value string) error {
  234. b, err := strconv.ParseBool(value)
  235. if err != nil {
  236. // This error message looks poor but package "flag" adds
  237. // "invalid boolean value %q for -NAME: %s"
  238. return fmt.Errorf("want true or false")
  239. }
  240. if b {
  241. *ts = setTrue
  242. } else {
  243. *ts = setFalse
  244. }
  245. return nil
  246. }
  247. func (ts *triState) String() string {
  248. switch *ts {
  249. case unset:
  250. return "true"
  251. case setTrue:
  252. return "true"
  253. case setFalse:
  254. return "false"
  255. }
  256. panic("not reached")
  257. }
  258. func (ts triState) IsBoolFlag() bool {
  259. return true
  260. }
  261. // Legacy flag support
  262. // vetLegacyFlags maps flags used by legacy vet to their corresponding
  263. // new names. The old names will continue to work.
  264. var vetLegacyFlags = map[string]string{
  265. // Analyzer name changes
  266. "bool": "bools",
  267. "buildtags": "buildtag",
  268. "methods": "stdmethods",
  269. "rangeloops": "loopclosure",
  270. // Analyzer flags
  271. "compositewhitelist": "composites.whitelist",
  272. "printfuncs": "printf.funcs",
  273. "shadowstrict": "shadow.strict",
  274. "unusedfuncs": "unusedresult.funcs",
  275. "unusedstringmethods": "unusedresult.stringmethods",
  276. }
  277. // ---- output helpers common to all drivers ----
  278. // PrintPlain prints a diagnostic in plain text form,
  279. // with context specified by the -c flag.
  280. func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
  281. posn := fset.Position(diag.Pos)
  282. fmt.Fprintf(os.Stderr, "%s: %s\n", posn, diag.Message)
  283. // -c=N: show offending line plus N lines of context.
  284. if Context >= 0 {
  285. posn := fset.Position(diag.Pos)
  286. end := fset.Position(diag.End)
  287. if !end.IsValid() {
  288. end = posn
  289. }
  290. data, _ := ioutil.ReadFile(posn.Filename)
  291. lines := strings.Split(string(data), "\n")
  292. for i := posn.Line - Context; i <= end.Line+Context; i++ {
  293. if 1 <= i && i <= len(lines) {
  294. fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
  295. }
  296. }
  297. }
  298. }
  299. // A JSONTree is a mapping from package ID to analysis name to result.
  300. // Each result is either a jsonError or a list of jsonDiagnostic.
  301. type JSONTree map[string]map[string]interface{}
  302. // Add adds the result of analysis 'name' on package 'id'.
  303. // The result is either a list of diagnostics or an error.
  304. func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
  305. var v interface{}
  306. if err != nil {
  307. type jsonError struct {
  308. Err string `json:"error"`
  309. }
  310. v = jsonError{err.Error()}
  311. } else if len(diags) > 0 {
  312. type jsonDiagnostic struct {
  313. Category string `json:"category,omitempty"`
  314. Posn string `json:"posn"`
  315. Message string `json:"message"`
  316. }
  317. var diagnostics []jsonDiagnostic
  318. // TODO(matloob): Should the JSON diagnostics contain ranges?
  319. // If so, how should they be formatted?
  320. for _, f := range diags {
  321. diagnostics = append(diagnostics, jsonDiagnostic{
  322. Category: f.Category,
  323. Posn: fset.Position(f.Pos).String(),
  324. Message: f.Message,
  325. })
  326. }
  327. v = diagnostics
  328. }
  329. if v != nil {
  330. m, ok := tree[id]
  331. if !ok {
  332. m = make(map[string]interface{})
  333. tree[id] = m
  334. }
  335. m[name] = v
  336. }
  337. }
  338. func (tree JSONTree) Print() {
  339. data, err := json.MarshalIndent(tree, "", "\t")
  340. if err != nil {
  341. log.Panicf("internal error: JSON marshalling failed: %v", err)
  342. }
  343. fmt.Printf("%s\n", data)
  344. }