summaryrefslogtreecommitdiffhomepage
path: root/tools/nogo
diff options
context:
space:
mode:
Diffstat (limited to 'tools/nogo')
-rw-r--r--tools/nogo/BUILD87
-rw-r--r--tools/nogo/README.md31
-rw-r--r--tools/nogo/analyzers.go129
-rw-r--r--tools/nogo/build.go38
-rw-r--r--tools/nogo/check/BUILD14
-rw-r--r--tools/nogo/check/main.go123
-rw-r--r--tools/nogo/config-schema.json97
-rw-r--r--tools/nogo/config.go319
-rw-r--r--tools/nogo/config_test.go301
-rw-r--r--tools/nogo/defs.bzl506
-rw-r--r--tools/nogo/filter/BUILD15
-rw-r--r--tools/nogo/filter/main.go190
-rw-r--r--tools/nogo/findings.go127
-rw-r--r--tools/nogo/nogo.go673
-rw-r--r--tools/nogo/objdump/BUILD10
-rw-r--r--tools/nogo/objdump/objdump.go96
16 files changed, 0 insertions, 2756 deletions
diff --git a/tools/nogo/BUILD b/tools/nogo/BUILD
deleted file mode 100644
index d72821377..000000000
--- a/tools/nogo/BUILD
+++ /dev/null
@@ -1,87 +0,0 @@
-load("//tools:defs.bzl", "bzl_library", "go_library", "go_test", "select_goarch", "select_goos")
-load("//tools/nogo:defs.bzl", "nogo_objdump_tool", "nogo_stdlib", "nogo_target")
-
-package(licenses = ["notice"])
-
-exports_files(["config-schema.json"])
-
-nogo_target(
- name = "target",
- goarch = select_goarch(),
- goos = select_goos(),
- visibility = ["//visibility:public"],
-)
-
-nogo_objdump_tool(
- name = "objdump_tool",
- visibility = ["//visibility:public"],
-)
-
-nogo_stdlib(
- name = "stdlib",
- visibility = ["//visibility:public"],
-)
-
-go_library(
- name = "nogo",
- srcs = [
- "analyzers.go",
- "build.go",
- "config.go",
- "findings.go",
- "nogo.go",
- ],
- nogo = False,
- visibility = ["//:sandbox"],
- deps = [
- "//tools/checkescape",
- "//tools/checklinkname",
- "//tools/checklocks",
- "//tools/checkunsafe",
- "//tools/nogo/objdump",
- "//tools/worker",
- "@co_honnef_go_tools//staticcheck:go_default_library",
- "@co_honnef_go_tools//stylecheck:go_default_library",
- "@org_golang_x_tools//go/analysis:go_default_library",
- "@org_golang_x_tools//go/analysis/internal/facts:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/asmdecl:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/assign:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/atomic:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/bools:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/buildtag:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/cgocall:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/composite:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/copylock:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/errorsas:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/httpresponse:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/loopclosure:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/lostcancel:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/nilfunc:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/nilness:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/printf:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/shadow:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/shift:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/stdmethods:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/stringintconv:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/structtag:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/tests:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/unmarshal:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/unreachable:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/unsafeptr:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/unusedresult:go_default_library",
- "@org_golang_x_tools//go/gcexportdata:go_default_library",
- "@org_golang_x_tools//go/types/objectpath:go_default_library",
- ],
-)
-
-go_test(
- name = "nogo_test",
- srcs = ["config_test.go"],
- library = ":nogo",
-)
-
-bzl_library(
- name = "defs_bzl",
- srcs = ["defs.bzl"],
- visibility = ["//visibility:private"],
-)
diff --git a/tools/nogo/README.md b/tools/nogo/README.md
deleted file mode 100644
index 6e4db18de..000000000
--- a/tools/nogo/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Extended "nogo" analysis
-
-This package provides a build aspect that perform nogo analysis. This will be
-automatically injected to all relevant libraries when using the default
-`go_binary` and `go_library` rules.
-
-It exists for several reasons.
-
-* The default `nogo` provided by bazel is insufficient with respect to the
- possibility of binary analysis. This package allows us to analyze the
- generated binary in addition to using the standard analyzers.
-
-* The configuration provided in this package is much richer than the standard
- `nogo` JSON blob. Specifically, it allows us to exclude specific structures
- from the composite rules (such as the Ranges that are common with the set
- types).
-
-* The bazel version of `nogo` is run directly against the `go_library` and
- `go_binary` targets, meaning that any change to the configuration requires a
- rebuild from scratch (for some reason included all C++ source files in the
- process). Using an aspect is more efficient in this regard.
-
-* The checks supported by this package are exported as tests, which makes it
- easier to reason about and plumb into the build system.
-
-* For uninteresting reasons, it is impossible to integrate the default `nogo`
- analyzer provided by bazel with internal Google tooling. To provide a
- consistent experience, this package allows those systems to be unified.
-
-To use this package, import `nogo_test` from `defs.bzl` and add a single
-dependency which is a `go_binary` or `go_library` rule.
diff --git a/tools/nogo/analyzers.go b/tools/nogo/analyzers.go
deleted file mode 100644
index db8bbdb8a..000000000
--- a/tools/nogo/analyzers.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2019 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 nogo
-
-import (
- "encoding/gob"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/asmdecl"
- "golang.org/x/tools/go/analysis/passes/assign"
- "golang.org/x/tools/go/analysis/passes/atomic"
- "golang.org/x/tools/go/analysis/passes/bools"
- "golang.org/x/tools/go/analysis/passes/buildtag"
- "golang.org/x/tools/go/analysis/passes/cgocall"
- "golang.org/x/tools/go/analysis/passes/composite"
- "golang.org/x/tools/go/analysis/passes/copylock"
- "golang.org/x/tools/go/analysis/passes/errorsas"
- "golang.org/x/tools/go/analysis/passes/httpresponse"
- "golang.org/x/tools/go/analysis/passes/loopclosure"
- "golang.org/x/tools/go/analysis/passes/lostcancel"
- "golang.org/x/tools/go/analysis/passes/nilfunc"
- "golang.org/x/tools/go/analysis/passes/nilness"
- "golang.org/x/tools/go/analysis/passes/printf"
- "golang.org/x/tools/go/analysis/passes/shadow"
- "golang.org/x/tools/go/analysis/passes/shift"
- "golang.org/x/tools/go/analysis/passes/stdmethods"
- "golang.org/x/tools/go/analysis/passes/stringintconv"
- "golang.org/x/tools/go/analysis/passes/structtag"
- "golang.org/x/tools/go/analysis/passes/tests"
- "golang.org/x/tools/go/analysis/passes/unmarshal"
- "golang.org/x/tools/go/analysis/passes/unreachable"
- "golang.org/x/tools/go/analysis/passes/unsafeptr"
- "golang.org/x/tools/go/analysis/passes/unusedresult"
- "honnef.co/go/tools/staticcheck"
- "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"
-)
-
-// AllAnalyzers is a list of all available analyzers.
-var AllAnalyzers = []*analysis.Analyzer{
- asmdecl.Analyzer,
- assign.Analyzer,
- atomic.Analyzer,
- bools.Analyzer,
- buildtag.Analyzer,
- cgocall.Analyzer,
- composite.Analyzer,
- copylock.Analyzer,
- errorsas.Analyzer,
- httpresponse.Analyzer,
- loopclosure.Analyzer,
- lostcancel.Analyzer,
- nilfunc.Analyzer,
- nilness.Analyzer,
- printf.Analyzer,
- shift.Analyzer,
- stdmethods.Analyzer,
- stringintconv.Analyzer,
- shadow.Analyzer,
- structtag.Analyzer,
- tests.Analyzer,
- unmarshal.Analyzer,
- unreachable.Analyzer,
- unsafeptr.Analyzer,
- unusedresult.Analyzer,
- checkescape.Analyzer,
- checkunsafe.Analyzer,
- checklinkname.Analyzer,
- checklocks.Analyzer,
-}
-
-func register(all []*analysis.Analyzer) {
- // Register all fact types.
- //
- // N.B. This needs to be done recursively, because there may be
- // analyzers in the Requires list that do not appear explicitly above.
- registered := make(map[*analysis.Analyzer]struct{})
- var registerOne func(*analysis.Analyzer)
- registerOne = func(a *analysis.Analyzer) {
- if _, ok := registered[a]; ok {
- return
- }
-
- // Register dependencies.
- for _, da := range a.Requires {
- registerOne(da)
- }
-
- // Register local facts.
- for _, f := range a.FactTypes {
- gob.Register(f)
- }
-
- registered[a] = struct{}{} // Done.
- }
- for _, a := range all {
- registerOne(a)
- }
-}
-
-func init() {
- // Add all staticcheck analyzers.
- for _, a := range staticcheck.Analyzers {
- AllAnalyzers = append(AllAnalyzers, a.Analyzer)
- }
- // Add all stylecheck analyzers.
- for _, a := range stylecheck.Analyzers {
- AllAnalyzers = append(AllAnalyzers, a.Analyzer)
- }
-
- // Register lists.
- register(AllAnalyzers)
-}
diff --git a/tools/nogo/build.go b/tools/nogo/build.go
deleted file mode 100644
index 003533c71..000000000
--- a/tools/nogo/build.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2019 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.
-
-//go:build go1.1
-// +build go1.1
-
-package nogo
-
-import (
- "fmt"
- "io"
- "os"
-)
-
-// findStdPkg needs to find the bundled standard library packages.
-func findStdPkg(GOOS, GOARCH, path string) (io.ReadCloser, error) {
- if path == "C" {
- // Cgo builds cannot be analyzed. Skip.
- return nil, ErrSkip
- }
- return os.Open(fmt.Sprintf("external/go_sdk/pkg/%s_%s/%s.a", GOOS, GOARCH, path))
-}
-
-// ReleaseTags returns nil, indicating that the defaults should be used.
-func ReleaseTags() ([]string, error) {
- return nil, nil
-}
diff --git a/tools/nogo/check/BUILD b/tools/nogo/check/BUILD
deleted file mode 100644
index 666780dd3..000000000
--- a/tools/nogo/check/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("//tools:defs.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "check",
- srcs = ["main.go"],
- nogo = False,
- visibility = ["//visibility:public"],
- deps = [
- "//tools/nogo",
- "//tools/worker",
- ],
-)
diff --git a/tools/nogo/check/main.go b/tools/nogo/check/main.go
deleted file mode 100644
index 17ca0d846..000000000
--- a/tools/nogo/check/main.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2019 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.
-
-// Binary check is the nogo entrypoint.
-package main
-
-import (
- "encoding/json"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "os"
-
- "gvisor.dev/gvisor/tools/nogo"
- "gvisor.dev/gvisor/tools/worker"
-)
-
-var (
- packageFile = flag.String("package", "", "package configuration file (in JSON format)")
- stdlibFile = flag.String("stdlib", "", "stdlib configuration file (in JSON format)")
- findingsOutput = flag.String("findings", "", "output file (or stdout, if not specified)")
- factsOutput = flag.String("facts", "", "output file for facts (optional)")
-)
-
-func loadConfig(file string, config interface{}) interface{} {
- // Load the configuration.
- f, err := os.Open(file)
- if err != nil {
- log.Fatalf("unable to open configuration %q: %v", file, err)
- }
- defer f.Close()
- dec := json.NewDecoder(f)
- dec.DisallowUnknownFields()
- if err := dec.Decode(config); err != nil {
- log.Fatalf("unable to decode configuration: %v", err)
- }
- return config
-}
-
-func main() {
- worker.Work(run)
-}
-
-func run([]string) int {
- var (
- findings []nogo.Finding
- factData []byte
- err error
- )
-
- // Check & load the configuration.
- if *packageFile != "" && *stdlibFile != "" {
- fmt.Fprintf(os.Stderr, "unable to perform stdlib and package analysis; provide only one!")
- return 1
- }
-
- releaseTags, err := nogo.ReleaseTags()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error determining release tags: %v", err)
- return 1
- }
-
- // Run the configuration.
- if *stdlibFile != "" {
- // Perform stdlib analysis.
- c := loadConfig(*stdlibFile, new(nogo.StdlibConfig)).(*nogo.StdlibConfig)
- c.ReleaseTags = releaseTags
- findings, factData, err = nogo.CheckStdlib(c, nogo.AllAnalyzers)
- } else if *packageFile != "" {
- // Perform standard analysis.
- c := loadConfig(*packageFile, new(nogo.PackageConfig)).(*nogo.PackageConfig)
- c.ReleaseTags = releaseTags
- findings, factData, err = nogo.CheckPackage(c, nogo.AllAnalyzers, nil)
- } else {
- fmt.Fprintf(os.Stderr, "please provide at least one of package or stdlib!")
- return 1
- }
-
- // Check that analysis was successful.
- if err != nil {
- fmt.Fprintf(os.Stderr, "error performing analysis: %v", err)
- return 1
- }
-
- // Save facts.
- if *factsOutput != "" {
- if err := ioutil.WriteFile(*factsOutput, factData, 0644); err != nil {
- fmt.Fprintf(os.Stderr, "error saving findings to %q: %v", *factsOutput, err)
- return 1
- }
- }
-
- // Write all findings.
- if *findingsOutput != "" {
- w, err := os.OpenFile(*findingsOutput, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- fmt.Fprintf(os.Stderr, "error opening output file %q: %v", *findingsOutput, err)
- return 1
- }
- if err := nogo.WriteFindingsTo(w, findings, false /* json */); err != nil {
- fmt.Fprintf(os.Stderr, "error writing findings to %q: %v", *findingsOutput, err)
- return 1
- }
- } else {
- for _, finding := range findings {
- fmt.Fprintf(os.Stdout, "%s\n", finding.String())
- }
- }
-
- return 0
-}
diff --git a/tools/nogo/config-schema.json b/tools/nogo/config-schema.json
deleted file mode 100644
index 3c25fe221..000000000
--- a/tools/nogo/config-schema.json
+++ /dev/null
@@ -1,97 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema",
- "definitions": {
- "group": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the group.",
- "type": "string"
- },
- "regex": {
- "description": "A regular expression for matching paths.",
- "type": "string"
- },
- "default": {
- "description": "Whether the group is enabled by default.",
- "type": "boolean"
- }
- },
- "required": [
- "name",
- "regex",
- "default"
- ],
- "additionalProperties": false
- },
- "regexlist": {
- "description": "A list of regular expressions.",
- "oneOf": [
- {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- {
- "type": "null"
- }
- ]
- },
- "rule": {
- "type": "object",
- "properties": {
- "exclude": {
- "description": "A regular expression for paths to exclude.",
- "$ref": "#/definitions/regexlist"
- },
- "suppress": {
- "description": "A regular expression for messages to suppress.",
- "$ref": "#/definitions/regexlist"
- }
- },
- "additionalProperties": false
- },
- "ruleList": {
- "type": "object",
- "additionalProperties": {
- "oneOf": [
- {
- "$ref": "#/definitions/rule"
- },
- {
- "type": "null"
- }
- ]
- }
- }
- },
- "properties": {
- "groups": {
- "description": "A definition of all groups.",
- "type": "array",
- "items": {
- "$ref": "#/definitions/group"
- },
- "minItems": 1
- },
- "global": {
- "description": "A global set of rules.",
- "type": "object",
- "additionalProperties": {
- "$ref": "#/definitions/rule"
- }
- },
- "analyzers": {
- "description": "A definition of all groups.",
- "type": "object",
- "additionalProperties": {
- "$ref": "#/definitions/ruleList"
- }
- }
- },
- "required": [
- "groups"
- ],
- "additionalProperties": false
-}
diff --git a/tools/nogo/config.go b/tools/nogo/config.go
deleted file mode 100644
index ee2533610..000000000
--- a/tools/nogo/config.go
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright 2019 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 nogo
-
-import (
- "fmt"
- "regexp"
-)
-
-// GroupName is a named group.
-type GroupName string
-
-// AnalyzerName is a named analyzer.
-type AnalyzerName string
-
-// Group represents a named collection of files.
-type Group struct {
- // Name is the short name for the group.
- Name GroupName `yaml:"name"`
-
- // Regex matches all full paths in the group.
- Regex string `yaml:"regex"`
- regex *regexp.Regexp `yaml:"-"`
-
- // Default determines the default group behavior.
- //
- // If Default is true, all Analyzers are enabled for this
- // group. Otherwise, Analyzers must be individually enabled
- // by specifying a (possible empty) ItemConfig for the group
- // in the AnalyzerConfig.
- Default bool `yaml:"default"`
-}
-
-func (g *Group) compile() error {
- r, err := regexp.Compile(g.Regex)
- if err != nil {
- return err
- }
- g.regex = r
- return nil
-}
-
-// ItemConfig is an (Analyzer,Group) configuration.
-type ItemConfig struct {
- // Exclude are analyzer exclusions.
- //
- // Exclude is a list of regular expressions. If the corresponding
- // Analyzer emits a Finding for which Finding.Position.String()
- // matches a regular expression in Exclude, the finding will not
- // be reported.
- Exclude []string `yaml:"exclude,omitempty"`
- exclude []*regexp.Regexp `yaml:"-"`
-
- // Suppress are analyzer suppressions.
- //
- // Suppress is a list of regular expressions. If the corresponding
- // Analyzer emits a Finding for which Finding.Message matches a regular
- // expression in Suppress, the finding will not be reported.
- Suppress []string `yaml:"suppress,omitempty"`
- suppress []*regexp.Regexp `yaml:"-"`
-}
-
-func compileRegexps(ss []string, rs *[]*regexp.Regexp) error {
- *rs = make([]*regexp.Regexp, len(ss))
- for i, s := range ss {
- r, err := regexp.Compile(s)
- if err != nil {
- return err
- }
- (*rs)[i] = r
- }
- return nil
-}
-
-// RegexpCount is used by AnalyzerConfig.RegexpCount.
-func (i *ItemConfig) RegexpCount() int64 {
- if i == nil {
- // See compile.
- return 0
- }
- // Return the number of regular expressions compiled for these items.
- // This is how the cache size of the configuration is measured.
- return int64(len(i.exclude) + len(i.suppress))
-}
-
-func (i *ItemConfig) compile() error {
- if i == nil {
- // This may be nil if nothing is included in the
- // item configuration. That's fine, there's nothing
- // to compile and nothing to exclude & suppress.
- return nil
- }
- if err := compileRegexps(i.Exclude, &i.exclude); err != nil {
- return fmt.Errorf("in exclude: %w", err)
- }
- if err := compileRegexps(i.Suppress, &i.suppress); err != nil {
- return fmt.Errorf("in suppress: %w", err)
- }
- return nil
-}
-
-func merge(a, b []string) []string {
- found := make(map[string]struct{})
- result := make([]string, 0, len(a)+len(b))
- for _, elem := range a {
- found[elem] = struct{}{}
- result = append(result, elem)
- }
- for _, elem := range b {
- if _, ok := found[elem]; ok {
- continue
- }
- result = append(result, elem)
- }
- return result
-}
-
-func (i *ItemConfig) merge(other *ItemConfig) {
- i.Exclude = merge(i.Exclude, other.Exclude)
- i.Suppress = merge(i.Suppress, other.Suppress)
-}
-
-func (i *ItemConfig) shouldReport(fullPos, msg string) bool {
- if i == nil {
- // See above.
- return true
- }
- for _, r := range i.exclude {
- if r.MatchString(fullPos) {
- return false
- }
- }
- for _, r := range i.suppress {
- if r.MatchString(msg) {
- return false
- }
- }
- return true
-}
-
-// AnalyzerConfig is the configuration for a single analyzers.
-//
-// This map is keyed by individual Group names, to allow for different
-// configurations depending on what Group the file belongs to.
-type AnalyzerConfig map[GroupName]*ItemConfig
-
-// RegexpCount is used by Config.Size.
-func (a AnalyzerConfig) RegexpCount() int64 {
- count := int64(0)
- for _, gc := range a {
- count += gc.RegexpCount()
- }
- return count
-}
-
-func (a AnalyzerConfig) compile() error {
- for name, gc := range a {
- if err := gc.compile(); err != nil {
- return fmt.Errorf("invalid group %q: %v", name, err)
- }
- }
- return nil
-}
-
-func (a AnalyzerConfig) merge(other AnalyzerConfig) {
- // Merge all the groups.
- for name, gc := range other {
- old, ok := a[name]
- if !ok || old == nil {
- a[name] = gc // Not configured in a.
- continue
- }
- old.merge(gc)
- }
-}
-
-// shouldReport returns whether the finding should be reported or suppressed.
-// It returns !ok if there is no configuration sufficient to decide one way or
-// another.
-func (a AnalyzerConfig) shouldReport(groupConfig *Group, fullPos, msg string) (report, ok bool) {
- gc, ok := a[groupConfig.Name]
- if !ok {
- return false, false
- }
-
- // Note that if a section appears for a particular group
- // for a particular analyzer, then it will now be enabled,
- // and the group default no longer applies.
- return gc.shouldReport(fullPos, msg), true
-}
-
-// Config is a nogo configuration.
-type Config struct {
- // Prefixes defines a set of regular expressions that
- // are standard "prefixes", so that files can be grouped
- // and specific rules applied to individual groups.
- Groups []Group `yaml:"groups"`
-
- // Global is the global analyzer config.
- Global AnalyzerConfig `yaml:"global"`
-
- // Analyzers are individual analyzer configurations. The
- // key for each analyzer is the name of the analyzer. The
- // value is either a boolean (enable/disable), or a map to
- // the groups above.
- Analyzers map[AnalyzerName]AnalyzerConfig `yaml:"analyzers"`
-}
-
-// Size implements worker.Sizer.Size.
-func (c *Config) Size() int64 {
- count := c.Global.RegexpCount()
- for _, config := range c.Analyzers {
- count += config.RegexpCount()
- }
- // The size is measured as the number of regexps that are compiled
- // here. We multiply by 1k to produce an estimate.
- return 1024 * count
-}
-
-// Merge merges two configurations.
-func (c *Config) Merge(other *Config) {
- // Merge all groups.
- //
- // Select the other first, as the order provided in the second will
- // provide precendence over the same group defined in the first one.
- seenGroups := make(map[GroupName]struct{})
- newGroups := make([]Group, 0, len(c.Groups)+len(other.Groups))
- for _, g := range other.Groups {
- newGroups = append(newGroups, g)
- seenGroups[g.Name] = struct{}{}
- }
- for _, g := range c.Groups {
- if _, ok := seenGroups[g.Name]; ok {
- continue
- }
- newGroups = append(newGroups, g)
- }
- c.Groups = newGroups
-
- // Merge global configurations.
- c.Global.merge(other.Global)
-
- // Merge all analyzer configurations.
- for name, ac := range other.Analyzers {
- old, ok := c.Analyzers[name]
- if !ok {
- c.Analyzers[name] = ac // No analyzer in original config.
- continue
- }
- old.merge(ac)
- }
-}
-
-// Compile compiles a configuration to make it useable.
-func (c *Config) Compile() error {
- for i := 0; i < len(c.Groups); i++ {
- if err := c.Groups[i].compile(); err != nil {
- return fmt.Errorf("invalid group %q: %w", c.Groups[i].Name, err)
- }
- }
- if err := c.Global.compile(); err != nil {
- return fmt.Errorf("invalid global: %w", err)
- }
- for name, ac := range c.Analyzers {
- if err := ac.compile(); err != nil {
- return fmt.Errorf("invalid analyzer %q: %w", name, err)
- }
- }
- return nil
-}
-
-// ShouldReport returns true iff the finding should match the Config.
-func (c *Config) ShouldReport(finding Finding) bool {
- fullPos := finding.Position.String()
-
- // Find the matching group.
- var groupConfig *Group
- for i := 0; i < len(c.Groups); i++ {
- if c.Groups[i].regex.MatchString(fullPos) {
- groupConfig = &c.Groups[i]
- break
- }
- }
-
- // If there is no group matching this path, then
- // we default to accept the finding.
- if groupConfig == nil {
- return true
- }
-
- // Suppress via global rule?
- report, ok := c.Global.shouldReport(groupConfig, fullPos, finding.Message)
- if ok && !report {
- return false
- }
-
- // Try the analyzer config.
- ac, ok := c.Analyzers[finding.Category]
- if !ok {
- return groupConfig.Default
- }
- report, ok = ac.shouldReport(groupConfig, fullPos, finding.Message)
- if !ok {
- return groupConfig.Default
- }
- return report
-}
diff --git a/tools/nogo/config_test.go b/tools/nogo/config_test.go
deleted file mode 100644
index 685cffbec..000000000
--- a/tools/nogo/config_test.go
+++ /dev/null
@@ -1,301 +0,0 @@
-// 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 nogo
-package nogo
-
-import (
- "go/token"
- "testing"
-)
-
-// TestShouldReport validates the suppression behavior of Config.ShouldReport.
-func TestShouldReport(t *testing.T) {
- config := &Config{
- Groups: []Group{
- {
- Name: "default-enabled",
- Regex: "^default-enabled/",
- Default: true,
- },
- {
- Name: "default-disabled",
- Regex: "^default-disabled/",
- Default: false,
- },
- {
- Name: "default-disabled-omitted-from-global",
- Regex: "^default-disabled-omitted-from-global/",
- Default: false,
- },
- },
- Global: AnalyzerConfig{
- "default-enabled": &ItemConfig{
- Exclude: []string{"excluded.go"},
- Suppress: []string{"suppressed"},
- },
- "default-disabled": &ItemConfig{
- Exclude: []string{"excluded.go"},
- Suppress: []string{"suppressed"},
- },
- // Omitting default-disabled-omitted-from-global here
- // has no effect on configuration below.
- },
- Analyzers: map[AnalyzerName]AnalyzerConfig{
- "analyzer-suppressions": AnalyzerConfig{
- // Suppress some.
- "default-enabled": &ItemConfig{
- Exclude: []string{"limited-exclude.go"},
- Suppress: []string{"limited suppress"},
- },
- // Enable all.
- "default-disabled": nil,
- },
- "enabled-for-default-disabled": AnalyzerConfig{
- "default-disabled": nil,
- "default-disabled-omitted-from-global": nil,
- },
- },
- }
-
- if err := config.Compile(); err != nil {
- t.Fatalf("Compile(%+v) = %v, want nil", config, err)
- }
-
- cases := []struct {
- name string
- finding Finding
- want bool
- }{
- {
- name: "enabled",
- finding: Finding{
- Category: "foo",
- Position: token.Position{
- Filename: "default-enabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- {
- name: "ungrouped",
- finding: Finding{
- Category: "foo",
- Position: token.Position{
- Filename: "ungrouped/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- {
- name: "suppressed",
- finding: Finding{
- Category: "foo",
- Position: token.Position{
- Filename: "default-enabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message suppressed",
- },
- want: false,
- },
- {
- name: "excluded",
- finding: Finding{
- Category: "foo",
- Position: token.Position{
- Filename: "default-enabled/excluded.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: false,
- },
- {
- name: "disabled",
- finding: Finding{
- Category: "foo",
- Position: token.Position{
- Filename: "default-disabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: false,
- },
- {
- name: "analyzer suppressed",
- finding: Finding{
- Category: "analyzer-suppressions",
- Position: token.Position{
- Filename: "default-enabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message limited suppress",
- },
- want: false,
- },
- {
- name: "analyzer suppressed not global",
- finding: Finding{
- // Doesn't apply outside of analyzer-suppressions.
- Category: "foo",
- Position: token.Position{
- Filename: "default-enabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message limited suppress",
- },
- want: true,
- },
- {
- name: "analyzer suppressed grouped",
- finding: Finding{
- Category: "analyzer-suppressions",
- Position: token.Position{
- // Doesn't apply outside of default-enabled.
- Filename: "default-disabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message limited suppress",
- },
- want: true,
- },
- {
- name: "analyzer excluded",
- finding: Finding{
- Category: "analyzer-suppressions",
- Position: token.Position{
- Filename: "default-enabled/limited-exclude.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: false,
- },
- {
- name: "analyzer excluded not global",
- finding: Finding{
- // Doesn't apply outside of analyzer-suppressions.
- Category: "foo",
- Position: token.Position{
- Filename: "default-enabled/limited-exclude.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- {
- name: "analyzer excluded grouped",
- finding: Finding{
- Category: "analyzer-suppressions",
- Position: token.Position{
- // Doesn't apply outside of default-enabled.
- Filename: "default-disabled/limited-exclude.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- {
- name: "disabled-omitted",
- finding: Finding{
- Category: "foo",
- Position: token.Position{
- Filename: "default-disabled-omitted-from-global/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: false,
- },
- {
- name: "default enabled applies to customized analyzer",
- finding: Finding{
- Category: "enabled-for-default-disabled",
- Position: token.Position{
- Filename: "default-enabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- {
- name: "default overridden in customized analyzer",
- finding: Finding{
- Category: "enabled-for-default-disabled",
- Position: token.Position{
- Filename: "default-disabled/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- {
- name: "default overridden in customized analyzer even when omitted from global",
- finding: Finding{
- Category: "enabled-for-default-disabled",
- Position: token.Position{
- Filename: "default-disabled-omitted-from-global/file.go",
- Offset: 0,
- Line: 1,
- Column: 1,
- },
- Message: "message",
- },
- want: true,
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- if got := config.ShouldReport(tc.finding); got != tc.want {
- t.Errorf("ShouldReport(%+v) = %v, want %v", tc.finding, got, tc.want)
- }
- })
- }
-}
diff --git a/tools/nogo/defs.bzl b/tools/nogo/defs.bzl
deleted file mode 100644
index bc0d6874f..000000000
--- a/tools/nogo/defs.bzl
+++ /dev/null
@@ -1,506 +0,0 @@
-"""Nogo rules."""
-
-load("//tools/bazeldefs:go.bzl", "go_context", "go_embed_libraries", "go_importpath", "go_rule")
-
-NogoConfigInfo = provider(
- "information about a nogo configuration",
- fields = {
- "srcs": "the collection of configuration files",
- },
-)
-
-def _nogo_config_impl(ctx):
- return [NogoConfigInfo(
- srcs = ctx.files.srcs,
- )]
-
-nogo_config = rule(
- implementation = _nogo_config_impl,
- attrs = {
- "srcs": attr.label_list(
- doc = "a list of yaml files (schema defined by tool/nogo/config.go).",
- allow_files = True,
- ),
- },
-)
-
-NogoTargetInfo = provider(
- "information about the Go target",
- fields = {
- "goarch": "the build architecture (GOARCH)",
- "goos": "the build OS target (GOOS)",
- "worker_debug": "transitive debugging",
- },
-)
-
-def _nogo_target_impl(ctx):
- return [NogoTargetInfo(
- goarch = ctx.attr.goarch,
- goos = ctx.attr.goos,
- worker_debug = ctx.attr.worker_debug,
- )]
-
-nogo_target = go_rule(
- rule,
- implementation = _nogo_target_impl,
- attrs = {
- "goarch": attr.string(
- doc = "the Go build architecture (propagated to other rules).",
- mandatory = True,
- ),
- "goos": attr.string(
- doc = "the Go OS target (propagated to other rules).",
- mandatory = True,
- ),
- "worker_debug": attr.bool(
- doc = "whether worker debugging should be enabled.",
- default = False,
- ),
- },
-)
-
-def _nogo_objdump_tool_impl(ctx):
- # Construct the magic dump command.
- #
- # Note that in some cases, the input is being fed into the tool via stdin.
- # Unfortunately, the Go objdump tool expects to see a seekable file [1], so
- # we need the tool to handle this case by creating a temporary file.
- #
- # [1] https://github.com/golang/go/issues/41051
- nogo_target_info = ctx.attr._target[NogoTargetInfo]
- go_ctx = go_context(ctx, goos = nogo_target_info.goos, goarch = nogo_target_info.goarch)
- env_prefix = " ".join(["%s=%s" % (key, value) for (key, value) in go_ctx.env.items()])
- dumper = ctx.actions.declare_file(ctx.label.name)
- ctx.actions.write(dumper, "\n".join([
- "#!/bin/bash",
- "set -euo pipefail",
- "if [[ $# -eq 0 ]]; then",
- " T=$(mktemp -u -t libXXXXXX.a)",
- " cat /dev/stdin > ${T}",
- "else",
- " T=$1;",
- "fi",
- "%s %s tool objdump ${T}" % (
- env_prefix,
- go_ctx.go.path,
- ),
- "if [[ $# -eq 0 ]]; then",
- " rm -rf ${T}",
- "fi",
- "",
- ]), is_executable = True)
-
- # Include the full runfiles.
- return [DefaultInfo(
- runfiles = ctx.runfiles(files = go_ctx.runfiles.to_list()),
- executable = dumper,
- )]
-
-nogo_objdump_tool = go_rule(
- rule,
- implementation = _nogo_objdump_tool_impl,
- attrs = {
- "_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- },
-)
-
-# NogoStdlibInfo is the set of standard library facts.
-NogoStdlibInfo = provider(
- "information for nogo analysis (standard library facts)",
- fields = {
- "facts": "serialized standard library facts",
- "raw_findings": "raw package findings (if relevant)",
- },
-)
-
-def _nogo_stdlib_impl(ctx):
- # Build the standard library facts.
- nogo_target_info = ctx.attr._target[NogoTargetInfo]
- go_ctx = go_context(ctx, goos = nogo_target_info.goos, goarch = nogo_target_info.goarch)
- facts = ctx.actions.declare_file(ctx.label.name + ".facts")
- raw_findings = ctx.actions.declare_file(ctx.label.name + ".raw_findings")
- config = struct(
- Srcs = [f.path for f in go_ctx.stdlib_srcs],
- GOOS = go_ctx.goos,
- GOARCH = go_ctx.goarch,
- BuildTags = go_ctx.gotags,
- )
- config_file = ctx.actions.declare_file(ctx.label.name + ".cfg")
- ctx.actions.write(config_file, config.to_json())
- args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
- ctx.actions.write(
- output = args_file,
- content = "\n".join(go_ctx.nogo_args + [
- "-objdump_tool=%s" % ctx.files._objdump_tool[0].path,
- "-stdlib=%s" % config_file.path,
- "-findings=%s" % raw_findings.path,
- "-facts=%s" % facts.path,
- ]),
- )
- ctx.actions.run(
- inputs = [config_file] + go_ctx.stdlib_srcs + [args_file],
- outputs = [facts, raw_findings],
- tools = depset(go_ctx.runfiles.to_list() + ctx.files._objdump_tool),
- executable = ctx.files._check[0],
- mnemonic = "GoStandardLibraryAnalysis",
- # Note that this does not support work execution currently. There is an
- # issue with stdout pollution that is not yet resolved, so this is kept
- # as a separate menomic.
- progress_message = "Analyzing Go Standard Library",
- arguments = [
- "--worker_debug=%s" % nogo_target_info.worker_debug,
- "@%s" % args_file.path,
- ],
- )
-
- # Return the stdlib facts as output.
- return [NogoStdlibInfo(
- facts = facts,
- raw_findings = raw_findings,
- ), DefaultInfo(
- # Declare the facts and findings as default outputs. This is not
- # strictly required, but ensures that the target still perform analysis
- # when built directly rather than just indirectly via a nogo_test.
- files = depset([facts, raw_findings]),
- )]
-
-nogo_stdlib = go_rule(
- rule,
- implementation = _nogo_stdlib_impl,
- attrs = {
- "_check": attr.label(
- default = "//tools/nogo/check:check",
- cfg = "host",
- ),
- "_objdump_tool": attr.label(
- default = "//tools/nogo:objdump_tool",
- cfg = "host",
- ),
- "_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- },
-)
-
-# NogoInfo is the serialized set of package facts for a nogo analysis.
-#
-# Each go_library rule will generate a corresponding nogo rule, which will run
-# with the source files as input. Note however, that the individual nogo rules
-# are simply stubs that enter into the shadow dependency tree (the "aspect").
-NogoInfo = provider(
- "information for nogo analysis",
- fields = {
- "facts": "serialized package facts",
- "raw_findings": "raw package findings (if relevant)",
- "importpath": "package import path",
- "binaries": "package binary files",
- "srcs": "srcs (for go_test support)",
- "deps": "deps (for go_test support)",
- },
-)
-
-def _select_objfile(files):
- """Returns (.a file, .x file, is_archive).
-
- If no .a file is available, then the first .x file will be returned
- instead, and vice versa. If neither are available, then the first provided
- file will be returned."""
- a_files = [f for f in files if f.path.endswith(".a")]
- x_files = [f for f in files if f.path.endswith(".x")]
- if not len(x_files) and not len(a_files):
- return (files[0], files[0], False)
- if not len(x_files):
- x_files = a_files
- if not len(a_files):
- a_files = x_files
- return a_files[0], x_files[0], True
-
-def _nogo_aspect_impl(target, ctx):
- # If this is a nogo rule itself (and not the shadow of a go_library or
- # go_binary rule created by such a rule), then we simply return nothing.
- # All work is done in the shadow properties for go rules. For a proto
- # library, we simply skip the analysis portion but still need to return a
- # valid NogoInfo to reference the generated binary.
- #
- # Note that we almost exclusively use go_library, not go_tool_library.
- # This is because nogo is manually annotated, so the go_tool_library kind
- # is not needed to avoid dependency loops. Unfortunately, bazel coverdata
- # is exported *only* as a go_tool_library. This does not cause a problem,
- # since there is guaranteed to be no conflict. However for consistency,
- # we should not introduce new go_tool_library dependencies unless strictly
- # necessary.
- if ctx.rule.kind in ("go_library", "go_tool_library", "go_binary", "go_test"):
- srcs = ctx.rule.files.srcs
- deps = ctx.rule.attr.deps
- elif ctx.rule.kind in ("go_proto_library", "go_wrap_cc"):
- srcs = []
- deps = ctx.rule.attr.deps
- else:
- return [NogoInfo()]
-
- # If we're using the "library" attribute, then we need to aggregate the
- # original library sources and dependencies into this target to perform
- # proper type analysis.
- for embed in go_embed_libraries(ctx.rule):
- info = embed[NogoInfo]
- if hasattr(info, "srcs"):
- srcs = srcs + info.srcs
- if hasattr(info, "deps"):
- deps = deps + info.deps
-
- # Start with all target files and srcs as input.
- binaries = target.files.to_list()
- inputs = binaries + srcs
-
- # Generate a shell script that dumps the binary. Annoyingly, this seems
- # necessary as the context in which a run_shell command runs does not seem
- # to cleanly allow us redirect stdout to the actual output file. Perhaps
- # I'm missing something here, but the intermediate script does work.
- target_objfile, target_xfile, has_objfile = _select_objfile(binaries)
- inputs.append(target_objfile)
-
- # Extract the importpath for this package.
- if ctx.rule.kind == "go_test":
- # If this is a test, then it will not be imported by anything else.
- # We can safely set the importapth to just "test". Note that this
- # is necessary if the library also imports the core library (in
- # addition to including the sources directly), which happens in
- # some complex cases (seccomp_victim).
- importpath = "test"
- else:
- importpath = go_importpath(target)
-
- # Collect all info from shadow dependencies.
- fact_map = dict()
- import_map = dict()
- all_raw_findings = []
- for dep in deps:
- # There will be no file attribute set for all transitive dependencies
- # that are not go_library or go_binary rules, such as a proto rules.
- # This is handled by the ctx.rule.kind check above.
- info = dep[NogoInfo]
- if not hasattr(info, "facts"):
- continue
-
- # Configure where to find the binary & fact files. Note that this will
- # use .x and .a regardless of whether this is a go_binary rule, since
- # these dependencies must be go_library rules.
- _, x_file, _ = _select_objfile(info.binaries)
- import_map[info.importpath] = x_file.path
- fact_map[info.importpath] = info.facts.path
-
- # Collect all findings; duplicates are resolved at the end.
- all_raw_findings.extend(info.raw_findings)
-
- # Ensure the above are available as inputs.
- inputs.append(info.facts)
- inputs += info.binaries
-
- # Add the module itself, for the type sanity check. This applies only to
- # the libraries, and not binaries or tests.
- if has_objfile:
- import_map[importpath] = target_xfile.path
-
- # Add the standard library facts.
- stdlib_info = ctx.attr._nogo_stdlib[NogoStdlibInfo]
- stdlib_facts = stdlib_info.facts
- inputs.append(stdlib_facts)
-
- # The nogo tool operates on a configuration serialized in JSON format.
- nogo_target_info = ctx.attr._target[NogoTargetInfo]
- go_ctx = go_context(ctx, goos = nogo_target_info.goos, goarch = nogo_target_info.goarch)
- facts = ctx.actions.declare_file(target.label.name + ".facts")
- raw_findings = ctx.actions.declare_file(target.label.name + ".raw_findings")
- config = struct(
- ImportPath = importpath,
- GoFiles = [src.path for src in srcs if src.path.endswith(".go")],
- NonGoFiles = [src.path for src in srcs if not src.path.endswith(".go")],
- GOOS = go_ctx.goos,
- GOARCH = go_ctx.goarch,
- BuildTags = go_ctx.gotags,
- FactMap = fact_map,
- ImportMap = import_map,
- StdlibFacts = stdlib_facts.path,
- )
- config_file = ctx.actions.declare_file(target.label.name + ".cfg")
- ctx.actions.write(config_file, config.to_json())
- inputs.append(config_file)
- args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
- ctx.actions.write(
- output = args_file,
- content = "\n".join(go_ctx.nogo_args + [
- "-binary=%s" % target_objfile.path,
- "-objdump_tool=%s" % ctx.files._objdump_tool[0].path,
- "-package=%s" % config_file.path,
- "-findings=%s" % raw_findings.path,
- "-facts=%s" % facts.path,
- ]),
- )
- ctx.actions.run(
- inputs = inputs + [args_file],
- outputs = [facts, raw_findings],
- tools = depset(go_ctx.runfiles.to_list() + ctx.files._objdump_tool),
- executable = ctx.files._check[0],
- mnemonic = "GoStaticAnalysis",
- progress_message = "Analyzing %s" % target.label,
- execution_requirements = {"supports-workers": "1"},
- arguments = [
- "--worker_debug=%s" % nogo_target_info.worker_debug,
- "@%s" % args_file.path,
- ],
- )
-
- # Flatten all findings from all dependencies.
- #
- # This is done because all the filtering must be done at the
- # top-level nogo_test to dynamically apply a configuration.
- # This does not actually add any additional work here, but
- # will simply propagate the full list of files.
- all_raw_findings = [stdlib_info.raw_findings] + depset(all_raw_findings).to_list() + [raw_findings]
-
- # Return the package facts as output.
- return [
- NogoInfo(
- facts = facts,
- raw_findings = all_raw_findings,
- importpath = importpath,
- binaries = binaries,
- srcs = srcs,
- deps = deps,
- ),
- ]
-
-nogo_aspect = go_rule(
- aspect,
- implementation = _nogo_aspect_impl,
- attr_aspects = [
- "deps",
- "library",
- "embed",
- ],
- attrs = {
- "_check": attr.label(
- default = "//tools/nogo/check:check",
- cfg = "host",
- ),
- "_objdump_tool": attr.label(
- default = "//tools/nogo:objdump_tool",
- cfg = "host",
- ),
- "_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- # The name of this attribute must not be _stdlib, since that
- # appears to be reserved for some internal bazel use.
- "_nogo_stdlib": attr.label(
- default = "//tools/nogo:stdlib",
- cfg = "host",
- ),
- },
-)
-
-def _nogo_test_impl(ctx):
- """Check nogo findings."""
- nogo_target_info = ctx.attr._target[NogoTargetInfo]
-
- # Ensure there's a single dependency.
- if len(ctx.attr.deps) != 1:
- fail("nogo_test requires exactly one dep.")
- raw_findings = ctx.attr.deps[0][NogoInfo].raw_findings
-
- # Build a step that applies the configuration.
- config_srcs = ctx.attr.config[NogoConfigInfo].srcs
- findings = ctx.actions.declare_file(ctx.label.name + ".findings")
- args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
- ctx.actions.write(
- output = args_file,
- content = "\n".join(
- ["-input=%s" % f.path for f in raw_findings] +
- ["-config=%s" % f.path for f in config_srcs] +
- ["-output=%s" % findings.path],
- ),
- )
- ctx.actions.run(
- inputs = raw_findings + ctx.files.srcs + config_srcs + [args_file],
- outputs = [findings],
- tools = depset(ctx.files._filter),
- executable = ctx.files._filter[0],
- mnemonic = "GoStaticAnalysis",
- progress_message = "Generating %s" % ctx.label,
- execution_requirements = {"supports-workers": "1"},
- arguments = [
- "--worker_debug=%s" % nogo_target_info.worker_debug,
- "@%s" % args_file.path,
- ],
- )
-
- # Build a runner that checks the filtered facts.
- #
- # Note that this calls the filter binary without any configuration, so all
- # findings will be included. But this is expected, since we've already
- # filtered out everything that should not be included.
- runner = ctx.actions.declare_file(ctx.label.name)
- runner_content = [
- "#!/bin/bash",
- "exec %s -check -input=%s" % (ctx.files._filter[0].short_path, findings.short_path),
- "",
- ]
- ctx.actions.write(runner, "\n".join(runner_content), is_executable = True)
-
- return [DefaultInfo(
- # The runner just executes the filter again, on the
- # newly generated filtered findings. We still need
- # the filter tool as part of our runfiles, however.
- runfiles = ctx.runfiles(files = ctx.files._filter + [findings]),
- executable = runner,
- ), OutputGroupInfo(
- # Propagate the filtered filters, for consumption by
- # build tooling. Note that the build tooling typically
- # pays attention to the mnemoic above, so this must be
- # what is expected by the tooling.
- nogo_findings = depset([findings]),
- )]
-
-nogo_test = rule(
- implementation = _nogo_test_impl,
- attrs = {
- "config": attr.label(
- mandatory = True,
- doc = "A rule of kind nogo_config.",
- ),
- "deps": attr.label_list(
- aspects = [nogo_aspect],
- doc = "Exactly one Go dependency to be analyzed.",
- ),
- "srcs": attr.label_list(
- allow_files = True,
- doc = "Relevant src files. This is ignored except to make the nogo_test directly affected by the files.",
- ),
- "_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- "_filter": attr.label(default = "//tools/nogo/filter:filter"),
- },
- test = True,
-)
-
-def _nogo_aspect_tricorder_impl(target, ctx):
- if ctx.rule.kind != "nogo_test" or OutputGroupInfo not in target:
- return []
- if not hasattr(target[OutputGroupInfo], "nogo_findings"):
- return []
- return [
- OutputGroupInfo(tricorder = target[OutputGroupInfo].nogo_findings),
- ]
-
-# Trivial aspect that forwards the findings from a nogo_test rule to
-# go/tricorder, which reads from the `tricorder` output group.
-nogo_aspect_tricorder = aspect(
- implementation = _nogo_aspect_tricorder_impl,
-)
diff --git a/tools/nogo/filter/BUILD b/tools/nogo/filter/BUILD
deleted file mode 100644
index e3049521e..000000000
--- a/tools/nogo/filter/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-load("//tools:defs.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "filter",
- srcs = ["main.go"],
- nogo = False,
- visibility = ["//visibility:public"],
- deps = [
- "//tools/nogo",
- "//tools/worker",
- "@in_gopkg_yaml_v2//:go_default_library",
- ],
-)
diff --git a/tools/nogo/filter/main.go b/tools/nogo/filter/main.go
deleted file mode 100644
index 4a925d03c..000000000
--- a/tools/nogo/filter/main.go
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2019 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.
-
-// Binary filter is the filters and reports nogo findings.
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "strings"
-
- yaml "gopkg.in/yaml.v2"
- "gvisor.dev/gvisor/tools/nogo"
- "gvisor.dev/gvisor/tools/worker"
-)
-
-type stringList []string
-
-func (s *stringList) String() string {
- return strings.Join(*s, ",")
-}
-
-func (s *stringList) Set(value string) error {
- *s = append(*s, value)
- return nil
-}
-
-var (
- inputFiles stringList
- configFiles stringList
- outputFile string
- showConfig bool
- check bool
-)
-
-func init() {
- flag.Var(&inputFiles, "input", "findings input files (gob format)")
- flag.StringVar(&outputFile, "output", "", "findings output file (json format)")
- flag.Var(&configFiles, "config", "findings configuration files")
- flag.BoolVar(&showConfig, "show-config", false, "dump configuration only")
- flag.BoolVar(&check, "check", false, "assume input is in json format")
-}
-
-func main() {
- worker.Work(run)
-}
-
-var (
- cachedFindings = worker.NewCache("findings") // With nogo.FindingSet.
- cachedFiltered = worker.NewCache("filtered") // With nogo.FindingSet.
- cachedConfigs = worker.NewCache("configs") // With nogo.Config.
- cachedFullConfigs = worker.NewCache("compiled") // With nogo.Config.
-)
-
-func loadFindings(filename string) nogo.FindingSet {
- return cachedFindings.Lookup([]string{filename}, func() worker.Sizer {
- r, err := os.Open(filename)
- if err != nil {
- log.Fatalf("unable to open input %q: %v", filename, err)
- }
- inputFindings, err := nogo.ExtractFindingsFrom(r, check /* json */)
- if err != nil {
- log.Fatalf("unable to extract findings from %s: %v", filename, err)
- }
- return inputFindings
- }).(nogo.FindingSet)
-}
-
-func loadConfig(filename string) *nogo.Config {
- return cachedConfigs.Lookup([]string{filename}, func() worker.Sizer {
- content, err := ioutil.ReadFile(filename)
- if err != nil {
- log.Fatalf("unable to read %s: %v", filename, err)
- }
- var newConfig nogo.Config // For current file.
- dec := yaml.NewDecoder(bytes.NewBuffer(content))
- dec.SetStrict(true)
- if err := dec.Decode(&newConfig); err != nil {
- log.Fatalf("unable to decode %s: %v", filename, err)
- }
- if showConfig {
- content, err := yaml.Marshal(&newConfig)
- if err != nil {
- log.Fatalf("error marshalling config: %v", err)
- }
- fmt.Fprintf(os.Stdout, "Loaded configuration from %s:\n%s\n", filename, string(content))
- }
- return &newConfig
- }).(*nogo.Config)
-}
-
-func loadConfigs(filenames []string) *nogo.Config {
- return cachedFullConfigs.Lookup(filenames, func() worker.Sizer {
- config := &nogo.Config{
- Global: make(nogo.AnalyzerConfig),
- Analyzers: make(map[nogo.AnalyzerName]nogo.AnalyzerConfig),
- }
- for _, filename := range configFiles {
- config.Merge(loadConfig(filename))
- if showConfig {
- mergedBytes, err := yaml.Marshal(config)
- if err != nil {
- log.Fatalf("error marshalling config: %v", err)
- }
- fmt.Fprintf(os.Stdout, "Merged configuration:\n%s\n", string(mergedBytes))
- }
- }
- if err := config.Compile(); err != nil {
- log.Fatalf("error compiling config: %v", err)
- }
- return config
- }).(*nogo.Config)
-}
-
-func run([]string) int {
- // Open and merge all configuations.
- config := loadConfigs(configFiles)
- if showConfig {
- return 0
- }
-
- // Load and filer available findings.
- var filteredFindings []nogo.Finding
- for _, filename := range inputFiles {
- // Note that this applies a caching strategy to the filtered
- // findings, because *this is by far the most expensive part of
- // evaluation*. The set of findings is large and applying the
- // configuration is complex. Therefore, we segment this cache
- // on each individual raw findings input file and the
- // configuration files. Note that this cache is keyed on all
- // the configuration files and each individual raw findings, so
- // is guaranteed to be safe. This allows us to reuse the same
- // filter result many times over, because e.g. all standard
- // library findings will be available to all packages.
- filteredFindings = append(filteredFindings,
- cachedFiltered.Lookup(append(configFiles, filename), func() worker.Sizer {
- inputFindings := loadFindings(filename)
- filteredFindings := make(nogo.FindingSet, 0, len(inputFindings))
- for _, finding := range inputFindings {
- if ok := config.ShouldReport(finding); ok {
- filteredFindings = append(filteredFindings, finding)
- }
- }
- return filteredFindings
- }).(nogo.FindingSet)...)
- }
-
- // Write the output (if required).
- //
- // If the outputFile is specified, then we exit here. Otherwise,
- // we continue to write to stdout and treat like a test.
- //
- // Note that the output of the filter is always json, which is
- // human readable and the format that is consumed by tricorder.
- if outputFile != "" {
- w, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- log.Fatalf("unable to open output file %q: %v", outputFile, err)
- }
- if err := nogo.WriteFindingsTo(w, filteredFindings, true /* json */); err != nil {
- log.Fatalf("unable to write findings: %v", err)
- }
- return 0
- }
-
- // Treat the run as a test.
- if len(filteredFindings) == 0 {
- fmt.Fprintf(os.Stdout, "PASS\n")
- return 0
- }
- for _, finding := range filteredFindings {
- fmt.Fprintf(os.Stdout, "%s\n", finding.String())
- }
- return 1
-}
diff --git a/tools/nogo/findings.go b/tools/nogo/findings.go
deleted file mode 100644
index a73bf1a09..000000000
--- a/tools/nogo/findings.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2019 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 nogo
-
-import (
- "encoding/gob"
- "encoding/json"
- "fmt"
- "go/token"
- "io"
- "os"
- "reflect"
- "sort"
-)
-
-// Finding is a single finding.
-type Finding struct {
- Category AnalyzerName
- Position token.Position
- Message string
-}
-
-// findingSize is the size of the finding struct itself.
-var findingSize = int64(reflect.TypeOf(Finding{}).Size())
-
-// Size implements worker.Sizer.Size.
-func (f *Finding) Size() int64 {
- return int64(len(f.Category)) + int64(len(f.Message)) + findingSize
-}
-
-// String implements fmt.Stringer.String.
-func (f *Finding) String() string {
- return fmt.Sprintf("%s: %s: %s", f.Category, f.Position.String(), f.Message)
-}
-
-// FindingSet is a collection of findings.
-type FindingSet []Finding
-
-// Size implmements worker.Sizer.Size.
-func (fs FindingSet) Size() int64 {
- size := int64(0)
- for _, finding := range fs {
- size += finding.Size()
- }
- return size
-}
-
-// Sort sorts all findings.
-func (fs FindingSet) Sort() {
- sort.Slice(fs, func(i, j int) bool {
- switch {
- case fs[i].Position.Filename < fs[j].Position.Filename:
- return true
- case fs[i].Position.Filename > fs[j].Position.Filename:
- return false
- case fs[i].Position.Line < fs[j].Position.Line:
- return true
- case fs[i].Position.Line > fs[j].Position.Line:
- return false
- case fs[i].Position.Column < fs[j].Position.Column:
- return true
- case fs[i].Position.Column > fs[j].Position.Column:
- return false
- case fs[i].Category < fs[j].Category:
- return true
- case fs[i].Category > fs[j].Category:
- return false
- case fs[i].Message < fs[j].Message:
- return true
- case fs[i].Message > fs[j].Message:
- return false
- default:
- return false
- }
- })
-}
-
-// WriteFindingsTo serializes findings.
-func WriteFindingsTo(w io.Writer, findings FindingSet, asJSON bool) error {
- // N.B. Sort all the findings in order to maximize cacheability.
- findings.Sort()
- if asJSON {
- enc := json.NewEncoder(w)
- return enc.Encode(findings)
- }
- enc := gob.NewEncoder(w)
- return enc.Encode(findings)
-}
-
-// ExtractFindingsFromFile loads findings from a file.
-func ExtractFindingsFromFile(filename string, asJSON bool) (FindingSet, error) {
- r, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- defer r.Close()
- return ExtractFindingsFrom(r, asJSON)
-}
-
-// ExtractFindingsFrom loads findings from an io.Reader.
-func ExtractFindingsFrom(r io.Reader, asJSON bool) (findings FindingSet, err error) {
- if asJSON {
- dec := json.NewDecoder(r)
- err = dec.Decode(&findings)
- } else {
- dec := gob.NewDecoder(r)
- err = dec.Decode(&findings)
- }
- return findings, err
-}
-
-func init() {
- gob.Register((*Finding)(nil))
- gob.Register((*FindingSet)(nil))
-}
diff --git a/tools/nogo/nogo.go b/tools/nogo/nogo.go
deleted file mode 100644
index a96cb400a..000000000
--- a/tools/nogo/nogo.go
+++ /dev/null
@@ -1,673 +0,0 @@
-// Copyright 2019 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 nogo implements binary analysis similar to bazel's nogo,
-// or the unitchecker package. It exists in order to provide additional
-// facilities for analysis, namely plumbing through the output from
-// dumping the generated binary (to analyze actual produced code).
-package nogo
-
-import (
- "bytes"
- "encoding/gob"
- "errors"
- "fmt"
- "go/ast"
- "go/build"
- "go/parser"
- "go/token"
- "go/types"
- "io"
- "io/ioutil"
- "log"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "sort"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/internal/facts"
- "golang.org/x/tools/go/gcexportdata"
- "golang.org/x/tools/go/types/objectpath"
-
- // Special case: flags live here and change overall behavior.
- "gvisor.dev/gvisor/tools/nogo/objdump"
- "gvisor.dev/gvisor/tools/worker"
-)
-
-// StdlibConfig is serialized as the configuration.
-//
-// This contains everything required for stdlib analysis.
-type StdlibConfig struct {
- Srcs []string
- GOOS string
- GOARCH string
- BuildTags []string
- ReleaseTags []string // Use build.Default if nil.
-}
-
-// PackageConfig is serialized as the configuration.
-//
-// This contains everything required for single package analysis.
-type PackageConfig struct {
- ImportPath string
- GoFiles []string
- NonGoFiles []string
- BuildTags []string
- ReleaseTags []string // Use build.Default if nil.
- GOOS string
- GOARCH string
- ImportMap map[string]string
- FactMap map[string]string
- StdlibFacts string
-}
-
-// loader is a fact-loader function.
-type loader func(string) ([]byte, error)
-
-// saver is a fact-saver function.
-type saver func([]byte) error
-
-// stdlibFact is used for serialiation.
-type stdlibFact struct {
- Package string
- Facts []byte
-}
-
-// stdlibFacts is a set of standard library facts.
-type stdlibFacts map[string][]byte
-
-// Size implements worker.Sizer.Size.
-func (sf stdlibFacts) Size() int64 {
- size := int64(0)
- for filename, data := range sf {
- size += int64(len(filename))
- size += int64(len(data))
- }
- return size
-}
-
-// EncodeTo serializes stdlibFacts.
-func (sf stdlibFacts) EncodeTo(w io.Writer) error {
- stdlibFactsSorted := make([]stdlibFact, 0, len(sf))
- for pkg, facts := range sf {
- stdlibFactsSorted = append(stdlibFactsSorted, stdlibFact{
- Package: pkg,
- Facts: facts,
- })
- }
- sort.Slice(stdlibFactsSorted, func(i, j int) bool {
- return stdlibFactsSorted[i].Package < stdlibFactsSorted[j].Package
- })
- enc := gob.NewEncoder(w)
- if err := enc.Encode(stdlibFactsSorted); err != nil {
- return err
- }
- return nil
-}
-
-// DecodeFrom deserializes stdlibFacts.
-func (sf stdlibFacts) DecodeFrom(r io.Reader) error {
- var stdlibFactsSorted []stdlibFact
- dec := gob.NewDecoder(r)
- if err := dec.Decode(&stdlibFactsSorted); err != nil {
- return err
- }
- for _, stdlibFact := range stdlibFactsSorted {
- sf[stdlibFact.Package] = stdlibFact.Facts
- }
- return nil
-}
-
-var (
- // cachedFacts caches by file (just byte data).
- cachedFacts = worker.NewCache("facts")
-
- // stdlibCachedFacts caches the standard library (stdlibFacts).
- stdlibCachedFacts = worker.NewCache("stdlib")
-)
-
-// factLoader loads facts.
-func (c *PackageConfig) factLoader(path string) (data []byte, err error) {
- filename, ok := c.FactMap[path]
- if ok {
- cb := cachedFacts.Lookup([]string{filename}, func() worker.Sizer {
- data, readErr := ioutil.ReadFile(filename)
- if readErr != nil {
- err = fmt.Errorf("error loading %q: %w", filename, readErr)
- return nil
- }
- return worker.CacheBytes(data)
- })
- if cb != nil {
- return []byte(cb.(worker.CacheBytes)), err
- }
- return nil, err
- }
- cb := stdlibCachedFacts.Lookup([]string{c.StdlibFacts}, func() worker.Sizer {
- r, openErr := os.Open(c.StdlibFacts)
- if openErr != nil {
- err = fmt.Errorf("error loading stdlib facts from %q: %w", c.StdlibFacts, openErr)
- return nil
- }
- defer r.Close()
- sf := make(stdlibFacts)
- if readErr := sf.DecodeFrom(r); readErr != nil {
- err = fmt.Errorf("error loading stdlib facts: %w", readErr)
- return nil
- }
- return sf
- })
- if cb != nil {
- return (cb.(stdlibFacts))[path], err
- }
- return nil, err
-}
-
-// shouldInclude indicates whether the file should be included.
-//
-// NOTE: This does only basic parsing of tags.
-func (c *PackageConfig) shouldInclude(path string) (bool, error) {
- ctx := build.Default
- ctx.GOOS = c.GOOS
- ctx.GOARCH = c.GOARCH
- ctx.BuildTags = c.BuildTags
- if c.ReleaseTags != nil {
- ctx.ReleaseTags = c.ReleaseTags
- }
- return ctx.MatchFile(filepath.Dir(path), filepath.Base(path))
-}
-
-// importer is an implementation of go/types.Importer.
-//
-// This wraps a configuration, which provides the map of package names to
-// files, and the facts. Note that this importer implementation will always
-// pass when a given package is not available.
-type importer struct {
- *PackageConfig
- fset *token.FileSet
- cache map[string]*types.Package
- lastErr error
- callback func(string) error
-}
-
-// Import implements types.Importer.Import.
-func (i *importer) Import(path string) (*types.Package, error) {
- if path == "unsafe" {
- // Special case: go/types has pre-defined type information for
- // unsafe. We ensure that this package is correct, in case any
- // analyzers are specifically looking for this.
- return types.Unsafe, nil
- }
-
- // Call the internal callback. This is used to resolve loading order
- // for the standard library. See checkStdlib.
- if i.callback != nil {
- if err := i.callback(path); err != nil {
- i.lastErr = err
- return nil, err
- }
- }
-
- // Check the cache.
- if pkg, ok := i.cache[path]; ok && pkg.Complete() {
- return pkg, nil
- }
-
- // Actually load the data.
- realPath, ok := i.ImportMap[path]
- var (
- rc io.ReadCloser
- err error
- )
- if !ok {
- // Not found in the import path. Attempt to find the package
- // via the standard library.
- rc, err = findStdPkg(i.GOOS, i.GOARCH, path)
- } else {
- // Open the file.
- rc, err = os.Open(realPath)
- }
- if err != nil {
- i.lastErr = err
- return nil, err
- }
- defer rc.Close()
-
- // Load all exported data.
- r, err := gcexportdata.NewReader(rc)
- if err != nil {
- return nil, err
- }
-
- return gcexportdata.Read(r, i.fset, i.cache, path)
-}
-
-// ErrSkip indicates the package should be skipped.
-var ErrSkip = errors.New("skipped")
-
-// CheckStdlib checks the standard library.
-//
-// This constructs a synthetic package configuration for each library in the
-// standard library sources, and call CheckPackage repeatedly.
-//
-// Note that not all parts of the source are expected to build. We skip obvious
-// test files, and cmd files, which should not be dependencies.
-func CheckStdlib(config *StdlibConfig, analyzers []*analysis.Analyzer) (allFindings FindingSet, facts []byte, err error) {
- if len(config.Srcs) == 0 {
- return nil, nil, nil
- }
-
- // Ensure all paths are normalized.
- for i := 0; i < len(config.Srcs); i++ {
- config.Srcs[i] = path.Clean(config.Srcs[i])
- }
-
- // Calculate the root source directory. This is always a directory
- // named 'src', of which we simply take the first we find. This is a
- // bit fragile, but works for all currently known Go source
- // configurations.
- //
- // Note that there may be extra files outside of the root source
- // directory; we simply ignore those.
- rootSrcPrefix := ""
- for _, file := range config.Srcs {
- const src = "/src/"
- i := strings.Index(file, src)
- if i == -1 {
- // Superfluous file.
- continue
- }
-
- // Index of first character after /src/.
- i += len(src)
- rootSrcPrefix = file[:i]
- break
- }
-
- // Go standard library packages using Go 1.18 type parameter features.
- //
- // As of writing, analysis tooling is not updated to support type
- // parameters and will choke on these packages. We skip these packages
- // entirely for now.
- //
- // TODO(b/201686256): remove once tooling can handle type parameters.
- usesTypeParams := map[string]struct{}{
- "constraints": struct{}{}, // golang.org/issue/45458
- "maps": struct{}{}, // golang.org/issue/47649
- "slices": struct{}{}, // golang.org/issue/45955
- }
-
- // Aggregate all files by directory.
- packages := make(map[string]*PackageConfig)
- for _, file := range config.Srcs {
- if !strings.HasPrefix(file, rootSrcPrefix) {
- // Superflouous file.
- continue
- }
-
- d := path.Dir(file)
- if len(rootSrcPrefix) >= len(d) {
- continue // Not a file.
- }
- pkg := d[len(rootSrcPrefix):]
-
- // Skip cmd packages and obvious test files: see above.
- if strings.HasPrefix(pkg, "cmd/") || strings.HasSuffix(file, "_test.go") {
- continue
- }
-
- if _, ok := usesTypeParams[pkg]; ok {
- log.Printf("WARNING: Skipping package %q: type param analysis not yet supported", pkg)
- continue
- }
-
- c, ok := packages[pkg]
- if !ok {
- c = &PackageConfig{
- ImportPath: pkg,
- GOOS: config.GOOS,
- GOARCH: config.GOARCH,
- BuildTags: config.BuildTags,
- ReleaseTags: config.ReleaseTags,
- }
- packages[pkg] = c
- }
- // Add the files appropriately. Note that they will be further
- // filtered by architecture and build tags below, so this need
- // not be done immediately.
- if strings.HasSuffix(file, ".go") {
- c.GoFiles = append(c.GoFiles, file)
- } else {
- c.NonGoFiles = append(c.NonGoFiles, file)
- }
- }
-
- // Closure to check a single package.
- localStdlibFacts := make(stdlibFacts)
- localStdlibErrs := make(map[string]error)
- stdlibCachedFacts.Lookup([]string{""}, func() worker.Sizer {
- return localStdlibFacts
- })
- var checkOne func(pkg string) error // Recursive.
- checkOne = func(pkg string) error {
- // Is this already done?
- if _, ok := localStdlibFacts[pkg]; ok {
- return nil
- }
- // Did this fail previously?
- if _, ok := localStdlibErrs[pkg]; ok {
- return nil
- }
-
- // Lookup the configuration.
- config, ok := packages[pkg]
- if !ok {
- return nil // Not known.
- }
-
- // Find the binary package, and provide to objdump.
- rc, err := findStdPkg(config.GOOS, config.GOARCH, pkg)
- if err != nil {
- // If there's no binary for this package, it is likely
- // not built with the distribution. That's fine, we can
- // just skip analysis.
- localStdlibErrs[pkg] = err
- return nil
- }
-
- // Provide the input.
- oldReader := objdump.Reader
- objdump.Reader = rc // For analysis.
- defer func() {
- rc.Close()
- objdump.Reader = oldReader // Restore.
- }()
-
- // Run the analysis.
- findings, factData, err := CheckPackage(config, analyzers, checkOne)
- if err != nil {
- // If we can't analyze a package from the standard library,
- // then we skip it. It will simply not have any findings.
- localStdlibErrs[pkg] = err
- return nil
- }
- localStdlibFacts[pkg] = factData
- allFindings = append(allFindings, findings...)
- return nil
- }
-
- // Check all packages.
- //
- // Note that this may call checkOne recursively, so it's not guaranteed
- // to evaluate in the order provided here. We do ensure however, that
- // all packages are evaluated.
- for pkg := range packages {
- if err := checkOne(pkg); err != nil {
- return nil, nil, err
- }
- }
-
- // Sanity check.
- if len(localStdlibFacts) == 0 {
- return nil, nil, fmt.Errorf("no stdlib facts found: misconfiguration?")
- }
-
- // Write out all findings.
- buf := bytes.NewBuffer(nil)
- if err := localStdlibFacts.EncodeTo(buf); err != nil {
- return nil, nil, fmt.Errorf("error serialized stdlib facts: %v", err)
- }
-
- // Write out all errors.
- for pkg, err := range localStdlibErrs {
- log.Printf("WARNING: error while processing %v: %v", pkg, err)
- }
-
- // Return all findings.
- return allFindings, buf.Bytes(), nil
-}
-
-// sanityCheckScope checks that all object in astTypes map to the correct
-// objects in binaryTypes. Note that we don't check whether the sets are the
-// same, we only care about the fidelity of objects in astTypes.
-//
-// When an inconsistency is identified, we record it in the astToBinaryMap.
-// This allows us to dynamically replace facts and correct for the issue. The
-// total number of mismatches is returned.
-func sanityCheckScope(astScope *types.Scope, binaryTypes *types.Package, binaryScope *types.Scope, astToBinary map[types.Object]types.Object) error {
- for _, x := range astScope.Names() {
- fe := astScope.Lookup(x)
- path, err := objectpath.For(fe)
- if err != nil {
- continue // Not an encoded object.
- }
- se, err := objectpath.Object(binaryTypes, path)
- if err != nil {
- continue // May be unused, see below.
- }
- if fe.Id() != se.Id() {
- // These types are incompatible. This means that when
- // this objectpath is loading from the binaryTypes (for
- // dependencies) it will resolve to a fact for that
- // type. We don't actually care about this error since
- // we do the rewritten, but may as well alert.
- log.Printf("WARNING: Object %s is a victim of go/issues/44195.", fe.Id())
- }
- se = binaryScope.Lookup(x)
- if se == nil {
- // The fact may not be exported in the objectdata, if
- // it is package internal. This is fine, as nothing out
- // of this package can use these symbols.
- continue
- }
- // Save the translation.
- astToBinary[fe] = se
- }
- for i := 0; i < astScope.NumChildren(); i++ {
- if err := sanityCheckScope(astScope.Child(i), binaryTypes, binaryScope, astToBinary); err != nil {
- return err
- }
- }
- return nil
-}
-
-// sanityCheckTypes checks that two types are sane. The total number of
-// mismatches is returned.
-func sanityCheckTypes(astTypes, binaryTypes *types.Package, astToBinary map[types.Object]types.Object) error {
- return sanityCheckScope(astTypes.Scope(), binaryTypes, binaryTypes.Scope(), astToBinary)
-}
-
-// CheckPackage runs all given analyzers.
-//
-// The implementation was adapted from [1], which was in turn adpated from [2].
-// This returns a list of matching analysis issues, or an error if the analysis
-// could not be completed.
-//
-// [1] bazelbuid/rules_go/tools/builders/nogo_main.go
-// [2] golang.org/x/tools/go/checker/internal/checker
-func CheckPackage(config *PackageConfig, analyzers []*analysis.Analyzer, importCallback func(string) error) (findings []Finding, factData []byte, err error) {
- imp := &importer{
- PackageConfig: config,
- fset: token.NewFileSet(),
- cache: make(map[string]*types.Package),
- callback: importCallback,
- }
-
- // Load all source files.
- var syntax []*ast.File
- for _, file := range config.GoFiles {
- include, err := config.shouldInclude(file)
- if err != nil {
- return nil, nil, fmt.Errorf("error evaluating file %q: %v", file, err)
- }
- if !include {
- continue
- }
- s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments)
- if err != nil {
- return nil, nil, fmt.Errorf("error parsing file %q: %v", file, err)
- }
- syntax = append(syntax, s)
- }
-
- // Check type information.
- typesSizes := types.SizesFor("gc", config.GOARCH)
- typeConfig := types.Config{Importer: imp}
- typesInfo := &types.Info{
- Types: make(map[ast.Expr]types.TypeAndValue),
- Uses: make(map[*ast.Ident]types.Object),
- Defs: make(map[*ast.Ident]types.Object),
- Implicits: make(map[ast.Node]types.Object),
- Scopes: make(map[ast.Node]*types.Scope),
- Selections: make(map[*ast.SelectorExpr]*types.Selection),
- }
- astTypes, err := typeConfig.Check(config.ImportPath, imp.fset, syntax, typesInfo)
- if err != nil && imp.lastErr != ErrSkip {
- return nil, nil, fmt.Errorf("error checking types: %w", err)
- }
-
- // Load all facts using the astTypes, although it may need reconciling
- // later on. See the fact functions below.
- astFacts, err := facts.Decode(astTypes, config.factLoader)
- if err != nil {
- return nil, nil, fmt.Errorf("error decoding facts: %w", err)
- }
-
- // Sanity check all types and record metadata to prevent
- // https://github.com/golang/go/issues/44195.
- //
- // This block loads the binary types, whose encoding will be well
- // defined and aligned with any downstream consumers. Below in the fact
- // functions for the analysis, we serialize types to both the astFacts
- // and the binaryFacts if available. The binaryFacts are the final
- // encoded facts in order to ensure compatibility. We keep the
- // intermediate astTypes in order to allow exporting and importing
- // within the local package under analysis.
- var (
- astToBinary = make(map[types.Object]types.Object)
- binaryFacts *facts.Set
- )
- if _, ok := config.ImportMap[config.ImportPath]; ok {
- binaryTypes, err := imp.Import(config.ImportPath)
- if err != nil {
- return nil, nil, fmt.Errorf("error loading self: %w", err)
- }
- if err := sanityCheckTypes(astTypes, binaryTypes, astToBinary); err != nil {
- return nil, nil, fmt.Errorf("error sanity checking types: %w", err)
- }
- binaryFacts, err = facts.Decode(binaryTypes, config.factLoader)
- if err != nil {
- return nil, nil, fmt.Errorf("error decoding facts: %w", err)
- }
- }
-
- // Register fact types and establish dependencies between analyzers.
- // The visit closure will execute recursively, and populate results
- // will all required analysis results.
- results := make(map[*analysis.Analyzer]interface{})
- var visit func(*analysis.Analyzer) error // For recursion.
- visit = func(a *analysis.Analyzer) error {
- if _, ok := results[a]; ok {
- return nil
- }
-
- // Run recursively for all dependencies.
- for _, req := range a.Requires {
- if err := visit(req); err != nil {
- return err
- }
- }
-
- // Run the analysis.
- localFactsFilter := make(map[reflect.Type]bool)
- for _, f := range a.FactTypes {
- localFactsFilter[reflect.TypeOf(f)] = true
- }
- p := &analysis.Pass{
- Analyzer: a,
- Fset: imp.fset,
- Files: syntax,
- Pkg: astTypes,
- TypesInfo: typesInfo,
- ResultOf: results, // All results.
- Report: func(d analysis.Diagnostic) {
- findings = append(findings, Finding{
- Category: AnalyzerName(a.Name),
- Position: imp.fset.Position(d.Pos),
- Message: d.Message,
- })
- },
- ImportPackageFact: astFacts.ImportPackageFact,
- ExportPackageFact: func(fact analysis.Fact) {
- astFacts.ExportPackageFact(fact)
- if binaryFacts != nil {
- binaryFacts.ExportPackageFact(fact)
- }
- },
- ImportObjectFact: astFacts.ImportObjectFact,
- ExportObjectFact: func(obj types.Object, fact analysis.Fact) {
- astFacts.ExportObjectFact(obj, fact)
- // Note that if no object is recorded in
- // astToBinary and binaryFacts != nil, then the
- // object doesn't appear in the exported data.
- // It was likely an internal object to the
- // package, and there is no meaningful
- // downstream consumer of the fact.
- if binaryObj, ok := astToBinary[obj]; ok && binaryFacts != nil {
- binaryFacts.ExportObjectFact(binaryObj, fact)
- }
- },
- AllPackageFacts: func() []analysis.PackageFact { return astFacts.AllPackageFacts(localFactsFilter) },
- AllObjectFacts: func() []analysis.ObjectFact { return astFacts.AllObjectFacts(localFactsFilter) },
- TypesSizes: typesSizes,
- }
- result, err := a.Run(p)
- if err != nil {
- return fmt.Errorf("error running analysis %s: %v", a, err)
- }
-
- // Sanity check & save the result.
- if got, want := reflect.TypeOf(result), a.ResultType; got != want {
- return fmt.Errorf("error: analyzer %s returned a result of type %v, but declared ResultType %v", a, got, want)
- }
- results[a] = result
- return nil // Success.
- }
-
- // Visit all analyzers recursively.
- for _, a := range analyzers {
- if imp.lastErr == ErrSkip {
- continue // No local analysis.
- }
- if err := visit(a); err != nil {
- return nil, nil, err // Already has context.
- }
- }
-
- // Return all findings. Note that we have a preference to returning the
- // binary facts if available, so that downstream consumers of these
- // facts will find the export aligns with the internal type details.
- // See the block above with the call to sanityCheckTypes.
- if binaryFacts != nil {
- return findings, binaryFacts.Encode(), nil
- }
- return findings, astFacts.Encode(), nil
-}
-
-func init() {
- gob.Register((*stdlibFact)(nil))
-}
diff --git a/tools/nogo/objdump/BUILD b/tools/nogo/objdump/BUILD
deleted file mode 100644
index da56efdf7..000000000
--- a/tools/nogo/objdump/BUILD
+++ /dev/null
@@ -1,10 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "objdump",
- srcs = ["objdump.go"],
- nogo = False,
- visibility = ["//tools:__subpackages__"],
-)
diff --git a/tools/nogo/objdump/objdump.go b/tools/nogo/objdump/objdump.go
deleted file mode 100644
index 48484abf3..000000000
--- a/tools/nogo/objdump/objdump.go
+++ /dev/null
@@ -1,96 +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 objdump is a wrapper around relevant objdump flags.
-package objdump
-
-import (
- "flag"
- "fmt"
- "io"
- "os"
- "os/exec"
-)
-
-var (
- // Binary is the binary under analysis.
- //
- // See Reader, below.
- binary = flag.String("binary", "", "binary under analysis")
-
- // Reader is the input stream.
- //
- // This may be set instead of Binary.
- Reader io.Reader
-
- // objdumpTool is the tool used to dump a binary.
- objdumpTool = flag.String("objdump_tool", "", "tool used to dump a binary")
-)
-
-// LoadRaw reads the raw object output.
-func LoadRaw(fn func(r io.Reader) error) error {
- var r io.Reader
- if *binary != "" {
- f, err := os.Open(*binary)
- if err != nil {
- return err
- }
- defer f.Close()
- r = f
- } else if Reader != nil {
- r = Reader
- } else {
- // We have no input stream.
- return fmt.Errorf("no binary or reader provided")
- }
- return fn(r)
-}
-
-// Load reads the objdump output.
-func Load(fn func(r io.Reader) error) error {
- var (
- args []string
- stdin io.Reader
- )
- if *binary != "" {
- args = append(args, *binary)
- } else if Reader != nil {
- stdin = Reader
- } else {
- // We have no input stream or binary.
- return fmt.Errorf("no binary or reader provided")
- }
-
- // Construct our command.
- cmd := exec.Command(*objdumpTool, args...)
- cmd.Stdin = stdin
- cmd.Stderr = os.Stderr
- out, err := cmd.StdoutPipe()
- if err != nil {
- return err
- }
- if err := cmd.Start(); err != nil {
- return err
- }
-
- // Call the user hook.
- userErr := fn(out)
-
- // Wait for the dump to finish.
- if err := cmd.Wait(); userErr == nil && err != nil {
- return err
- }
-
- return userErr
-}