1
2
3
4
5
6 package vet
7
8 import (
9 "archive/zip"
10 "bytes"
11 "context"
12 "encoding/json"
13 "errors"
14 "fmt"
15 "io"
16 "os"
17 "slices"
18 "strconv"
19 "strings"
20 "sync"
21
22 "cmd/go/internal/base"
23 "cmd/go/internal/cfg"
24 "cmd/go/internal/load"
25 "cmd/go/internal/modload"
26 "cmd/go/internal/trace"
27 "cmd/go/internal/work"
28 )
29
30 var CmdVet = &base.Command{
31 CustomFlags: true,
32 UsageLine: "go vet [build flags] [-vettool prog] [vet flags] [packages]",
33 Short: "report likely mistakes in packages",
34 Long: `
35 Vet runs the Go vet tool (cmd/vet) on the named packages
36 and reports diagnostics.
37
38 It supports these flags:
39
40 -c int
41 display offending line with this many lines of context (default -1)
42 -json
43 emit JSON output
44 -fix
45 instead of printing each diagnostic, apply its first fix (if any)
46 -diff
47 instead of applying each fix, print the patch as a unified diff
48
49 The -vettool=prog flag selects a different analysis tool with
50 alternative or additional checks. For example, the 'shadow' analyzer
51 can be built and run using these commands:
52
53 go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
54 go vet -vettool=$(which shadow)
55
56 Alternative vet tools should be built atop golang.org/x/tools/go/analysis/unitchecker,
57 which handles the interaction with go vet.
58
59 The default vet tool is 'go tool vet' or cmd/vet.
60 For help on its checkers and their flags, run 'go tool vet help'.
61 For details of a specific checker such as 'printf', see 'go tool vet help printf'.
62
63 For more about specifying packages, see 'go help packages'.
64
65 The build flags supported by go vet are those that control package resolution
66 and execution, such as -C, -n, -x, -v, -tags, and -toolexec.
67 For more about these flags, see 'go help build'.
68
69 See also: go fmt, go fix.
70 `,
71 }
72
73 var CmdFix = &base.Command{
74 CustomFlags: true,
75 UsageLine: "go fix [build flags] [-fixtool prog] [fix flags] [packages]",
76 Short: "apply fixes suggested by static checkers",
77 Long: `
78 Fix runs the Go fix tool (cmd/fix) on the named packages
79 and applies suggested fixes.
80
81 It supports these flags:
82
83 -diff
84 instead of applying each fix, print the patch as a unified diff
85
86 The -fixtool=prog flag selects a different analysis tool with
87 alternative or additional fixers; see the documentation for go vet's
88 -vettool flag for details.
89
90 The default fix tool is 'go tool fix' or cmd/fix.
91 For help on its fixers and their flags, run 'go tool fix help'.
92 For details of a specific fixer such as 'hostport', see 'go tool fix help hostport'.
93
94 For more about specifying packages, see 'go help packages'.
95
96 The build flags supported by go fix are those that control package resolution
97 and execution, such as -C, -n, -x, -v, -tags, and -toolexec.
98 For more about these flags, see 'go help build'.
99
100 See also: go fmt, go vet.
101 `,
102 }
103
104 func init() {
105
106 CmdVet.Run = run
107 CmdFix.Run = run
108
109 addFlags(CmdVet)
110 addFlags(CmdFix)
111 }
112
113 var (
114
115 vetFixFlag = CmdVet.Flag.Bool("fix", false, "apply the first fix (if any) for each diagnostic")
116
117
118
119 fixFixFlag = CmdFix.Flag.String("fix", "", "obsolete; no effect")
120 )
121
122
123
124 func run(ctx context.Context, cmd *base.Command, args []string) {
125 moduleLoaderState := modload.NewState()
126
127 toolFlags, pkgArgs := toolFlags(cmd, args)
128
129
130
131 moduleLoaderState.InitWorkfile()
132
133 if cfg.DebugTrace != "" {
134 var close func() error
135 var err error
136 ctx, close, err = trace.Start(ctx, cfg.DebugTrace)
137 if err != nil {
138 base.Fatalf("failed to start trace: %v", err)
139 }
140 defer func() {
141 if err := close(); err != nil {
142 base.Fatalf("failed to stop trace: %v", err)
143 }
144 }()
145 }
146
147 ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command"))
148 defer span.Done()
149
150 work.BuildInit(moduleLoaderState)
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 work.VetExplicit = len(toolFlags) > 0
180
181 applyFixes := false
182 if cmd.Name() == "fix" || *vetFixFlag {
183
184 if jsonFlag {
185 if diffFlag {
186 base.Fatalf("-json and -diff cannot be used together")
187 }
188 } else {
189 toolFlags = append(toolFlags, "-fix")
190 if diffFlag {
191 toolFlags = append(toolFlags, "-diff")
192 } else {
193 applyFixes = true
194 }
195 }
196 if contextFlag != -1 {
197 base.Fatalf("-c flag cannot be used when applying fixes")
198 }
199 } else {
200
201 if !jsonFlag {
202
203
204
205
206
207 toolFlags = append(toolFlags, "-json")
208 work.VetHandleStdout = printJSONDiagnostics
209 }
210 if diffFlag {
211 base.Fatalf("go vet -diff flag requires -fix")
212 }
213 }
214
215
216 if *fixFixFlag != "" {
217 fmt.Fprintf(os.Stderr, "go %s: the -fix=%s flag is obsolete and has no effect", cmd.Name(), *fixFixFlag)
218
219
220 if slices.Contains(strings.Split(*fixFixFlag, ","), "buildtag") {
221 fmt.Fprintf(os.Stderr, "go %s: to enable the buildtag check, use -buildtag", cmd.Name())
222 }
223 }
224
225 work.VetFlags = toolFlags
226
227 pkgOpts := load.PackageOpts{ModResolveTests: true}
228 pkgs := load.PackagesAndErrors(moduleLoaderState, ctx, pkgOpts, pkgArgs)
229 load.CheckPackageErrors(pkgs)
230 if len(pkgs) == 0 {
231 base.Fatalf("no packages to %s", cmd.Name())
232 }
233
234
235 b := work.NewBuilder("", moduleLoaderState.VendorDirOrEmpty)
236 defer func() {
237 if err := b.Close(); err != nil {
238 base.Fatal(err)
239 }
240 }()
241
242 root := &work.Action{Mode: "go " + cmd.Name()}
243
244 addVetAction := func(p *load.Package) {
245 act := b.VetAction(moduleLoaderState, work.ModeBuild, work.ModeBuild, applyFixes, p)
246 root.Deps = append(root.Deps, act)
247 }
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 for _, p := range pkgs {
265
266
267
268 if applyFixes {
269 if p.Standard && strings.HasPrefix(p.ImportPath, "vendor/") ||
270 p.Module != nil && !p.Module.Main {
271 continue
272 }
273 }
274 _, ptest, pxtest, perr := load.TestPackagesFor(moduleLoaderState, ctx, pkgOpts, p, nil)
275 if perr != nil {
276 base.Errorf("%v", perr.Error)
277 continue
278 }
279 if len(ptest.GoFiles) == 0 && len(ptest.CgoFiles) == 0 && pxtest == nil {
280 base.Errorf("go: can't %s %s: no Go files in %s", cmd.Name(), p.ImportPath, p.Dir)
281 continue
282 }
283 if len(ptest.GoFiles) > 0 || len(ptest.CgoFiles) > 0 {
284
285 addVetAction(ptest)
286 }
287 if pxtest != nil {
288 addVetAction(pxtest)
289 }
290 }
291 b.Do(ctx, root)
292
293
294
295
296
297
298
299
300 if applyFixes {
301 contents := make(map[string][]byte)
302
303 for _, act := range root.Deps {
304 if act.FixArchive != "" {
305 if err := readZip(act.FixArchive, contents); err != nil {
306 base.Errorf("reading archive of fixes: %v", err)
307 return
308 }
309 }
310 }
311
312 for filename, content := range contents {
313 if err := os.WriteFile(filename, content, 0644); err != nil {
314 base.Errorf("applying fix: %v", err)
315 }
316 }
317 }
318 }
319
320
321
322 func readZip(zipfile string, out map[string][]byte) error {
323 r, err := zip.OpenReader(zipfile)
324 if err != nil {
325 return err
326 }
327 defer r.Close()
328 for _, f := range r.File {
329 rc, err := f.Open()
330 if err != nil {
331 return err
332 }
333 content, err := io.ReadAll(rc)
334 rc.Close()
335 if err != nil {
336 return err
337 }
338 if prev, ok := out[f.Name]; ok && !bytes.Equal(prev, content) {
339 return fmt.Errorf("inconsistent fixes to file %v", f.Name)
340 }
341 out[f.Name] = content
342 }
343 return nil
344 }
345
346
347
348
349 func printJSONDiagnostics(r io.Reader) error {
350 stdout, err := io.ReadAll(r)
351 if err != nil {
352 return err
353 }
354 if len(stdout) > 0 {
355
356
357 var tree jsonTree
358 if err := json.Unmarshal(stdout, &tree); err != nil {
359 return fmt.Errorf("parsing JSON: %v", err)
360 }
361 for _, units := range tree {
362 for analyzer, msg := range units {
363 if msg[0] == '[' {
364
365 var diags []jsonDiagnostic
366 if err := json.Unmarshal([]byte(msg), &diags); err != nil {
367 return fmt.Errorf("parsing JSON diagnostics: %v", err)
368 }
369 for _, diag := range diags {
370 base.SetExitStatus(1)
371 printJSONDiagnostic(analyzer, diag)
372 }
373 } else {
374
375 var e jsonError
376 if err := json.Unmarshal([]byte(msg), &e); err != nil {
377 return fmt.Errorf("parsing JSON error: %v", err)
378 }
379
380 base.SetExitStatus(1)
381 return errors.New(e.Err)
382 }
383 }
384 }
385 }
386 return nil
387 }
388
389 var stderrMu sync.Mutex
390
391 func printJSONDiagnostic(analyzer string, diag jsonDiagnostic) {
392 stderrMu.Lock()
393 defer stderrMu.Unlock()
394
395 type posn struct {
396 file string
397 line, col int
398 }
399 parsePosn := func(s string) (_ posn, _ bool) {
400 colon2 := strings.LastIndexByte(s, ':')
401 if colon2 < 0 {
402 return
403 }
404 colon1 := strings.LastIndexByte(s[:colon2], ':')
405 if colon1 < 0 {
406 return
407 }
408 line, err := strconv.Atoi(s[colon1+len(":") : colon2])
409 if err != nil {
410 return
411 }
412 col, err := strconv.Atoi(s[colon2+len(":"):])
413 if err != nil {
414 return
415 }
416 return posn{s[:colon1], line, col}, true
417 }
418
419 print := func(start, end, message string) {
420 if posn, ok := parsePosn(start); ok {
421
422
423
424
425 fmt.Fprintf(os.Stderr, "%s:%d:%d: %v\n", base.ShortPath(posn.file), posn.line, posn.col, message)
426 } else {
427 fmt.Fprintf(os.Stderr, "%s: %v\n", start, message)
428 }
429
430
431
432 if contextFlag >= 0 {
433 if end == "" {
434 end = start
435 }
436 var (
437 startPosn, ok1 = parsePosn(start)
438 endPosn, ok2 = parsePosn(end)
439 )
440 if ok1 && ok2 {
441
442 data, _ := os.ReadFile(startPosn.file)
443 lines := strings.Split(string(data), "\n")
444 for i := startPosn.line - contextFlag; i <= endPosn.line+contextFlag; i++ {
445 if 1 <= i && i <= len(lines) {
446 fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
447 }
448 }
449 }
450 }
451 }
452
453
454
455 _ = analyzer
456 print(diag.Posn, diag.End, diag.Message)
457 for _, rel := range diag.Related {
458 print(rel.Posn, rel.End, "\t"+rel.Message)
459 }
460 }
461
462
463
464
465
466
467
468 type jsonTree map[string]map[string]json.RawMessage
469
470 type jsonError struct {
471 Err string `json:"error"`
472 }
473
474
475
476
477 type jsonTextEdit struct {
478 Filename string `json:"filename"`
479 Start int `json:"start"`
480 End int `json:"end"`
481 New string `json:"new"`
482 }
483
484
485
486
487 type jsonSuggestedFix struct {
488 Message string `json:"message"`
489 Edits []jsonTextEdit `json:"edits"`
490 }
491
492
493 type jsonDiagnostic struct {
494 Category string `json:"category,omitempty"`
495 Posn string `json:"posn"`
496 End string `json:"end"`
497 Message string `json:"message"`
498 SuggestedFixes []jsonSuggestedFix `json:"suggested_fixes,omitempty"`
499 Related []jsonRelatedInformation `json:"related,omitempty"`
500 }
501
502
503
504 type jsonRelatedInformation struct {
505 Posn string `json:"posn"`
506 End string `json:"end"`
507 Message string `json:"message"`
508 }
509
View as plain text