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.

unitchecker.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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. // The unitchecker package defines the main function for an analysis
  5. // driver that analyzes a single compilation unit during a build.
  6. // It is invoked by a build system such as "go vet":
  7. //
  8. // $ go vet -vettool=$(which vet)
  9. //
  10. // It supports the following command-line protocol:
  11. //
  12. // -V=full describe executable (to the build tool)
  13. // -flags describe flags (to the build tool)
  14. // foo.cfg description of compilation unit (from the build tool)
  15. //
  16. // This package does not depend on go/packages.
  17. // If you need a standalone tool, use multichecker,
  18. // which supports this mode but can also load packages
  19. // from source using go/packages.
  20. package unitchecker
  21. // TODO(adonovan):
  22. // - with gccgo, go build does not build standard library,
  23. // so we will not get to analyze it. Yet we must in order
  24. // to create base facts for, say, the fmt package for the
  25. // printf checker.
  26. import (
  27. "encoding/gob"
  28. "encoding/json"
  29. "flag"
  30. "fmt"
  31. "go/ast"
  32. "go/build"
  33. "go/importer"
  34. "go/parser"
  35. "go/token"
  36. "go/types"
  37. "io"
  38. "io/ioutil"
  39. "log"
  40. "os"
  41. "path/filepath"
  42. "reflect"
  43. "sort"
  44. "strings"
  45. "sync"
  46. "time"
  47. "golang.org/x/tools/go/analysis"
  48. "golang.org/x/tools/go/analysis/internal/analysisflags"
  49. "golang.org/x/tools/go/analysis/internal/facts"
  50. )
  51. // A Config describes a compilation unit to be analyzed.
  52. // It is provided to the tool in a JSON-encoded file
  53. // whose name ends with ".cfg".
  54. type Config struct {
  55. ID string // e.g. "fmt [fmt.test]"
  56. Compiler string
  57. Dir string
  58. ImportPath string
  59. GoFiles []string
  60. NonGoFiles []string
  61. ImportMap map[string]string
  62. PackageFile map[string]string
  63. Standard map[string]bool
  64. PackageVetx map[string]string
  65. VetxOnly bool
  66. VetxOutput string
  67. SucceedOnTypecheckFailure bool
  68. }
  69. // Main is the main function of a vet-like analysis tool that must be
  70. // invoked by a build system to analyze a single package.
  71. //
  72. // The protocol required by 'go vet -vettool=...' is that the tool must support:
  73. //
  74. // -flags describe flags in JSON
  75. // -V=full describe executable for build caching
  76. // foo.cfg perform separate modular analyze on the single
  77. // unit described by a JSON config file foo.cfg.
  78. //
  79. func Main(analyzers ...*analysis.Analyzer) {
  80. progname := filepath.Base(os.Args[0])
  81. log.SetFlags(0)
  82. log.SetPrefix(progname + ": ")
  83. if err := analysis.Validate(analyzers); err != nil {
  84. log.Fatal(err)
  85. }
  86. flag.Usage = func() {
  87. fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
  88. Usage of %[1]s:
  89. %.16[1]s unit.cfg # execute analysis specified by config file
  90. %.16[1]s help # general help
  91. %.16[1]s help name # help on specific analyzer and its flags
  92. `, progname)
  93. os.Exit(1)
  94. }
  95. analyzers = analysisflags.Parse(analyzers, true)
  96. args := flag.Args()
  97. if len(args) == 0 {
  98. flag.Usage()
  99. }
  100. if args[0] == "help" {
  101. analysisflags.Help(progname, analyzers, args[1:])
  102. os.Exit(0)
  103. }
  104. if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
  105. log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
  106. }
  107. Run(args[0], analyzers)
  108. }
  109. // Run reads the *.cfg file, runs the analysis,
  110. // and calls os.Exit with an appropriate error code.
  111. // It assumes flags have already been set.
  112. func Run(configFile string, analyzers []*analysis.Analyzer) {
  113. cfg, err := readConfig(configFile)
  114. if err != nil {
  115. log.Fatal(err)
  116. }
  117. fset := token.NewFileSet()
  118. results, err := run(fset, cfg, analyzers)
  119. if err != nil {
  120. log.Fatal(err)
  121. }
  122. // In VetxOnly mode, the analysis is run only for facts.
  123. if !cfg.VetxOnly {
  124. if analysisflags.JSON {
  125. // JSON output
  126. tree := make(analysisflags.JSONTree)
  127. for _, res := range results {
  128. tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err)
  129. }
  130. tree.Print()
  131. } else {
  132. // plain text
  133. exit := 0
  134. for _, res := range results {
  135. if res.err != nil {
  136. log.Println(res.err)
  137. exit = 1
  138. }
  139. }
  140. for _, res := range results {
  141. for _, diag := range res.diagnostics {
  142. analysisflags.PrintPlain(fset, diag)
  143. exit = 1
  144. }
  145. }
  146. os.Exit(exit)
  147. }
  148. }
  149. os.Exit(0)
  150. }
  151. func readConfig(filename string) (*Config, error) {
  152. data, err := ioutil.ReadFile(filename)
  153. if err != nil {
  154. return nil, err
  155. }
  156. cfg := new(Config)
  157. if err := json.Unmarshal(data, cfg); err != nil {
  158. return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
  159. }
  160. if len(cfg.GoFiles) == 0 {
  161. // The go command disallows packages with no files.
  162. // The only exception is unsafe, but the go command
  163. // doesn't call vet on it.
  164. return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
  165. }
  166. return cfg, nil
  167. }
  168. var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer {
  169. // broken legacy implementation (https://golang.org/issue/28995)
  170. return importer.For(compiler, lookup)
  171. }
  172. func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
  173. // Load, parse, typecheck.
  174. var files []*ast.File
  175. for _, name := range cfg.GoFiles {
  176. f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
  177. if err != nil {
  178. if cfg.SucceedOnTypecheckFailure {
  179. // Silently succeed; let the compiler
  180. // report parse errors.
  181. err = nil
  182. }
  183. return nil, err
  184. }
  185. files = append(files, f)
  186. }
  187. compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
  188. // path is a resolved package path, not an import path.
  189. file, ok := cfg.PackageFile[path]
  190. if !ok {
  191. if cfg.Compiler == "gccgo" && cfg.Standard[path] {
  192. return nil, nil // fall back to default gccgo lookup
  193. }
  194. return nil, fmt.Errorf("no package file for %q", path)
  195. }
  196. return os.Open(file)
  197. })
  198. importer := importerFunc(func(importPath string) (*types.Package, error) {
  199. path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
  200. if !ok {
  201. return nil, fmt.Errorf("can't resolve import %q", path)
  202. }
  203. return compilerImporter.Import(path)
  204. })
  205. tc := &types.Config{
  206. Importer: importer,
  207. Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
  208. }
  209. info := &types.Info{
  210. Types: make(map[ast.Expr]types.TypeAndValue),
  211. Defs: make(map[*ast.Ident]types.Object),
  212. Uses: make(map[*ast.Ident]types.Object),
  213. Implicits: make(map[ast.Node]types.Object),
  214. Scopes: make(map[ast.Node]*types.Scope),
  215. Selections: make(map[*ast.SelectorExpr]*types.Selection),
  216. }
  217. pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
  218. if err != nil {
  219. if cfg.SucceedOnTypecheckFailure {
  220. // Silently succeed; let the compiler
  221. // report type errors.
  222. err = nil
  223. }
  224. return nil, err
  225. }
  226. // Register fact types with gob.
  227. // In VetxOnly mode, analyzers are only for their facts,
  228. // so we can skip any analysis that neither produces facts
  229. // nor depends on any analysis that produces facts.
  230. // Also build a map to hold working state and result.
  231. type action struct {
  232. once sync.Once
  233. result interface{}
  234. err error
  235. usesFacts bool // (transitively uses)
  236. diagnostics []analysis.Diagnostic
  237. }
  238. actions := make(map[*analysis.Analyzer]*action)
  239. var registerFacts func(a *analysis.Analyzer) bool
  240. registerFacts = func(a *analysis.Analyzer) bool {
  241. act, ok := actions[a]
  242. if !ok {
  243. act = new(action)
  244. var usesFacts bool
  245. for _, f := range a.FactTypes {
  246. usesFacts = true
  247. gob.Register(f)
  248. }
  249. for _, req := range a.Requires {
  250. if registerFacts(req) {
  251. usesFacts = true
  252. }
  253. }
  254. act.usesFacts = usesFacts
  255. actions[a] = act
  256. }
  257. return act.usesFacts
  258. }
  259. var filtered []*analysis.Analyzer
  260. for _, a := range analyzers {
  261. if registerFacts(a) || !cfg.VetxOnly {
  262. filtered = append(filtered, a)
  263. }
  264. }
  265. analyzers = filtered
  266. // Read facts from imported packages.
  267. read := func(path string) ([]byte, error) {
  268. if vetx, ok := cfg.PackageVetx[path]; ok {
  269. return ioutil.ReadFile(vetx)
  270. }
  271. return nil, nil // no .vetx file, no facts
  272. }
  273. facts, err := facts.Decode(pkg, read)
  274. if err != nil {
  275. return nil, err
  276. }
  277. // In parallel, execute the DAG of analyzers.
  278. var exec func(a *analysis.Analyzer) *action
  279. var execAll func(analyzers []*analysis.Analyzer)
  280. exec = func(a *analysis.Analyzer) *action {
  281. act := actions[a]
  282. act.once.Do(func() {
  283. execAll(a.Requires) // prefetch dependencies in parallel
  284. // The inputs to this analysis are the
  285. // results of its prerequisites.
  286. inputs := make(map[*analysis.Analyzer]interface{})
  287. var failed []string
  288. for _, req := range a.Requires {
  289. reqact := exec(req)
  290. if reqact.err != nil {
  291. failed = append(failed, req.String())
  292. continue
  293. }
  294. inputs[req] = reqact.result
  295. }
  296. // Report an error if any dependency failed.
  297. if failed != nil {
  298. sort.Strings(failed)
  299. act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
  300. return
  301. }
  302. factFilter := make(map[reflect.Type]bool)
  303. for _, f := range a.FactTypes {
  304. factFilter[reflect.TypeOf(f)] = true
  305. }
  306. pass := &analysis.Pass{
  307. Analyzer: a,
  308. Fset: fset,
  309. Files: files,
  310. OtherFiles: cfg.NonGoFiles,
  311. Pkg: pkg,
  312. TypesInfo: info,
  313. TypesSizes: tc.Sizes,
  314. ResultOf: inputs,
  315. Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
  316. ImportObjectFact: facts.ImportObjectFact,
  317. ExportObjectFact: facts.ExportObjectFact,
  318. AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
  319. ImportPackageFact: facts.ImportPackageFact,
  320. ExportPackageFact: facts.ExportPackageFact,
  321. AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
  322. }
  323. t0 := time.Now()
  324. act.result, act.err = a.Run(pass)
  325. if false {
  326. log.Printf("analysis %s = %s", pass, time.Since(t0))
  327. }
  328. })
  329. return act
  330. }
  331. execAll = func(analyzers []*analysis.Analyzer) {
  332. var wg sync.WaitGroup
  333. for _, a := range analyzers {
  334. wg.Add(1)
  335. go func(a *analysis.Analyzer) {
  336. _ = exec(a)
  337. wg.Done()
  338. }(a)
  339. }
  340. wg.Wait()
  341. }
  342. execAll(analyzers)
  343. // Return diagnostics and errors from root analyzers.
  344. results := make([]result, len(analyzers))
  345. for i, a := range analyzers {
  346. act := actions[a]
  347. results[i].a = a
  348. results[i].err = act.err
  349. results[i].diagnostics = act.diagnostics
  350. }
  351. data := facts.Encode()
  352. if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil {
  353. return nil, fmt.Errorf("failed to write analysis facts: %v", err)
  354. }
  355. return results, nil
  356. }
  357. type result struct {
  358. a *analysis.Analyzer
  359. diagnostics []analysis.Diagnostic
  360. err error
  361. }
  362. type importerFunc func(path string) (*types.Package, error)
  363. func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }