build/analysis: copy cockroachdb code to third_party

We are currently fetching the full cockroach repository just for a hand
full of nogo passes. As the version we use is licensed under Apache 2.0,
we can copy them to third_party, allowing us to modify them and keep them
stable.

Change-Id: Ia28b181296138eef922485b6517d1e0066766715
Reviewed-on: https://review.monogon.dev/c/monogon/+/4486
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/third_party/com_github_cockroachdb_cockroach/fmtsafe/BUILD.bazel b/third_party/com_github_cockroachdb_cockroach/fmtsafe/BUILD.bazel
new file mode 100644
index 0000000..adebac2
--- /dev/null
+++ b/third_party/com_github_cockroachdb_cockroach/fmtsafe/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "fmtsafe",
+    srcs = [
+        "fmtsafe.go",
+        "functions.go",
+    ],
+    importpath = "source.monogon.dev/third_party/com_github_cockroachdb_cockroach/fmtsafe",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//third_party/com_github_cockroachdb_cockroach/errwrap",
+        "@org_golang_x_tools//go/analysis",
+        "@org_golang_x_tools//go/analysis/passes/inspect",
+        "@org_golang_x_tools//go/ast/inspector",
+        "@org_golang_x_tools//go/types/typeutil",
+    ],
+)
diff --git a/third_party/com_github_cockroachdb_cockroach/fmtsafe/fmtsafe.go b/third_party/com_github_cockroachdb_cockroach/fmtsafe/fmtsafe.go
new file mode 100644
index 0000000..a32efc3
--- /dev/null
+++ b/third_party/com_github_cockroachdb_cockroach/fmtsafe/fmtsafe.go
@@ -0,0 +1,294 @@
+// Copyright 2020 The Cockroach Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package fmtsafe
+
+import (
+	"errors"
+	"fmt"
+	"go/ast"
+	"go/token"
+	"go/types"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+	"golang.org/x/tools/go/ast/inspector"
+	"golang.org/x/tools/go/types/typeutil"
+)
+
+// Doc documents this pass.
+var Doc = `checks that log and error functions don't leak PII.
+
+This linter checks the following:
+
+- that the format string in Infof(), Errorf() and similar calls is a
+  constant string.
+
+  This check is essential for correctness because format strings
+  are assumed to be PII-free and always safe for reporting in
+  telemetry or PII-free logs.
+
+- that the message strings in errors.New() and similar calls that
+  construct error objects is a constant string.
+
+  This check is provided to encourage hygiene: errors
+  constructed using non-constant strings are better constructed using
+  a formatting function instead, which makes the construction more
+  readable and encourage the provision of PII-free reportable details.
+
+It is possible for a call site *in a test file* to opt the format/message
+string out of the linter using /* nolint:fmtsafe */ after the format
+argument. This escape hatch is not available in non-test code.
+`
+
+// Analyzer defines this pass.
+var Analyzer = &analysis.Analyzer{
+	Name:     "fmtsafe",
+	Doc:      Doc,
+	Requires: []*analysis.Analyzer{inspect.Analyzer},
+	Run:      run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+	// Our analyzer just wants to see function definitions
+	// and call points.
+	nodeFilter := []ast.Node{
+		(*ast.FuncDecl)(nil),
+		(*ast.CallExpr)(nil),
+	}
+
+	// fmtOrMsgStr, if non-nil, indicates an incoming
+	// format or message string in the argument list of the
+	// current function.
+	//
+	// The pointer is set at the beginning of every function declaration
+	// for a function recognized by this linter (= any of those listed
+	// in functions.go). In non-recognized function, it is set to nil to
+	// indicate there is no known format or message string.
+	var fmtOrMsgStr *types.Var
+	var enclosingFnName string
+
+	// Now traverse the ASTs. The preorder traversal visits each
+	// function declaration node before its body, so we always get to
+	// set fmtOrMsgStr before the call points inside the body are
+	// visited.
+	inspect.Preorder(nodeFilter, func(n ast.Node) {
+		// Catch-all for possible bugs in the linter code.
+		defer func() {
+			if r := recover(); r != nil {
+				if err, ok := r.(error); ok {
+					pass.Reportf(n.Pos(), "internal linter error: %v", err)
+					return
+				}
+				panic(r)
+			}
+		}()
+
+		if fd, ok := n.(*ast.FuncDecl); ok {
+			// This is the function declaration header. Obtain the formal
+			// parameter and the name of the function being defined.
+			// We use the name in subsequent error messages to provide
+			// more context, and to facilitate the definition
+			// of precise exceptions in lint_test.go.
+			enclosingFnName, fmtOrMsgStr = maybeGetConstStr(pass, fd)
+			return
+		}
+		// At a call site.
+		call := n.(*ast.CallExpr)
+		checkCallExpr(pass, enclosingFnName, call, fmtOrMsgStr)
+	})
+	return nil, nil
+}
+
+func maybeGetConstStr(
+	pass *analysis.Pass, fd *ast.FuncDecl,
+) (enclosingFnName string, res *types.Var) {
+	if fd.Body == nil {
+		// No body. Since there won't be any callee, take
+		// an early return.
+		return "", nil
+	}
+
+	// What's the function being defined?
+	fn := pass.TypesInfo.Defs[fd.Name].(*types.Func)
+	if fn == nil {
+		return "", nil
+	}
+	fnName := stripVendor(fn.FullName())
+
+	var wantVariadic bool
+	var argIdx int
+
+	if requireConstFmt[fnName] {
+		// Expect a variadic function and the format parameter
+		// next-to-last in the parameter list.
+		wantVariadic = true
+		argIdx = -2
+	} else if requireConstMsg[fnName] {
+		// Expect a non-variadic function and the format parameter last in
+		// the parameter list.
+		wantVariadic = false
+		argIdx = -1
+	} else {
+		// Not a recognized function. Bail.
+		return fn.Name(), nil
+	}
+
+	sig := fn.Type().(*types.Signature)
+	if sig.Variadic() != wantVariadic {
+		panic(fmt.Errorf("expected variadic %v, got %v", wantVariadic, sig.Variadic()))
+	}
+
+	params := sig.Params()
+	nparams := params.Len()
+
+	// Is message or format param a string?
+	if nparams+argIdx < 0 {
+		panic(errors.New("not enough arguments"))
+	}
+	if p := params.At(nparams + argIdx); p.Type() == types.Typ[types.String] {
+		// Found it!
+		return fn.Name(), p
+	}
+	return fn.Name(), nil
+}
+
+func checkCallExpr(pass *analysis.Pass, enclosingFnName string, call *ast.CallExpr, fv *types.Var) {
+	// What's the function being called?
+	cfn := typeutil.Callee(pass.TypesInfo, call)
+	if cfn == nil {
+		// Not a call to a statically identified function.
+		// We can't lint.
+		return
+	}
+	fn, ok := cfn.(*types.Func)
+	if !ok {
+		// Not a function with a name. We can't lint either.
+		return
+	}
+
+	// What's the full name of the callee? This includes the package
+	// path and, for methods, the type of the supporting object.
+	fnName := stripVendor(fn.FullName())
+
+	var wantVariadic bool
+	var argIdx int
+	var argType string
+
+	// Do the analysis of the callee.
+	if requireConstFmt[fnName] {
+		// Expect a variadic function and the format parameter
+		// next-to-last in the parameter list.
+		wantVariadic = true
+		argIdx = -2
+		argType = "format"
+	} else if requireConstMsg[fnName] {
+		// Expect a non-variadic function and the format parameter last in
+		// the parameter list.
+		wantVariadic = false
+		argIdx = -1
+		argType = "message"
+	} else {
+		// Not a recognized function. Bail.
+		return
+	}
+
+	typ := pass.TypesInfo.Types[call.Fun].Type
+	if typ == nil {
+		panic(errors.New("can't find function type"))
+	}
+
+	sig, ok := typ.(*types.Signature)
+	if !ok {
+		panic(errors.New("can't derive signature"))
+	}
+	if sig.Variadic() != wantVariadic {
+		panic(fmt.Errorf("expected variadic %v, got %v", wantVariadic, sig.Variadic()))
+	}
+
+	idx := sig.Params().Len() + argIdx
+	if idx < 0 {
+		panic(errors.New("not enough parameters"))
+	}
+
+	lit := pass.TypesInfo.Types[call.Args[idx]].Value
+	if lit != nil {
+		// A literal or constant! All is well.
+		return
+	}
+
+	// Not a constant. If it's a variable and the variable
+	// refers to the incoming format/message from the arg list,
+	// tolerate that.
+	if fv != nil {
+		if id, ok := call.Args[idx].(*ast.Ident); ok {
+			if pass.TypesInfo.ObjectOf(id) == fv {
+				// Same arg as incoming. All good.
+				return
+			}
+		}
+	}
+
+	// If the argument is opting out of the linter with a special
+	// comment, tolerate that.
+	if hasNoLintComment(pass, call, idx) {
+		return
+	}
+
+	pass.Reportf(call.Lparen, escNl("%s(): %s argument is not a constant expression"+Tip),
+		enclosingFnName, argType)
+}
+
+// Tip is exported for use in tests.
+var Tip = `
+Tip: use YourFuncf("descriptive prefix %%s", ...) or list new formatting wrappers in pkg/testutils/lint/passes/fmtsafe/functions.go.`
+
+func hasNoLintComment(pass *analysis.Pass, call *ast.CallExpr, idx int) bool {
+	fPos, f := findContainingFile(pass, call)
+
+	if !strings.HasSuffix(fPos.Name(), "_test.go") {
+		// The nolint: escape hatch is only supported in test files.
+		return false
+	}
+
+	startPos := call.Args[idx].End()
+	endPos := call.Rparen
+	if idx < len(call.Args)-1 {
+		endPos = call.Args[idx+1].Pos()
+	}
+	for _, cg := range f.Comments {
+		if cg.Pos() > endPos || cg.End() < startPos {
+			continue
+		}
+		for _, c := range cg.List {
+			if strings.Contains(c.Text, "nolint:fmtsafe") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func findContainingFile(pass *analysis.Pass, n ast.Node) (*token.File, *ast.File) {
+	fPos := pass.Fset.File(n.Pos())
+	for _, f := range pass.Files {
+		if pass.Fset.File(f.Pos()) == fPos {
+			return fPos, f
+		}
+	}
+	panic(fmt.Errorf("cannot file file for %v", n))
+}
+
+func stripVendor(s string) string {
+	if i := strings.Index(s, "/vendor/"); i != -1 {
+		s = s[i+len("/vendor/"):]
+	}
+	return s
+}
+
+func escNl(msg string) string {
+	return strings.ReplaceAll(msg, "\n", "\\n++")
+}
diff --git a/third_party/com_github_cockroachdb_cockroach/fmtsafe/functions.go b/third_party/com_github_cockroachdb_cockroach/fmtsafe/functions.go
new file mode 100644
index 0000000..717fa35
--- /dev/null
+++ b/third_party/com_github_cockroachdb_cockroach/fmtsafe/functions.go
@@ -0,0 +1,45 @@
+// Copyright 2020 The Cockroach Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package fmtsafe
+
+import (
+	"source.monogon.dev/third_party/com_github_cockroachdb_cockroach/errwrap"
+)
+
+// requireConstMsg records functions for which the last string
+// argument must be a constant string.
+var requireConstMsg = map[string]bool{}
+
+// requireConstFmt records functions for which the string arg
+// before the final ellipsis must be a constant string.
+var requireConstFmt = map[string]bool{
+	// Logging things.
+	"log.Printf":           true,
+	"log.Fatalf":           true,
+	"log.Panicf":           true,
+	"(*log.Logger).Fatalf": true,
+	"(*log.Logger).Panicf": true,
+	"(*log.Logger).Printf": true,
+
+	"(go.etcd.io/etcd/raft/v3.Logger).Debugf":   true,
+	"(go.etcd.io/etcd/raft/v3.Logger).Infof":    true,
+	"(go.etcd.io/etcd/raft/v3.Logger).Warningf": true,
+	"(go.etcd.io/etcd/raft/v3.Logger).Errorf":   true,
+	"(go.etcd.io/etcd/raft/v3.Logger).Fatalf":   true,
+	"(go.etcd.io/etcd/raft/v3.Logger).Panicf":   true,
+
+	"(google.golang.org/grpc/grpclog.Logger).Infof":    true,
+	"(google.golang.org/grpc/grpclog.Logger).Warningf": true,
+	"(google.golang.org/grpc/grpclog.Logger).Errorf":   true,
+}
+
+func init() {
+	for errorFn, formatStringIndex := range errwrap.ErrorFnFormatStringIndex {
+		if formatStringIndex < 0 {
+			requireConstMsg[errorFn] = true
+		} else {
+			requireConstFmt[errorFn] = true
+		}
+	}
+}