cmd/go/internal/work: copy vet tool's stdout to our stdout

The go command connects both the stdout and stderr files of
its child commands (cmd/compile, cmd/vet, etc) to the go
command's own stderr. If the child command is supposed to
produce structure output on stderr, as is the case for
go vet -json or go fix -diff, it will be merged with the
error stream, making it useless.

This change to the go vet <-> unitchecker protocol specifies
the name of a file into which the vet tool should write its
stdout. On success, the go command will then copy the entire
content of that file to its own stdout, under a lock.
This ensures that partial writes to stdout in case of failure,
concurrent writes to stdout by parallel vet tasks, or other
junk on stderr, cannot interfere with the integrity of the
go command's structure output on stdout.

CL 702835 is the corresponding change on the x/tools side.

For #75432

Change-Id: Ib4db25b6b0095d359152d7543bd9bf692551bbfa
Reviewed-on: https://go-review.googlesource.com/c/go/+/702815
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Matloob <matloob@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
This commit is contained in:
Alan Donovan
2025-09-11 11:02:03 -04:00
committed by Gopher Robot
parent 889e71c2ac
commit ac803b5949
2 changed files with 22 additions and 7 deletions

View File

@@ -1184,6 +1184,7 @@ type vetConfig struct {
PackageVetx map[string]string // map package path to vetx data from earlier vet run
VetxOnly bool // only compute vetx data; don't report detected problems
VetxOutput string // write vetx data to this output file
Stdout string // write stdout (JSON, unified diff) to this output file
GoVersion string // Go version for package
SucceedOnTypecheckFailure bool // awful hack; see #18395 and below
@@ -1297,6 +1298,7 @@ func (b *Builder) vet(ctx context.Context, a *Action) error {
vcfg.VetxOnly = a.VetxOnly
vcfg.VetxOutput = a.Objdir + "vet.out"
vcfg.Stdout = a.Objdir + "vet.stdout"
vcfg.PackageVetx = make(map[string]string)
h := cache.NewHash("vet " + a.Package.ImportPath)
@@ -1392,13 +1394,25 @@ func (b *Builder) vet(ctx context.Context, a *Action) error {
// If vet wrote export data, save it for input to future vets.
if f, err := os.Open(vcfg.VetxOutput); err == nil {
a.built = vcfg.VetxOutput
cache.Default().Put(key, f)
f.Close()
cache.Default().Put(key, f) // ignore error
f.Close() // ignore error
}
// If vet wrote to stdout, copy it to go's stdout, atomically.
if f, err := os.Open(vcfg.Stdout); err == nil {
stdoutMu.Lock()
if _, err := io.Copy(os.Stdout, f); err != nil && runErr == nil {
runErr = fmt.Errorf("copying vet tool stdout: %w", err)
}
f.Close() // ignore error
stdoutMu.Unlock()
}
return runErr
}
var stdoutMu sync.Mutex // serializes concurrent writes (e.g. JSON values) to stdout
// linkActionID computes the action ID for a link action.
func (b *Builder) linkActionID(a *Action) cache.ActionID {
p := a.Package

View File

@@ -14,12 +14,13 @@ stderr '2 MOVW'
stderr '3 RET'
stderr '4'
# -json causes success, even with diagnostics and errors.
# -json causes success, even with diagnostics and errors,
# and writes to stdout.
go vet -json -asmdecl a
stderr '"a": {'
stderr '"asmdecl":'
stderr '"posn": ".*asm.s:2:1",'
stderr '"message": ".*invalid MOVW.*"'
stdout '"a": {'
stdout '"asmdecl":'
stdout '"posn": ".*asm.s:2:1",'
stdout '"message": ".*invalid MOVW.*"'
-- a/a.go --
package a