go/analysis/passes/buildtag: report invalid go versions in build tags. Report invalid Go versions within build tags. Additionally reports likely misspellings of Go version tags. Fixes golang/go#64127 Change-Id: I9b698c94c7da29aafbe45b4c90d58c0bfe8efa1d Reviewed-on: https://go-review.googlesource.com/c/tools/+/597576 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/analysis/passes/buildtag/buildtag.go b/go/analysis/passes/buildtag/buildtag.go index 5b4cf9d..b5a2d27 100644 --- a/go/analysis/passes/buildtag/buildtag.go +++ b/go/analysis/passes/buildtag/buildtag.go
@@ -15,6 +15,7 @@ "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/internal/versions" ) const Doc = "check //go:build and // +build directives" @@ -264,6 +265,8 @@ return } + check.tags(pos, x) + if check.goBuild == nil { check.goBuild = x } @@ -323,6 +326,8 @@ check.crossCheck = false return } + check.tags(pos, y) + if check.plusBuild == nil { check.plusBuild = y } else { @@ -363,3 +368,51 @@ return } } + +// tags reports issues in go versions in tags within the expression e. +func (check *checker) tags(pos token.Pos, e constraint.Expr) { + // Check that constraint.GoVersion is meaningful (>= go1.21). + if versions.ConstraintGoVersion == nil { + return + } + + // Use Eval to visit each tag. + _ = e.Eval(func(tag string) bool { + if malformedGoTag(tag) { + check.pass.Reportf(pos, "invalid go version %q in build constraint", tag) + } + return false // result is immaterial as Eval does not short-circuit + }) +} + +// malformedGoTag returns true if a tag is likely to be a malformed +// go version constraint. +func malformedGoTag(tag string) bool { + // Not a go version? + if !strings.HasPrefix(tag, "go1") { + // Check for close misspellings of the "go1." prefix. + for _, pre := range []string{"go.", "g1.", "go"} { + suffix := strings.TrimPrefix(tag, pre) + if suffix != tag { + if valid, ok := validTag("go1." + suffix); ok && valid { + return true + } + } + } + return false + } + + // The tag starts with "go1" so it is almost certainly a GoVersion. + // Report it if it is not a valid build constraint. + valid, ok := validTag(tag) + return ok && !valid +} + +// validTag returns (valid, ok) where valid reports when a tag is valid, +// and ok reports determining if the tag is valid succeeded. +func validTag(tag string) (valid bool, ok bool) { + if versions.ConstraintGoVersion != nil { + return versions.ConstraintGoVersion(&constraint.TagExpr{Tag: tag}) != "", true + } + return false, false +}
diff --git a/go/analysis/passes/buildtag/buildtag_test.go b/go/analysis/passes/buildtag/buildtag_test.go index 110343c..6109cba 100644 --- a/go/analysis/passes/buildtag/buildtag_test.go +++ b/go/analysis/passes/buildtag/buildtag_test.go
@@ -10,6 +10,7 @@ "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/analysistest" "golang.org/x/tools/go/analysis/passes/buildtag" + "golang.org/x/tools/internal/versions" ) func Test(t *testing.T) { @@ -30,5 +31,9 @@ return buildtag.Analyzer.Run(pass) } - analysistest.Run(t, analysistest.TestData(), &analyzer, "a") + patterns := []string{"a"} + if versions.ConstraintGoVersion != nil { + patterns = append(patterns, "b") + } + analysistest.Run(t, analysistest.TestData(), &analyzer, patterns...) }
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers.go b/go/analysis/passes/buildtag/testdata/src/b/vers.go new file mode 100644 index 0000000..71cf71d --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers.go
@@ -0,0 +1,10 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// want +3 `invalid go version \"go1.20.1\" in build constraint` +// want +1 `invalid go version \"go1.20.1\" in build constraint` +//go:build go1.20.1 +// +build go1.20.1 + +package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers1.go b/go/analysis/passes/buildtag/testdata/src/b/vers1.go new file mode 100644 index 0000000..37f9182 --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers1.go
@@ -0,0 +1,7 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is intentionally so its build tags always match. + +package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers2.go b/go/analysis/passes/buildtag/testdata/src/b/vers2.go new file mode 100644 index 0000000..c91941f --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers2.go
@@ -0,0 +1,8 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// want +1 `invalid go version \"go120\" in build constraint` +//go:build go120 + +package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers3.go b/go/analysis/passes/buildtag/testdata/src/b/vers3.go new file mode 100644 index 0000000..e26ac75 --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers3.go
@@ -0,0 +1,8 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// want +1 `invalid go version \"go1..20\" in build constraint` +//go:build go1..20 + +package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers4.go b/go/analysis/passes/buildtag/testdata/src/b/vers4.go new file mode 100644 index 0000000..2ddbe18 --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers4.go
@@ -0,0 +1,8 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// want +1 `invalid go version \"go.20\" in build constraint` +//go:build go.20 + +package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers5.go b/go/analysis/passes/buildtag/testdata/src/b/vers5.go new file mode 100644 index 0000000..83964f8 --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers5.go
@@ -0,0 +1,8 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// want +1 `invalid go version \"g1.20\" in build constraint` +//go:build g1.20 + +package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers6.go b/go/analysis/passes/buildtag/testdata/src/b/vers6.go new file mode 100644 index 0000000..219e2db --- /dev/null +++ b/go/analysis/passes/buildtag/testdata/src/b/vers6.go
@@ -0,0 +1,8 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// want +1 `invalid go version \"go20\" in build constraint` +//go:build go20 + +package b
diff --git a/internal/versions/constraint.go b/internal/versions/constraint.go new file mode 100644 index 0000000..179063d --- /dev/null +++ b/internal/versions/constraint.go
@@ -0,0 +1,13 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package versions + +import "go/build/constraint" + +// ConstraintGoVersion is constraint.GoVersion (if built with go1.21+). +// Otherwise nil. +// +// Deprecate once x/tools is after go1.21. +var ConstraintGoVersion func(x constraint.Expr) string
diff --git a/internal/versions/constraint_go121.go b/internal/versions/constraint_go121.go new file mode 100644 index 0000000..3801140 --- /dev/null +++ b/internal/versions/constraint_go121.go
@@ -0,0 +1,14 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.21 +// +build go1.21 + +package versions + +import "go/build/constraint" + +func init() { + ConstraintGoVersion = constraint.GoVersion +}