Skip to content

Commit 5617864

Browse files
committed
cmd/go: pass package config to vet during "go vet"
After this CL, "go vet" can be guaranteed to have complete type information about the packages being checked, even if cgo or swig is in use, which will in turn make it reasonable for vet checks to insist on type information. It also fixes vet's understanding of unusual import paths like relative paths and vendored packages. For now "go tool vet" will continue to cope without type information, but the eventual plan is for "go tool vet" to query the go command for what it needs, and also to be able to query alternate build systems like bazel. But that's future work. Fixes #4889. Fixes #12556 (if not already fixed). Fixes #15182. Fixes #16086. Fixes #17571. Change-Id: I932626ee7da649b302cd269b82eb6fe5d7b9f0f2 Reviewed-on: https://go-review.googlesource.com/74750 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 4fe4279 commit 5617864

File tree

7 files changed

+177
-28
lines changed

7 files changed

+177
-28
lines changed

src/cmd/dist/deps.go

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/go_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3763,9 +3763,11 @@ func TestBinaryOnlyPackages(t *testing.T) {
37633763
tg.grepStdout("hello from p1", "did not see message from p1")
37643764

37653765
tg.tempFile("src/p4/p4.go", `package main`)
3766+
// The odd string split below avoids vet complaining about
3767+
// a // +build line appearing too late in this source file.
37663768
tg.tempFile("src/p4/p4not.go", `//go:binary-only-package
37673769
3768-
// +build asdf
3770+
/`+`/ +build asdf
37693771
37703772
package main
37713773
`)

src/cmd/go/internal/load/pkg.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type PackageInternal struct {
9595
// Unexported fields are not part of the public API.
9696
Build *build.Package
9797
Imports []*Package // this package's direct imports
98+
RawImports []string // this package's original imports as they appear in the text of the program
9899
ForceLibrary bool // this package is a library (even if named "main")
99100
Cmdline bool // defined by files listed on command line
100101
Local bool // imported via local path (./ or ../)
@@ -208,6 +209,7 @@ func (p *Package) copyBuild(pp *build.Package) {
208209
// We modify p.Imports in place, so make copy now.
209210
p.Imports = make([]string, len(pp.Imports))
210211
copy(p.Imports, pp.Imports)
212+
p.Internal.RawImports = pp.Imports
211213
p.TestGoFiles = pp.TestGoFiles
212214
p.TestImports = pp.TestImports
213215
p.XTestGoFiles = pp.XTestGoFiles

src/cmd/go/internal/vet/vet.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66
package vet
77

88
import (
9-
"path/filepath"
10-
119
"cmd/go/internal/base"
12-
"cmd/go/internal/cfg"
1310
"cmd/go/internal/load"
14-
"cmd/go/internal/str"
11+
"cmd/go/internal/work"
1512
)
1613

1714
var CmdVet = &base.Command{
@@ -37,22 +34,23 @@ See also: go fmt, go fix.
3734
}
3835

3936
func runVet(cmd *base.Command, args []string) {
40-
vetFlags, packages := vetFlags(args)
41-
for _, p := range load.Packages(packages) {
42-
// Vet expects to be given a set of files all from the same package.
43-
// Run once for package p and once for package p_test.
44-
if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles) > 0 {
45-
runVetFiles(p, vetFlags, str.StringList(p.GoFiles, p.CgoFiles, p.TestGoFiles, p.SFiles))
46-
}
47-
if len(p.XTestGoFiles) > 0 {
48-
runVetFiles(p, vetFlags, str.StringList(p.XTestGoFiles))
49-
}
37+
vetFlags, pkgArgs := vetFlags(args)
38+
39+
work.InstrumentInit()
40+
work.BuildModeInit()
41+
work.VetFlags = vetFlags
42+
43+
pkgs := load.PackagesForBuild(pkgArgs)
44+
if len(pkgs) == 0 {
45+
base.Fatalf("no packages to vet")
5046
}
51-
}
5247

53-
func runVetFiles(p *load.Package, flags, files []string) {
54-
for i := range files {
55-
files[i] = filepath.Join(p.Dir, files[i])
48+
var b work.Builder
49+
b.Init()
50+
51+
root := &work.Action{Mode: "go vet"}
52+
for _, p := range pkgs {
53+
root.Deps = append(root.Deps, b.VetAction(work.ModeBuild, work.ModeBuild, p))
5654
}
57-
base.Run(cfg.BuildToolexec, base.Tool("vet"), flags, base.RelPaths(files))
55+
b.Do(root)
5856
}

src/cmd/go/internal/work/action.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ type Action struct {
7474
built string // the actual created package or executable
7575
buildID string // build ID of action output
7676

77+
needVet bool // Mode=="build": need to fill in vet config
78+
vetCfg *vetConfig // vet config
79+
7780
// Execution state.
7881
pending int // number of deps yet to complete
7982
priority int // relative execution priority
@@ -349,6 +352,51 @@ func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Actio
349352
return a
350353
}
351354

355+
// VetAction returns the action for running go vet on package p.
356+
// It depends on the action for compiling p.
357+
// If the caller may be causing p to be installed, it is up to the caller
358+
// to make sure that the install depends on (runs after) vet.
359+
func (b *Builder) VetAction(mode, depMode BuildMode, p *load.Package) *Action {
360+
// Construct vet action.
361+
a := b.cacheAction("vet", p, func() *Action {
362+
a1 := b.CompileAction(mode, depMode, p)
363+
364+
// vet expects to be able to import "fmt".
365+
var stk load.ImportStack
366+
stk.Push("vet")
367+
p1 := load.LoadPackage("fmt", &stk)
368+
stk.Pop()
369+
aFmt := b.CompileAction(ModeBuild, depMode, p1)
370+
371+
a := &Action{
372+
Mode: "vet",
373+
Package: p,
374+
Deps: []*Action{a1, aFmt},
375+
Objdir: a1.Objdir,
376+
}
377+
if a1.Func == nil {
378+
// Built-in packages like unsafe.
379+
return a
380+
}
381+
a1.needVet = true
382+
a.Func = (*Builder).vet
383+
384+
// If there might be an install action, make it depend on vet,
385+
// so that the temporary files generated by the build step
386+
// are not deleted before vet can use them.
387+
// If nothing was going to install p, calling b.CompileAction with
388+
// ModeInstall here creates the action, but nothing links it into the
389+
// graph, so it will still not be installed.
390+
install := b.CompileAction(ModeInstall, depMode, p)
391+
if install != a1 {
392+
install.Deps = append(install.Deps, a)
393+
}
394+
395+
return a
396+
})
397+
return a
398+
}
399+
352400
// LinkAction returns the action for linking p into an executable
353401
// and possibly installing the result (according to mode).
354402
// depMode is the action (build or install) to use when compiling dependencies.

src/cmd/go/internal/work/exec.go

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package work
88

99
import (
1010
"bytes"
11+
"encoding/json"
1112
"errors"
1213
"fmt"
1314
"io"
@@ -254,9 +255,13 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
254255
// Note that any new influence on this logic must be reported in b.buildActionID above as well.
255256
func (b *Builder) build(a *Action) (err error) {
256257
p := a.Package
258+
cached := false
257259
if !p.BinaryOnly {
258260
if b.useCache(a, p, b.buildActionID(a), p.Target) {
259-
return nil
261+
if !a.needVet {
262+
return nil
263+
}
264+
cached = true
260265
}
261266
}
262267

@@ -417,6 +422,34 @@ func (b *Builder) build(a *Action) (err error) {
417422
}
418423
}
419424

425+
// Prepare Go vet config if needed.
426+
var vcfg *vetConfig
427+
if a.needVet {
428+
// Pass list of absolute paths to vet,
429+
// so that vet's error messages will use absolute paths,
430+
// so that we can reformat them relative to the directory
431+
// in which the go command is invoked.
432+
absfiles := make([]string, len(gofiles))
433+
for i, f := range gofiles {
434+
if !filepath.IsAbs(f) {
435+
f = filepath.Join(a.Package.Dir, f)
436+
}
437+
absfiles[i] = f
438+
}
439+
vcfg = &vetConfig{
440+
Compiler: cfg.BuildToolchainName,
441+
Dir: a.Package.Dir,
442+
GoFiles: absfiles,
443+
ImportMap: make(map[string]string),
444+
PackageFile: make(map[string]string),
445+
}
446+
a.vetCfg = vcfg
447+
for i, raw := range a.Package.Internal.RawImports {
448+
final := a.Package.Imports[i]
449+
vcfg.ImportMap[raw] = final
450+
}
451+
}
452+
420453
// Prepare Go import config.
421454
var icfg bytes.Buffer
422455
for _, a1 := range a.Deps {
@@ -434,13 +467,42 @@ func (b *Builder) build(a *Action) (err error) {
434467
continue
435468
}
436469
fmt.Fprintf(&icfg, "importmap %s=%s\n", path[i:], path)
470+
if vcfg != nil {
471+
vcfg.ImportMap[path[i:]] = path
472+
}
437473
}
474+
475+
// Compute the list of mapped imports in the vet config
476+
// so that we can add any missing mappings below.
477+
var vcfgMapped map[string]bool
478+
if vcfg != nil {
479+
vcfgMapped = make(map[string]bool)
480+
for _, p := range vcfg.ImportMap {
481+
vcfgMapped[p] = true
482+
}
483+
}
484+
438485
for _, a1 := range a.Deps {
439486
p1 := a1.Package
440487
if p1 == nil || p1.ImportPath == "" || a1.built == "" {
441488
continue
442489
}
443490
fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, a1.built)
491+
if vcfg != nil {
492+
// Add import mapping if needed
493+
// (for imports like "runtime/cgo" that appear only in generated code).
494+
if !vcfgMapped[p1.ImportPath] {
495+
vcfg.ImportMap[p1.ImportPath] = p1.ImportPath
496+
}
497+
vcfg.PackageFile[p1.ImportPath] = a1.built
498+
}
499+
}
500+
501+
if cached {
502+
// The cached package file is OK, so we don't need to run the compile.
503+
// We've only going through the motions to prepare the vet configuration,
504+
// which is now complete.
505+
return nil
444506
}
445507

446508
// Compile Go.
@@ -532,6 +594,50 @@ func (b *Builder) build(a *Action) (err error) {
532594
return nil
533595
}
534596

597+
type vetConfig struct {
598+
Compiler string
599+
Dir string
600+
GoFiles []string
601+
ImportMap map[string]string
602+
PackageFile map[string]string
603+
}
604+
605+
// VetFlags are the flags to pass to vet.
606+
// The caller is expected to set them before executing any vet actions.
607+
var VetFlags []string
608+
609+
func (b *Builder) vet(a *Action) error {
610+
// a.Deps[0] is the build of the package being vetted.
611+
// a.Deps[1] is the build of the "fmt" package.
612+
613+
vcfg := a.Deps[0].vetCfg
614+
if vcfg == nil {
615+
// Vet config should only be missing if the build failed.
616+
if !a.Deps[0].Failed {
617+
return fmt.Errorf("vet config not found")
618+
}
619+
return nil
620+
}
621+
622+
if vcfg.ImportMap["fmt"] == "" {
623+
a1 := a.Deps[1]
624+
vcfg.ImportMap["fmt"] = "fmt"
625+
vcfg.PackageFile["fmt"] = a1.built
626+
}
627+
628+
js, err := json.MarshalIndent(vcfg, "", "\t")
629+
if err != nil {
630+
return fmt.Errorf("internal error marshaling vet config: %v", err)
631+
}
632+
js = append(js, '\n')
633+
if err := b.writeFile(a.Objdir+"vet.cfg", js); err != nil {
634+
return err
635+
}
636+
637+
p := a.Package
638+
return b.run(p.Dir, p.ImportPath, nil, cfg.BuildToolexec, base.Tool("vet"), VetFlags, a.Objdir+"vet.cfg")
639+
}
640+
535641
// linkActionID computes the action ID for a link action.
536642
func (b *Builder) linkActionID(a *Action) cache.ActionID {
537643
h := cache.NewHash("link")

src/cmd/vet/all/whitelist/all.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
// Non-platform-specific vet whitelist. See readme.txt for details.
22

3-
// Issue 17580 (remove when fixed)
4-
cmd/go/go_test.go: +build comment must appear before package clause and be followed by a blank line
5-
6-
73
// Real problems that we can't fix.
84

95
// This is a bad WriteTo signature. Errors are being ignored!

0 commit comments

Comments
 (0)