summaryrefslogtreecommitdiffhomepage
path: root/tools/nogo
diff options
context:
space:
mode:
Diffstat (limited to 'tools/nogo')
-rw-r--r--tools/nogo/build.go3
-rw-r--r--tools/nogo/matchers.go24
-rw-r--r--tools/nogo/nogo.go45
-rw-r--r--tools/nogo/util/BUILD9
-rw-r--r--tools/nogo/util/util.go85
5 files changed, 149 insertions, 17 deletions
diff --git a/tools/nogo/build.go b/tools/nogo/build.go
index 37947b5c3..39c2ae418 100644
--- a/tools/nogo/build.go
+++ b/tools/nogo/build.go
@@ -26,6 +26,9 @@ var (
// and should not have any special prefix applied.
internalPrefix = fmt.Sprintf("^")
+ // internalDefault is applied when no paths are provided.
+ internalDefault = fmt.Sprintf("%s/.*", notPath("external"))
+
// externalPrefix is external workspace packages.
externalPrefix = "^external/"
)
diff --git a/tools/nogo/matchers.go b/tools/nogo/matchers.go
index 57a250501..5c39be630 100644
--- a/tools/nogo/matchers.go
+++ b/tools/nogo/matchers.go
@@ -16,7 +16,6 @@ package nogo
import (
"go/token"
- "path/filepath"
"regexp"
"strings"
@@ -44,11 +43,30 @@ type pathRegexps struct {
func buildRegexps(prefix string, args ...string) []*regexp.Regexp {
result := make([]*regexp.Regexp, 0, len(args))
for _, arg := range args {
- result = append(result, regexp.MustCompile(filepath.Join(prefix, arg)))
+ result = append(result, regexp.MustCompile(prefix+arg))
}
return result
}
+// notPath works around the lack of backtracking.
+//
+// It is used to construct a regular expression for non-matching components.
+func notPath(name string) string {
+ sb := strings.Builder{}
+ sb.WriteString("(")
+ for i := range name {
+ if i > 0 {
+ sb.WriteString("|")
+ }
+ sb.WriteString(name[:i])
+ sb.WriteString("[^")
+ sb.WriteByte(name[i])
+ sb.WriteString("/][^/]*")
+ }
+ sb.WriteString(")")
+ return sb.String()
+}
+
// ShouldReport implements matcher.ShouldReport.
func (p *pathRegexps) ShouldReport(d analysis.Diagnostic, fs *token.FileSet) bool {
fullPos := fs.Position(d.Pos).String()
@@ -79,7 +97,7 @@ func externalExcluded(paths ...string) *pathRegexps {
// internalMatches returns a path matcher for internal packages.
func internalMatches() *pathRegexps {
return &pathRegexps{
- expr: buildRegexps(internalPrefix, ".*"),
+ expr: buildRegexps(internalPrefix, internalDefault),
include: true,
}
}
diff --git a/tools/nogo/nogo.go b/tools/nogo/nogo.go
index 40e48540d..120fdcff5 100644
--- a/tools/nogo/nogo.go
+++ b/tools/nogo/nogo.go
@@ -202,29 +202,41 @@ func checkStdlib(config *stdlibConfig, ac map[*analysis.Analyzer]matcher) ([]str
config.Srcs[i] = path.Clean(config.Srcs[i])
}
- // Calculate the root directory.
- longestPrefix := path.Dir(config.Srcs[0])
- for _, file := range config.Srcs[1:] {
- for i := 0; i < len(file) && i < len(longestPrefix); i++ {
- if file[i] != longestPrefix[i] {
- // Truncate here; will stop the loop.
- longestPrefix = longestPrefix[:i]
- break
- }
+ // 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
}
- }
- if len(longestPrefix) > 0 && longestPrefix[len(longestPrefix)-1] != '/' {
- longestPrefix += "/"
+
+ // Index of first character after /src/.
+ i += len(src)
+ rootSrcPrefix = file[:i]
+ break
}
// 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(longestPrefix) >= len(d) {
+ if len(rootSrcPrefix) >= len(d) {
continue // Not a file.
}
- pkg := path.Dir(file)[len(longestPrefix):]
+ pkg := d[len(rootSrcPrefix):]
// Skip cmd packages and obvious test files: see above.
if strings.HasPrefix(pkg, "cmd/") || strings.HasSuffix(file, "_test.go") {
continue
@@ -303,6 +315,11 @@ func checkStdlib(config *stdlibConfig, ac map[*analysis.Analyzer]matcher) ([]str
checkOne(pkg)
}
+ // Sanity check.
+ if len(stdlibFacts) == 0 {
+ return nil, nil, fmt.Errorf("no stdlib facts found: misconfiguration?")
+ }
+
// Write out all findings.
factData, err := json.Marshal(stdlibFacts)
if err != nil {
diff --git a/tools/nogo/util/BUILD b/tools/nogo/util/BUILD
new file mode 100644
index 000000000..7ab340b51
--- /dev/null
+++ b/tools/nogo/util/BUILD
@@ -0,0 +1,9 @@
+load("//tools:defs.bzl", "go_library")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "util",
+ srcs = ["util.go"],
+ visibility = ["//visibility:public"],
+)
diff --git a/tools/nogo/util/util.go b/tools/nogo/util/util.go
new file mode 100644
index 000000000..919fec799
--- /dev/null
+++ b/tools/nogo/util/util.go
@@ -0,0 +1,85 @@
+// 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 util contains nogo-related utilities.
+package util
+
+import (
+ "fmt"
+ "io/ioutil"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// findingRegexp is used to parse findings.
+var findingRegexp = regexp.MustCompile(`([a-zA-Z0-9_\/\.-]+): (-|([a-zA-Z0-9_\/\.-]+):([0-9]+)(:([0-9]+))?): (.*)`)
+
+const (
+ categoryIndex = 1
+ fullPathAndLineIndex = 2
+ fullPathIndex = 3
+ lineIndex = 4
+ messageIndex = 7
+)
+
+// Finding is a single finding.
+type Finding struct {
+ Category string
+ Path string
+ Line int
+ Message string
+}
+
+// ExtractFindingsFromFile loads findings from a file.
+func ExtractFindingsFromFile(filename string) ([]Finding, error) {
+ content, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ return ExtractFindingsFromBytes(content)
+}
+
+// ExtractFindingsFromBytes loads findings from bytes.
+func ExtractFindingsFromBytes(content []byte) (findings []Finding, err error) {
+ lines := strings.Split(string(content), "\n")
+ for _, singleLine := range lines {
+ // Skip blank lines.
+ singleLine = strings.TrimSpace(singleLine)
+ if singleLine == "" {
+ continue
+ }
+ m := findingRegexp.FindStringSubmatch(singleLine)
+ if m == nil {
+ // We shouldn't see findings like this.
+ return findings, fmt.Errorf("poorly formated line: %v", singleLine)
+ }
+ if m[fullPathAndLineIndex] == "-" {
+ continue // No source file available.
+ }
+ // Cleanup the message.
+ message := m[messageIndex]
+ message = strings.Replace(message, " → ", "\n → ", -1)
+ message = strings.Replace(message, " or ", "\n or ", -1)
+ // Construct a new annotation.
+ lineNumber, _ := strconv.ParseUint(m[lineIndex], 10, 32)
+ findings = append(findings, Finding{
+ Category: m[categoryIndex],
+ Path: m[fullPathIndex],
+ Line: int(lineNumber),
+ Message: message,
+ })
+ }
+ return findings, nil
+}