util.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // Copyright (c) 2013 The Go Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file or at
  5. // https://developers.google.com/open-source/licenses/bsd.
  6. // Package lintutil provides helpers for writing linter command lines.
  7. package lintutil // import "honnef.co/go/tools/lint/lintutil"
  8. import (
  9. "crypto/sha256"
  10. "errors"
  11. "flag"
  12. "fmt"
  13. "go/build"
  14. "go/token"
  15. "io"
  16. "log"
  17. "os"
  18. "os/signal"
  19. "regexp"
  20. "runtime"
  21. "runtime/pprof"
  22. "strconv"
  23. "strings"
  24. "sync/atomic"
  25. "honnef.co/go/tools/config"
  26. "honnef.co/go/tools/internal/cache"
  27. "honnef.co/go/tools/lint"
  28. "honnef.co/go/tools/lint/lintutil/format"
  29. "honnef.co/go/tools/version"
  30. "golang.org/x/tools/go/analysis"
  31. "golang.org/x/tools/go/buildutil"
  32. "golang.org/x/tools/go/packages"
  33. )
  34. func NewVersionFlag() flag.Getter {
  35. tags := build.Default.ReleaseTags
  36. v := tags[len(tags)-1][2:]
  37. version := new(VersionFlag)
  38. if err := version.Set(v); err != nil {
  39. panic(fmt.Sprintf("internal error: %s", err))
  40. }
  41. return version
  42. }
  43. type VersionFlag int
  44. func (v *VersionFlag) String() string {
  45. return fmt.Sprintf("1.%d", *v)
  46. }
  47. func (v *VersionFlag) Set(s string) error {
  48. if len(s) < 3 {
  49. return errors.New("invalid Go version")
  50. }
  51. if s[0] != '1' {
  52. return errors.New("invalid Go version")
  53. }
  54. if s[1] != '.' {
  55. return errors.New("invalid Go version")
  56. }
  57. i, err := strconv.Atoi(s[2:])
  58. *v = VersionFlag(i)
  59. return err
  60. }
  61. func (v *VersionFlag) Get() interface{} {
  62. return int(*v)
  63. }
  64. func usage(name string, flags *flag.FlagSet) func() {
  65. return func() {
  66. fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
  67. fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
  68. fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
  69. fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
  70. fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
  71. fmt.Fprintf(os.Stderr, "Flags:\n")
  72. flags.PrintDefaults()
  73. }
  74. }
  75. type list []string
  76. func (list *list) String() string {
  77. return `"` + strings.Join(*list, ",") + `"`
  78. }
  79. func (list *list) Set(s string) error {
  80. if s == "" {
  81. *list = nil
  82. return nil
  83. }
  84. *list = strings.Split(s, ",")
  85. return nil
  86. }
  87. func FlagSet(name string) *flag.FlagSet {
  88. flags := flag.NewFlagSet("", flag.ExitOnError)
  89. flags.Usage = usage(name, flags)
  90. flags.String("tags", "", "List of `build tags`")
  91. flags.Bool("tests", true, "Include tests")
  92. flags.Bool("version", false, "Print version and exit")
  93. flags.Bool("show-ignored", false, "Don't filter ignored problems")
  94. flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
  95. flags.String("explain", "", "Print description of `check`")
  96. flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
  97. flags.String("debug.memprofile", "", "Write memory profile to `file`")
  98. flags.Bool("debug.version", false, "Print detailed version information about this program")
  99. flags.Bool("debug.no-compile-errors", false, "Don't print compile errors")
  100. checks := list{"inherit"}
  101. fail := list{"all"}
  102. flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
  103. flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
  104. tags := build.Default.ReleaseTags
  105. v := tags[len(tags)-1][2:]
  106. version := new(VersionFlag)
  107. if err := version.Set(v); err != nil {
  108. panic(fmt.Sprintf("internal error: %s", err))
  109. }
  110. flags.Var(version, "go", "Target Go `version` in the format '1.x'")
  111. return flags
  112. }
  113. func findCheck(cs []*analysis.Analyzer, check string) (*analysis.Analyzer, bool) {
  114. for _, c := range cs {
  115. if c.Name == check {
  116. return c, true
  117. }
  118. }
  119. return nil, false
  120. }
  121. func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs *flag.FlagSet) {
  122. tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
  123. tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
  124. goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
  125. formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
  126. printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
  127. showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
  128. explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string)
  129. cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
  130. memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
  131. debugVersion := fs.Lookup("debug.version").Value.(flag.Getter).Get().(bool)
  132. debugNoCompile := fs.Lookup("debug.no-compile-errors").Value.(flag.Getter).Get().(bool)
  133. cfg := config.Config{}
  134. cfg.Checks = *fs.Lookup("checks").Value.(*list)
  135. exit := func(code int) {
  136. if cpuProfile != "" {
  137. pprof.StopCPUProfile()
  138. }
  139. if memProfile != "" {
  140. f, err := os.Create(memProfile)
  141. if err != nil {
  142. panic(err)
  143. }
  144. runtime.GC()
  145. pprof.WriteHeapProfile(f)
  146. }
  147. os.Exit(code)
  148. }
  149. if cpuProfile != "" {
  150. f, err := os.Create(cpuProfile)
  151. if err != nil {
  152. log.Fatal(err)
  153. }
  154. pprof.StartCPUProfile(f)
  155. }
  156. if debugVersion {
  157. version.Verbose()
  158. exit(0)
  159. }
  160. if printVersion {
  161. version.Print()
  162. exit(0)
  163. }
  164. // Validate that the tags argument is well-formed. go/packages
  165. // doesn't detect malformed build flags and returns unhelpful
  166. // errors.
  167. tf := buildutil.TagsFlag{}
  168. if err := tf.Set(tags); err != nil {
  169. fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", tags, err))
  170. exit(1)
  171. }
  172. if explain != "" {
  173. var haystack []*analysis.Analyzer
  174. haystack = append(haystack, cs...)
  175. for _, cum := range cums {
  176. haystack = append(haystack, cum.Analyzer())
  177. }
  178. check, ok := findCheck(haystack, explain)
  179. if !ok {
  180. fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
  181. exit(1)
  182. }
  183. if check.Doc == "" {
  184. fmt.Fprintln(os.Stderr, explain, "has no documentation")
  185. exit(1)
  186. }
  187. fmt.Println(check.Doc)
  188. exit(0)
  189. }
  190. ps, err := Lint(cs, cums, fs.Args(), &Options{
  191. Tags: tags,
  192. LintTests: tests,
  193. GoVersion: goVersion,
  194. Config: cfg,
  195. })
  196. if err != nil {
  197. fmt.Fprintln(os.Stderr, err)
  198. exit(1)
  199. }
  200. var f format.Formatter
  201. switch formatter {
  202. case "text":
  203. f = format.Text{W: os.Stdout}
  204. case "stylish":
  205. f = &format.Stylish{W: os.Stdout}
  206. case "json":
  207. f = format.JSON{W: os.Stdout}
  208. default:
  209. fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
  210. exit(2)
  211. }
  212. var (
  213. total int
  214. errors int
  215. warnings int
  216. )
  217. fail := *fs.Lookup("fail").Value.(*list)
  218. analyzers := make([]*analysis.Analyzer, len(cs), len(cs)+len(cums))
  219. copy(analyzers, cs)
  220. for _, cum := range cums {
  221. analyzers = append(analyzers, cum.Analyzer())
  222. }
  223. shouldExit := lint.FilterChecks(analyzers, fail)
  224. shouldExit["compile"] = true
  225. total = len(ps)
  226. for _, p := range ps {
  227. if p.Check == "compile" && debugNoCompile {
  228. continue
  229. }
  230. if p.Severity == lint.Ignored && !showIgnored {
  231. continue
  232. }
  233. if shouldExit[p.Check] {
  234. errors++
  235. } else {
  236. p.Severity = lint.Warning
  237. warnings++
  238. }
  239. f.Format(p)
  240. }
  241. if f, ok := f.(format.Statter); ok {
  242. f.Stats(total, errors, warnings)
  243. }
  244. if errors > 0 {
  245. exit(1)
  246. }
  247. exit(0)
  248. }
  249. type Options struct {
  250. Config config.Config
  251. Tags string
  252. LintTests bool
  253. GoVersion int
  254. }
  255. func computeSalt() ([]byte, error) {
  256. if version.Version != "devel" {
  257. return []byte(version.Version), nil
  258. }
  259. p, err := os.Executable()
  260. if err != nil {
  261. return nil, err
  262. }
  263. f, err := os.Open(p)
  264. if err != nil {
  265. return nil, err
  266. }
  267. defer f.Close()
  268. h := sha256.New()
  269. if _, err := io.Copy(h, f); err != nil {
  270. return nil, err
  271. }
  272. return h.Sum(nil), nil
  273. }
  274. func Lint(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, paths []string, opt *Options) ([]lint.Problem, error) {
  275. salt, err := computeSalt()
  276. if err != nil {
  277. return nil, fmt.Errorf("could not compute salt for cache: %s", err)
  278. }
  279. cache.SetSalt(salt)
  280. if opt == nil {
  281. opt = &Options{}
  282. }
  283. l := &lint.Linter{
  284. Checkers: cs,
  285. CumulativeCheckers: cums,
  286. GoVersion: opt.GoVersion,
  287. Config: opt.Config,
  288. }
  289. cfg := &packages.Config{}
  290. if opt.LintTests {
  291. cfg.Tests = true
  292. }
  293. if opt.Tags != "" {
  294. cfg.BuildFlags = append(cfg.BuildFlags, "-tags", opt.Tags)
  295. }
  296. printStats := func() {
  297. // Individual stats are read atomically, but overall there
  298. // is no synchronisation. For printing rough progress
  299. // information, this doesn't matter.
  300. switch atomic.LoadUint32(&l.Stats.State) {
  301. case lint.StateInitializing:
  302. fmt.Fprintln(os.Stderr, "Status: initializing")
  303. case lint.StateGraph:
  304. fmt.Fprintln(os.Stderr, "Status: loading package graph")
  305. case lint.StateProcessing:
  306. fmt.Fprintf(os.Stderr, "Packages: %d/%d initial, %d/%d total; Workers: %d/%d; Problems: %d\n",
  307. atomic.LoadUint32(&l.Stats.ProcessedInitialPackages),
  308. atomic.LoadUint32(&l.Stats.InitialPackages),
  309. atomic.LoadUint32(&l.Stats.ProcessedPackages),
  310. atomic.LoadUint32(&l.Stats.TotalPackages),
  311. atomic.LoadUint32(&l.Stats.ActiveWorkers),
  312. atomic.LoadUint32(&l.Stats.TotalWorkers),
  313. atomic.LoadUint32(&l.Stats.Problems),
  314. )
  315. case lint.StateCumulative:
  316. fmt.Fprintln(os.Stderr, "Status: processing cumulative checkers")
  317. }
  318. }
  319. if len(infoSignals) > 0 {
  320. ch := make(chan os.Signal, 1)
  321. signal.Notify(ch, infoSignals...)
  322. defer signal.Stop(ch)
  323. go func() {
  324. for range ch {
  325. printStats()
  326. }
  327. }()
  328. }
  329. return l.Lint(cfg, paths)
  330. }
  331. var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
  332. func parsePos(pos string) token.Position {
  333. if pos == "-" || pos == "" {
  334. return token.Position{}
  335. }
  336. parts := posRe.FindStringSubmatch(pos)
  337. if parts == nil {
  338. panic(fmt.Sprintf("internal error: malformed position %q", pos))
  339. }
  340. file := parts[1]
  341. line, _ := strconv.Atoi(parts[2])
  342. col, _ := strconv.Atoi(parts[3])
  343. return token.Position{
  344. Filename: file,
  345. Line: line,
  346. Column: col,
  347. }
  348. }