summaryrefslogtreecommitdiffhomepage
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/checklinkname/BUILD16
-rw-r--r--tools/checklinkname/README.md54
-rw-r--r--tools/checklinkname/check_linkname.go229
-rw-r--r--tools/checklinkname/known.go110
-rw-r--r--tools/checklinkname/test/BUILD9
-rw-r--r--tools/checklinkname/test/test_unsafe.go34
-rw-r--r--tools/constraintutil/BUILD18
-rw-r--r--tools/constraintutil/constraintutil.go169
-rw-r--r--tools/constraintutil/constraintutil_test.go138
-rw-r--r--tools/go_generics/go_merge/BUILD2
-rw-r--r--tools/go_generics/go_merge/main.go13
-rw-r--r--tools/go_marshal/gomarshal/BUILD2
-rw-r--r--tools/go_marshal/gomarshal/generator.go33
-rw-r--r--tools/go_stateify/BUILD2
-rw-r--r--tools/go_stateify/main.go11
-rw-r--r--tools/nogo/BUILD1
-rw-r--r--tools/nogo/analyzers.go2
-rw-r--r--tools/nogo/filter/main.go2
-rw-r--r--tools/tags/BUILD11
-rw-r--r--tools/tags/tags.go89
20 files changed, 817 insertions, 128 deletions
diff --git a/tools/checklinkname/BUILD b/tools/checklinkname/BUILD
new file mode 100644
index 000000000..0f1b07e24
--- /dev/null
+++ b/tools/checklinkname/BUILD
@@ -0,0 +1,16 @@
+load("//tools:defs.bzl", "go_library")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "checklinkname",
+ srcs = [
+ "check_linkname.go",
+ "known.go",
+ ],
+ nogo = False,
+ visibility = ["//tools/nogo:__subpackages__"],
+ deps = [
+ "@org_golang_x_tools//go/analysis:go_default_library",
+ ],
+)
diff --git a/tools/checklinkname/README.md b/tools/checklinkname/README.md
new file mode 100644
index 000000000..06b3c302d
--- /dev/null
+++ b/tools/checklinkname/README.md
@@ -0,0 +1,54 @@
+# `checklinkname` Analyzer
+
+`checklinkname` is an analyzer to provide rudimentary type-checking for
+`//go:linkname` directives. Since `//go:linkname` only affects linker behavior,
+there is no built-in type safety and it is the programmer's responsibility to
+ensure the types on either side are compatible.
+
+`checklinkname` helps with this by checking that uses match expectations, as
+defined in this package.
+
+`known.go` contains the set of known linkname targets. For most functions, we
+expect identical types on both sides of the linkname. In a few cases, the types
+may be slightly different (e.g., local redefinition of internal type). It is
+still the responsibility of the programmer to ensure the signatures in
+`known.go` are compatible and safe.
+
+## Findings
+
+Here are the most common findings from this package, and how to resolve them.
+
+### `runtime.foo signature got "BAR" want "BAZ"; stdlib type changed?`
+
+The definition of `runtime.foo` in the standard library does not match the
+expected type in `known.go`. This means that the function signature in the
+standard library changed.
+
+Addressing this will require creating a new linkname directive in a new Go
+version build-tagged in any packages using this symbol. Be sure to also check to
+ensure use with the new version is safe, as function constraints may have
+changed in addition to the signature.
+
+<!-- TODO(b/165820485): This isn't yet explicitly supported. -->
+
+`known.go` will also need to be updated to accept the new signature for the new
+version of Go.
+
+### `Cannot find known symbol "runtime.foo"`
+
+The standard library has removed runtime.foo entirely. Handling is similar to
+above, except existing code must transition away from the symbol entirely (note
+that is may simply be renamed).
+
+### `linkname to unknown symbol "mypkg.foo"; add this symbol to checklinkname.knownLinknames type-check against the remote type`
+
+A package has added a new linkname directive for a symbol not listed in
+`known.go`. Address this by adding a new entry for the target symbol. The
+`local` field should be the expected type in your package, while `remote` should
+be expected type in the remote package (e.g., in the standard library). These
+are typically identical, in which case `remote` can be omitted.
+
+### `usage: //go:linkname localname [linkname]`
+
+Malformed `//go:linkname` directive. This should be accompanied by a build
+failure in the package.
diff --git a/tools/checklinkname/check_linkname.go b/tools/checklinkname/check_linkname.go
new file mode 100644
index 000000000..5373dd762
--- /dev/null
+++ b/tools/checklinkname/check_linkname.go
@@ -0,0 +1,229 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package checklinkname ensures that linkname declarations match their source.
+package checklinkname
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// Analyzer implements the checklinkname analyzer.
+var Analyzer = &analysis.Analyzer{
+ Name: "checklinkname",
+ Doc: "verifies that linkname declarations match their source",
+ Run: run,
+}
+
+// go:linkname can be rather confusing. https://pkg.go.dev/cmd/compile says:
+//
+// //go:linkname localname [importpath.name]
+//
+// This special directive does not apply to the Go code that follows it.
+// Instead, the //go:linkname directive instructs the compiler to use
+// “importpath.name” as the object file symbol name for the variable or
+// function declared as “localname” in the source code. If the
+// “importpath.name” argument is omitted, the directive uses the symbol's
+// default object file symbol name and only has the effect of making the symbol
+// accessible to other packages. Because this directive can subvert the type
+// system and package modularity, it is only enabled in files that have
+// imported "unsafe".
+//
+// In this package we use the term "local" to refer to the symbol name in the
+// same package as the //go:linkname directive, whose name will be changed by
+// the linker. We use the term "remote" to refer to the symbol name that we are
+// changing to.
+//
+// In the general case, the local symbol is a function declaration, and the
+// remote symbol is a real function in the standard library.
+
+// linknameSignatures describes a the type signatures of the symbols in a
+// //go:linkname directive.
+type linknameSignatures struct {
+ local string
+ remote string // equivalent to local if "".
+}
+
+func (l *linknameSignatures) Remote() string {
+ if l.remote == "" {
+ return l.local
+ }
+ return l.remote
+}
+
+// linknameSymbols describes the symbol namess in a single //go:linkname
+// directive.
+type linknameSymbols struct {
+ pos token.Pos
+ local string
+ remote string
+}
+
+func findLinknames(pass *analysis.Pass, f *ast.File) []linknameSymbols {
+ var names []linknameSymbols
+
+ for _, cg := range f.Comments {
+ for _, c := range cg.List {
+ if len(c.Text) <= 2 || !strings.HasPrefix(c.Text[2:], "go:linkname ") {
+ continue
+ }
+
+ f := strings.Fields(c.Text)
+ if len(f) < 2 || len(f) > 3 {
+ // Malformed linkname. This is the same error the compiler emits.
+ pass.Reportf(c.Slash, "usage: //go:linkname localname [linkname]")
+ }
+
+ if len(f) == 2 {
+ // "If the “importpath.name” argument is
+ // omitted, the directive uses the symbol's
+ // default object file symbol name and only has
+ // the effect of making the symbol accessible
+ // to other packages."
+ // -https://golang.org/cmd/compile
+ //
+ // There is no type-checking to be done here.
+ continue
+ }
+
+ names = append(names, linknameSymbols{
+ pos: c.Slash,
+ local: f[1],
+ remote: f[2],
+ })
+ }
+ }
+
+ return names
+}
+
+func splitSymbol(pkg *types.Package, symbol string) (packagePath, name string) {
+ // Note that some runtime symbols can have multiple dots. e.g.,
+ // runtime..init_task.
+ s := strings.SplitN(symbol, ".", 2)
+
+ switch len(s) {
+ case 1:
+ // Package name omitted, use current package.
+ return pkg.Path(), symbol
+ case 2:
+ return s[0], s[1]
+ default:
+ panic("unreachable")
+ }
+}
+
+func findObject(pkg *types.Package, symbol string) (types.Object, error) {
+ packagePath, symbolName := splitSymbol(pkg, symbol)
+ return findPackageObject(pkg, packagePath, symbolName)
+}
+
+func findPackageObject(pkg *types.Package, packagePath, symbolName string) (types.Object, error) {
+ if pkg.Path() == packagePath {
+ o := pkg.Scope().Lookup(symbolName)
+ if o == nil {
+ return nil, fmt.Errorf("%q not found in %q (names: %+v)", symbolName, packagePath, pkg.Scope().Names())
+ }
+ return o, nil
+ }
+
+ for _, p := range pkg.Imports() {
+ if o, err := findPackageObject(p, packagePath, symbolName); err == nil {
+ return o, nil
+ }
+ }
+
+ return nil, fmt.Errorf("package %q not found", packagePath)
+}
+
+// checkOneLinkname verifies that the type of sym.local matches the type from
+// knownLinknames.
+func checkOneLinkname(pass *analysis.Pass, f *ast.File, sym linknameSymbols) {
+ remotePackage, remoteName := splitSymbol(pass.Pkg, sym.remote)
+
+ m, ok := knownLinknames[remotePackage]
+ if !ok {
+ pass.Reportf(sym.pos, "linkname to unknown symbol %q; add this symbol to checklinkname.knownLinknames type-check against the remote type", sym.remote)
+ return
+ }
+
+ linkname, ok := m[remoteName]
+ if !ok {
+ pass.Reportf(sym.pos, "linkname to unknown symbol %q; add this symbol to checklinkname.knownLinknames type-check against the remote type", sym.remote)
+ return
+ }
+
+ local, err := findObject(pass.Pkg, sym.local)
+ if err != nil {
+ pass.Reportf(sym.pos, "Unable to find symbol %q: %v", sym.local, err)
+ return
+ }
+
+ localSig, ok := local.Type().(*types.Signature)
+ if !ok {
+ pass.Reportf(local.Pos(), "%q object is not a signature: %+#v", sym.local, local)
+ return
+ }
+
+ if linkname.local != localSig.String() {
+ pass.Reportf(local.Pos(), "%q signature got %q want %q; mismatched types?", sym.local, localSig.String(), linkname.local)
+ return
+ }
+}
+
+// checkOneRemote verifies that the type of sym matches wantSig.
+func checkOneRemote(pass *analysis.Pass, sym, wantSig string) {
+ o := pass.Pkg.Scope().Lookup(sym)
+ if o == nil {
+ pass.Reportf(pass.Files[0].Package, "Cannot find known symbol %q", sym)
+ return
+ }
+
+ sig, ok := o.Type().(*types.Signature)
+ if !ok {
+ pass.Reportf(o.Pos(), "%q object is not a signature: %+#v", sym, o)
+ return
+ }
+
+ if sig.String() != wantSig {
+ pass.Reportf(o.Pos(), "%q signature got %q want %q; stdlib type changed?", sym, sig.String(), wantSig)
+ return
+ }
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ // First, check if any remote symbols are in this package.
+ p, ok := knownLinknames[pass.Pkg.Path()]
+ if ok {
+ for sym, l := range p {
+ checkOneRemote(pass, sym, l.Remote())
+ }
+ }
+
+ // Then check for local //go:linkname directives in this package.
+ for _, f := range pass.Files {
+ names := findLinknames(pass, f)
+ for _, n := range names {
+ checkOneLinkname(pass, f, n)
+ }
+ }
+
+ return nil, nil
+}
diff --git a/tools/checklinkname/known.go b/tools/checklinkname/known.go
new file mode 100644
index 000000000..54e5155fc
--- /dev/null
+++ b/tools/checklinkname/known.go
@@ -0,0 +1,110 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checklinkname
+
+// knownLinknames is the set of the symbols for which we can do a rudimentary
+// type-check on.
+//
+// When analyzing the remote package (e.g., runtime), we verify the symbol
+// signature matches 'remote'. When analyzing local packages with //go:linkname
+// directives, we verify the symbol signature matches 'local'.
+//
+// Usually these are identical, but may differ slightly if equivalent
+// replacement types are used in the local packages, such as a copy of a struct
+// or uintptr instead of a pointer type.
+//
+// NOTE: It is the responsibility of the developer to verify the safety of the
+// signatures used here! This analyzer only checks that types match this map;
+// it does not verify compatibility of the entries themselves.
+//
+// //go:linkname directives with no corresponding entry here will trigger a
+// finding.
+//
+// We preform only rudimentary string-based type-checking due to limitations in
+// the analysis framework. Ideally, from the local package we'd lookup the
+// remote symbol's types.Object and perform robust type-checking.
+// Unfortunately, remote symbols are typically loaded from the remote package's
+// gcexportdata. Since //go:linkname targets are usually not exported symbols,
+// they are no included in gcexportdata and we cannot load their types.Object.
+//
+// TODO(b/165820485): Add option to specific per-version signatures.
+var knownLinknames = map[string]map[string]linknameSignatures{
+ "runtime": map[string]linknameSignatures{
+ "entersyscall": linknameSignatures{
+ local: "func()",
+ },
+ "entersyscallblock": linknameSignatures{
+ local: "func()",
+ },
+ "exitsyscall": linknameSignatures{
+ local: "func()",
+ },
+ "fastrand": linknameSignatures{
+ local: "func() uint32",
+ },
+ "gopark": linknameSignatures{
+ // TODO(b/165820485): add verification of waitReason
+ // size and reason and traceEv values.
+ local: "func(unlockf func(uintptr, unsafe.Pointer) bool, lock unsafe.Pointer, reason uint8, traceEv byte, traceskip int)",
+ remote: "func(unlockf func(*runtime.g, unsafe.Pointer) bool, lock unsafe.Pointer, reason runtime.waitReason, traceEv byte, traceskip int)",
+ },
+ "goready": linknameSignatures{
+ local: "func(gp uintptr, traceskip int)",
+ remote: "func(gp *runtime.g, traceskip int)",
+ },
+ "goyield": linknameSignatures{
+ local: "func()",
+ },
+ "memmove": linknameSignatures{
+ local: "func(to unsafe.Pointer, from unsafe.Pointer, n uintptr)",
+ },
+ "throw": linknameSignatures{
+ local: "func(s string)",
+ },
+ },
+ "sync": map[string]linknameSignatures{
+ "runtime_canSpin": linknameSignatures{
+ local: "func(i int) bool",
+ },
+ "runtime_doSpin": linknameSignatures{
+ local: "func()",
+ },
+ "runtime_Semacquire": linknameSignatures{
+ // The only difference here is the parameter names. We
+ // can't just change our local use to match remote, as
+ // the stdlib runtime and sync packages also disagree
+ // on the name, and the analyzer checks that use as
+ // well.
+ local: "func(addr *uint32)",
+ remote: "func(s *uint32)",
+ },
+ "runtime_Semrelease": linknameSignatures{
+ // See above.
+ local: "func(addr *uint32, handoff bool, skipframes int)",
+ remote: "func(s *uint32, handoff bool, skipframes int)",
+ },
+ },
+ "syscall": map[string]linknameSignatures{
+ "runtime_BeforeFork": linknameSignatures{
+ local: "func()",
+ },
+ "runtime_AfterFork": linknameSignatures{
+ local: "func()",
+ },
+ "runtime_AfterForkInChild": linknameSignatures{
+ local: "func()",
+ },
+ },
+}
diff --git a/tools/checklinkname/test/BUILD b/tools/checklinkname/test/BUILD
new file mode 100644
index 000000000..b29bd84f2
--- /dev/null
+++ b/tools/checklinkname/test/BUILD
@@ -0,0 +1,9 @@
+load("//tools:defs.bzl", "go_library")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "test",
+ testonly = 1,
+ srcs = ["test_unsafe.go"],
+)
diff --git a/tools/checklinkname/test/test_unsafe.go b/tools/checklinkname/test/test_unsafe.go
new file mode 100644
index 000000000..a7504591c
--- /dev/null
+++ b/tools/checklinkname/test/test_unsafe.go
@@ -0,0 +1,34 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package test provides linkname test targets.
+package test
+
+import (
+ _ "unsafe" // for go:linkname.
+)
+
+//go:linkname DetachedLinkname runtime.fastrand
+
+//go:linkname attachedLinkname runtime.entersyscall
+func attachedLinkname()
+
+// AttachedLinkname reexports attachedLinkname because go vet doesn't like an
+// exported go:linkname without a comment starting with "// AttachedLinkname".
+func AttachedLinkname() {
+ attachedLinkname()
+}
+
+// DetachedLinkname has a linkname elsewhere in the file.
+func DetachedLinkname() uint32
diff --git a/tools/constraintutil/BUILD b/tools/constraintutil/BUILD
new file mode 100644
index 000000000..004b708c4
--- /dev/null
+++ b/tools/constraintutil/BUILD
@@ -0,0 +1,18 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "constraintutil",
+ srcs = ["constraintutil.go"],
+ marshal = False,
+ stateify = False,
+ visibility = ["//tools:__subpackages__"],
+)
+
+go_test(
+ name = "constraintutil_test",
+ size = "small",
+ srcs = ["constraintutil_test.go"],
+ library = ":constraintutil",
+)
diff --git a/tools/constraintutil/constraintutil.go b/tools/constraintutil/constraintutil.go
new file mode 100644
index 000000000..fb3fbe5c2
--- /dev/null
+++ b/tools/constraintutil/constraintutil.go
@@ -0,0 +1,169 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package constraintutil provides utilities for working with Go build
+// constraints.
+package constraintutil
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "go/build/constraint"
+ "io"
+ "os"
+ "strings"
+)
+
+// FromReader extracts the build constraint from the Go source or assembly file
+// whose contents are read by r.
+func FromReader(r io.Reader) (constraint.Expr, error) {
+ // See go/build.parseFileHeader() for the "official" logic that this is
+ // derived from.
+ const (
+ slashStar = "/*"
+ starSlash = "*/"
+ gobuildPrefix = "//go:build"
+ )
+ s := bufio.NewScanner(r)
+ var (
+ inSlashStar = false // between /* and */
+ haveGobuild = false
+ e constraint.Expr
+ )
+Lines:
+ for s.Scan() {
+ line := bytes.TrimSpace(s.Bytes())
+ if !inSlashStar && constraint.IsGoBuild(string(line)) {
+ if haveGobuild {
+ return nil, fmt.Errorf("multiple go:build directives")
+ }
+ haveGobuild = true
+ var err error
+ e, err = constraint.Parse(string(line))
+ if err != nil {
+ return nil, err
+ }
+ }
+ ThisLine:
+ for len(line) > 0 {
+ if inSlashStar {
+ if i := bytes.Index(line, []byte(starSlash)); i >= 0 {
+ inSlashStar = false
+ line = bytes.TrimSpace(line[i+len(starSlash):])
+ continue ThisLine
+ }
+ continue Lines
+ }
+ if bytes.HasPrefix(line, []byte("//")) {
+ continue Lines
+ }
+ // Note that if /* appears in the line, but not at the beginning,
+ // then the line is still non-empty, so skipping this and
+ // terminating below is correct.
+ if bytes.HasPrefix(line, []byte(slashStar)) {
+ inSlashStar = true
+ line = bytes.TrimSpace(line[len(slashStar):])
+ continue ThisLine
+ }
+ // A non-empty non-comment line terminates scanning for go:build.
+ break Lines
+ }
+ }
+ return e, s.Err()
+}
+
+// FromString extracts the build constraint from the Go source or assembly file
+// containing the given data. If no build constraint applies to the file, it
+// returns nil.
+func FromString(str string) (constraint.Expr, error) {
+ return FromReader(strings.NewReader(str))
+}
+
+// FromFile extracts the build constraint from the Go source or assembly file
+// at the given path. If no build constraint applies to the file, it returns
+// nil.
+func FromFile(path string) (constraint.Expr, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return FromReader(f)
+}
+
+// Combine returns a constraint.Expr that evaluates to true iff all expressions
+// in es evaluate to true. If es is empty, Combine returns nil.
+//
+// Preconditions: All constraint.Exprs in es are non-nil.
+func Combine(es []constraint.Expr) constraint.Expr {
+ switch len(es) {
+ case 0:
+ return nil
+ case 1:
+ return es[0]
+ default:
+ a := &constraint.AndExpr{es[0], es[1]}
+ for i := 2; i < len(es); i++ {
+ a = &constraint.AndExpr{a, es[i]}
+ }
+ return a
+ }
+}
+
+// CombineFromFiles returns a build constraint expression that evaluates to
+// true iff the build constraints from all of the given Go source or assembly
+// files evaluate to true. If no build constraints apply to any of the given
+// files, it returns nil.
+func CombineFromFiles(paths []string) (constraint.Expr, error) {
+ var es []constraint.Expr
+ for _, path := range paths {
+ e, err := FromFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read build constraints from %q: %v", path, err)
+ }
+ if e != nil {
+ es = append(es, e)
+ }
+ }
+ return Combine(es), nil
+}
+
+// Lines returns a string containing build constraint directives for the given
+// constraint.Expr, including two trailing newlines, as appropriate for a Go
+// source or assembly file. At least a go:build directive will be emitted; if
+// the constraint is expressible using +build directives as well, then +build
+// directives will also be emitted.
+//
+// If e is nil, Lines returns the empty string.
+func Lines(e constraint.Expr) string {
+ if e == nil {
+ return ""
+ }
+
+ var b strings.Builder
+ b.WriteString("//go:build ")
+ b.WriteString(e.String())
+ b.WriteByte('\n')
+
+ if pblines, err := constraint.PlusBuildLines(e); err == nil {
+ for _, line := range pblines {
+ b.WriteString(line)
+ b.WriteByte('\n')
+ }
+ }
+
+ b.WriteByte('\n')
+ return b.String()
+}
diff --git a/tools/constraintutil/constraintutil_test.go b/tools/constraintutil/constraintutil_test.go
new file mode 100644
index 000000000..eeabd8dcf
--- /dev/null
+++ b/tools/constraintutil/constraintutil_test.go
@@ -0,0 +1,138 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package constraintutil
+
+import (
+ "go/build/constraint"
+ "testing"
+)
+
+func TestFileParsing(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ data string
+ expr string
+ }{
+ {
+ name: "Empty",
+ },
+ {
+ name: "NoConstraint",
+ data: "// copyright header\n\npackage main",
+ },
+ {
+ name: "ConstraintOnFirstLine",
+ data: "//go:build amd64\n#include \"textflag.h\"",
+ expr: "amd64",
+ },
+ {
+ name: "ConstraintAfterSlashSlashComment",
+ data: "// copyright header\n\n//go:build linux\n\npackage newlib",
+ expr: "linux",
+ },
+ {
+ name: "ConstraintAfterSlashStarComment",
+ data: "/*\ncopyright header\n*/\n\n//go:build !race\n\npackage oldlib",
+ expr: "!race",
+ },
+ {
+ name: "ConstraintInSlashSlashComment",
+ data: "// blah blah //go:build windows",
+ },
+ {
+ name: "ConstraintInSlashStarComment",
+ data: "/*\n//go:build windows\n*/",
+ },
+ {
+ name: "ConstraintAfterPackageClause",
+ data: "package oops\n//go:build race",
+ },
+ {
+ name: "ConstraintAfterCppInclude",
+ data: "#include \"textflag.h\"\n//go:build arm64",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ e, err := FromString(test.data)
+ if err != nil {
+ t.Fatalf("FromString(%q) failed: %v", test.data, err)
+ }
+ if e == nil {
+ if len(test.expr) != 0 {
+ t.Errorf("FromString(%q): got no constraint, wanted %q", test.data, test.expr)
+ }
+ } else {
+ got := e.String()
+ if len(test.expr) == 0 {
+ t.Errorf("FromString(%q): got %q, wanted no constraint", test.data, got)
+ } else if got != test.expr {
+ t.Errorf("FromString(%q): got %q, wanted %q", test.data, got, test.expr)
+ }
+ }
+ })
+ }
+}
+
+func TestCombine(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ in []string
+ out string
+ }{
+ {
+ name: "0",
+ },
+ {
+ name: "1",
+ in: []string{"amd64 || arm64"},
+ out: "amd64 || arm64",
+ },
+ {
+ name: "2",
+ in: []string{"amd64", "amd64 && linux"},
+ out: "amd64 && amd64 && linux",
+ },
+ {
+ name: "3",
+ in: []string{"amd64", "amd64 || arm64", "amd64 || riscv64"},
+ out: "amd64 && (amd64 || arm64) && (amd64 || riscv64)",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ inexprs := make([]constraint.Expr, 0, len(test.in))
+ for _, estr := range test.in {
+ line := "//go:build " + estr
+ e, err := constraint.Parse(line)
+ if err != nil {
+ t.Fatalf("constraint.Parse(%q) failed: %v", line, err)
+ }
+ inexprs = append(inexprs, e)
+ }
+ outexpr := Combine(inexprs)
+ if outexpr == nil {
+ if len(test.out) != 0 {
+ t.Errorf("Combine(%v): got no constraint, wanted %q", test.in, test.out)
+ }
+ } else {
+ got := outexpr.String()
+ if len(test.out) == 0 {
+ t.Errorf("Combine(%v): got %q, wanted no constraint", test.in, got)
+ } else if got != test.out {
+ t.Errorf("Combine(%v): got %q, wanted %q", test.in, got, test.out)
+ }
+ }
+ })
+ }
+}
diff --git a/tools/go_generics/go_merge/BUILD b/tools/go_generics/go_merge/BUILD
index 5e0487e93..211e6b3ed 100644
--- a/tools/go_generics/go_merge/BUILD
+++ b/tools/go_generics/go_merge/BUILD
@@ -7,6 +7,6 @@ go_binary(
srcs = ["main.go"],
visibility = ["//:sandbox"],
deps = [
- "//tools/tags",
+ "//tools/constraintutil",
],
)
diff --git a/tools/go_generics/go_merge/main.go b/tools/go_generics/go_merge/main.go
index 801f2354f..81394ddce 100644
--- a/tools/go_generics/go_merge/main.go
+++ b/tools/go_generics/go_merge/main.go
@@ -25,9 +25,8 @@ import (
"os"
"path/filepath"
"strconv"
- "strings"
- "gvisor.dev/gvisor/tools/tags"
+ "gvisor.dev/gvisor/tools/constraintutil"
)
var (
@@ -131,6 +130,12 @@ func main() {
}
f.Decls = newDecls
+ // Infer build constraints for the output file.
+ bcexpr, err := constraintutil.CombineFromFiles(flag.Args())
+ if err != nil {
+ fatalf("Failed to read build constraints: %v\n", err)
+ }
+
// Write the output file.
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
@@ -141,9 +146,7 @@ func main() {
fatalf("opening output: %v\n", err)
}
defer outf.Close()
- if t := tags.Aggregate(flag.Args()); len(t) > 0 {
- fmt.Fprintf(outf, "%s\n\n", strings.Join(t.Lines(), "\n"))
- }
+ outf.WriteString(constraintutil.Lines(bcexpr))
if _, err := outf.Write(buf.Bytes()); err != nil {
fatalf("write: %v\n", err)
}
diff --git a/tools/go_marshal/gomarshal/BUILD b/tools/go_marshal/gomarshal/BUILD
index c2747d94c..aaa203115 100644
--- a/tools/go_marshal/gomarshal/BUILD
+++ b/tools/go_marshal/gomarshal/BUILD
@@ -18,5 +18,5 @@ go_library(
visibility = [
"//:sandbox",
],
- deps = ["//tools/tags"],
+ deps = ["//tools/constraintutil"],
)
diff --git a/tools/go_marshal/gomarshal/generator.go b/tools/go_marshal/gomarshal/generator.go
index 00961c90d..4c23637c0 100644
--- a/tools/go_marshal/gomarshal/generator.go
+++ b/tools/go_marshal/gomarshal/generator.go
@@ -25,7 +25,7 @@ import (
"sort"
"strings"
- "gvisor.dev/gvisor/tools/tags"
+ "gvisor.dev/gvisor/tools/constraintutil"
)
// List of identifiers we use in generated code that may conflict with a
@@ -123,16 +123,18 @@ func (g *Generator) writeHeader() error {
var b sourceBuffer
b.emit("// Automatically generated marshal implementation. See tools/go_marshal.\n\n")
- // Emit build tags.
- b.emit("// If there are issues with build tag aggregation, see\n")
- b.emit("// tools/go_marshal/gomarshal/generator.go:writeHeader(). The build tags here\n")
- b.emit("// come from the input set of files used to generate this file. This input set\n")
- b.emit("// is filtered based on pre-defined file suffixes related to build tags, see \n")
- b.emit("// tools/defs.bzl:calculate_sets().\n\n")
-
- if t := tags.Aggregate(g.inputs); len(t) > 0 {
- b.emit(strings.Join(t.Lines(), "\n"))
- b.emit("\n\n")
+ bcexpr, err := constraintutil.CombineFromFiles(g.inputs)
+ if err != nil {
+ return err
+ }
+ if bcexpr != nil {
+ // Emit build constraints.
+ b.emit("// If there are issues with build constraint aggregation, see\n")
+ b.emit("// tools/go_marshal/gomarshal/generator.go:writeHeader(). The constraints here\n")
+ b.emit("// come from the input set of files used to generate this file. This input set\n")
+ b.emit("// is filtered based on pre-defined file suffixes related to build constraints,\n")
+ b.emit("// see tools/defs.bzl:calculate_sets().\n\n")
+ b.emit(constraintutil.Lines(bcexpr))
}
// Package header.
@@ -553,11 +555,12 @@ func (g *Generator) writeTests(ts []*testGenerator) error {
b.reset()
b.emit("// Automatically generated marshal tests. See tools/go_marshal.\n\n")
- // Emit build tags.
- if t := tags.Aggregate(g.inputs); len(t) > 0 {
- b.emit(strings.Join(t.Lines(), "\n"))
- b.emit("\n\n")
+ // Emit build constraints.
+ bcexpr, err := constraintutil.CombineFromFiles(g.inputs)
+ if err != nil {
+ return err
}
+ b.emit(constraintutil.Lines(bcexpr))
b.emit("package %s\n\n", g.pkg)
if err := b.write(g.outputTest); err != nil {
diff --git a/tools/go_stateify/BUILD b/tools/go_stateify/BUILD
index 913558b4e..ad66981c7 100644
--- a/tools/go_stateify/BUILD
+++ b/tools/go_stateify/BUILD
@@ -6,7 +6,7 @@ go_binary(
name = "stateify",
srcs = ["main.go"],
visibility = ["//:sandbox"],
- deps = ["//tools/tags"],
+ deps = ["//tools/constraintutil"],
)
bzl_library(
diff --git a/tools/go_stateify/main.go b/tools/go_stateify/main.go
index 93022f504..7216388a0 100644
--- a/tools/go_stateify/main.go
+++ b/tools/go_stateify/main.go
@@ -28,7 +28,7 @@ import (
"strings"
"sync"
- "gvisor.dev/gvisor/tools/tags"
+ "gvisor.dev/gvisor/tools/constraintutil"
)
var (
@@ -214,10 +214,13 @@ func main() {
// Automated warning.
fmt.Fprint(outputFile, "// automatically generated by stateify.\n\n")
- // Emit build tags.
- if t := tags.Aggregate(flag.Args()); len(t) > 0 {
- fmt.Fprintf(outputFile, "%s\n\n", strings.Join(t.Lines(), "\n"))
+ // Emit build constraints.
+ bcexpr, err := constraintutil.CombineFromFiles(flag.Args())
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to infer build constraints: %v", err)
+ os.Exit(1)
}
+ outputFile.WriteString(constraintutil.Lines(bcexpr))
// Emit the package name.
_, pkg := filepath.Split(*fullPkg)
diff --git a/tools/nogo/BUILD b/tools/nogo/BUILD
index 27fe48680..d72821377 100644
--- a/tools/nogo/BUILD
+++ b/tools/nogo/BUILD
@@ -35,6 +35,7 @@ go_library(
visibility = ["//:sandbox"],
deps = [
"//tools/checkescape",
+ "//tools/checklinkname",
"//tools/checklocks",
"//tools/checkunsafe",
"//tools/nogo/objdump",
diff --git a/tools/nogo/analyzers.go b/tools/nogo/analyzers.go
index 2b3c03fec..6705fc905 100644
--- a/tools/nogo/analyzers.go
+++ b/tools/nogo/analyzers.go
@@ -47,6 +47,7 @@ import (
"honnef.co/go/tools/stylecheck"
"gvisor.dev/gvisor/tools/checkescape"
+ "gvisor.dev/gvisor/tools/checklinkname"
"gvisor.dev/gvisor/tools/checklocks"
"gvisor.dev/gvisor/tools/checkunsafe"
)
@@ -80,6 +81,7 @@ var AllAnalyzers = []*analysis.Analyzer{
unusedresult.Analyzer,
checkescape.Analyzer,
checkunsafe.Analyzer,
+ checklinkname.Analyzer,
checklocks.Analyzer,
}
diff --git a/tools/nogo/filter/main.go b/tools/nogo/filter/main.go
index d50336b9b..4a925d03c 100644
--- a/tools/nogo/filter/main.go
+++ b/tools/nogo/filter/main.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Binary check is the nogo entrypoint.
+// Binary filter is the filters and reports nogo findings.
package main
import (
diff --git a/tools/tags/BUILD b/tools/tags/BUILD
deleted file mode 100644
index 1c02e2c89..000000000
--- a/tools/tags/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "tags",
- srcs = ["tags.go"],
- marshal = False,
- stateify = False,
- visibility = ["//tools:__subpackages__"],
-)
diff --git a/tools/tags/tags.go b/tools/tags/tags.go
deleted file mode 100644
index f35904e0a..000000000
--- a/tools/tags/tags.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package tags is a utility for parsing build tags.
-package tags
-
-import (
- "fmt"
- "io/ioutil"
- "strings"
-)
-
-// OrSet is a set of tags on a single line.
-//
-// Note that tags may include ",", and we don't distinguish this case in the
-// logic below. Ideally, this constraints can be split into separate top-level
-// build tags in order to resolve any issues.
-type OrSet []string
-
-// Line returns the line for this or.
-func (or OrSet) Line() string {
- return fmt.Sprintf("// +build %s", strings.Join([]string(or), " "))
-}
-
-// AndSet is the set of all OrSets.
-type AndSet []OrSet
-
-// Lines returns the lines to be printed.
-func (and AndSet) Lines() (ls []string) {
- for _, or := range and {
- ls = append(ls, or.Line())
- }
- return
-}
-
-// Join joins this AndSet with another.
-func (and AndSet) Join(other AndSet) AndSet {
- return append(and, other...)
-}
-
-// Tags returns the unique set of +build tags.
-//
-// Derived form the runtime's canBuild.
-func Tags(file string) (tags AndSet) {
- data, err := ioutil.ReadFile(file)
- if err != nil {
- return nil
- }
- // Check file contents for // +build lines.
- for _, p := range strings.Split(string(data), "\n") {
- p = strings.TrimSpace(p)
- if p == "" {
- continue
- }
- if !strings.HasPrefix(p, "//") {
- break
- }
- if !strings.Contains(p, "+build") {
- continue
- }
- fields := strings.Fields(p[2:])
- if len(fields) < 1 || fields[0] != "+build" {
- continue
- }
- tags = append(tags, OrSet(fields[1:]))
- }
- return tags
-}
-
-// Aggregate aggregates all tags from a set of files.
-//
-// Note that these may be in conflict, in which case the build will fail.
-func Aggregate(files []string) (tags AndSet) {
- for _, file := range files {
- tags = tags.Join(Tags(file))
- }
- return tags
-}