diff --git a/.gitignore b/.gitignore index 12ce4b2da..004846edc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,7 @@ postgres_exporter_integration_test cover.out cover.*.out .coverage +bin +release *.prom .metrics.*.*.prom diff --git a/.travis.yml b/.travis.yml index f0bb1e296..322be1a33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ script: - make docker - make test-integration - make cover.out +- make release - $HOME/gopath/bin/goveralls -coverprofile=cover.out -service=travis-ci after_success: - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS @@ -38,7 +39,8 @@ deploy: provider: releases api_key: secure: rwlge/Rs3wnWyfKRhD9fd5GviVe0foYUp20DY3AjKdDjhtwScA1EeR9QHOkB3raze52en0+KkpqlLCWbt3q4CRT7+ku1DNKhd6VWALdTZ1RPJYvNlU6CKJdRnWUJsECmSBsShXlbiYR8axqNVedzFPFGKzS9gYlFN6rr7pez/JZhxqucopZ6I+TkRHMELrFXyQK7/Y2bNRCLC4a+rGsjKeLLtYXbRXCmS0G4BSJEBRk7d69fIRzBApCMfrcLftgHzPuPth616yyUusQSCQYvaZ5tlwrPP8/E0wG3SVJVeDCMuDOSBZ9M6vNzR8W8VR/hxQamegn1OQgC5kNOaLZCTcJ5xguRouqb+FNFBqrd/Zi6vESo7RiVLULawzwxkh9sIPa3WZYDb3VK/Z/cpggUeR7wAu0S5ZYEvJHRefIZpqofZEHzDE3Blqp5yErz05e/zmjpd6HHK3f/UHmRRYfbulkvGT3aL/dlq5GcFvuxVC/vTL2VPvg9cGbqtf7PakC5IhoHpDs35tOyLxifOBLHvkwtGSxEfsCohIG8Hz2XFD83EsxgOiKSXVPLNd6yxjdqZj7OeAKFFU3bzGndnRbDIXaf987IN1imgUtP6wegfImoRStqxN4gEwwIMFsZCF86Ug4eLhlajLbWhudriDxDPBM/F9950aVxLwmWh9l5cRI= - file: postgres_exporter + file_glob: true + file: release/* on: tags: true branch: master diff --git a/Dockerfile b/Dockerfile index 6e71fc2dd..c640287a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ FROM scratch -COPY postgres_exporter /postgres_exporter +ARG binary + +COPY $binary /postgres_exporter EXPOSE 9187 diff --git a/Makefile b/Makefile index 1ad4b41e9..cdc2e8932 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,78 @@ COVERDIR = .coverage TOOLDIR = tools +BINDIR = bin +RELEASEDIR = release -GO_SRC := $(shell find . -name '*.go' ! -path '*/vendor/*' ! -path 'tools/*' ) -GO_DIRS := $(shell find . -type d -name '*.go' ! -path '*/vendor/*' ! -path 'tools/*' ) +DIRS = $(BINDIR) $(RELEASEDIR) + +GO_SRC := $(shell find . -name '*.go' ! -path '*/vendor/*' ! -path 'tools/*' ! -path 'bin/*' ! -path 'release/*' ) +GO_DIRS := $(shell find . -type d -name '*.go' ! -path '*/vendor/*' ! -path 'tools/*' ! -path 'bin/*' ! -path 'release/*' ) GO_PKGS := $(shell go list ./... | grep -v '/vendor/') CONTAINER_NAME ?= wrouesnel/postgres_exporter:latest -VERSION ?= $(shell git describe --dirty) +BINARY := $(shell basename $(shell pwd)) +VERSION ?= $(shell git describe --dirty 2>/dev/null) +VERSION_SHORT ?= $(shell git describe --abbrev=0 2>/dev/null) + +ifeq ($(VERSION),) +VERSION := v0.0.0 +endif + +ifeq ($(VERSION_SHORT),) +VERSION_SHORT := v0.0.0 +endif + +# By default this list is filtered down to some common platforms. +platforms := $(subst /,-,$(shell go tool dist list | grep -e linux -e windows -e darwin | grep -e 386 -e amd64)) +PLATFORM_BINS := $(patsubst %,$(BINDIR)/$(BINARY)_$(VERSION_SHORT)_%/$(BINARY),$(platforms)) +PLATFORM_DIRS := $(patsubst %,$(BINDIR)/$(BINARY)_$(VERSION_SHORT)_%,$(platforms)) +PLATFORM_TARS := $(patsubst %,$(RELEASEDIR)/$(BINARY)_$(VERSION_SHORT)_%.tar.gz,$(platforms)) + +# These are evaluated on use, and so will have the correct values in the build +# rule (https://vic.demuzere.be/articles/golang-makefile-crosscompile/) +PLATFORMS_TEMP = $(subst -, ,$(patsubst $(BINDIR)/$(BINARY)_$(VERSION_SHORT)_%/$(BINARY),%,$@)) +GOOS = $(word 1, $(PLATFORMS_TEMP)) +GOARCH = $(word 2, $(PLATFORMS_TEMP)) + +CURRENT_PLATFORM := $(BINDIR)/$(BINARY)_$(VERSION_SHORT)_$(shell go env GOOS)-$(shell go env GOARCH)/$(BINARY) + +CONCURRENT_LINTERS ?= +ifeq ($(CONCURRENT_LINTERS),) +CONCURRENT_LINTERS = $(shell gometalinter --help | grep -o 'concurrency=\w*' | cut -d= -f2 | cut -d' ' -f1) +endif -CONCURRENT_LINTERS ?= $(shell cat /proc/cpuinfo | grep processor | wc -l) LINTER_DEADLINE ?= 30s +$(shell mkdir -p $(DIRS)) + export PATH := $(TOOLDIR)/bin:$(PATH) SHELL := env PATH=$(PATH) /bin/bash -all: style lint test postgres_exporter +all: style lint test binary -# Cross compilation (e.g. if you are on a Mac) -cross: docker-build docker +binary: $(BINARY) -# Simple go build -postgres_exporter: $(GO_SRC) - CGO_ENABLED=0 go build -a -ldflags "-extldflags '-static' -X main.Version=$(VERSION)" -o postgres_exporter . +$(BINARY): $(CURRENT_PLATFORM) + ln -sf $< $@ -postgres_exporter_integration_test: $(GO_SRC) - CGO_ENABLED=0 go test -c -tags integration \ - -a -ldflags "-extldflags '-static' -X main.Version=$(VERSION)" -o postgres_exporter_integration_test -cover -covermode count . +$(PLATFORM_BINS): $(GO_SRC) + CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -a \ + -ldflags "-extldflags '-static' -X main.Version=$(VERSION)" \ + -o $@ . + +$(PLATFORM_DIRS): $(PLATFORM_BINS) + +$(PLATFORM_TARS): $(RELEASEDIR)/%.tar.gz : $(BINDIR)/% + tar -czf $@ -C $(BINDIR) $$(basename $<) + +release-bin: $(PLATFORM_BINS) + +release: $(PLATFORM_TARS) # Take a go build and turn it into a minimal container -docker: postgres_exporter - docker build -t $(CONTAINER_NAME) . +docker: $(CURRENT_PLATFORM) + docker build --build-arg=binary=$(CURRENT_PLATFORM) -t $(CONTAINER_NAME) . style: tools gometalinter --disable-all --enable=gofmt --vendor @@ -42,14 +84,17 @@ lint: tools fmt: tools gofmt -s -w $(GO_SRC) -run-tests: tools - mkdir -p $(COVERDIR) - rm -f $(COVERDIR)/* +postgres_exporter_integration_test: $(GO_SRC) + CGO_ENABLED=0 go test -c -tags integration \ + -a -ldflags "-extldflags '-static' -X main.Version=$(VERSION)" \ + -o postgres_exporter_integration_test -cover -covermode count . + +test: tools + @mkdir -p $(COVERDIR) + @rm -f $(COVERDIR)/* for pkg in $(GO_PKGS) ; do \ go test -v -covermode count -coverprofile=$(COVERDIR)/$$(echo $$pkg | tr '/' '-').out $$pkg || exit 1 ; \ done - -test: run-tests gocovmerge $(shell find $(COVERDIR) -name '*.out') > cover.test.out test-integration: postgres_exporter postgres_exporter_integration_test @@ -58,24 +103,13 @@ test-integration: postgres_exporter postgres_exporter_integration_test cover.out: tools gocovmerge cover.*.out > cover.out -# Do a self-contained docker build - we pull the official upstream container -# and do a self-contained build. -docker-build: - docker run -v $(shell pwd):/go/src/github.com/wrouesnel/postgres_exporter \ - -v $(shell pwd):/real_src \ - -e SHELL_UID=$(shell id -u) -e SHELL_GID=$(shell id -g) \ - -w /go/src/github.com/wrouesnel/postgres_exporter \ - golang:1.9-wheezy \ - /bin/bash -c "make >&2 && chown $$SHELL_UID:$$SHELL_GID ./postgres_exporter" - docker build -t $(CONTAINER_NAME) . - -push: - docker push $(CONTAINER_NAME) - +clean: + [ ! -z $(BINDIR) ] && [ -e $(BINDIR) ] && find $(BINDIR) -print -delete || /bin/true + [ ! -z $(COVERDIR) ] && [ -e $(COVERDIR) ] && find $(COVERDIR) -print -delete || /bin/true + [ ! -z $(RELEASEDIR) ] && [ -e $(RELEASEDIR) ] && find $(RELEASEDIR) -print -delete || /bin/true + rm -f postgres_exporter postgres_exporter_integration_test + tools: $(MAKE) -C $(TOOLDIR) - -clean: - rm -rf postgres_exporter postgres_exporter_integration_test $(COVERDIR) - -.PHONY: tools docker-build docker lint fmt test vet push cross clean + +.PHONY: tools style fmt test all release binary clean diff --git a/README.md b/README.md index d4487babf..33c703cde 100644 --- a/README.md +++ b/README.md @@ -126,3 +126,11 @@ GRANT SELECT ON postgres_exporter.pg_stat_replication TO postgres_exporter; > ``` > DATA_SOURCE_NAME=postgresql://postgres_exporter:password@localhost:5432/postgres?sslmode=disable > ``` + +# Hacking + +* The build system is currently only supported for Linux-like platforms. It + depends on GNU Make. +* To build a copy for your current architecture run `make binary` or just `make` + This will create a symlink to the just built binary in the root directory. +* To build release tar balls run `make release`. diff --git a/tools/Makefile b/tools/Makefile index 00a21db8c..331c0b6b3 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -9,13 +9,15 @@ SHELL := env PATH=$(PATH) /bin/bash THIS_FILE := $(lastword $(MAKEFILE_LIST)) # This function is used to get the linters used by metalinter -get_metalinters := gometalinter --help | grep -oP ' \w+ \(.+\)' | tr -s ' ' | cut -d' ' -f3 | grep -oP '[^()]+' +get_metalinters := gometalinter --help | grep -oP '\s+\w+:\s*\(.+\)' | tr -s ' ' | cut -d' ' -f3 | grep -oP '[^()]+' +# This is a list of external tools we want to vendor TOOL_SRCS := github.com/kardianos/govendor \ github.com/wadey/gocovmerge \ github.com/mattn/goveralls \ github.com/alecthomas/gometalinter +# This is populated by imported dependencies from gometalinter METATOOL_SRCS := GO_SRC := $(shell find $(SOURCEDIR) -name '*.go') diff --git a/tools/vendor/github.com/GoASTScanner/gas/Dockerfile b/tools/vendor/github.com/GoASTScanner/gas/Dockerfile new file mode 100644 index 000000000..7fea92ca2 --- /dev/null +++ b/tools/vendor/github.com/GoASTScanner/gas/Dockerfile @@ -0,0 +1,23 @@ +# Docker version must be 17.05 or higher to allow multistage build +# See build and run instructions in README.md + +# Builds Gas for utilization +FROM golang:1.8.1-alpine as builder +ENV workspace /go/src/github.com/GoASTScanner/gas +ENV GOPATH /go +COPY . $workspace +WORKDIR $workspace + +RUN go vet $(go list ./... | grep -v /vendor/) +RUN CGO_ENABLED=0 go build -o gas . + +######################################################## + +# Runs Gas on all Go files in the current directory when +# 'docker run' command in README is given +FROM alpine:3.6 + +COPY --from=builder /go/src/github.com/GoASTScanner/gas/gas / + +# Mounted directory should be placed into the workdir +CMD /gas $(find . -path ./vendor -prune -o -type f -name "*.go") diff --git a/tools/vendor/github.com/GoASTScanner/gas/README.md b/tools/vendor/github.com/GoASTScanner/gas/README.md index 23a824a91..81c2ed5e3 100644 --- a/tools/vendor/github.com/GoASTScanner/gas/README.md +++ b/tools/vendor/github.com/GoASTScanner/gas/README.md @@ -112,3 +112,20 @@ file. The output format is controlled by the '-fmt' flag, and the output file is # Write output in json format to results.json $ gas -fmt=json -out=results.json *.go ``` + +### Docker container + +A Dockerfile is included with the Gas source code to provide a container that +allows users to easily run Gas on their code. It builds Gas, then runs it on +all Go files in your current directory. Use the following commands to build +and run locally: + +To build: (run command in cloned Gas source code directory) + docker build --build-arg http_proxy --build-arg https_proxy + --build-arg no_proxy -t goastscanner/gas:latest . + +To run: (run command in desired directory with Go files) + docker run -v $PWD:$PWD --workdir $PWD goastscanner/gas:latest + +Note: Docker version 17.05 or later is required (to permit multistage build). +``` diff --git a/tools/vendor/github.com/alecthomas/gometalinter/README.md b/tools/vendor/github.com/alecthomas/gometalinter/README.md index b9d8b3d1f..809a60ad9 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/README.md +++ b/tools/vendor/github.com/alecthomas/gometalinter/README.md @@ -19,7 +19,6 @@ - [2. Analyse the debug output](#2-analyse-the-debug-output) - [3. Report an issue.](#3-report-an-issue) - [How do I filter issues between two git refs?](#how-do-i-filter-issues-between-two-git-refs) -- [Details](#details) - [Checkstyle XML format](#checkstyle-xml-format) @@ -57,12 +56,13 @@ It is intended for use with editor/IDE integration. - [go vet](https://golang.org/cmd/vet/) - Reports potential errors that otherwise compile. - [go tool vet --shadow](https://golang.org/cmd/vet/#hdr-Shadowed_variables) - Reports variables that may have been unintentionally shadowed. - [gotype](https://golang.org/x/tools/cmd/gotype) - Syntactic and semantic analysis similar to the Go compiler. +- [gotype -x](https://golang.org/x/tools/cmd/gotype) - Syntactic and semantic analysis in external test packages (similar to the Go compiler). - [deadcode](https://github.com/tsenart/deadcode) - Finds unused code. - [gocyclo](https://github.com/alecthomas/gocyclo) - Computes the cyclomatic complexity of functions. - [golint](https://github.com/golang/lint) - Google's (mostly stylistic) linter. - [varcheck](https://github.com/opennota/check) - Find unused global variables and constants. - [structcheck](https://github.com/opennota/check) - Find unused struct fields. -- [aligncheck](https://github.com/opennota/check) - Warn about un-optimally aligned structures. +- [maligned](https://github.com/mdempsky/maligned) - Detect structs that would take less memory if their fields were sorted. - [errcheck](https://github.com/kisielk/errcheck) - Check that error return values are used. - [megacheck](https://github.com/dominikh/go-tools/tree/master/cmd/megacheck) - Run staticcheck, gosimple and unused, sharing work. - [dupl](https://github.com/mibk/dupl) - Reports potentially duplicated code. @@ -81,6 +81,7 @@ Disabled by default (enable with `--enable=`): - [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) - Report simplifications in code. - [lll](https://github.com/walle/lll) - Report long lines (see `--line-length=N`). - [misspell](https://github.com/client9/misspell) - Finds commonly misspelled English words. +- [nakedret](https://github.com/alexkohler/nakedret) - Finds naked returns. - [unparam](https://github.com/mvdan/unparam) - Find unused function parameters. - [unused](https://github.com/dominikh/go-tools/tree/master/cmd/unused) - Find unused variables. - [safesql](https://github.com/stripe/safesql) - Finds potential SQL injection vulnerabilities. @@ -91,14 +92,15 @@ Additional linters can be added through the command line with `--linter=NAME:COM ## Configuration file gometalinter now supports a JSON configuration file which can be loaded via -`--config=`. The format of this file is determined by the Config struct -in `config.go`. +`--config=`. The format of this file is determined by the `Config` struct +in [config.go](https://github.com/alecthomas/gometalinter/blob/master/config.go). The configuration file mostly corresponds to command-line flags, with the following exceptions: - Linters defined in the configuration file will overlay existing definitions, not replace them. - "Enable" defines the exact set of linters that will be enabled (default - linters are disabled). + linters are disabled). `--help` displays the list of default linters with the exact names + you must use. Here is an example configuration file: @@ -108,6 +110,34 @@ Here is an example configuration file: } ``` +### Adding Custom linters + +Linters can be added and customized from the config file using the `Linters` field. +Linters supports the following fields: + +* `Command` - the path to the linter binary and any default arguments +* `Pattern` - a regular expression used to parse the linter output +* `IsFast` - if the linter should be run when the `--fast` flag is used +* `PartitionStrategy` - how paths args should be passed to the linter command: + * `directories` - call the linter once with a list of all the directories + * `files` - call the linter once with a list of all the files + * `packages` - call the linter once with a list of all the package paths + * `files-by-package` - call the linter once per package with a list of the + files in the package. + * `single-directory` - call the linter once per directory + +The config for default linters can be overridden by using the name of the +linter. + +Additional linters can be configured via the command line using the format +`NAME:COMMAND:PATTERN`. + +Example: + +``` +$ gometalinter --linter='vet:go tool vet -printfuncs=Infof,Debugf,Warningf,Errorf:PATH:LINE:MESSAGE' . +``` + ## Installing There are two options for installing gometalinter. @@ -171,7 +201,8 @@ Install all known linters: $ gometalinter --install Installing: structcheck - aligncheck + maligned + nakedret deadcode gocyclo ineffassign @@ -308,21 +339,6 @@ gometalinter |& revgrep master # Show issues between master and HEAD (or gometalinter |& revgrep origin/master # Show issues that haven't been pushed. ``` -## Details - -Additional linters can be configured via the command line: - -``` -$ gometalinter --linter='vet:go tool vet -printfuncs=Infof,Debugf,Warningf,Errorf {path}:PATH:LINE:MESSAGE' . -stutter.go:21:15:warning: error return value not checked (defer a.Close()) (errcheck) -stutter.go:22:15:warning: error return value not checked (defer a.Close()) (errcheck) -stutter.go:27:6:warning: error return value not checked (doit() // test for errcheck) (errcheck) -stutter.go:9::warning: unused global variable unusedGlobal (varcheck) -stutter.go:13::warning: unused struct field MyStruct.Unused (structcheck) -stutter.go:12:6:warning: exported type MyStruct should have comment or be unexported (golint) -stutter.go:16:6:warning: exported type PublicUndocumented should have comment or be unexported (deadcode) -``` - ## Checkstyle XML format `gometalinter` supports [checkstyle](http://checkstyle.sourceforge.net/) diff --git a/tools/vendor/github.com/alecthomas/gometalinter/aggregate.go b/tools/vendor/github.com/alecthomas/gometalinter/aggregate.go index 18f8f38e5..f4b44e4b0 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/aggregate.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/aggregate.go @@ -5,27 +5,21 @@ import ( "strings" ) -type ( - issueKey struct { - path string - line, col int - message string - } - - multiIssue struct { - *Issue - linterNames []string - } -) +type issueKey struct { + path string + line, col int + message string +} -func maybeAggregateIssues(issues chan *Issue) chan *Issue { - if !config.Aggregate { - return issues - } - return aggregateIssues(issues) +type multiIssue struct { + *Issue + linterNames []string } -func aggregateIssues(issues chan *Issue) chan *Issue { +// AggregateIssueChan reads issues from a channel, aggregates issues which have +// the same file, line, vol, and message, and returns aggregated issues on +// a new channel. +func AggregateIssueChan(issues chan *Issue) chan *Issue { out := make(chan *Issue, 1000000) issueMap := make(map[issueKey]*multiIssue) go func() { diff --git a/tools/vendor/github.com/alecthomas/gometalinter/checkstyle.go b/tools/vendor/github.com/alecthomas/gometalinter/checkstyle.go index b5f09dc3b..5122604e7 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/checkstyle.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/checkstyle.go @@ -4,7 +4,7 @@ import ( "encoding/xml" "fmt" - "gopkg.in/alecthomas/kingpin.v3-unstable" + kingpin "gopkg.in/alecthomas/kingpin.v3-unstable" ) type checkstyleOutput struct { diff --git a/tools/vendor/github.com/alecthomas/gometalinter/config.go b/tools/vendor/github.com/alecthomas/gometalinter/config.go index 9e125a4bd..bdcb4d04f 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/config.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/config.go @@ -8,12 +8,12 @@ import ( ) // Config for gometalinter. This can be loaded from a JSON file with --config. -type Config struct { // nolint: aligncheck - // A map of linter name to ":". +type Config struct { // nolint: maligned + // A map from linter name -> . // - // should always include {path} as the target directory to execute. Globs in - // are expanded by gometalinter (not by the shell). - Linters map[string]string + // For backwards compatibility, the value stored in the JSON blob can also + // be a string of the form ":". + Linters map[string]StringOrLinterConfig // The set of linters that should be enabled. Enable []string @@ -51,6 +51,35 @@ type Config struct { // nolint: aligncheck EnableGC bool Aggregate bool EnableAll bool + + // Warn if a nolint directive was never matched to a linter issue + WarnUnmatchedDirective bool + + formatTemplate *template.Template +} + +type StringOrLinterConfig LinterConfig + +func (c *StringOrLinterConfig) UnmarshalJSON(raw []byte) error { + var linterConfig LinterConfig + // first try to un-marshall directly into struct + origErr := json.Unmarshal(raw, &linterConfig) + if origErr == nil { + *c = StringOrLinterConfig(linterConfig) + return nil + } + + // i.e. bytes didn't represent the struct, treat them as a string + var linterSpec string + if err := json.Unmarshal(raw, &linterSpec); err != nil { + return origErr + } + linter, err := parseLinterConfigSpec("", linterSpec) + if err != nil { + return err + } + *c = StringOrLinterConfig(linter) + return nil } type jsonDuration time.Duration @@ -70,17 +99,16 @@ func (td *jsonDuration) Duration() time.Duration { return time.Duration(*td) } -// TODO: should be a field on Config struct -var formatTemplate = &template.Template{} - var sortKeys = []string{"none", "path", "line", "column", "severity", "message", "linter"} // Configuration defaults. var config = &Config{ - Format: "{{.Path}}:{{.Line}}:{{if .Col}}{{.Col}}{{end}}:{{.Severity}}: {{.Message}} ({{.Linter}})", + Format: DefaultIssueFormat, + Linters: map[string]StringOrLinterConfig{}, Severity: map[string]string{ "gotype": "error", + "gotypex": "error", "test": "error", "testify": "error", "vet": "error", diff --git a/tools/vendor/github.com/alecthomas/gometalinter/directives.go b/tools/vendor/github.com/alecthomas/gometalinter/directives.go index bd3ffa31a..10f2903d2 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/directives.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/directives.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "go/ast" "go/parser" "go/token" @@ -14,6 +15,7 @@ type ignoredRange struct { col int start, end int linters []string + matched bool } func (i *ignoredRange) matches(issue *Issue) bool { @@ -35,6 +37,14 @@ func (i *ignoredRange) near(col, start int) bool { return col == i.col && i.end == start-1 } +func (i *ignoredRange) String() string { + linters := strings.Join(i.linters, ",") + if len(i.linters) == 0 { + linters = "all" + } + return fmt.Sprintf("%s:%d-%d", linters, i.start, i.end) +} + type ignoredRanges []*ignoredRange func (ir ignoredRanges) Len() int { return len(ir) } @@ -66,12 +76,43 @@ func (d *directiveParser) IsIgnored(issue *Issue) bool { d.lock.Unlock() for _, r := range ranges { if r.matches(issue) { + debug("nolint: matched %s to issue %s", r, issue) + r.matched = true return true } } return false } +// Unmatched returns all the ranges which were never used to ignore an issue +func (d *directiveParser) Unmatched() map[string]ignoredRanges { + unmatched := map[string]ignoredRanges{} + for path, ranges := range d.files { + for _, ignore := range ranges { + if !ignore.matched { + unmatched[path] = append(unmatched[path], ignore) + } + } + } + return unmatched +} + +// LoadFiles from a list of directories +func (d *directiveParser) LoadFiles(paths []string) error { + d.lock.Lock() + defer d.lock.Unlock() + filenames, err := pathsToFileGlobs(paths) + if err != nil { + return err + } + for _, filename := range filenames { + ranges := d.parseFile(filename) + sort.Sort(ranges) + d.files[filename] = ranges + } + return nil +} + // Takes a set of ignoredRanges, determines if they immediately precede a statement // construct, and expands the range to include that construct. Why? So you can // precede a function or struct with //nolint @@ -150,7 +191,28 @@ func filterIssuesViaDirectives(directives *directiveParser, issues chan *Issue) out <- issue } } + + if config.WarnUnmatchedDirective { + for _, issue := range warnOnUnusedDirective(directives) { + out <- issue + } + } close(out) }() return out } + +func warnOnUnusedDirective(directives *directiveParser) []*Issue { + out := []*Issue{} + for path, ranges := range directives.Unmatched() { + for _, ignore := range ranges { + issue, _ := NewIssue("nolint", config.formatTemplate) + issue.Path = path + issue.Line = ignore.start + issue.Col = ignore.col + issue.Message = "nolint directive did not match any issue" + out = append(out, issue) + } + } + return out +} diff --git a/tools/vendor/github.com/alecthomas/gometalinter/execute.go b/tools/vendor/github.com/alecthomas/gometalinter/execute.go index 9dc90352e..57267a5d6 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/execute.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/execute.go @@ -8,14 +8,13 @@ import ( "path/filepath" "reflect" "regexp" - "sort" "strconv" "strings" "sync" "time" "github.com/google/shlex" - "gopkg.in/alecthomas/kingpin.v3-unstable" + kingpin "gopkg.in/alecthomas/kingpin.v3-unstable" ) type Vars map[string]string @@ -41,34 +40,8 @@ func (v Vars) Replace(s string) string { return s } -// Severity of linter message. -type Severity string - -// Linter message severity levels. -const ( // nolint: deadcode - Error Severity = "error" - Warning Severity = "warning" -) - -type Issue struct { - Linter string `json:"linter"` - Severity Severity `json:"severity"` - Path string `json:"path"` - Line int `json:"line"` - Col int `json:"col"` - Message string `json:"message"` -} - -func (i *Issue) String() string { - buf := new(bytes.Buffer) - err := formatTemplate.Execute(buf, i) - kingpin.FatalIfError(err, "Invalid output format") - return buf.String() -} - type linterState struct { *Linter - paths []string issues chan *Issue vars Vars exclude *regexp.Regexp @@ -76,26 +49,34 @@ type linterState struct { deadline <-chan time.Time } -func (l *linterState) Partitions() ([][]string, error) { - command := l.vars.Replace(l.Command) - cmdArgs, err := parseCommand(command) +func (l *linterState) Partitions(paths []string) ([][]string, error) { + cmdArgs, err := parseCommand(l.command()) if err != nil { return nil, err } - parts, err := l.Linter.PartitionStrategy(cmdArgs, l.paths) + parts, err := l.Linter.PartitionStrategy(cmdArgs, paths) if err != nil { return nil, err } return parts, nil } +func (l *linterState) command() string { + return l.vars.Replace(l.Command) +} + func runLinters(linters map[string]*Linter, paths []string, concurrency int, exclude, include *regexp.Regexp) (chan *Issue, chan error) { errch := make(chan error, len(linters)) concurrencych := make(chan bool, concurrency) incomingIssues := make(chan *Issue, 1000000) - processedIssues := filterIssuesViaDirectives( - newDirectiveParser(), - maybeSortIssues(maybeAggregateIssues(incomingIssues))) + + directiveParser := newDirectiveParser() + if config.WarnUnmatchedDirective { + directiveParser.LoadFiles(paths) + } + + processedIssues := maybeSortIssues(filterIssuesViaDirectives( + directiveParser, maybeAggregateIssues(incomingIssues))) vars := Vars{ "duplthreshold": fmt.Sprintf("%d", config.DuplThreshold), @@ -105,43 +86,46 @@ func runLinters(linters map[string]*Linter, paths []string, concurrency int, exc "min_occurrences": fmt.Sprintf("%d", config.MinOccurrences), "min_const_length": fmt.Sprintf("%d", config.MinConstLength), "tests": "", + "not_tests": "true", } if config.Test { - vars["tests"] = "-t" + vars["tests"] = "true" + vars["not_tests"] = "" } wg := &sync.WaitGroup{} + id := 1 for _, linter := range linters { deadline := time.After(config.Deadline.Duration()) state := &linterState{ Linter: linter, issues: incomingIssues, - paths: paths, vars: vars, exclude: exclude, include: include, deadline: deadline, } - partitions, err := state.Partitions() + partitions, err := state.Partitions(paths) if err != nil { errch <- err continue } for _, args := range partitions { wg.Add(1) + concurrencych <- true // Call the goroutine with a copy of the args array so that the // contents of the array are not modified by the next iteration of // the above for loop - go func(args []string) { - concurrencych <- true - err := executeLinter(state, args) + go func(id int, args []string) { + err := executeLinter(id, state, args) if err != nil { errch <- err } <-concurrencych wg.Done() - }(append(args)) + }(id, args) + id++ } } @@ -153,13 +137,14 @@ func runLinters(linters map[string]*Linter, paths []string, concurrency int, exc return processedIssues, errch } -func executeLinter(state *linterState, args []string) error { +func executeLinter(id int, state *linterState, args []string) error { if len(args) == 0 { return fmt.Errorf("missing linter command") } start := time.Now() - debug("executing %s", strings.Join(args, " ")) + dbg := namespacedDebug(fmt.Sprintf("[%s.%d]: ", state.Name, id)) + dbg("executing %s", strings.Join(args, " ")) buf := bytes.NewBuffer(nil) command := args[0] cmd := exec.Command(command, args[1:]...) // nolint: gas @@ -191,12 +176,12 @@ func executeLinter(state *linterState, args []string) error { } if err != nil { - debug("warning: %s returned %s: %s", command, err, buf.String()) + dbg("warning: %s returned %s: %s", command, err, buf.String()) } - processOutput(state, buf.Bytes()) + processOutput(dbg, state, buf.Bytes()) elapsed := time.Since(start) - debug("%s linter took %s", state.Name, elapsed) + dbg("%s linter took %s", state.Name, elapsed) return nil } @@ -216,10 +201,10 @@ func parseCommand(command string) ([]string, error) { } // nolint: gocyclo -func processOutput(state *linterState, out []byte) { +func processOutput(dbg debugFunction, state *linterState, out []byte) { re := state.regex all := re.FindAllSubmatchIndex(out, -1) - debug("%s hits %d: %s", state.Name, len(all), state.Pattern) + dbg("%s hits %d: %s", state.Name, len(all), state.Pattern) cwd, err := os.Getwd() if err != nil { @@ -239,7 +224,9 @@ func processOutput(state *linterState, out []byte) { group = append(group, fragment) } - issue := &Issue{Line: 1, Linter: state.Linter.Name} + issue, err := NewIssue(state.Linter.Name, config.formatTemplate) + kingpin.FatalIfError(err, "Invalid output format") + for i, name := range re.SubexpNames() { if group[i] == nil { continue @@ -275,8 +262,6 @@ func processOutput(state *linterState, out []byte) { } if sev, ok := config.Severity[state.Name]; ok { issue.Severity = Severity(sev) - } else { - issue.Severity = Warning } if state.exclude != nil && state.exclude.MatchString(issue.String()) { continue @@ -319,66 +304,16 @@ func resolvePath(path string) string { return path } -type sortedIssues struct { - issues []*Issue - order []string -} - -func (s *sortedIssues) Len() int { return len(s.issues) } -func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] } - -// nolint: gocyclo -func (s *sortedIssues) Less(i, j int) bool { - l, r := s.issues[i], s.issues[j] - for _, key := range s.order { - switch key { - case "path": - if l.Path > r.Path { - return false - } - case "line": - if l.Line > r.Line { - return false - } - case "column": - if l.Col > r.Col { - return false - } - case "severity": - if l.Severity > r.Severity { - return false - } - case "message": - if l.Message > r.Message { - return false - } - case "linter": - if l.Linter > r.Linter { - return false - } - } - } - return true -} - func maybeSortIssues(issues chan *Issue) chan *Issue { if reflect.DeepEqual([]string{"none"}, config.Sort) { return issues } - out := make(chan *Issue, 1000000) - sorted := &sortedIssues{ - issues: []*Issue{}, - order: config.Sort, + return SortIssueChan(issues, config.Sort) +} + +func maybeAggregateIssues(issues chan *Issue) chan *Issue { + if !config.Aggregate { + return issues } - go func() { - for issue := range issues { - sorted.issues = append(sorted.issues, issue) - } - sort.Sort(sorted) - for _, issue := range sorted.issues { - out <- issue - } - close(out) - }() - return out + return AggregateIssueChan(issues) } diff --git a/tools/vendor/github.com/alecthomas/gometalinter/issue.go b/tools/vendor/github.com/alecthomas/gometalinter/issue.go new file mode 100644 index 000000000..982f45047 --- /dev/null +++ b/tools/vendor/github.com/alecthomas/gometalinter/issue.go @@ -0,0 +1,114 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "sort" + "strings" + "text/template" +) + +// DefaultIssueFormat used to print an issue +const DefaultIssueFormat = "{{.Path}}:{{.Line}}:{{if .Col}}{{.Col}}{{end}}:{{.Severity}}: {{.Message}} ({{.Linter}})" + +// Severity of linter message +type Severity string + +// Linter message severity levels. +const ( + Error Severity = "error" + Warning Severity = "warning" +) + +type Issue struct { + Linter string `json:"linter"` + Severity Severity `json:"severity"` + Path string `json:"path"` + Line int `json:"line"` + Col int `json:"col"` + Message string `json:"message"` + formatTmpl *template.Template +} + +// NewIssue returns a new issue. Returns an error if formatTmpl is not a valid +// template for an Issue. +func NewIssue(linter string, formatTmpl *template.Template) (*Issue, error) { + issue := &Issue{ + Line: 1, + Severity: Warning, + Linter: linter, + formatTmpl: formatTmpl, + } + err := formatTmpl.Execute(ioutil.Discard, issue) + return issue, err +} + +func (i *Issue) String() string { + if i.formatTmpl == nil { + col := "" + if i.Col != 0 { + col = fmt.Sprintf("%d", i.Col) + } + return fmt.Sprintf("%s:%d:%s:%s: %s (%s)", strings.TrimSpace(i.Path), i.Line, col, i.Severity, strings.TrimSpace(i.Message), i.Linter) + } + buf := new(bytes.Buffer) + _ = i.formatTmpl.Execute(buf, i) + return buf.String() +} + +type sortedIssues struct { + issues []*Issue + order []string +} + +func (s *sortedIssues) Len() int { return len(s.issues) } +func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] } + +func (s *sortedIssues) Less(i, j int) bool { + l, r := s.issues[i], s.issues[j] + return CompareIssue(*l, *r, s.order) +} + +// CompareIssue two Issues and return true if left should sort before right +// nolint: gocyclo +func CompareIssue(l, r Issue, order []string) bool { + for _, key := range order { + switch { + case key == "path" && l.Path != r.Path: + return l.Path < r.Path + case key == "line" && l.Line != r.Line: + return l.Line < r.Line + case key == "column" && l.Col != r.Col: + return l.Col < r.Col + case key == "severity" && l.Severity != r.Severity: + return l.Severity < r.Severity + case key == "message" && l.Message != r.Message: + return l.Message < r.Message + case key == "linter" && l.Linter != r.Linter: + return l.Linter < r.Linter + } + } + return true +} + +// SortIssueChan reads issues from one channel, sorts them, and returns them to another +// channel +func SortIssueChan(issues chan *Issue, order []string) chan *Issue { + out := make(chan *Issue, 1000000) + sorted := &sortedIssues{ + issues: []*Issue{}, + order: order, + } + go func() { + for issue := range issues { + sorted.issues = append(sorted.issues, issue) + } + sort.Sort(sorted) + for _, issue := range sorted.issues { + out <- issue + } + close(out) + }() + return out +} diff --git a/tools/vendor/github.com/alecthomas/gometalinter/linters.go b/tools/vendor/github.com/alecthomas/gometalinter/linters.go index fdb15bc29..c779b8c1c 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/linters.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/linters.go @@ -8,11 +8,10 @@ import ( "sort" "strings" - "gopkg.in/alecthomas/kingpin.v3-unstable" + kingpin "gopkg.in/alecthomas/kingpin.v3-unstable" ) type LinterConfig struct { - Name string Command string Pattern string InstallFrom string @@ -23,11 +22,12 @@ type LinterConfig struct { type Linter struct { LinterConfig + Name string regex *regexp.Regexp } // NewLinter returns a new linter from a config -func NewLinter(config LinterConfig) (*Linter, error) { +func NewLinter(name string, config LinterConfig) (*Linter, error) { if p, ok := predefinedPatterns[config.Pattern]; ok { config.Pattern = p } @@ -35,8 +35,12 @@ func NewLinter(config LinterConfig) (*Linter, error) { if err != nil { return nil, err } + if config.PartitionStrategy == nil { + config.PartitionStrategy = partitionPathsAsDirectories + } return &Linter{ LinterConfig: config, + Name: name, regex: regex, }, nil } @@ -50,26 +54,41 @@ var predefinedPatterns = map[string]string{ "PATH:LINE:MESSAGE": `^(?P.*?\.go):(?P\d+):\s*(?P.*)$`, } -func getLinterByName(name string, customSpec string) *Linter { - if customSpec != "" { - return parseLinterSpec(name, customSpec) +func getLinterByName(name string, overrideConf LinterConfig) *Linter { + conf := defaultLinters[name] + if val := overrideConf.Command; val != "" { + conf.Command = val + } + if val := overrideConf.Pattern; val != "" { + conf.Pattern = val + } + if val := overrideConf.InstallFrom; val != "" { + conf.InstallFrom = val + } + if overrideConf.IsFast { + conf.IsFast = true + } + if val := overrideConf.PartitionStrategy; val != nil { + conf.PartitionStrategy = val } - linter, _ := NewLinter(defaultLinters[name]) + + linter, _ := NewLinter(name, conf) return linter } -func parseLinterSpec(name string, spec string) *Linter { +func parseLinterConfigSpec(name string, spec string) (LinterConfig, error) { parts := strings.SplitN(spec, ":", 2) if len(parts) < 2 { - kingpin.Fatalf("invalid linter: %q", spec) + return LinterConfig{}, fmt.Errorf("linter spec needs at least two components") } config := defaultLinters[name] config.Command, config.Pattern = parts[0], parts[1] + if predefined, ok := predefinedPatterns[config.Pattern]; ok { + config.Pattern = predefined + } - linter, err := NewLinter(config) - kingpin.FatalIfError(err, "invalid linter %q", name) - return linter + return config, nil } func makeInstallCommand(linters ...string) []string { @@ -148,9 +167,9 @@ func installLinters() { func getDefaultLinters() []*Linter { out := []*Linter{} - for _, config := range defaultLinters { - linter, err := NewLinter(config) - kingpin.FatalIfError(err, "invalid linter %q", config.Name) + for name, config := range defaultLinters { + linter, err := NewLinter(name, config) + kingpin.FatalIfError(err, "invalid linter %q", name) out = append(out, linter) } return out @@ -166,226 +185,228 @@ func defaultEnabled() []string { return enabled } +func validateLinters(linters map[string]*Linter, config *Config) error { + var unknownLinters []string + for name := range linters { + if _, isDefault := defaultLinters[name]; !isDefault { + if _, isCustom := config.Linters[name]; !isCustom { + unknownLinters = append(unknownLinters, name) + } + } + } + if len(unknownLinters) > 0 { + return fmt.Errorf("unknown linters: %s", strings.Join(unknownLinters, ", ")) + } + return nil +} + const vetPattern = `^(?:vet:.*?\.go:\s+(?P.*?\.go):(?P\d+):(?P\d+):\s*(?P.*))|(?:(?P.*?\.go):(?P\d+):\s*(?P.*))$` var defaultLinters = map[string]LinterConfig{ - "aligncheck": { - Name: "aligncheck", - Command: "aligncheck", + "maligned": { + Command: "maligned", Pattern: `^(?:[^:]+: )?(?P.*?\.go):(?P\d+):(?P\d+):\s*(?P.+)$`, - InstallFrom: "github.com/opennota/check/cmd/aligncheck", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + InstallFrom: "github.com/mdempsky/maligned", + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "deadcode": { - Name: "deadcode", Command: "deadcode", Pattern: `^deadcode: (?P.*?\.go):(?P\d+):(?P\d+):\s*(?P.*)$`, InstallFrom: "github.com/tsenart/deadcode", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, }, "dupl": { - Name: "dupl", Command: `dupl -plumbing -threshold {duplthreshold}`, Pattern: `^(?P.*?\.go):(?P\d+)-\d+:\s*(?P.*)$`, InstallFrom: "github.com/mibk/dupl", - PartitionStrategy: partitionToMaxArgSizeWithFileGlobs, + PartitionStrategy: partitionPathsAsFiles, IsFast: true, }, "errcheck": { - Name: "errcheck", - Command: `errcheck -abspath`, + Command: `errcheck -abspath {not_tests=-ignoretests}`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "github.com/kisielk/errcheck", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "gas": { - Name: "gas", Command: `gas -fmt=csv`, Pattern: `^(?P.*?\.go),(?P\d+),(?P[^,]+,[^,]+,[^,]+)`, InstallFrom: "github.com/GoASTScanner/gas", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsAsFiles, defaultEnabled: true, IsFast: true, }, "goconst": { - Name: "goconst", Command: `goconst -min-occurrences {min_occurrences} -min-length {min_const_length}`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "github.com/jgautheron/goconst/cmd/goconst", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, IsFast: true, }, "gocyclo": { - Name: "gocyclo", Command: `gocyclo -over {mincyclo}`, Pattern: `^(?P\d+)\s+\S+\s(?P\S+)\s+(?P.*?\.go):(?P\d+):(\d+)$`, InstallFrom: "github.com/alecthomas/gocyclo", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, IsFast: true, }, "gofmt": { - Name: "gofmt", Command: `gofmt -l -s`, Pattern: `^(?P.*?\.go)$`, - PartitionStrategy: partitionToMaxArgSizeWithFileGlobs, + PartitionStrategy: partitionPathsAsFiles, IsFast: true, }, "goimports": { - Name: "goimports", Command: `goimports -l`, Pattern: `^(?P.*?\.go)$`, InstallFrom: "golang.org/x/tools/cmd/goimports", - PartitionStrategy: partitionToMaxArgSizeWithFileGlobs, + PartitionStrategy: partitionPathsAsFiles, IsFast: true, }, "golint": { - Name: "golint", Command: `golint -min_confidence {min_confidence}`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "github.com/golang/lint/golint", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, IsFast: true, }, "gosimple": { - Name: "gosimple", Command: `gosimple`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "honnef.co/go/tools/cmd/gosimple", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, }, "gotype": { - Name: "gotype", Command: `gotype -e {tests=-t}`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "golang.org/x/tools/cmd/gotype", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsByDirectory, + defaultEnabled: true, + IsFast: true, + }, + "gotypex": { + Command: `gotype -e -x`, + Pattern: `PATH:LINE:COL:MESSAGE`, + InstallFrom: "golang.org/x/tools/cmd/gotype", + PartitionStrategy: partitionPathsByDirectory, defaultEnabled: true, IsFast: true, }, "ineffassign": { - Name: "ineffassign", Command: `ineffassign -n`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "github.com/gordonklaus/ineffassign", - PartitionStrategy: partitionToMaxArgSize, + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, IsFast: true, }, "interfacer": { - Name: "interfacer", Command: `interfacer`, Pattern: `PATH:LINE:COL:MESSAGE`, - InstallFrom: "github.com/mvdan/interfacer/cmd/interfacer", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + InstallFrom: "mvdan.cc/interfacer", + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "lll": { - Name: "lll", Command: `lll -g -l {maxlinelength}`, Pattern: `PATH:LINE:MESSAGE`, InstallFrom: "github.com/walle/lll/cmd/lll", - PartitionStrategy: partitionToMaxArgSizeWithFileGlobs, + PartitionStrategy: partitionPathsAsFiles, IsFast: true, }, "megacheck": { - Name: "megacheck", Command: `megacheck`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "honnef.co/go/tools/cmd/megacheck", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "misspell": { - Name: "misspell", Command: `misspell -j 1`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "github.com/client9/misspell/cmd/misspell", - PartitionStrategy: partitionToMaxArgSizeWithFileGlobs, + PartitionStrategy: partitionPathsAsFiles, IsFast: true, }, + "nakedret": { + Command: `nakedret`, + Pattern: `^(?P.*?\.go):(?P\d+)\s*(?P.*)$`, + InstallFrom: "github.com/alexkohler/nakedret", + PartitionStrategy: partitionPathsAsDirectories, + }, "safesql": { - Name: "safesql", Command: `safesql`, Pattern: `^- (?P.*?\.go):(?P\d+):(?P\d+)$`, InstallFrom: "github.com/stripe/safesql", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, }, "staticcheck": { - Name: "staticcheck", Command: `staticcheck`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "honnef.co/go/tools/cmd/staticcheck", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, }, "structcheck": { - Name: "structcheck", Command: `structcheck {tests=-t}`, Pattern: `^(?:[^:]+: )?(?P.*?\.go):(?P\d+):(?P\d+):\s*(?P.+)$`, InstallFrom: "github.com/opennota/check/cmd/structcheck", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "test": { - Name: "test", Command: `go test`, Pattern: `^--- FAIL: .*$\s+(?P.*?\.go):(?P\d+): (?P.*)$`, - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, }, "testify": { - Name: "testify", Command: `go test`, Pattern: `Location:\s+(?P.*?\.go):(?P\d+)$\s+Error:\s+(?P[^\n]+)`, - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, }, "unconvert": { - Name: "unconvert", Command: `unconvert`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "github.com/mdempsky/unconvert", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "unparam": { - Name: "unparam", - Command: `unparam`, + Command: `unparam {not_tests=-tests=false}`, Pattern: `PATH:LINE:COL:MESSAGE`, - InstallFrom: "github.com/mvdan/unparam", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + InstallFrom: "mvdan.cc/unparam", + PartitionStrategy: partitionPathsAsPackages, }, "unused": { - Name: "unused", Command: `unused`, Pattern: `PATH:LINE:COL:MESSAGE`, InstallFrom: "honnef.co/go/tools/cmd/unused", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, }, "varcheck": { - Name: "varcheck", Command: `varcheck`, Pattern: `^(?:[^:]+: )?(?P.*?\.go):(?P\d+):(?P\d+):\s*(?P.*)$`, InstallFrom: "github.com/opennota/check/cmd/varcheck", - PartitionStrategy: partitionToMaxArgSizeWithPackagePaths, + PartitionStrategy: partitionPathsAsPackages, defaultEnabled: true, }, "vet": { - Name: "vet", - Command: `go tool vet`, + Command: `govet --no-recurse`, Pattern: vetPattern, - PartitionStrategy: partitionToPackageFileGlobs, + InstallFrom: "github.com/dnephin/govet", + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, IsFast: true, }, "vetshadow": { - Name: "vetshadow", - Command: `go tool vet --shadow`, + Command: `govet --no-recurse --shadow`, Pattern: vetPattern, - PartitionStrategy: partitionToPackageFileGlobs, + PartitionStrategy: partitionPathsAsDirectories, defaultEnabled: true, IsFast: true, }, diff --git a/tools/vendor/github.com/alecthomas/gometalinter/main.go b/tools/vendor/github.com/alecthomas/gometalinter/main.go index 90e1c72e3..3de9534f2 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/main.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/main.go @@ -14,7 +14,7 @@ import ( "text/template" "time" - "gopkg.in/alecthomas/kingpin.v3-unstable" + kingpin "gopkg.in/alecthomas/kingpin.v3-unstable" ) var ( @@ -26,10 +26,10 @@ var ( ) func setupFlags(app *kingpin.Application) { - app.Flag("config", "Load JSON configuration from file.").Action(loadConfig).String() + app.Flag("config", "Load JSON configuration from file.").Envar("GOMETALINTER_CONFIG").Action(loadConfig).String() app.Flag("disable", "Disable previously enabled linters.").PlaceHolder("LINTER").Short('D').Action(disableAction).Strings() app.Flag("enable", "Enable previously disabled linters.").PlaceHolder("LINTER").Short('E').Action(enableAction).Strings() - app.Flag("linter", "Define a linter.").PlaceHolder("NAME:COMMAND:PATTERN").StringMapVar(&config.Linters) + app.Flag("linter", "Define a linter.").PlaceHolder("NAME:COMMAND:PATTERN").Action(cliLinterOverrides).StringMap() app.Flag("message-overrides", "Override message from linter. {message} will be expanded to the original message.").PlaceHolder("LINTER:MESSAGE").StringMapVar(&config.MessageOverride) app.Flag("severity", "Map of linter severities.").PlaceHolder("LINTER:SEVERITY").StringMapVar(&config.Severity) app.Flag("disable-all", "Disable all linters.").Action(disableAllAction).Bool() @@ -51,19 +51,36 @@ func setupFlags(app *kingpin.Application) { app.Flag("line-length", "Report lines longer than N (using lll).").PlaceHolder("80").IntVar(&config.LineLength) app.Flag("min-confidence", "Minimum confidence interval to pass to golint.").PlaceHolder(".80").FloatVar(&config.MinConfidence) app.Flag("min-occurrences", "Minimum occurrences to pass to goconst.").PlaceHolder("3").IntVar(&config.MinOccurrences) - app.Flag("min-const-length", "Minimumum constant length.").PlaceHolder("3").IntVar(&config.MinConstLength) + app.Flag("min-const-length", "Minimum constant length.").PlaceHolder("3").IntVar(&config.MinConstLength) app.Flag("dupl-threshold", "Minimum token sequence as a clone for dupl.").PlaceHolder("50").IntVar(&config.DuplThreshold) app.Flag("sort", fmt.Sprintf("Sort output by any of %s.", strings.Join(sortKeys, ", "))).PlaceHolder("none").EnumsVar(&config.Sort, sortKeys...) - app.Flag("tests", "Include test files for linters that support this option").Short('t').BoolVar(&config.Test) + app.Flag("tests", "Include test files for linters that support this option.").Short('t').BoolVar(&config.Test) app.Flag("deadline", "Cancel linters if they have not completed within this duration.").PlaceHolder("30s").DurationVar((*time.Duration)(&config.Deadline)) app.Flag("errors", "Only show errors.").BoolVar(&config.Errors) app.Flag("json", "Generate structured JSON rather than standard line-based output.").BoolVar(&config.JSON) app.Flag("checkstyle", "Generate checkstyle XML rather than standard line-based output.").BoolVar(&config.Checkstyle) app.Flag("enable-gc", "Enable GC for linters (useful on large repositories).").BoolVar(&config.EnableGC) app.Flag("aggregate", "Aggregate issues reported by several linters.").BoolVar(&config.Aggregate) + app.Flag("warn-unmatched-nolint", "Warn if a nolint directive is not matched with an issue.").BoolVar(&config.WarnUnmatchedDirective) app.GetFlag("help").Short('h') } +func cliLinterOverrides(app *kingpin.Application, element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { + // expected input structure - : + parts := strings.SplitN(*element.Value, ":", 2) + if len(parts) < 2 { + return fmt.Errorf("incorrectly formatted input: %s", *element.Value) + } + name := parts[0] + spec := parts[1] + conf, err := parseLinterConfigSpec(name, spec) + if err != nil { + return fmt.Errorf("incorrectly formatted input: %s", *element.Value) + } + config.Linters[name] = StringOrLinterConfig(conf) + return nil +} + func loadConfig(app *kingpin.Application, element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { r, err := os.Open(*element.Value) if err != nil { @@ -114,12 +131,20 @@ func enableAllAction(app *kingpin.Application, element *kingpin.ParseElement, ct return nil } +type debugFunction func(format string, args ...interface{}) + func debug(format string, args ...interface{}) { if config.Debug { fmt.Fprintf(os.Stderr, "DEBUG: "+format+"\n", args...) } } +func namespacedDebug(prefix string) debugFunction { + return func(format string, args ...interface{}) { + debug(prefix+format, args...) + } +} + func warning(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, "WARNING: "+format+"\n", args...) } @@ -131,8 +156,8 @@ func formatLinters() string { if install == "()" { install = "" } - fmt.Fprintf(w, " %s %s\n %s\n %s\n", - linter.Name, install, linter.Command, linter.Pattern) + fmt.Fprintf(w, " %s: %s\n\tcommand: %s\n\tregex: %s\n\tfast: %t\n\tdefault enabled: %t\n\n", + linter.Name, install, linter.Command, linter.Pattern, linter.IsFast, linter.defaultEnabled) } return w.String() } @@ -176,6 +201,9 @@ Severity override map (default is "warning"): paths := resolvePaths(*pathsArg, config.Skip) linters := lintersFromConfig(config) + err := validateLinters(linters, config) + kingpin.FatalIfError(err, "") + issues, errch := runLinters(linters, paths, config.Concurrency, exclude, include) status := 0 if config.JSON { @@ -198,7 +226,7 @@ Severity override map (default is "warning"): func processConfig(config *Config) (include *regexp.Regexp, exclude *regexp.Regexp) { tmpl, err := template.New("output").Parse(config.Format) kingpin.FatalIfError(err, "invalid format %q", config.Format) - formatTemplate = tmpl + config.formatTemplate = tmpl // Linters are by their very nature, short lived, so disable GC. // Reduced (user) linting time on kingpin from 0.97s to 0.64s. @@ -340,8 +368,7 @@ func lintersFromConfig(config *Config) map[string]*Linter { out := map[string]*Linter{} config.Enable = replaceWithMegacheck(config.Enable, config.EnableAll) for _, name := range config.Enable { - linter := getLinterByName(name, config.Linters[name]) - + linter := getLinterByName(name, LinterConfig(config.Linters[name])) if config.Fast && !linter.IsFast { continue } diff --git a/tools/vendor/github.com/alecthomas/gometalinter/partition.go b/tools/vendor/github.com/alecthomas/gometalinter/partition.go index 708beb8fc..a4a8ce5a5 100644 --- a/tools/vendor/github.com/alecthomas/gometalinter/partition.go +++ b/tools/vendor/github.com/alecthomas/gometalinter/partition.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "path/filepath" ) @@ -10,6 +11,29 @@ const MaxCommandBytes = 32000 type partitionStrategy func([]string, []string) ([][]string, error) +func (ps *partitionStrategy) UnmarshalJSON(raw []byte) error { + var strategyName string + if err := json.Unmarshal(raw, &strategyName); err != nil { + return err + } + + switch strategyName { + case "directories": + *ps = partitionPathsAsDirectories + case "files": + *ps = partitionPathsAsFiles + case "packages": + *ps = partitionPathsAsPackages + case "files-by-package": + *ps = partitionPathsAsFilesGroupedByPackage + case "single-directory": + *ps = partitionPathsByDirectory + default: + return fmt.Errorf("unknown parition strategy %s", strategyName) + } + return nil +} + func pathsToFileGlobs(paths []string) ([]string, error) { filePaths := []string{} for _, dir := range paths { @@ -22,7 +46,7 @@ func pathsToFileGlobs(paths []string) ([]string, error) { return filePaths, nil } -func partitionToMaxArgSize(cmdArgs []string, paths []string) ([][]string, error) { +func partitionPathsAsDirectories(cmdArgs []string, paths []string) ([][]string, error) { return partitionToMaxSize(cmdArgs, paths, MaxCommandBytes), nil } @@ -72,15 +96,15 @@ func (p *sizePartitioner) end() [][]string { return p.parts } -func partitionToMaxArgSizeWithFileGlobs(cmdArgs []string, paths []string) ([][]string, error) { +func partitionPathsAsFiles(cmdArgs []string, paths []string) ([][]string, error) { filePaths, err := pathsToFileGlobs(paths) if err != nil || len(filePaths) == 0 { return nil, err } - return partitionToMaxArgSize(cmdArgs, filePaths) + return partitionPathsAsDirectories(cmdArgs, filePaths) } -func partitionToPackageFileGlobs(cmdArgs []string, paths []string) ([][]string, error) { +func partitionPathsAsFilesGroupedByPackage(cmdArgs []string, paths []string) ([][]string, error) { parts := [][]string{} for _, path := range paths { filePaths, err := pathsToFileGlobs([]string{path}) @@ -95,12 +119,12 @@ func partitionToPackageFileGlobs(cmdArgs []string, paths []string) ([][]string, return parts, nil } -func partitionToMaxArgSizeWithPackagePaths(cmdArgs []string, paths []string) ([][]string, error) { +func partitionPathsAsPackages(cmdArgs []string, paths []string) ([][]string, error) { packagePaths, err := pathsToPackagePaths(paths) if err != nil || len(packagePaths) == 0 { return nil, err } - return partitionToMaxArgSize(cmdArgs, packagePaths) + return partitionPathsAsDirectories(cmdArgs, packagePaths) } func pathsToPackagePaths(paths []string) ([]string, error) { @@ -129,3 +153,11 @@ func packageNameFromPath(path string) (string, error) { } return "", fmt.Errorf("%s not in GOPATH", path) } + +func partitionPathsByDirectory(cmdArgs []string, paths []string) ([][]string, error) { + parts := [][]string{} + for _, path := range paths { + parts = append(parts, append(cmdArgs, path)) + } + return parts, nil +} diff --git a/tools/vendor/github.com/alexkohler/nakedret/LICENSE b/tools/vendor/github.com/alexkohler/nakedret/LICENSE new file mode 100644 index 000000000..9310fbcff --- /dev/null +++ b/tools/vendor/github.com/alexkohler/nakedret/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Alex Kohler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/vendor/github.com/alexkohler/nakedret/README.md b/tools/vendor/github.com/alexkohler/nakedret/README.md new file mode 100644 index 000000000..303063b8d --- /dev/null +++ b/tools/vendor/github.com/alexkohler/nakedret/README.md @@ -0,0 +1,103 @@ +# nakedret + +nakedret is a Go static analysis tool to find naked returns in functions greater than a specified function length. + +## Installation + + go get -u github.com/alexkohler/nakedret + +## Usage + +Similar to other Go static anaylsis tools (such as golint, go vet) , nakedret can be invoked with one or more filenames, directories, or packages named by its import path. Nakedret also supports the `...` wildcard. + + nakedret [flags] files/directories/packages + +Currently, the only flag supported is -l, which is an optional numeric flag to specify the maximum length a function can be (in terms of line length). If not specified, it defaults to 5. + +## Purpose + +As noted in Go's [Code Review comments](https://github.com/golang/go/wiki/CodeReviewComments#named-result-parameters): + +> Naked returns are okay if the function is a handful of lines. Once it's a medium sized function, be explicit with your return +> values. Corollary: it's not worth it to name result parameters just because it enables you to use naked returns. Clarity of docs is always more important than saving a line or two in your function. + +This tool aims to catch naked returns on non-trivial functions. + +## Example + +Let's take the `types` package in the Go source as an example: + +```Bash +$ nakedret -l 25 types/ +types/check.go:245 checkFiles naked returns on 26 line function +types/typexpr.go:443 collectParams naked returns on 53 line function +types/stmt.go:275 caseTypes naked returns on 27 line function +types/lookup.go:275 MissingMethod naked returns on 39 line function +``` + +Below is one of the not so intuitive uses of naked returns in `types/lookup.go` found by nakedret (nakedret will return the line number of the last naked return in the function): + + +```Go +func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) { + // fast path for common case + if T.Empty() { + return + } + + // TODO(gri) Consider using method sets here. Might be more efficient. + + if ityp, _ := V.Underlying().(*Interface); ityp != nil { + // TODO(gri) allMethods is sorted - can do this more efficiently + for _, m := range T.allMethods { + _, obj := lookupMethod(ityp.allMethods, m.pkg, m.name) + switch { + case obj == nil: + if static { + return m, false + } + case !Identical(obj.Type(), m.typ): + return m, true + } + } + return + } + + // A concrete type implements T if it implements all methods of T. + for _, m := range T.allMethods { + obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name) + + f, _ := obj.(*Func) + if f == nil { + return m, false + } + + if !Identical(f.typ, m.typ) { + return m, true + } + } + + return +} +``` + +## TODO + +- Unit tests (may require some refactoring to do correctly) +- supporting toggling of `build.Context.UseAllFiles` may be useful for some. +- Configuration on whether or not to run on test files +- Vim quickfix format? +- Globbing support (e.g. nakedret *.go) + + +## Contributing + +Pull requests welcome! + + +## Other static analysis tools + +If you've enjoyed nakedret, take a look at my other static anaylsis tools! + +- [unimport](https://github.com/alexkohler/unimport) - Finds unnecessary import aliases +- [prealloc](https://github.com/alexkohler/prealloc) - Finds slice declarations that could potentially be preallocated. diff --git a/tools/vendor/github.com/alexkohler/nakedret/import.go b/tools/vendor/github.com/alexkohler/nakedret/import.go new file mode 100644 index 000000000..e02869163 --- /dev/null +++ b/tools/vendor/github.com/alexkohler/nakedret/import.go @@ -0,0 +1,310 @@ +package main + +/* + +This file holds a direct copy of the import path matching code of +https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be +replaced when https://golang.org/issue/8768 is resolved. + +It has been updated to follow upstream changes in a few ways. + +*/ + +import ( + "fmt" + "go/build" + "log" + "os" + "path" + "path/filepath" + "regexp" + "runtime" + "strings" +) + +var buildContext = build.Default + +var ( + goroot = filepath.Clean(runtime.GOROOT()) + gorootSrc = filepath.Join(goroot, "src") +) + +// importPathsNoDotExpansion returns the import paths to use for the given +// command line, but it does no ... expansion. +func importPathsNoDotExpansion(args []string) []string { + if len(args) == 0 { + return []string{"."} + } + var out []string + for _, a := range args { + // Arguments are supposed to be import paths, but + // as a courtesy to Windows developers, rewrite \ to / + // in command-line arguments. Handles .\... and so on. + if filepath.Separator == '\\' { + a = strings.Replace(a, `\`, `/`, -1) + } + + // Put argument in canonical form, but preserve leading ./. + if strings.HasPrefix(a, "./") { + a = "./" + path.Clean(a) + if a == "./." { + a = "." + } + } else { + a = path.Clean(a) + } + if a == "all" || a == "std" { + out = append(out, allPackages(a)...) + continue + } + out = append(out, a) + } + return out +} + +// importPaths returns the import paths to use for the given command line. +func importPaths(args []string) []string { + args = importPathsNoDotExpansion(args) + var out []string + for _, a := range args { + if strings.Contains(a, "...") { + if build.IsLocalImport(a) { + out = append(out, allPackagesInFS(a)...) + } else { + out = append(out, allPackages(a)...) + } + continue + } + out = append(out, a) + } + return out +} + +// matchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +func matchPattern(pattern string) func(name string) bool { + re := regexp.QuoteMeta(pattern) + re = strings.Replace(re, `\.\.\.`, `.*`, -1) + // Special case: foo/... matches foo too. + if strings.HasSuffix(re, `/.*`) { + re = re[:len(re)-len(`/.*`)] + `(/.*)?` + } + reg := regexp.MustCompile(`^` + re + `$`) + return func(name string) bool { + return reg.MatchString(name) + } +} + +// hasPathPrefix reports whether the path s begins with the +// elements in prefix. +func hasPathPrefix(s, prefix string) bool { + switch { + default: + return false + case len(s) == len(prefix): + return s == prefix + case len(s) > len(prefix): + if prefix != "" && prefix[len(prefix)-1] == '/' { + return strings.HasPrefix(s, prefix) + } + return s[len(prefix)] == '/' && s[:len(prefix)] == prefix + } +} + +// treeCanMatchPattern(pattern)(name) reports whether +// name or children of name can possibly match pattern. +// Pattern is the same limited glob accepted by matchPattern. +func treeCanMatchPattern(pattern string) func(name string) bool { + wildCard := false + if i := strings.Index(pattern, "..."); i >= 0 { + wildCard = true + pattern = pattern[:i] + } + return func(name string) bool { + return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || + wildCard && strings.HasPrefix(name, pattern) + } +} + +// allPackages returns all the packages that can be found +// under the $GOPATH directories and $GOROOT matching pattern. +// The pattern is either "all" (all packages), "std" (standard packages) +// or a path including "...". +func allPackages(pattern string) []string { + pkgs := matchPackages(pattern) + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +func matchPackages(pattern string) []string { + match := func(string) bool { return true } + treeCanMatch := func(string) bool { return true } + if pattern != "all" && pattern != "std" { + match = matchPattern(pattern) + treeCanMatch = treeCanMatchPattern(pattern) + } + + have := map[string]bool{ + "builtin": true, // ignore pseudo-package that exists only for documentation + } + if !buildContext.CgoEnabled { + have["runtime/cgo"] = true // ignore during walk + } + var pkgs []string + + // Commands + cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator) + filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() || path == cmd { + return nil + } + name := path[len(cmd):] + if !treeCanMatch(name) { + return filepath.SkipDir + } + // Commands are all in cmd/, not in subdirectories. + if strings.Contains(name, string(filepath.Separator)) { + return filepath.SkipDir + } + + // We use, e.g., cmd/gofmt as the pseudo import path for gofmt. + name = "cmd/" + name + if have[name] { + return nil + } + have[name] = true + if !match(name) { + return nil + } + _, err = buildContext.ImportDir(path, 0) + if err != nil { + if _, noGo := err.(*build.NoGoError); !noGo { + log.Print(err) + } + return nil + } + pkgs = append(pkgs, name) + return nil + }) + + for _, src := range buildContext.SrcDirs() { + if (pattern == "std" || pattern == "cmd") && src != gorootSrc { + continue + } + src = filepath.Clean(src) + string(filepath.Separator) + root := src + if pattern == "cmd" { + root += "cmd" + string(filepath.Separator) + } + filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() || path == src { + return nil + } + + // Avoid .foo, _foo, testdata and vendor directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" || elem == "vendor" { + return filepath.SkipDir + } + + name := filepath.ToSlash(path[len(src):]) + if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") { + // The name "std" is only the standard library. + // If the name is cmd, it's the root of the command tree. + return filepath.SkipDir + } + if !treeCanMatch(name) { + return filepath.SkipDir + } + if have[name] { + return nil + } + have[name] = true + if !match(name) { + return nil + } + _, err = buildContext.ImportDir(path, 0) + if err != nil { + if _, noGo := err.(*build.NoGoError); noGo { + return nil + } + } + pkgs = append(pkgs, name) + return nil + }) + } + return pkgs +} + +// allPackagesInFS is like allPackages but is passed a pattern +// beginning ./ or ../, meaning it should scan the tree rooted +// at the given directory. There are ... in the pattern too. +func allPackagesInFS(pattern string) []string { + pkgs := matchPackagesInFS(pattern) + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +func matchPackagesInFS(pattern string) []string { + // Find directory to begin the scan. + // Could be smarter but this one optimization + // is enough for now, since ... is usually at the + // end of a path. + i := strings.Index(pattern, "...") + dir, _ := path.Split(pattern[:i]) + + // pattern begins with ./ or ../. + // path.Clean will discard the ./ but not the ../. + // We need to preserve the ./ for pattern matching + // and in the returned import paths. + prefix := "" + if strings.HasPrefix(pattern, "./") { + prefix = "./" + } + match := matchPattern(pattern) + + var pkgs []string + filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() { + return nil + } + if path == dir { + // filepath.Walk starts at dir and recurses. For the recursive case, + // the path is the result of filepath.Join, which calls filepath.Clean. + // The initial case is not Cleaned, though, so we do this explicitly. + // + // This converts a path like "./io/" to "io". Without this step, running + // "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io + // package, because prepending the prefix "./" to the unclean path would + // result in "././io", and match("././io") returns false. + path = filepath.Clean(path) + } + + // Avoid .foo, _foo, testdata and vendor directory trees, but do not avoid "." or "..". + _, elem := filepath.Split(path) + dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." + if dot || strings.HasPrefix(elem, "_") || elem == "testdata" || elem == "vendor" { + return filepath.SkipDir + } + + name := prefix + filepath.ToSlash(path) + if !match(name) { + return nil + } + if _, err = build.ImportDir(path, 0); err != nil { + if _, noGo := err.(*build.NoGoError); !noGo { + log.Print(err) + } + return nil + } + pkgs = append(pkgs, name) + return nil + }) + return pkgs +} diff --git a/tools/vendor/github.com/alexkohler/nakedret/nakedret.go b/tools/vendor/github.com/alexkohler/nakedret/nakedret.go new file mode 100644 index 000000000..b65177028 --- /dev/null +++ b/tools/vendor/github.com/alexkohler/nakedret/nakedret.go @@ -0,0 +1,213 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "strings" +) + +const ( + pwd = "./" +) + +func init() { + //TODO allow build tags + build.Default.UseAllFiles = true +} + +func usage() { + log.Printf("Usage of %s:\n", os.Args[0]) + log.Printf("\nnakedret [flags] # runs on package in current directory\n") + log.Printf("\nnakedret [flags] [packages]\n") + log.Printf("Flags:\n") + flag.PrintDefaults() +} + +type returnsVisitor struct { + f *token.FileSet + maxLength uint +} + +func main() { + + // Remove log timestamp + log.SetFlags(0) + + maxLength := flag.Uint("l", 5, "maximum number of lines for a naked return function") + flag.Usage = usage + flag.Parse() + + if err := checkNakedReturns(flag.Args(), maxLength); err != nil { + log.Println(err) + } +} + +func checkNakedReturns(args []string, maxLength *uint) error { + + fset := token.NewFileSet() + + files, err := parseInput(args, fset) + if err != nil { + return fmt.Errorf("could not parse input %v", err) + } + + if maxLength == nil { + return errors.New("max length nil") + } + + retVis := &returnsVisitor{ + f: fset, + maxLength: *maxLength, + } + + for _, f := range files { + ast.Walk(retVis, f) + } + + return nil +} + +func parseInput(args []string, fset *token.FileSet) ([]*ast.File, error) { + var directoryList []string + var fileMode bool + files := make([]*ast.File, 0) + + if len(args) == 0 { + directoryList = append(directoryList, pwd) + } else { + for _, arg := range args { + if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-len("/...")]) { + + for _, dirname := range allPackagesInFS(arg) { + directoryList = append(directoryList, dirname) + } + + } else if isDir(arg) { + directoryList = append(directoryList, arg) + + } else if exists(arg) { + if strings.HasSuffix(arg, ".go") { + fileMode = true + f, err := parser.ParseFile(fset, arg, nil, 0) + if err != nil { + return nil, err + } + files = append(files, f) + } else { + return nil, fmt.Errorf("invalid file %v specified", arg) + } + } else { + + //TODO clean this up a bit + imPaths := importPaths([]string{arg}) + for _, importPath := range imPaths { + pkg, err := build.Import(importPath, ".", 0) + if err != nil { + return nil, err + } + var stringFiles []string + stringFiles = append(stringFiles, pkg.GoFiles...) + // files = append(files, pkg.CgoFiles...) + stringFiles = append(stringFiles, pkg.TestGoFiles...) + if pkg.Dir != "." { + for i, f := range stringFiles { + stringFiles[i] = filepath.Join(pkg.Dir, f) + } + } + + fileMode = true + for _, stringFile := range stringFiles { + f, err := parser.ParseFile(fset, stringFile, nil, 0) + if err != nil { + return nil, err + } + files = append(files, f) + } + + } + } + } + } + + // if we're not in file mode, then we need to grab each and every package in each directory + // we can to grab all the files + if !fileMode { + for _, fpath := range directoryList { + pkgs, err := parser.ParseDir(fset, fpath, nil, 0) + if err != nil { + return nil, err + } + + for _, pkg := range pkgs { + for _, f := range pkg.Files { + files = append(files, f) + } + } + } + } + + return files, nil +} + +func isDir(filename string) bool { + fi, err := os.Stat(filename) + return err == nil && fi.IsDir() +} + +func exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor { + var namedReturns []*ast.Ident + + funcDecl, ok := node.(*ast.FuncDecl) + if !ok { + return v + } + var functionLineLength int + // We've found a function + if funcDecl.Type != nil && funcDecl.Type.Results != nil { + for _, field := range funcDecl.Type.Results.List { + for _, ident := range field.Names { + if ident != nil { + namedReturns = append(namedReturns, ident) + } + } + } + file := v.f.File(funcDecl.Pos()) + functionLineLength = file.Position(funcDecl.End()).Line - file.Position(funcDecl.Pos()).Line + } + + if len(namedReturns) > 0 && funcDecl.Body != nil { + // Scan the body for usage of the named returns + for _, stmt := range funcDecl.Body.List { + + switch s := stmt.(type) { + case *ast.ReturnStmt: + if len(s.Results) == 0 { + file := v.f.File(s.Pos()) + if file != nil && uint(functionLineLength) > v.maxLength { + if funcDecl.Name != nil { + log.Printf("%v:%v %v naked returns on %v line function \n", file.Name(), file.Position(s.Pos()).Line, funcDecl.Name.Name, functionLineLength) + } + } + continue + } + + default: + } + } + } + + return v +} diff --git a/tools/vendor/github.com/client9/misspell/cmd/misspell/main.go b/tools/vendor/github.com/client9/misspell/cmd/misspell/main.go index 3d2c2b4d1..174d79d88 100644 --- a/tools/vendor/github.com/client9/misspell/cmd/misspell/main.go +++ b/tools/vendor/github.com/client9/misspell/cmd/misspell/main.go @@ -1,3 +1,4 @@ +// The misspell command corrects commonly misspelled English words in source files. package main import ( diff --git a/tools/vendor/honnef.co/go/tools/gcsizes/LICENSE b/tools/vendor/github.com/dnephin/govet/LICENSE similarity index 100% rename from tools/vendor/honnef.co/go/tools/gcsizes/LICENSE rename to tools/vendor/github.com/dnephin/govet/LICENSE diff --git a/tools/vendor/github.com/dnephin/govet/README b/tools/vendor/github.com/dnephin/govet/README new file mode 100644 index 000000000..56d48897e --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/README @@ -0,0 +1,33 @@ +Vet is a tool that checks correctness of Go programs. It runs a suite of tests, +each tailored to check for a particular class of errors. Examples include incorrect +Printf format verbs or malformed build tags. + +Over time many checks have been added to vet's suite, but many more have been +rejected as not appropriate for the tool. The criteria applied when selecting which +checks to add are: + +Correctness: + +Vet's tools are about correctness, not style. A vet check must identify real or +potential bugs that could cause incorrect compilation or execution. A check that +only identifies stylistic points or alternative correct approaches to a situation +is not acceptable. + +Frequency: + +Vet is run every day by many programmers, often as part of every compilation or +submission. The cost in execution time is considerable, especially in aggregate, +so checks must be likely enough to find real problems that they are worth the +overhead of the added check. A new check that finds only a handful of problems +across all existing programs, even if the problem is significant, is not worth +adding to the suite everyone runs daily. + +Precision: + +Most of vet's checks are heuristic and can generate both false positives (flagging +correct programs) and false negatives (not flagging incorrect ones). The rate of +both these failures must be very small. A check that is too noisy will be ignored +by the programmer overwhelmed by the output; a check that misses too many of the +cases it's looking for will give a false sense of security. Neither is acceptable. +A vet check must be accurate enough that everything it reports is worth examining, +and complete enough to encourage real confidence. diff --git a/tools/vendor/github.com/dnephin/govet/asmdecl.go b/tools/vendor/github.com/dnephin/govet/asmdecl.go new file mode 100644 index 000000000..d543b2ee5 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/asmdecl.go @@ -0,0 +1,682 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Identify mismatches between assembly files and Go func declarations. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "regexp" + "strconv" + "strings" +) + +// 'kind' is a kind of assembly variable. +// The kinds 1, 2, 4, 8 stand for values of that size. +type asmKind int + +// These special kinds are not valid sizes. +const ( + asmString asmKind = 100 + iota + asmSlice + asmInterface + asmEmptyInterface +) + +// An asmArch describes assembly parameters for an architecture +type asmArch struct { + name string + ptrSize int + intSize int + maxAlign int + bigEndian bool + stack string + lr bool +} + +// An asmFunc describes the expected variables for a function on a given architecture. +type asmFunc struct { + arch *asmArch + size int // size of all arguments + vars map[string]*asmVar + varByOffset map[int]*asmVar +} + +// An asmVar describes a single assembly variable. +type asmVar struct { + name string + kind asmKind + typ string + off int + size int + inner []*asmVar +} + +var ( + asmArch386 = asmArch{"386", 4, 4, 4, false, "SP", false} + asmArchArm = asmArch{"arm", 4, 4, 4, false, "R13", true} + asmArchArm64 = asmArch{"arm64", 8, 8, 8, false, "RSP", true} + asmArchAmd64 = asmArch{"amd64", 8, 8, 8, false, "SP", false} + asmArchAmd64p32 = asmArch{"amd64p32", 4, 4, 8, false, "SP", false} + asmArchMips64 = asmArch{"mips64", 8, 8, 8, true, "R29", true} + asmArchMips64LE = asmArch{"mips64", 8, 8, 8, false, "R29", true} + asmArchPpc64 = asmArch{"ppc64", 8, 8, 8, true, "R1", true} + asmArchPpc64LE = asmArch{"ppc64le", 8, 8, 8, false, "R1", true} + + arches = []*asmArch{ + &asmArch386, + &asmArchArm, + &asmArchArm64, + &asmArchAmd64, + &asmArchAmd64p32, + &asmArchMips64, + &asmArchMips64LE, + &asmArchPpc64, + &asmArchPpc64LE, + } +) + +var ( + re = regexp.MustCompile + asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) + asmTEXT = re(`\bTEXT\b.*·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) + asmDATA = re(`\b(DATA|GLOBL)\b`) + asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) + asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`) + asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) + asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) + ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) +) + +func asmCheck(pkg *Package) { + if !vet("asmdecl") { + return + } + + // No work if no assembly files. + if !pkg.hasFileWithSuffix(".s") { + return + } + + // Gather declarations. knownFunc[name][arch] is func description. + knownFunc := make(map[string]map[string]*asmFunc) + + for _, f := range pkg.files { + if f.file != nil { + for _, decl := range f.file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil { + knownFunc[decl.Name.Name] = f.asmParseDecl(decl) + } + } + } + } + +Files: + for _, f := range pkg.files { + if !strings.HasSuffix(f.name, ".s") { + continue + } + Println("Checking file", f.name) + + // Determine architecture from file name if possible. + var arch string + var archDef *asmArch + for _, a := range arches { + if strings.HasSuffix(f.name, "_"+a.name+".s") { + arch = a.name + archDef = a + break + } + } + + lines := strings.SplitAfter(string(f.content), "\n") + var ( + fn *asmFunc + fnName string + localSize, argSize int + wroteSP bool + haveRetArg bool + retLine []int + ) + + flushRet := func() { + if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 { + v := fn.vars["ret"] + for _, line := range retLine { + f.Badf(token.NoPos, "%s:%d: [%s] %s: RET without writing to %d-byte ret+%d(FP)", f.name, line, arch, fnName, v.size, v.off) + } + } + retLine = nil + } + for lineno, line := range lines { + lineno++ + + badf := func(format string, args ...interface{}) { + f.Badf(token.NoPos, "%s:%d: [%s] %s: %s", f.name, lineno, arch, fnName, fmt.Sprintf(format, args...)) + } + + if arch == "" { + // Determine architecture from +build line if possible. + if m := asmPlusBuild.FindStringSubmatch(line); m != nil { + Fields: + for _, fld := range strings.Fields(m[1]) { + for _, a := range arches { + if a.name == fld { + arch = a.name + archDef = a + break Fields + } + } + } + } + } + + if m := asmTEXT.FindStringSubmatch(line); m != nil { + flushRet() + if arch == "" { + f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name) + continue Files + } + fnName = m[1] + fn = knownFunc[m[1]][arch] + if fn != nil { + size, _ := strconv.Atoi(m[4]) + if size != fn.size && (m[2] != "7" && !strings.Contains(m[2], "NOSPLIT") || size != 0) { + badf("wrong argument size %d; expected $...-%d", size, fn.size) + } + } + localSize, _ = strconv.Atoi(m[3]) + localSize += archDef.intSize + if archDef.lr { + // Account for caller's saved LR + localSize += archDef.intSize + } + argSize, _ = strconv.Atoi(m[4]) + if fn == nil && !strings.Contains(fnName, "<>") { + badf("function %s missing Go declaration", fnName) + } + wroteSP = false + haveRetArg = false + continue + } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") { + // function, but not visible from Go (didn't match asmTEXT), so stop checking + flushRet() + fn = nil + fnName = "" + continue + } + + if strings.Contains(line, "RET") { + retLine = append(retLine, lineno) + } + + if fnName == "" { + continue + } + + if asmDATA.FindStringSubmatch(line) != nil { + fn = nil + } + + if archDef == nil { + continue + } + + if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) { + wroteSP = true + continue + } + + for _, m := range asmSP.FindAllStringSubmatch(line, -1) { + if m[3] != archDef.stack || wroteSP { + continue + } + off := 0 + if m[1] != "" { + off, _ = strconv.Atoi(m[2]) + } + if off >= localSize { + if fn != nil { + v := fn.varByOffset[off-localSize] + if v != nil { + badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize) + continue + } + } + if off >= localSize+argSize { + badf("use of %s points beyond argument frame", m[1]) + continue + } + badf("use of %s to access argument frame", m[1]) + } + } + + if fn == nil { + continue + } + + for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) { + off, _ := strconv.Atoi(m[2]) + v := fn.varByOffset[off] + if v != nil { + badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off) + } else { + badf("use of unnamed argument %s", m[1]) + } + } + + for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) { + name := m[1] + off := 0 + if m[2] != "" { + off, _ = strconv.Atoi(m[2]) + } + if name == "ret" || strings.HasPrefix(name, "ret_") { + haveRetArg = true + } + v := fn.vars[name] + if v == nil { + // Allow argframe+0(FP). + if name == "argframe" && off == 0 { + continue + } + v = fn.varByOffset[off] + if v != nil { + badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off) + } else { + badf("unknown variable %s", name) + } + continue + } + asmCheckVar(badf, fn, line, m[0], off, v) + } + } + flushRet() + } +} + +// asmParseDecl parses a function decl for expected assembly variables. +func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc { + var ( + arch *asmArch + fn *asmFunc + offset int + failed bool + ) + + addVar := func(outer string, v asmVar) { + if vo := fn.vars[outer]; vo != nil { + vo.inner = append(vo.inner, &v) + } + fn.vars[v.name] = &v + for i := 0; i < v.size; i++ { + fn.varByOffset[v.off+i] = &v + } + } + + addParams := func(list []*ast.Field) { + for i, fld := range list { + // Determine alignment, size, and kind of type in declaration. + var align, size int + var kind asmKind + names := fld.Names + typ := f.gofmt(fld.Type) + switch t := fld.Type.(type) { + default: + switch typ { + default: + f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ) + failed = true + return + case "int8", "uint8", "byte", "bool": + size = 1 + case "int16", "uint16": + size = 2 + case "int32", "uint32", "float32": + size = 4 + case "int64", "uint64", "float64": + align = arch.maxAlign + size = 8 + case "int", "uint": + size = arch.intSize + case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer": + size = arch.ptrSize + case "string", "ErrorString": + size = arch.ptrSize * 2 + align = arch.ptrSize + kind = asmString + } + case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr: + size = arch.ptrSize + case *ast.InterfaceType: + align = arch.ptrSize + size = 2 * arch.ptrSize + if len(t.Methods.List) > 0 { + kind = asmInterface + } else { + kind = asmEmptyInterface + } + case *ast.ArrayType: + if t.Len == nil { + size = arch.ptrSize + 2*arch.intSize + align = arch.ptrSize + kind = asmSlice + break + } + f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ) + failed = true + case *ast.StructType: + f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ) + failed = true + } + if align == 0 { + align = size + } + if kind == 0 { + kind = asmKind(size) + } + offset += -offset & (align - 1) + + // Create variable for each name being declared with this type. + if len(names) == 0 { + name := "unnamed" + if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 { + // Assume assembly will refer to single unnamed result as r. + name = "ret" + } + names = []*ast.Ident{{Name: name}} + } + for _, id := range names { + name := id.Name + addVar("", asmVar{ + name: name, + kind: kind, + typ: typ, + off: offset, + size: size, + }) + switch kind { + case 8: + if arch.ptrSize == 4 { + w1, w2 := "lo", "hi" + if arch.bigEndian { + w1, w2 = w2, w1 + } + addVar(name, asmVar{ + name: name + "_" + w1, + kind: 4, + typ: "half " + typ, + off: offset, + size: 4, + }) + addVar(name, asmVar{ + name: name + "_" + w2, + kind: 4, + typ: "half " + typ, + off: offset + 4, + size: 4, + }) + } + + case asmEmptyInterface: + addVar(name, asmVar{ + name: name + "_type", + kind: asmKind(arch.ptrSize), + typ: "interface type", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_data", + kind: asmKind(arch.ptrSize), + typ: "interface data", + off: offset + arch.ptrSize, + size: arch.ptrSize, + }) + + case asmInterface: + addVar(name, asmVar{ + name: name + "_itable", + kind: asmKind(arch.ptrSize), + typ: "interface itable", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_data", + kind: asmKind(arch.ptrSize), + typ: "interface data", + off: offset + arch.ptrSize, + size: arch.ptrSize, + }) + + case asmSlice: + addVar(name, asmVar{ + name: name + "_base", + kind: asmKind(arch.ptrSize), + typ: "slice base", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_len", + kind: asmKind(arch.intSize), + typ: "slice len", + off: offset + arch.ptrSize, + size: arch.intSize, + }) + addVar(name, asmVar{ + name: name + "_cap", + kind: asmKind(arch.intSize), + typ: "slice cap", + off: offset + arch.ptrSize + arch.intSize, + size: arch.intSize, + }) + + case asmString: + addVar(name, asmVar{ + name: name + "_base", + kind: asmKind(arch.ptrSize), + typ: "string base", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_len", + kind: asmKind(arch.intSize), + typ: "string len", + off: offset + arch.ptrSize, + size: arch.intSize, + }) + } + offset += size + } + } + } + + m := make(map[string]*asmFunc) + for _, arch = range arches { + fn = &asmFunc{ + arch: arch, + vars: make(map[string]*asmVar), + varByOffset: make(map[int]*asmVar), + } + offset = 0 + addParams(decl.Type.Params.List) + if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { + offset += -offset & (arch.maxAlign - 1) + addParams(decl.Type.Results.List) + } + fn.size = offset + m[arch.name] = fn + } + + if failed { + return nil + } + return m +} + +// asmCheckVar checks a single variable reference. +func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) { + m := asmOpcode.FindStringSubmatch(line) + if m == nil { + if !strings.HasPrefix(strings.TrimSpace(line), "//") { + badf("cannot find assembly opcode") + } + return + } + + // Determine operand sizes from instruction. + // Typically the suffix suffices, but there are exceptions. + var src, dst, kind asmKind + op := m[1] + switch fn.arch.name + "." + op { + case "386.FMOVLP": + src, dst = 8, 4 + case "arm.MOVD": + src = 8 + case "arm.MOVW": + src = 4 + case "arm.MOVH", "arm.MOVHU": + src = 2 + case "arm.MOVB", "arm.MOVBU": + src = 1 + // LEA* opcodes don't really read the second arg. + // They just take the address of it. + case "386.LEAL": + dst = 4 + case "amd64.LEAQ": + dst = 8 + case "amd64p32.LEAL": + dst = 4 + default: + switch fn.arch.name { + case "386", "amd64": + if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) { + // FMOVDP, FXCHD, etc + src = 8 + break + } + if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") { + // PINSRD, PEXTRD, etc + src = 4 + break + } + if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) { + // FMOVFP, FXCHF, etc + src = 4 + break + } + if strings.HasSuffix(op, "SD") { + // MOVSD, SQRTSD, etc + src = 8 + break + } + if strings.HasSuffix(op, "SS") { + // MOVSS, SQRTSS, etc + src = 4 + break + } + if strings.HasPrefix(op, "SET") { + // SETEQ, etc + src = 1 + break + } + switch op[len(op)-1] { + case 'B': + src = 1 + case 'W': + src = 2 + case 'L': + src = 4 + case 'D', 'Q': + src = 8 + } + case "ppc64", "ppc64le": + // Strip standard suffixes to reveal size letter. + m := ppc64Suff.FindStringSubmatch(op) + if m != nil { + switch m[1][0] { + case 'B': + src = 1 + case 'H': + src = 2 + case 'W': + src = 4 + case 'D': + src = 8 + } + } + case "mips64", "mips64le": + switch op { + case "MOVB", "MOVBU": + src = 1 + case "MOVH", "MOVHU": + src = 2 + case "MOVW", "MOVWU", "MOVF": + src = 4 + case "MOVV", "MOVD": + src = 8 + } + } + } + if dst == 0 { + dst = src + } + + // Determine whether the match we're holding + // is the first or second argument. + if strings.Index(line, expr) > strings.Index(line, ",") { + kind = dst + } else { + kind = src + } + + vk := v.kind + vt := v.typ + switch vk { + case asmInterface, asmEmptyInterface, asmString, asmSlice: + // allow reference to first word (pointer) + vk = v.inner[0].kind + vt = v.inner[0].typ + } + + if off != v.off { + var inner bytes.Buffer + for i, vi := range v.inner { + if len(v.inner) > 1 { + fmt.Fprintf(&inner, ",") + } + fmt.Fprintf(&inner, " ") + if i == len(v.inner)-1 { + fmt.Fprintf(&inner, "or ") + } + fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) + } + badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String()) + return + } + if kind != 0 && kind != vk { + var inner bytes.Buffer + if len(v.inner) > 0 { + fmt.Fprintf(&inner, " containing") + for i, vi := range v.inner { + if i > 0 && len(v.inner) > 2 { + fmt.Fprintf(&inner, ",") + } + fmt.Fprintf(&inner, " ") + if i > 0 && i == len(v.inner)-1 { + fmt.Fprintf(&inner, "and ") + } + fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) + } + } + badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String()) + } +} diff --git a/tools/vendor/github.com/dnephin/govet/assign.go b/tools/vendor/github.com/dnephin/govet/assign.go new file mode 100644 index 000000000..54c1ae1fd --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/assign.go @@ -0,0 +1,49 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for useless assignments. +*/ + +package main + +import ( + "go/ast" + "go/token" + "reflect" +) + +func init() { + register("assign", + "check for useless assignments", + checkAssignStmt, + assignStmt) +} + +// TODO: should also check for assignments to struct fields inside methods +// that are on T instead of *T. + +// checkAssignStmt checks for assignments of the form " = ". +// These are almost always useless, and even when they aren't they are usually a mistake. +func checkAssignStmt(f *File, node ast.Node) { + stmt := node.(*ast.AssignStmt) + if stmt.Tok != token.ASSIGN { + return // ignore := + } + if len(stmt.Lhs) != len(stmt.Rhs) { + // If LHS and RHS have different cardinality, they can't be the same. + return + } + for i, lhs := range stmt.Lhs { + rhs := stmt.Rhs[i] + if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) { + continue // short-circuit the heavy-weight gofmt check + } + le := f.gofmt(lhs) + re := f.gofmt(rhs) + if le == re { + f.Badf(stmt.Pos(), "self-assignment of %s to %s", re, le) + } + } +} diff --git a/tools/vendor/github.com/dnephin/govet/atomic.go b/tools/vendor/github.com/dnephin/govet/atomic.go new file mode 100644 index 000000000..b2ca2d80f --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/atomic.go @@ -0,0 +1,69 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "go/token" +) + +func init() { + register("atomic", + "check for common mistaken usages of the sync/atomic package", + checkAtomicAssignment, + assignStmt) +} + +// checkAtomicAssignment walks the assignment statement checking for common +// mistaken usage of atomic package, such as: x = atomic.AddUint64(&x, 1) +func checkAtomicAssignment(f *File, node ast.Node) { + n := node.(*ast.AssignStmt) + if len(n.Lhs) != len(n.Rhs) { + return + } + if len(n.Lhs) == 1 && n.Tok == token.DEFINE { + return + } + + for i, right := range n.Rhs { + call, ok := right.(*ast.CallExpr) + if !ok { + continue + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + pkg, ok := sel.X.(*ast.Ident) + if !ok || pkg.Name != "atomic" { + continue + } + + switch sel.Sel.Name { + case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": + f.checkAtomicAddAssignment(n.Lhs[i], call) + } + } +} + +// checkAtomicAddAssignment walks the atomic.Add* method calls checking for assigning the return value +// to the same variable being used in the operation +func (f *File) checkAtomicAddAssignment(left ast.Expr, call *ast.CallExpr) { + if len(call.Args) != 2 { + return + } + arg := call.Args[0] + broken := false + + if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND { + broken = f.gofmt(left) == f.gofmt(uarg.X) + } else if star, ok := left.(*ast.StarExpr); ok { + broken = f.gofmt(star.X) == f.gofmt(arg) + } + + if broken { + f.Bad(left.Pos(), "direct assignment to atomic value") + } +} diff --git a/tools/vendor/github.com/dnephin/govet/bool.go b/tools/vendor/github.com/dnephin/govet/bool.go new file mode 100644 index 000000000..07c2a93df --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/bool.go @@ -0,0 +1,186 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains boolean condition tests. + +package main + +import ( + "go/ast" + "go/token" +) + +func init() { + register("bool", + "check for mistakes involving boolean operators", + checkBool, + binaryExpr) +} + +func checkBool(f *File, n ast.Node) { + e := n.(*ast.BinaryExpr) + + var op boolOp + switch e.Op { + case token.LOR: + op = or + case token.LAND: + op = and + default: + return + } + + comm := op.commutativeSets(e) + for _, exprs := range comm { + op.checkRedundant(f, exprs) + op.checkSuspect(f, exprs) + } +} + +type boolOp struct { + name string + tok token.Token // token corresponding to this operator + badEq token.Token // token corresponding to the equality test that should not be used with this operator +} + +var ( + or = boolOp{"or", token.LOR, token.NEQ} + and = boolOp{"and", token.LAND, token.EQL} +) + +// commutativeSets returns all side effect free sets of +// expressions in e that are connected by op. +// For example, given 'a || b || f() || c || d' with the or op, +// commutativeSets returns {{b, a}, {d, c}}. +func (op boolOp) commutativeSets(e *ast.BinaryExpr) [][]ast.Expr { + exprs := op.split(e) + + // Partition the slice of expressions into commutative sets. + i := 0 + var sets [][]ast.Expr + for j := 0; j <= len(exprs); j++ { + if j == len(exprs) || hasSideEffects(exprs[j]) { + if i < j { + sets = append(sets, exprs[i:j]) + } + i = j + 1 + } + } + + return sets +} + +// checkRedundant checks for expressions of the form +// e && e +// e || e +// Exprs must contain only side effect free expressions. +func (op boolOp) checkRedundant(f *File, exprs []ast.Expr) { + seen := make(map[string]bool) + for _, e := range exprs { + efmt := f.gofmt(e) + if seen[efmt] { + f.Badf(e.Pos(), "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt) + } else { + seen[efmt] = true + } + } +} + +// checkSuspect checks for expressions of the form +// x != c1 || x != c2 +// x == c1 && x == c2 +// where c1 and c2 are constant expressions. +// If c1 and c2 are the same then it's redundant; +// if c1 and c2 are different then it's always true or always false. +// Exprs must contain only side effect free expressions. +func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) { + // seen maps from expressions 'x' to equality expressions 'x != c'. + seen := make(map[string]string) + + for _, e := range exprs { + bin, ok := e.(*ast.BinaryExpr) + if !ok || bin.Op != op.badEq { + continue + } + + // In order to avoid false positives, restrict to cases + // in which one of the operands is constant. We're then + // interested in the other operand. + // In the rare case in which both operands are constant + // (e.g. runtime.GOOS and "windows"), we'll only catch + // mistakes if the LHS is repeated, which is how most + // code is written. + var x ast.Expr + switch { + case f.pkg.types[bin.Y].Value != nil: + x = bin.X + case f.pkg.types[bin.X].Value != nil: + x = bin.Y + default: + continue + } + + // e is of the form 'x != c' or 'x == c'. + xfmt := f.gofmt(x) + efmt := f.gofmt(e) + if prev, found := seen[xfmt]; found { + // checkRedundant handles the case in which efmt == prev. + if efmt != prev { + f.Badf(e.Pos(), "suspect %s: %s %s %s", op.name, efmt, op.tok, prev) + } + } else { + seen[xfmt] = efmt + } + } +} + +// hasSideEffects reports whether evaluation of e has side effects. +func hasSideEffects(e ast.Expr) bool { + safe := true + ast.Inspect(e, func(node ast.Node) bool { + switch n := node.(type) { + // Using CallExpr here will catch conversions + // as well as function and method invocations. + // We'll live with the false negatives for now. + case *ast.CallExpr: + safe = false + return false + case *ast.UnaryExpr: + if n.Op == token.ARROW { + safe = false + return false + } + } + return true + }) + return !safe +} + +// split returns a slice of all subexpressions in e that are connected by op. +// For example, given 'a || (b || c) || d' with the or op, +// split returns []{d, c, b, a}. +func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) { + for { + e = unparen(e) + if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok { + exprs = append(exprs, op.split(b.Y)...) + e = b.X + } else { + exprs = append(exprs, e) + break + } + } + return +} + +// unparen returns e with any enclosing parentheses stripped. +func unparen(e ast.Expr) ast.Expr { + for { + p, ok := e.(*ast.ParenExpr) + if !ok { + return e + } + e = p.X + } +} diff --git a/tools/vendor/github.com/dnephin/govet/buildtag.go b/tools/vendor/github.com/dnephin/govet/buildtag.go new file mode 100644 index 000000000..ccf764ef8 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/buildtag.go @@ -0,0 +1,91 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "os" + "strings" + "unicode" +) + +var ( + nl = []byte("\n") + slashSlash = []byte("//") + plusBuild = []byte("+build") +) + +// checkBuildTag checks that build tags are in the correct location and well-formed. +func checkBuildTag(name string, data []byte) { + if !vet("buildtags") { + return + } + lines := bytes.SplitAfter(data, nl) + + // Determine cutpoint where +build comments are no longer valid. + // They are valid in leading // comments in the file followed by + // a blank line. + var cutoff int + for i, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + cutoff = i + continue + } + if bytes.HasPrefix(line, slashSlash) { + continue + } + break + } + + for i, line := range lines { + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, slashSlash) { + continue + } + text := bytes.TrimSpace(line[2:]) + if bytes.HasPrefix(text, plusBuild) { + fields := bytes.Fields(text) + if !bytes.Equal(fields[0], plusBuild) { + // Comment is something like +buildasdf not +build. + fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1) + continue + } + if i >= cutoff { + fmt.Fprintf(os.Stderr, "%s:%d: +build comment must appear before package clause and be followed by a blank line\n", name, i+1) + setExit(1) + continue + } + // Check arguments. + Args: + for _, arg := range fields[1:] { + for _, elem := range strings.Split(string(arg), ",") { + if strings.HasPrefix(elem, "!!") { + fmt.Fprintf(os.Stderr, "%s:%d: invalid double negative in build constraint: %s\n", name, i+1, arg) + setExit(1) + break Args + } + if strings.HasPrefix(elem, "!") { + elem = elem[1:] + } + for _, c := range elem { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + fmt.Fprintf(os.Stderr, "%s:%d: invalid non-alphanumeric build constraint: %s\n", name, i+1, arg) + setExit(1) + break Args + } + } + } + } + continue + } + // Comment with +build but not at beginning. + if bytes.Contains(line, plusBuild) && i < cutoff { + fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1) + continue + } + } +} diff --git a/tools/vendor/github.com/dnephin/govet/cgo.go b/tools/vendor/github.com/dnephin/govet/cgo.go new file mode 100644 index 000000000..b896862c8 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/cgo.go @@ -0,0 +1,130 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check for invalid cgo pointer passing. +// This looks for code that uses cgo to call C code passing values +// whose types are almost always invalid according to the cgo pointer +// sharing rules. +// Specifically, it warns about attempts to pass a Go chan, map, func, +// or slice to C, either directly, or via a pointer, array, or struct. + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("cgocall", + "check for types that may not be passed to cgo calls", + checkCgoCall, + callExpr) +} + +func checkCgoCall(f *File, node ast.Node) { + x := node.(*ast.CallExpr) + + // We are only looking for calls to functions imported from + // the "C" package. + sel, ok := x.Fun.(*ast.SelectorExpr) + if !ok { + return + } + id, ok := sel.X.(*ast.Ident) + if !ok || id.Name != "C" { + return + } + + for _, arg := range x.Args { + if !typeOKForCgoCall(cgoBaseType(f, arg)) { + f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C") + } + + // Check for passing the address of a bad type. + if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && f.hasBasicType(conv.Fun, types.UnsafePointer) { + arg = conv.Args[0] + } + if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND { + if !typeOKForCgoCall(cgoBaseType(f, u.X)) { + f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C") + } + } + } +} + +// cgoBaseType tries to look through type conversions involving +// unsafe.Pointer to find the real type. It converts: +// unsafe.Pointer(x) => x +// *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x +func cgoBaseType(f *File, arg ast.Expr) types.Type { + switch arg := arg.(type) { + case *ast.CallExpr: + if len(arg.Args) == 1 && f.hasBasicType(arg.Fun, types.UnsafePointer) { + return cgoBaseType(f, arg.Args[0]) + } + case *ast.StarExpr: + call, ok := arg.X.(*ast.CallExpr) + if !ok || len(call.Args) != 1 { + break + } + // Here arg is *f(v). + t := f.pkg.types[call.Fun].Type + if t == nil { + break + } + ptr, ok := t.Underlying().(*types.Pointer) + if !ok { + break + } + // Here arg is *(*p)(v) + elem, ok := ptr.Elem().Underlying().(*types.Basic) + if !ok || elem.Kind() != types.UnsafePointer { + break + } + // Here arg is *(*unsafe.Pointer)(v) + call, ok = call.Args[0].(*ast.CallExpr) + if !ok || len(call.Args) != 1 { + break + } + // Here arg is *(*unsafe.Pointer)(f(v)) + if !f.hasBasicType(call.Fun, types.UnsafePointer) { + break + } + // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) + u, ok := call.Args[0].(*ast.UnaryExpr) + if !ok || u.Op != token.AND { + break + } + // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v)) + return cgoBaseType(f, u.X) + } + + return f.pkg.types[arg].Type +} + +// typeOKForCgoCall returns true if the type of arg is OK to pass to a +// C function using cgo. This is not true for Go types with embedded +// pointers. +func typeOKForCgoCall(t types.Type) bool { + if t == nil { + return true + } + switch t := t.Underlying().(type) { + case *types.Chan, *types.Map, *types.Signature, *types.Slice: + return false + case *types.Pointer: + return typeOKForCgoCall(t.Elem()) + case *types.Array: + return typeOKForCgoCall(t.Elem()) + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + if !typeOKForCgoCall(t.Field(i).Type()) { + return false + } + } + } + return true +} diff --git a/tools/vendor/github.com/dnephin/govet/composite.go b/tools/vendor/github.com/dnephin/govet/composite.go new file mode 100644 index 000000000..8594c4897 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/composite.go @@ -0,0 +1,82 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for unkeyed struct literals. + +package main + +import ( + "github.com/dnephin/govet/internal/whitelist" + "flag" + "go/ast" + "go/types" + "strings" +) + +var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only") + +func init() { + register("composites", + "check that composite literals used field-keyed elements", + checkUnkeyedLiteral, + compositeLit) +} + +// checkUnkeyedLiteral checks if a composite literal is a struct literal with +// unkeyed fields. +func checkUnkeyedLiteral(f *File, node ast.Node) { + cl := node.(*ast.CompositeLit) + + typ := f.pkg.types[cl].Type + if typ == nil { + // cannot determine composite literals' type, skip it + return + } + typeName := typ.String() + if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] { + // skip whitelisted types + return + } + if _, ok := typ.Underlying().(*types.Struct); !ok { + // skip non-struct composite literals + return + } + if isLocalType(f, typeName) { + // allow unkeyed locally defined composite literal + return + } + + // check if the CompositeLit contains an unkeyed field + allKeyValue := true + for _, e := range cl.Elts { + if _, ok := e.(*ast.KeyValueExpr); !ok { + allKeyValue = false + break + } + } + if allKeyValue { + // all the composite literal fields are keyed + return + } + + f.Badf(cl.Pos(), "%s composite literal uses unkeyed fields", typeName) +} + +func isLocalType(f *File, typeName string) bool { + if strings.HasPrefix(typeName, "struct{") { + // struct literals are local types + return true + } + + pkgname := f.pkg.path + if strings.HasPrefix(typeName, pkgname+".") { + return true + } + + // treat types as local inside test packages with _test name suffix + if strings.HasSuffix(pkgname, "_test") { + pkgname = pkgname[:len(pkgname)-len("_test")] + } + return strings.HasPrefix(typeName, pkgname+".") +} diff --git a/tools/vendor/github.com/dnephin/govet/copylock.go b/tools/vendor/github.com/dnephin/govet/copylock.go new file mode 100644 index 000000000..65337688b --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/copylock.go @@ -0,0 +1,239 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code to check that locks are not passed by value. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("copylocks", + "check that locks are not passed by value", + checkCopyLocks, + funcDecl, rangeStmt, funcLit, callExpr, assignStmt, genDecl, compositeLit, returnStmt) +} + +// checkCopyLocks checks whether node might +// inadvertently copy a lock. +func checkCopyLocks(f *File, node ast.Node) { + switch node := node.(type) { + case *ast.RangeStmt: + checkCopyLocksRange(f, node) + case *ast.FuncDecl: + checkCopyLocksFunc(f, node.Name.Name, node.Recv, node.Type) + case *ast.FuncLit: + checkCopyLocksFunc(f, "func", nil, node.Type) + case *ast.CallExpr: + checkCopyLocksCallExpr(f, node) + case *ast.AssignStmt: + checkCopyLocksAssign(f, node) + case *ast.GenDecl: + checkCopyLocksGenDecl(f, node) + case *ast.CompositeLit: + checkCopyLocksCompositeLit(f, node) + case *ast.ReturnStmt: + checkCopyLocksReturnStmt(f, node) + } +} + +// checkCopyLocksAssign checks whether an assignment +// copies a lock. +func checkCopyLocksAssign(f *File, as *ast.AssignStmt) { + for i, x := range as.Rhs { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "assignment copies lock value to %v: %v", f.gofmt(as.Lhs[i]), path) + } + } +} + +// checkCopyLocksGenDecl checks whether lock is copied +// in variable declaration. +func checkCopyLocksGenDecl(f *File, gd *ast.GenDecl) { + if gd.Tok != token.VAR { + return + } + for _, spec := range gd.Specs { + valueSpec := spec.(*ast.ValueSpec) + for i, x := range valueSpec.Values { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path) + } + } + } +} + +// checkCopyLocksCompositeLit detects lock copy inside a composite literal +func checkCopyLocksCompositeLit(f *File, cl *ast.CompositeLit) { + for _, x := range cl.Elts { + if node, ok := x.(*ast.KeyValueExpr); ok { + x = node.Value + } + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "literal copies lock value from %v: %v", f.gofmt(x), path) + } + } +} + +// checkCopyLocksReturnStmt detects lock copy in return statement +func checkCopyLocksReturnStmt(f *File, rs *ast.ReturnStmt) { + for _, x := range rs.Results { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "return copies lock value: %v", path) + } + } +} + +// checkCopyLocksCallExpr detects lock copy in the arguments to a function call +func checkCopyLocksCallExpr(f *File, ce *ast.CallExpr) { + if id, ok := ce.Fun.(*ast.Ident); ok && id.Name == "new" && f.pkg.types[id].IsBuiltin() { + // Skip 'new(Type)' for built-in 'new' + return + } + for _, x := range ce.Args { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "function call copies lock value: %v", path) + } + } +} + +// checkCopyLocksFunc checks whether a function might +// inadvertently copy a lock, by checking whether +// its receiver, parameters, or return values +// are locks. +func checkCopyLocksFunc(f *File, name string, recv *ast.FieldList, typ *ast.FuncType) { + if recv != nil && len(recv.List) > 0 { + expr := recv.List[0].Type + if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil { + f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path) + } + } + + if typ.Params != nil { + for _, field := range typ.Params.List { + expr := field.Type + if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil { + f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path) + } + } + } + + // Don't check typ.Results. If T has a Lock field it's OK to write + // return T{} + // because that is returning the zero value. Leave result checking + // to the return statement. +} + +// checkCopyLocksRange checks whether a range statement +// might inadvertently copy a lock by checking whether +// any of the range variables are locks. +func checkCopyLocksRange(f *File, r *ast.RangeStmt) { + checkCopyLocksRangeVar(f, r.Tok, r.Key) + checkCopyLocksRangeVar(f, r.Tok, r.Value) +} + +func checkCopyLocksRangeVar(f *File, rtok token.Token, e ast.Expr) { + if e == nil { + return + } + id, isId := e.(*ast.Ident) + if isId && id.Name == "_" { + return + } + + var typ types.Type + if rtok == token.DEFINE { + if !isId { + return + } + obj := f.pkg.defs[id] + if obj == nil { + return + } + typ = obj.Type() + } else { + typ = f.pkg.types[e].Type + } + + if typ == nil { + return + } + if path := lockPath(f.pkg.typesPkg, typ); path != nil { + f.Badf(e.Pos(), "range var %s copies lock: %v", f.gofmt(e), path) + } +} + +type typePath []types.Type + +// String pretty-prints a typePath. +func (path typePath) String() string { + n := len(path) + var buf bytes.Buffer + for i := range path { + if i > 0 { + fmt.Fprint(&buf, " contains ") + } + // The human-readable path is in reverse order, outermost to innermost. + fmt.Fprint(&buf, path[n-i-1].String()) + } + return buf.String() +} + +func lockPathRhs(f *File, x ast.Expr) typePath { + if _, ok := x.(*ast.CompositeLit); ok { + return nil + } + if _, ok := x.(*ast.CallExpr); ok { + // A call may return a zero value. + return nil + } + if star, ok := x.(*ast.StarExpr); ok { + if _, ok := star.X.(*ast.CallExpr); ok { + // A call may return a pointer to a zero value. + return nil + } + } + return lockPath(f.pkg.typesPkg, f.pkg.types[x].Type) +} + +// lockPath returns a typePath describing the location of a lock value +// contained in typ. If there is no contained lock, it returns nil. +func lockPath(tpkg *types.Package, typ types.Type) typePath { + if typ == nil { + return nil + } + + // We're only interested in the case in which the underlying + // type is a struct. (Interfaces and pointers are safe to copy.) + styp, ok := typ.Underlying().(*types.Struct) + if !ok { + return nil + } + + // We're looking for cases in which a reference to this type + // can be locked, but a value cannot. This differentiates + // embedded interfaces from embedded values. + if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil { + if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil { + return []types.Type{typ} + } + } + + nfields := styp.NumFields() + for i := 0; i < nfields; i++ { + ftyp := styp.Field(i).Type() + subpath := lockPath(tpkg, ftyp) + if subpath != nil { + return append(subpath, typ) + } + } + + return nil +} diff --git a/tools/vendor/github.com/dnephin/govet/deadcode.go b/tools/vendor/github.com/dnephin/govet/deadcode.go new file mode 100644 index 000000000..b1077aef3 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/deadcode.go @@ -0,0 +1,298 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check for syntactically unreachable code. + +package main + +import ( + "go/ast" + "go/token" +) + +func init() { + register("unreachable", + "check for unreachable code", + checkUnreachable, + funcDecl, funcLit) +} + +type deadState struct { + f *File + hasBreak map[ast.Stmt]bool + hasGoto map[string]bool + labels map[string]ast.Stmt + breakTarget ast.Stmt + + reachable bool +} + +// checkUnreachable checks a function body for dead code. +// +// TODO(adonovan): use the new cfg package, which is more precise. +func checkUnreachable(f *File, node ast.Node) { + var body *ast.BlockStmt + switch n := node.(type) { + case *ast.FuncDecl: + body = n.Body + case *ast.FuncLit: + body = n.Body + } + if body == nil { + return + } + + d := &deadState{ + f: f, + hasBreak: make(map[ast.Stmt]bool), + hasGoto: make(map[string]bool), + labels: make(map[string]ast.Stmt), + } + + d.findLabels(body) + + d.reachable = true + d.findDead(body) +} + +// findLabels gathers information about the labels defined and used by stmt +// and about which statements break, whether a label is involved or not. +func (d *deadState) findLabels(stmt ast.Stmt) { + switch x := stmt.(type) { + default: + d.f.Warnf(x.Pos(), "internal error in findLabels: unexpected statement %T", x) + + case *ast.AssignStmt, + *ast.BadStmt, + *ast.DeclStmt, + *ast.DeferStmt, + *ast.EmptyStmt, + *ast.ExprStmt, + *ast.GoStmt, + *ast.IncDecStmt, + *ast.ReturnStmt, + *ast.SendStmt: + // no statements inside + + case *ast.BlockStmt: + for _, stmt := range x.List { + d.findLabels(stmt) + } + + case *ast.BranchStmt: + switch x.Tok { + case token.GOTO: + if x.Label != nil { + d.hasGoto[x.Label.Name] = true + } + + case token.BREAK: + stmt := d.breakTarget + if x.Label != nil { + stmt = d.labels[x.Label.Name] + } + if stmt != nil { + d.hasBreak[stmt] = true + } + } + + case *ast.IfStmt: + d.findLabels(x.Body) + if x.Else != nil { + d.findLabels(x.Else) + } + + case *ast.LabeledStmt: + d.labels[x.Label.Name] = x.Stmt + d.findLabels(x.Stmt) + + // These cases are all the same, but the x.Body only works + // when the specific type of x is known, so the cases cannot + // be merged. + case *ast.ForStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.RangeStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.SelectStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.SwitchStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.TypeSwitchStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.CommClause: + for _, stmt := range x.Body { + d.findLabels(stmt) + } + + case *ast.CaseClause: + for _, stmt := range x.Body { + d.findLabels(stmt) + } + } +} + +// findDead walks the statement looking for dead code. +// If d.reachable is false on entry, stmt itself is dead. +// When findDead returns, d.reachable tells whether the +// statement following stmt is reachable. +func (d *deadState) findDead(stmt ast.Stmt) { + // Is this a labeled goto target? + // If so, assume it is reachable due to the goto. + // This is slightly conservative, in that we don't + // check that the goto is reachable, so + // L: goto L + // will not provoke a warning. + // But it's good enough. + if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] { + d.reachable = true + } + + if !d.reachable { + switch stmt.(type) { + case *ast.EmptyStmt: + // do not warn about unreachable empty statements + default: + d.f.Bad(stmt.Pos(), "unreachable code") + d.reachable = true // silence error about next statement + } + } + + switch x := stmt.(type) { + default: + d.f.Warnf(x.Pos(), "internal error in findDead: unexpected statement %T", x) + + case *ast.AssignStmt, + *ast.BadStmt, + *ast.DeclStmt, + *ast.DeferStmt, + *ast.EmptyStmt, + *ast.GoStmt, + *ast.IncDecStmt, + *ast.SendStmt: + // no control flow + + case *ast.BlockStmt: + for _, stmt := range x.List { + d.findDead(stmt) + } + + case *ast.BranchStmt: + switch x.Tok { + case token.BREAK, token.GOTO, token.FALLTHROUGH: + d.reachable = false + case token.CONTINUE: + // NOTE: We accept "continue" statements as terminating. + // They are not necessary in the spec definition of terminating, + // because a continue statement cannot be the final statement + // before a return. But for the more general problem of syntactically + // identifying dead code, continue redirects control flow just + // like the other terminating statements. + d.reachable = false + } + + case *ast.ExprStmt: + // Call to panic? + call, ok := x.X.(*ast.CallExpr) + if ok { + name, ok := call.Fun.(*ast.Ident) + if ok && name.Name == "panic" && name.Obj == nil { + d.reachable = false + } + } + + case *ast.ForStmt: + d.findDead(x.Body) + d.reachable = x.Cond != nil || d.hasBreak[x] + + case *ast.IfStmt: + d.findDead(x.Body) + if x.Else != nil { + r := d.reachable + d.reachable = true + d.findDead(x.Else) + d.reachable = d.reachable || r + } else { + // might not have executed if statement + d.reachable = true + } + + case *ast.LabeledStmt: + d.findDead(x.Stmt) + + case *ast.RangeStmt: + d.findDead(x.Body) + d.reachable = true + + case *ast.ReturnStmt: + d.reachable = false + + case *ast.SelectStmt: + // NOTE: Unlike switch and type switch below, we don't care + // whether a select has a default, because a select without a + // default blocks until one of the cases can run. That's different + // from a switch without a default, which behaves like it has + // a default with an empty body. + anyReachable := false + for _, comm := range x.Body.List { + d.reachable = true + for _, stmt := range comm.(*ast.CommClause).Body { + d.findDead(stmt) + } + anyReachable = anyReachable || d.reachable + } + d.reachable = anyReachable || d.hasBreak[x] + + case *ast.SwitchStmt: + anyReachable := false + hasDefault := false + for _, cas := range x.Body.List { + cc := cas.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + d.reachable = true + for _, stmt := range cc.Body { + d.findDead(stmt) + } + anyReachable = anyReachable || d.reachable + } + d.reachable = anyReachable || d.hasBreak[x] || !hasDefault + + case *ast.TypeSwitchStmt: + anyReachable := false + hasDefault := false + for _, cas := range x.Body.List { + cc := cas.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + d.reachable = true + for _, stmt := range cc.Body { + d.findDead(stmt) + } + anyReachable = anyReachable || d.reachable + } + d.reachable = anyReachable || d.hasBreak[x] || !hasDefault + } +} diff --git a/tools/vendor/github.com/dnephin/govet/doc.go b/tools/vendor/github.com/dnephin/govet/doc.go new file mode 100644 index 000000000..69d5f9cc7 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/doc.go @@ -0,0 +1,205 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + +Vet examines Go source code and reports suspicious constructs, such as Printf +calls whose arguments do not align with the format string. Vet uses heuristics +that do not guarantee all reports are genuine problems, but it can find errors +not caught by the compilers. + +It can be invoked three ways: + +By package, from the go tool: + go vet package/path/name +vets the package whose path is provided. + +By files: + go tool vet source/directory/*.go +vets the files named, all of which must be in the same package. + +By directory: + go tool vet source/directory +recursively descends the directory, vetting each package it finds. + +Vet's exit code is 2 for erroneous invocation of the tool, 1 if a +problem was reported, and 0 otherwise. Note that the tool does not +check every possible problem and depends on unreliable heuristics +so it should be used as guidance only, not as a firm indicator of +program correctness. + +By default the -all flag is set so all checks are performed. +If any flags are explicitly set to true, only those tests are run. Conversely, if +any flag is explicitly set to false, only those tests are disabled. Thus -printf=true +runs the printf check, -printf=false runs all checks except the printf check. + +Available checks: + +Assembly declarations + +Flag: -asmdecl + +Mismatches between assembly files and Go function declarations. + +Useless assignments + +Flag: -assign + +Check for useless assignments. + +Atomic mistakes + +Flag: -atomic + +Common mistaken usages of the sync/atomic package. + +Boolean conditions + +Flag: -bool + +Mistakes involving boolean operators. + +Build tags + +Flag: -buildtags + +Badly formed or misplaced +build tags. + +Invalid uses of cgo + +Flag: -cgocall + +Detect some violations of the cgo pointer passing rules. + +Unkeyed composite literals + +Flag: -composites + +Composite struct literals that do not use the field-keyed syntax. + +Copying locks + +Flag: -copylocks + +Locks that are erroneously passed by value. + +Tests, benchmarks and documentation examples + +Flag: -tests + +Mistakes involving tests including functions with incorrect names or signatures +and example tests that document identifiers not in the package. + +Failure to call the cancelation function returned by context.WithCancel. + +Flag: -lostcancel + +The cancelation function returned by context.WithCancel, WithTimeout, +and WithDeadline must be called or the new context will remain live +until its parent context is cancelled. +(The background context is never cancelled.) + +Methods + +Flag: -methods + +Non-standard signatures for methods with familiar names, including: + Format GobEncode GobDecode MarshalJSON MarshalXML + Peek ReadByte ReadFrom ReadRune Scan Seek + UnmarshalJSON UnreadByte UnreadRune WriteByte + WriteTo + +Nil function comparison + +Flag: -nilfunc + +Comparisons between functions and nil. + +Printf family + +Flag: -printf + +Suspicious calls to functions in the Printf family, including any functions +with these names, disregarding case: + Print Printf Println + Fprint Fprintf Fprintln + Sprint Sprintf Sprintln + Error Errorf + Fatal Fatalf + Log Logf + Panic Panicf Panicln +The -printfuncs flag can be used to redefine this list. +If the function name ends with an 'f', the function is assumed to take +a format descriptor string in the manner of fmt.Printf. If not, vet +complains about arguments that look like format descriptor strings. + +It also checks for errors such as using a Writer as the first argument of +Printf. + +Struct tags + +Range loop variables + +Flag: -rangeloops + +Incorrect uses of range loop variables in closures. + +Shadowed variables + +Flag: -shadow=false (experimental; must be set explicitly) + +Variables that may have been unintentionally shadowed. + +Shifts + +Flag: -shift + +Shifts equal to or longer than the variable's length. + +Flag: -structtags + +Struct tags that do not follow the format understood by reflect.StructTag.Get. +Well-known encoding struct tags (json, xml) used with unexported fields. + +Unreachable code + +Flag: -unreachable + +Unreachable code. + +Misuse of unsafe Pointers + +Flag: -unsafeptr + +Likely incorrect uses of unsafe.Pointer to convert integers to pointers. +A conversion from uintptr to unsafe.Pointer is invalid if it implies that +there is a uintptr-typed word in memory that holds a pointer value, +because that word will be invisible to stack copying and to the garbage +collector. + +Unused result of certain function calls + +Flag: -unusedresult + +Calls to well-known functions and methods that return a value that is +discarded. By default, this includes functions like fmt.Errorf and +fmt.Sprintf and methods like String and Error. The flags -unusedfuncs +and -unusedstringmethods control the set. + +Other flags + +These flags configure the behavior of vet: + + -all (default true) + Enable all non-experimental checks. + -v + Verbose mode + -printfuncs + A comma-separated list of print-like function names + to supplement the standard list. + For more information, see the discussion of the -printf flag. + -shadowstrict + Whether to be strict about shadowing; can be noisy. +*/ +package main diff --git a/tools/vendor/github.com/dnephin/govet/internal/cfg/builder.go b/tools/vendor/github.com/dnephin/govet/internal/cfg/builder.go new file mode 100644 index 000000000..da1cc7e63 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/internal/cfg/builder.go @@ -0,0 +1,512 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cfg + +// This file implements the CFG construction pass. + +import ( + "fmt" + "go/ast" + "go/token" +) + +type builder struct { + cfg *CFG + mayReturn func(*ast.CallExpr) bool + current *Block + lblocks map[*ast.Object]*lblock // labeled blocks + targets *targets // linked stack of branch targets +} + +func (b *builder) stmt(_s ast.Stmt) { + // The label of the current statement. If non-nil, its _goto + // target is always set; its _break and _continue are set only + // within the body of switch/typeswitch/select/for/range. + // It is effectively an additional default-nil parameter of stmt(). + var label *lblock +start: + switch s := _s.(type) { + case *ast.BadStmt, + *ast.SendStmt, + *ast.IncDecStmt, + *ast.GoStmt, + *ast.DeferStmt, + *ast.EmptyStmt, + *ast.AssignStmt: + // No effect on control flow. + b.add(s) + + case *ast.ExprStmt: + b.add(s) + if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) { + // Calls to panic, os.Exit, etc, never return. + b.current = b.newUnreachableBlock("unreachable.call") + } + + case *ast.DeclStmt: + // Treat each var ValueSpec as a separate statement. + d := s.Decl.(*ast.GenDecl) + if d.Tok == token.VAR { + for _, spec := range d.Specs { + if spec, ok := spec.(*ast.ValueSpec); ok { + b.add(spec) + } + } + } + + case *ast.LabeledStmt: + label = b.labeledBlock(s.Label) + b.jump(label._goto) + b.current = label._goto + _s = s.Stmt + goto start // effectively: tailcall stmt(g, s.Stmt, label) + + case *ast.ReturnStmt: + b.add(s) + b.current = b.newUnreachableBlock("unreachable.return") + + case *ast.BranchStmt: + var block *Block + switch s.Tok { + case token.BREAK: + if s.Label != nil { + if lb := b.labeledBlock(s.Label); lb != nil { + block = lb._break + } + } else { + for t := b.targets; t != nil && block == nil; t = t.tail { + block = t._break + } + } + + case token.CONTINUE: + if s.Label != nil { + if lb := b.labeledBlock(s.Label); lb != nil { + block = lb._continue + } + } else { + for t := b.targets; t != nil && block == nil; t = t.tail { + block = t._continue + } + } + + case token.FALLTHROUGH: + for t := b.targets; t != nil; t = t.tail { + block = t._fallthrough + } + + case token.GOTO: + if s.Label != nil { + block = b.labeledBlock(s.Label)._goto + } + } + if block == nil { + block = b.newBlock("undefined.branch") + } + b.jump(block) + b.current = b.newUnreachableBlock("unreachable.branch") + + case *ast.BlockStmt: + b.stmtList(s.List) + + case *ast.IfStmt: + if s.Init != nil { + b.stmt(s.Init) + } + then := b.newBlock("if.then") + done := b.newBlock("if.done") + _else := done + if s.Else != nil { + _else = b.newBlock("if.else") + } + b.add(s.Cond) + b.ifelse(then, _else) + b.current = then + b.stmt(s.Body) + b.jump(done) + + if s.Else != nil { + b.current = _else + b.stmt(s.Else) + b.jump(done) + } + + b.current = done + + case *ast.SwitchStmt: + b.switchStmt(s, label) + + case *ast.TypeSwitchStmt: + b.typeSwitchStmt(s, label) + + case *ast.SelectStmt: + b.selectStmt(s, label) + + case *ast.ForStmt: + b.forStmt(s, label) + + case *ast.RangeStmt: + b.rangeStmt(s, label) + + default: + panic(fmt.Sprintf("unexpected statement kind: %T", s)) + } +} + +func (b *builder) stmtList(list []ast.Stmt) { + for _, s := range list { + b.stmt(s) + } +} + +func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { + if s.Init != nil { + b.stmt(s.Init) + } + if s.Tag != nil { + b.add(s.Tag) + } + done := b.newBlock("switch.done") + if label != nil { + label._break = done + } + // We pull the default case (if present) down to the end. + // But each fallthrough label must point to the next + // body block in source order, so we preallocate a + // body block (fallthru) for the next case. + // Unfortunately this makes for a confusing block order. + var defaultBody *[]ast.Stmt + var defaultFallthrough *Block + var fallthru, defaultBlock *Block + ncases := len(s.Body.List) + for i, clause := range s.Body.List { + body := fallthru + if body == nil { + body = b.newBlock("switch.body") // first case only + } + + // Preallocate body block for the next case. + fallthru = done + if i+1 < ncases { + fallthru = b.newBlock("switch.body") + } + + cc := clause.(*ast.CaseClause) + if cc.List == nil { + // Default case. + defaultBody = &cc.Body + defaultFallthrough = fallthru + defaultBlock = body + continue + } + + var nextCond *Block + for _, cond := range cc.List { + nextCond = b.newBlock("switch.next") + b.add(cond) // one half of the tag==cond condition + b.ifelse(body, nextCond) + b.current = nextCond + } + b.current = body + b.targets = &targets{ + tail: b.targets, + _break: done, + _fallthrough: fallthru, + } + b.stmtList(cc.Body) + b.targets = b.targets.tail + b.jump(done) + b.current = nextCond + } + if defaultBlock != nil { + b.jump(defaultBlock) + b.current = defaultBlock + b.targets = &targets{ + tail: b.targets, + _break: done, + _fallthrough: defaultFallthrough, + } + b.stmtList(*defaultBody) + b.targets = b.targets.tail + } + b.jump(done) + b.current = done +} + +func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) { + if s.Init != nil { + b.stmt(s.Init) + } + if s.Assign != nil { + b.add(s.Assign) + } + + done := b.newBlock("typeswitch.done") + if label != nil { + label._break = done + } + var default_ *ast.CaseClause + for _, clause := range s.Body.List { + cc := clause.(*ast.CaseClause) + if cc.List == nil { + default_ = cc + continue + } + body := b.newBlock("typeswitch.body") + var next *Block + for _, casetype := range cc.List { + next = b.newBlock("typeswitch.next") + // casetype is a type, so don't call b.add(casetype). + // This block logically contains a type assertion, + // x.(casetype), but it's unclear how to represent x. + _ = casetype + b.ifelse(body, next) + b.current = next + } + b.current = body + b.typeCaseBody(cc, done) + b.current = next + } + if default_ != nil { + b.typeCaseBody(default_, done) + } else { + b.jump(done) + } + b.current = done +} + +func (b *builder) typeCaseBody(cc *ast.CaseClause, done *Block) { + b.targets = &targets{ + tail: b.targets, + _break: done, + } + b.stmtList(cc.Body) + b.targets = b.targets.tail + b.jump(done) +} + +func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) { + // First evaluate channel expressions. + // TODO(adonovan): fix: evaluate only channel exprs here. + for _, clause := range s.Body.List { + if comm := clause.(*ast.CommClause).Comm; comm != nil { + b.stmt(comm) + } + } + + done := b.newBlock("select.done") + if label != nil { + label._break = done + } + + var defaultBody *[]ast.Stmt + for _, cc := range s.Body.List { + clause := cc.(*ast.CommClause) + if clause.Comm == nil { + defaultBody = &clause.Body + continue + } + body := b.newBlock("select.body") + next := b.newBlock("select.next") + b.ifelse(body, next) + b.current = body + b.targets = &targets{ + tail: b.targets, + _break: done, + } + switch comm := clause.Comm.(type) { + case *ast.ExprStmt: // <-ch + // nop + case *ast.AssignStmt: // x := <-states[state].Chan + b.add(comm.Lhs[0]) + } + b.stmtList(clause.Body) + b.targets = b.targets.tail + b.jump(done) + b.current = next + } + if defaultBody != nil { + b.targets = &targets{ + tail: b.targets, + _break: done, + } + b.stmtList(*defaultBody) + b.targets = b.targets.tail + b.jump(done) + } + b.current = done +} + +func (b *builder) forStmt(s *ast.ForStmt, label *lblock) { + // ...init... + // jump loop + // loop: + // if cond goto body else done + // body: + // ...body... + // jump post + // post: (target of continue) + // ...post... + // jump loop + // done: (target of break) + if s.Init != nil { + b.stmt(s.Init) + } + body := b.newBlock("for.body") + done := b.newBlock("for.done") // target of 'break' + loop := body // target of back-edge + if s.Cond != nil { + loop = b.newBlock("for.loop") + } + cont := loop // target of 'continue' + if s.Post != nil { + cont = b.newBlock("for.post") + } + if label != nil { + label._break = done + label._continue = cont + } + b.jump(loop) + b.current = loop + if loop != body { + b.add(s.Cond) + b.ifelse(body, done) + b.current = body + } + b.targets = &targets{ + tail: b.targets, + _break: done, + _continue: cont, + } + b.stmt(s.Body) + b.targets = b.targets.tail + b.jump(cont) + + if s.Post != nil { + b.current = cont + b.stmt(s.Post) + b.jump(loop) // back-edge + } + b.current = done +} + +func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) { + b.add(s.X) + + if s.Key != nil { + b.add(s.Key) + } + if s.Value != nil { + b.add(s.Value) + } + + // ... + // loop: (target of continue) + // if ... goto body else done + // body: + // ... + // jump loop + // done: (target of break) + + loop := b.newBlock("range.loop") + b.jump(loop) + b.current = loop + + body := b.newBlock("range.body") + done := b.newBlock("range.done") + b.ifelse(body, done) + b.current = body + + if label != nil { + label._break = done + label._continue = loop + } + b.targets = &targets{ + tail: b.targets, + _break: done, + _continue: loop, + } + b.stmt(s.Body) + b.targets = b.targets.tail + b.jump(loop) // back-edge + b.current = done +} + +// -------- helpers -------- + +// Destinations associated with unlabeled for/switch/select stmts. +// We push/pop one of these as we enter/leave each construct and for +// each BranchStmt we scan for the innermost target of the right type. +// +type targets struct { + tail *targets // rest of stack + _break *Block + _continue *Block + _fallthrough *Block +} + +// Destinations associated with a labeled block. +// We populate these as labels are encountered in forward gotos or +// labeled statements. +// +type lblock struct { + _goto *Block + _break *Block + _continue *Block +} + +// labeledBlock returns the branch target associated with the +// specified label, creating it if needed. +// +func (b *builder) labeledBlock(label *ast.Ident) *lblock { + lb := b.lblocks[label.Obj] + if lb == nil { + lb = &lblock{_goto: b.newBlock(label.Name)} + if b.lblocks == nil { + b.lblocks = make(map[*ast.Object]*lblock) + } + b.lblocks[label.Obj] = lb + } + return lb +} + +// newBlock appends a new unconnected basic block to b.cfg's block +// slice and returns it. +// It does not automatically become the current block. +// comment is an optional string for more readable debugging output. +func (b *builder) newBlock(comment string) *Block { + g := b.cfg + block := &Block{ + index: int32(len(g.Blocks)), + comment: comment, + } + block.Succs = block.succs2[:0] + g.Blocks = append(g.Blocks, block) + return block +} + +func (b *builder) newUnreachableBlock(comment string) *Block { + block := b.newBlock(comment) + block.unreachable = true + return block +} + +func (b *builder) add(n ast.Node) { + b.current.Nodes = append(b.current.Nodes, n) +} + +// jump adds an edge from the current block to the target block, +// and sets b.current to nil. +func (b *builder) jump(target *Block) { + b.current.Succs = append(b.current.Succs, target) + b.current = nil +} + +// ifelse emits edges from the current block to the t and f blocks, +// and sets b.current to nil. +func (b *builder) ifelse(t, f *Block) { + b.current.Succs = append(b.current.Succs, t, f) + b.current = nil +} diff --git a/tools/vendor/github.com/dnephin/govet/internal/cfg/cfg.go b/tools/vendor/github.com/dnephin/govet/internal/cfg/cfg.go new file mode 100644 index 000000000..e4d5bfe5d --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/internal/cfg/cfg.go @@ -0,0 +1,142 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This package constructs a simple control-flow graph (CFG) of the +// statements and expressions within a single function. +// +// Use cfg.New to construct the CFG for a function body. +// +// The blocks of the CFG contain all the function's non-control +// statements. The CFG does not contain control statements such as If, +// Switch, Select, and Branch, but does contain their subexpressions. +// For example, this source code: +// +// if x := f(); x != nil { +// T() +// } else { +// F() +// } +// +// produces this CFG: +// +// 1: x := f() +// x != nil +// succs: 2, 3 +// 2: T() +// succs: 4 +// 3: F() +// succs: 4 +// 4: +// +// The CFG does contain Return statements; even implicit returns are +// materialized (at the position of the function's closing brace). +// +// The CFG does not record conditions associated with conditional branch +// edges, nor the short-circuit semantics of the && and || operators, +// nor abnormal control flow caused by panic. If you need this +// information, use golang.org/x/tools/go/ssa instead. +// +package cfg + +// Although the vet tool has type information, it is often extremely +// fragmentary, so for simplicity this package does not depend on +// go/types. Consequently control-flow conditions are ignored even +// when constant, and "mayReturn" information must be provided by the +// client. +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" +) + +// A CFG represents the control-flow graph of a single function. +// +// The entry point is Blocks[0]; there may be multiple return blocks. +type CFG struct { + Blocks []*Block // block[0] is entry; order otherwise undefined +} + +// A Block represents a basic block: a list of statements and +// expressions that are always evaluated sequentially. +// +// A block may have 0-2 successors: zero for a return block or a block +// that calls a function such as panic that never returns; one for a +// normal (jump) block; and two for a conditional (if) block. +type Block struct { + Nodes []ast.Node // statements, expressions, and ValueSpecs + Succs []*Block // successor nodes in the graph + + comment string // for debugging + index int32 // index within CFG.Blocks + unreachable bool // is block of stmts following return/panic/for{} + succs2 [2]*Block // underlying array for Succs +} + +// New returns a new control-flow graph for the specified function body, +// which must be non-nil. +// +// The CFG builder calls mayReturn to determine whether a given function +// call may return. For example, calls to panic, os.Exit, and log.Fatal +// do not return, so the builder can remove infeasible graph edges +// following such calls. The builder calls mayReturn only for a +// CallExpr beneath an ExprStmt. +func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { + b := builder{ + mayReturn: mayReturn, + cfg: new(CFG), + } + b.current = b.newBlock("entry") + b.stmt(body) + + // Does control fall off the end of the function's body? + // Make implicit return explicit. + if b.current != nil && !b.current.unreachable { + b.add(&ast.ReturnStmt{ + Return: body.End() - 1, + }) + } + + return b.cfg +} + +func (b *Block) String() string { + return fmt.Sprintf("block %d (%s)", b.index, b.comment) +} + +// Return returns the return statement at the end of this block if present, nil otherwise. +func (b *Block) Return() (ret *ast.ReturnStmt) { + if len(b.Nodes) > 0 { + ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt) + } + return +} + +// Format formats the control-flow graph for ease of debugging. +func (g *CFG) Format(fset *token.FileSet) string { + var buf bytes.Buffer + for _, b := range g.Blocks { + fmt.Fprintf(&buf, ".%d: # %s\n", b.index, b.comment) + for _, n := range b.Nodes { + fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) + } + if len(b.Succs) > 0 { + fmt.Fprintf(&buf, "\tsuccs:") + for _, succ := range b.Succs { + fmt.Fprintf(&buf, " %d", succ.index) + } + buf.WriteByte('\n') + } + buf.WriteByte('\n') + } + return buf.String() +} + +func formatNode(fset *token.FileSet, n ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, fset, n) + // Indent secondary lines by a tab. + return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1)) +} diff --git a/tools/vendor/github.com/dnephin/govet/internal/whitelist/whitelist.go b/tools/vendor/github.com/dnephin/govet/internal/whitelist/whitelist.go new file mode 100644 index 000000000..fdd65d373 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/internal/whitelist/whitelist.go @@ -0,0 +1,28 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package whitelist defines exceptions for the vet tool. +package whitelist + +// UnkeyedLiteral is a white list of types in the standard packages +// that are used with unkeyed literals we deem to be acceptable. +var UnkeyedLiteral = map[string]bool{ + // These image and image/color struct types are frozen. We will never add fields to them. + "image/color.Alpha16": true, + "image/color.Alpha": true, + "image/color.CMYK": true, + "image/color.Gray16": true, + "image/color.Gray": true, + "image/color.NRGBA64": true, + "image/color.NRGBA": true, + "image/color.NYCbCrA": true, + "image/color.RGBA64": true, + "image/color.RGBA": true, + "image/color.YCbCr": true, + "image.Point": true, + "image.Rectangle": true, + "image.Uniform": true, + + "unicode.Range16": true, +} diff --git a/tools/vendor/github.com/dnephin/govet/lostcancel.go b/tools/vendor/github.com/dnephin/govet/lostcancel.go new file mode 100644 index 000000000..adaff23e1 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/lostcancel.go @@ -0,0 +1,318 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "github.com/dnephin/govet/internal/cfg" + "fmt" + "go/ast" + "go/types" + "strconv" +) + +func init() { + register("lostcancel", + "check for failure to call cancelation function returned by context.WithCancel", + checkLostCancel, + funcDecl, funcLit) +} + +const debugLostCancel = false + +var contextPackage = "context" + +// checkLostCancel reports a failure to the call the cancel function +// returned by context.WithCancel, either because the variable was +// assigned to the blank identifier, or because there exists a +// control-flow path from the call to a return statement and that path +// does not "use" the cancel function. Any reference to the variable +// counts as a use, even within a nested function literal. +// +// checkLostCancel analyzes a single named or literal function. +func checkLostCancel(f *File, node ast.Node) { + // Fast path: bypass check if file doesn't use context.WithCancel. + if !hasImport(f.file, contextPackage) { + return + } + + // Maps each cancel variable to its defining ValueSpec/AssignStmt. + cancelvars := make(map[*types.Var]ast.Node) + + // Find the set of cancel vars to analyze. + stack := make([]ast.Node, 0, 32) + ast.Inspect(node, func(n ast.Node) bool { + switch n.(type) { + case *ast.FuncLit: + if len(stack) > 0 { + return false // don't stray into nested functions + } + case nil: + stack = stack[:len(stack)-1] // pop + return true + } + stack = append(stack, n) // push + + // Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]: + // + // ctx, cancel := context.WithCancel(...) + // ctx, cancel = context.WithCancel(...) + // var ctx, cancel = context.WithCancel(...) + // + if isContextWithCancel(f, n) && isCall(stack[len(stack)-2]) { + var id *ast.Ident // id of cancel var + stmt := stack[len(stack)-3] + switch stmt := stmt.(type) { + case *ast.ValueSpec: + if len(stmt.Names) > 1 { + id = stmt.Names[1] + } + case *ast.AssignStmt: + if len(stmt.Lhs) > 1 { + id, _ = stmt.Lhs[1].(*ast.Ident) + } + } + if id != nil { + if id.Name == "_" { + f.Badf(id.Pos(), "the cancel function returned by context.%s should be called, not discarded, to avoid a context leak", + n.(*ast.SelectorExpr).Sel.Name) + } else if v, ok := f.pkg.uses[id].(*types.Var); ok { + cancelvars[v] = stmt + } else if v, ok := f.pkg.defs[id].(*types.Var); ok { + cancelvars[v] = stmt + } + } + } + + return true + }) + + if len(cancelvars) == 0 { + return // no need to build CFG + } + + // Tell the CFG builder which functions never return. + info := &types.Info{Uses: f.pkg.uses, Selections: f.pkg.selectors} + mayReturn := func(call *ast.CallExpr) bool { + name := callName(info, call) + return !noReturnFuncs[name] + } + + // Build the CFG. + var g *cfg.CFG + var sig *types.Signature + switch node := node.(type) { + case *ast.FuncDecl: + sig, _ = f.pkg.defs[node.Name].Type().(*types.Signature) + g = cfg.New(node.Body, mayReturn) + case *ast.FuncLit: + sig, _ = f.pkg.types[node.Type].Type.(*types.Signature) + g = cfg.New(node.Body, mayReturn) + } + + // Print CFG. + if debugLostCancel { + fmt.Println(g.Format(f.fset)) + } + + // Examine the CFG for each variable in turn. + // (It would be more efficient to analyze all cancelvars in a + // single pass over the AST, but seldom is there more than one.) + for v, stmt := range cancelvars { + if ret := lostCancelPath(f, g, v, stmt, sig); ret != nil { + lineno := f.fset.Position(stmt.Pos()).Line + f.Badf(stmt.Pos(), "the %s function is not used on all paths (possible context leak)", v.Name()) + f.Badf(ret.Pos(), "this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno) + } + } +} + +func isCall(n ast.Node) bool { _, ok := n.(*ast.CallExpr); return ok } + +func hasImport(f *ast.File, path string) bool { + for _, imp := range f.Imports { + v, _ := strconv.Unquote(imp.Path.Value) + if v == path { + return true + } + } + return false +} + +// isContextWithCancel reports whether n is one of the qualified identifiers +// context.With{Cancel,Timeout,Deadline}. +func isContextWithCancel(f *File, n ast.Node) bool { + if sel, ok := n.(*ast.SelectorExpr); ok { + switch sel.Sel.Name { + case "WithCancel", "WithTimeout", "WithDeadline": + if x, ok := sel.X.(*ast.Ident); ok { + if pkgname, ok := f.pkg.uses[x].(*types.PkgName); ok { + return pkgname.Imported().Path() == contextPackage + } + // Import failed, so we can't check package path. + // Just check the local package name (heuristic). + return x.Name == "context" + } + } + } + return false +} + +// lostCancelPath finds a path through the CFG, from stmt (which defines +// the 'cancel' variable v) to a return statement, that doesn't "use" v. +// If it finds one, it returns the return statement (which may be synthetic). +// sig is the function's type, if known. +func lostCancelPath(f *File, g *cfg.CFG, v *types.Var, stmt ast.Node, sig *types.Signature) *ast.ReturnStmt { + vIsNamedResult := sig != nil && tupleContains(sig.Results(), v) + + // uses reports whether stmts contain a "use" of variable v. + uses := func(f *File, v *types.Var, stmts []ast.Node) bool { + found := false + for _, stmt := range stmts { + ast.Inspect(stmt, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.Ident: + if f.pkg.uses[n] == v { + found = true + } + case *ast.ReturnStmt: + // A naked return statement counts as a use + // of the named result variables. + if n.Results == nil && vIsNamedResult { + found = true + } + } + return !found + }) + } + return found + } + + // blockUses computes "uses" for each block, caching the result. + memo := make(map[*cfg.Block]bool) + blockUses := func(f *File, v *types.Var, b *cfg.Block) bool { + res, ok := memo[b] + if !ok { + res = uses(f, v, b.Nodes) + memo[b] = res + } + return res + } + + // Find the var's defining block in the CFG, + // plus the rest of the statements of that block. + var defblock *cfg.Block + var rest []ast.Node +outer: + for _, b := range g.Blocks { + for i, n := range b.Nodes { + if n == stmt { + defblock = b + rest = b.Nodes[i+1:] + break outer + } + } + } + if defblock == nil { + panic("internal error: can't find defining block for cancel var") + } + + // Is v "used" in the remainder of its defining block? + if uses(f, v, rest) { + return nil + } + + // Does the defining block return without using v? + if ret := defblock.Return(); ret != nil { + return ret + } + + // Search the CFG depth-first for a path, from defblock to a + // return block, in which v is never "used". + seen := make(map[*cfg.Block]bool) + var search func(blocks []*cfg.Block) *ast.ReturnStmt + search = func(blocks []*cfg.Block) *ast.ReturnStmt { + for _, b := range blocks { + if !seen[b] { + seen[b] = true + + // Prune the search if the block uses v. + if blockUses(f, v, b) { + continue + } + + // Found path to return statement? + if ret := b.Return(); ret != nil { + if debugLostCancel { + fmt.Printf("found path to return in block %s\n", b) + } + return ret // found + } + + // Recur + if ret := search(b.Succs); ret != nil { + if debugLostCancel { + fmt.Printf(" from block %s\n", b) + } + return ret + } + } + } + return nil + } + return search(defblock.Succs) +} + +func tupleContains(tuple *types.Tuple, v *types.Var) bool { + for i := 0; i < tuple.Len(); i++ { + if tuple.At(i) == v { + return true + } + } + return false +} + +var noReturnFuncs = map[string]bool{ + "(*testing.common).FailNow": true, + "(*testing.common).Fatal": true, + "(*testing.common).Fatalf": true, + "(*testing.common).Skip": true, + "(*testing.common).SkipNow": true, + "(*testing.common).Skipf": true, + "log.Fatal": true, + "log.Fatalf": true, + "log.Fatalln": true, + "os.Exit": true, + "panic": true, + "runtime.Goexit": true, +} + +// callName returns the canonical name of the builtin, method, or +// function called by call, if known. +func callName(info *types.Info, call *ast.CallExpr) string { + switch fun := call.Fun.(type) { + case *ast.Ident: + // builtin, e.g. "panic" + if obj, ok := info.Uses[fun].(*types.Builtin); ok { + return obj.Name() + } + case *ast.SelectorExpr: + if sel, ok := info.Selections[fun]; ok && sel.Kind() == types.MethodVal { + // method call, e.g. "(*testing.common).Fatal" + meth := sel.Obj() + return fmt.Sprintf("(%s).%s", + meth.Type().(*types.Signature).Recv().Type(), + meth.Name()) + } + if obj, ok := info.Uses[fun.Sel]; ok { + // qualified identifier, e.g. "os.Exit" + return fmt.Sprintf("%s.%s", + obj.Pkg().Path(), + obj.Name()) + } + } + + // function with no name, or defined in missing imported package + return "" +} diff --git a/tools/vendor/github.com/dnephin/govet/main.go b/tools/vendor/github.com/dnephin/govet/main.go new file mode 100644 index 000000000..df6e0e16f --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/main.go @@ -0,0 +1,504 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Vet is a simple checker for static errors in Go source code. +// See doc.go for more information. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/printer" + "go/token" + "go/types" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" +) + +var ( + verbose = flag.Bool("v", false, "verbose") + tags = flag.String("tags", "", "comma-separated list of build tags to apply when parsing") + noRecurse = flag.Bool("no-recurse", false, "disable recursive directory walking") + tagList = []string{} // exploded version of tags flag; set in main +) + +var exitCode = 0 + +// "-all" flag enables all non-experimental checks +var all = triStateFlag("all", unset, "enable all non-experimental checks") + +// Flags to control which individual checks to perform. +var report = map[string]*triState{ + // Only unusual checks are written here. + // Most checks that operate during the AST walk are added by register. + "asmdecl": triStateFlag("asmdecl", unset, "check assembly against Go declarations"), + "buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"), +} + +// experimental records the flags enabling experimental features. These must be +// requested explicitly; they are not enabled by -all. +var experimental = map[string]bool{} + +// setTrueCount record how many flags are explicitly set to true. +var setTrueCount int + +// dirsRun and filesRun indicate whether the vet is applied to directory or +// file targets. The distinction affects which checks are run. +var dirsRun, filesRun bool + +// includesNonTest indicates whether the vet is applied to non-test targets. +// Certain checks are relevant only if they touch both test and non-test files. +var includesNonTest bool + +// A triState is a boolean that knows whether it has been set to either true or false. +// It is used to identify if a flag appears; the standard boolean flag cannot +// distinguish missing from unset. It also satisfies flag.Value. +type triState int + +const ( + unset triState = iota + setTrue + setFalse +) + +func triStateFlag(name string, value triState, usage string) *triState { + flag.Var(&value, name, usage) + return &value +} + +// triState implements flag.Value, flag.Getter, and flag.boolFlag. +// They work like boolean flags: we can say vet -printf as well as vet -printf=true +func (ts *triState) Get() interface{} { + return *ts == setTrue +} + +func (ts triState) isTrue() bool { + return ts == setTrue +} + +func (ts *triState) Set(value string) error { + b, err := strconv.ParseBool(value) + if err != nil { + return err + } + if b { + *ts = setTrue + setTrueCount++ + } else { + *ts = setFalse + } + return nil +} + +func (ts *triState) String() string { + switch *ts { + case unset: + return "true" // An unset flag will be set by -all, so defaults to true. + case setTrue: + return "true" + case setFalse: + return "false" + } + panic("not reached") +} + +func (ts triState) IsBoolFlag() bool { + return true +} + +// vet tells whether to report errors for the named check, a flag name. +func vet(name string) bool { + return report[name].isTrue() +} + +// setExit sets the value for os.Exit when it is called, later. It +// remembers the highest value. +func setExit(err int) { + if err > exitCode { + exitCode = err + } +} + +var ( + // Each of these vars has a corresponding case in (*File).Visit. + assignStmt *ast.AssignStmt + binaryExpr *ast.BinaryExpr + callExpr *ast.CallExpr + compositeLit *ast.CompositeLit + exprStmt *ast.ExprStmt + field *ast.Field + funcDecl *ast.FuncDecl + funcLit *ast.FuncLit + genDecl *ast.GenDecl + interfaceType *ast.InterfaceType + rangeStmt *ast.RangeStmt + returnStmt *ast.ReturnStmt + + // checkers is a two-level map. + // The outer level is keyed by a nil pointer, one of the AST vars above. + // The inner level is keyed by checker name. + checkers = make(map[ast.Node]map[string]func(*File, ast.Node)) +) + +func register(name, usage string, fn func(*File, ast.Node), types ...ast.Node) { + report[name] = triStateFlag(name, unset, usage) + for _, typ := range types { + m := checkers[typ] + if m == nil { + m = make(map[string]func(*File, ast.Node)) + checkers[typ] = m + } + m[name] = fn + } +} + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n") + fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n") + fmt.Fprintf(os.Stderr, "For more information run\n") + fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n") + fmt.Fprintf(os.Stderr, "Flags:\n") + flag.PrintDefaults() + os.Exit(2) +} + +// File is a wrapper for the state of a file used in the parser. +// The parse tree walkers are all methods of this type. +type File struct { + pkg *Package + fset *token.FileSet + name string + content []byte + file *ast.File + b bytes.Buffer // for use by methods + + // Parsed package "foo" when checking package "foo_test" + basePkg *Package + + // The objects that are receivers of a "String() string" method. + // This is used by the recursiveStringer method in print.go. + stringers map[*ast.Object]bool + + // Registered checkers to run. + checkers map[ast.Node][]func(*File, ast.Node) +} + +func main() { + flag.Usage = Usage + flag.Parse() + + // If any flag is set, we run only those checks requested. + // If all flag is set true or if no flags are set true, set all the non-experimental ones + // not explicitly set (in effect, set the "-all" flag). + if setTrueCount == 0 || *all == setTrue { + for name, setting := range report { + if *setting == unset && !experimental[name] { + *setting = setTrue + } + } + } + + tagList = strings.Split(*tags, ",") + + initPrintFlags() + initUnusedFlags() + + if flag.NArg() == 0 { + Usage() + } + for _, name := range flag.Args() { + // Is it a directory? + fi, err := os.Stat(name) + if err != nil { + warnf("error walking tree: %s", err) + continue + } + if fi.IsDir() { + dirsRun = true + } else { + filesRun = true + if !strings.HasSuffix(name, "_test.go") { + includesNonTest = true + } + } + } + if dirsRun && filesRun { + Usage() + } + if dirsRun { + for _, name := range flag.Args() { + if *noRecurse { + doPackageDir(name) + } else { + walkDir(name) + } + } + os.Exit(exitCode) + } + if doPackage(".", flag.Args(), nil) == nil { + warnf("no files checked") + } + os.Exit(exitCode) +} + +// prefixDirectory places the directory name on the beginning of each name in the list. +func prefixDirectory(directory string, names []string) { + if directory != "." { + for i, name := range names { + names[i] = filepath.Join(directory, name) + } + } +} + +// doPackageDir analyzes the single package found in the directory, if there is one, +// plus a test package, if there is one. +func doPackageDir(directory string) { + context := build.Default + if len(context.BuildTags) != 0 { + warnf("build tags %s previously set", context.BuildTags) + } + context.BuildTags = append(tagList, context.BuildTags...) + + pkg, err := context.ImportDir(directory, 0) + if err != nil { + // If it's just that there are no go source files, that's fine. + if _, nogo := err.(*build.NoGoError); nogo { + return + } + // Non-fatal: we are doing a recursive walk and there may be other directories. + warnf("cannot process directory %s: %s", directory, err) + return + } + var names []string + names = append(names, pkg.GoFiles...) + names = append(names, pkg.CgoFiles...) + names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. + names = append(names, pkg.SFiles...) + prefixDirectory(directory, names) + basePkg := doPackage(directory, names, nil) + // Is there also a "foo_test" package? If so, do that one as well. + if len(pkg.XTestGoFiles) > 0 { + names = pkg.XTestGoFiles + prefixDirectory(directory, names) + doPackage(directory, names, basePkg) + } +} + +type Package struct { + path string + defs map[*ast.Ident]types.Object + uses map[*ast.Ident]types.Object + selectors map[*ast.SelectorExpr]*types.Selection + types map[ast.Expr]types.TypeAndValue + spans map[types.Object]Span + files []*File + typesPkg *types.Package +} + +// doPackage analyzes the single package constructed from the named files. +// It returns the parsed Package or nil if none of the files have been checked. +func doPackage(directory string, names []string, basePkg *Package) *Package { + var files []*File + var astFiles []*ast.File + fs := token.NewFileSet() + for _, name := range names { + data, err := ioutil.ReadFile(name) + if err != nil { + // Warn but continue to next package. + warnf("%s: %s", name, err) + return nil + } + checkBuildTag(name, data) + var parsedFile *ast.File + if strings.HasSuffix(name, ".go") { + parsedFile, err = parser.ParseFile(fs, name, data, 0) + if err != nil { + warnf("%s: %s", name, err) + return nil + } + astFiles = append(astFiles, parsedFile) + } + files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile}) + } + if len(astFiles) == 0 { + return nil + } + pkg := new(Package) + pkg.path = astFiles[0].Name.Name + pkg.files = files + // Type check the package. + err := pkg.check(fs, astFiles) + if err != nil && *verbose { + warnf("%s", err) + } + + // Check. + chk := make(map[ast.Node][]func(*File, ast.Node)) + for typ, set := range checkers { + for name, fn := range set { + if vet(name) { + chk[typ] = append(chk[typ], fn) + } + } + } + for _, file := range files { + file.pkg = pkg + file.basePkg = basePkg + file.checkers = chk + if file.file != nil { + file.walkFile(file.name, file.file) + } + } + asmCheck(pkg) + return pkg +} + +func visit(path string, f os.FileInfo, err error) error { + if err != nil { + warnf("walk error: %s", err) + return err + } + // One package per directory. Ignore the files themselves. + if !f.IsDir() { + return nil + } + doPackageDir(path) + return nil +} + +func (pkg *Package) hasFileWithSuffix(suffix string) bool { + for _, f := range pkg.files { + if strings.HasSuffix(f.name, suffix) { + return true + } + } + return false +} + +// walkDir recursively walks the tree looking for Go packages. +func walkDir(root string) { + filepath.Walk(root, visit) +} + +// errorf formats the error to standard error, adding program +// identification and a newline, and exits. +func errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + os.Exit(2) +} + +// warnf formats the error to standard error, adding program +// identification and a newline, but does not exit. +func warnf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + setExit(1) +} + +// Println is fmt.Println guarded by -v. +func Println(args ...interface{}) { + if !*verbose { + return + } + fmt.Println(args...) +} + +// Printf is fmt.Printf guarded by -v. +func Printf(format string, args ...interface{}) { + if !*verbose { + return + } + fmt.Printf(format+"\n", args...) +} + +// Bad reports an error and sets the exit code.. +func (f *File) Bad(pos token.Pos, args ...interface{}) { + f.Warn(pos, args...) + setExit(1) +} + +// Badf reports a formatted error and sets the exit code. +func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { + f.Warnf(pos, format, args...) + setExit(1) +} + +// loc returns a formatted representation of the position. +func (f *File) loc(pos token.Pos) string { + if pos == token.NoPos { + return "" + } + // Do not print columns. Because the pos often points to the start of an + // expression instead of the inner part with the actual error, the + // precision can mislead. + posn := f.fset.Position(pos) + return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) +} + +// Warn reports an error but does not set the exit code. +func (f *File) Warn(pos token.Pos, args ...interface{}) { + fmt.Fprintf(os.Stderr, "%s: %s", f.loc(pos), fmt.Sprintln(args...)) +} + +// Warnf reports a formatted error but does not set the exit code. +func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "%s: %s\n", f.loc(pos), fmt.Sprintf(format, args...)) +} + +// walkFile walks the file's tree. +func (f *File) walkFile(name string, file *ast.File) { + Println("Checking file", name) + ast.Walk(f, file) +} + +// Visit implements the ast.Visitor interface. +func (f *File) Visit(node ast.Node) ast.Visitor { + var key ast.Node + switch node.(type) { + case *ast.AssignStmt: + key = assignStmt + case *ast.BinaryExpr: + key = binaryExpr + case *ast.CallExpr: + key = callExpr + case *ast.CompositeLit: + key = compositeLit + case *ast.ExprStmt: + key = exprStmt + case *ast.Field: + key = field + case *ast.FuncDecl: + key = funcDecl + case *ast.FuncLit: + key = funcLit + case *ast.GenDecl: + key = genDecl + case *ast.InterfaceType: + key = interfaceType + case *ast.RangeStmt: + key = rangeStmt + case *ast.ReturnStmt: + key = returnStmt + } + for _, fn := range f.checkers[key] { + fn(f, node) + } + return f +} + +// gofmt returns a string representation of the expression. +func (f *File) gofmt(x ast.Expr) string { + f.b.Reset() + printer.Fprint(&f.b, f.fset, x) + return f.b.String() +} diff --git a/tools/vendor/github.com/dnephin/govet/method.go b/tools/vendor/github.com/dnephin/govet/method.go new file mode 100644 index 000000000..8a554e152 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/method.go @@ -0,0 +1,182 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code to check canonical methods. + +package main + +import ( + "fmt" + "go/ast" + "go/printer" + "strings" +) + +func init() { + register("methods", + "check that canonically named methods are canonically defined", + checkCanonicalMethod, + funcDecl, interfaceType) +} + +type MethodSig struct { + args []string + results []string +} + +// canonicalMethods lists the input and output types for Go methods +// that are checked using dynamic interface checks. Because the +// checks are dynamic, such methods would not cause a compile error +// if they have the wrong signature: instead the dynamic check would +// fail, sometimes mysteriously. If a method is found with a name listed +// here but not the input/output types listed here, vet complains. +// +// A few of the canonical methods have very common names. +// For example, a type might implement a Scan method that +// has nothing to do with fmt.Scanner, but we still want to check +// the methods that are intended to implement fmt.Scanner. +// To do that, the arguments that have a = prefix are treated as +// signals that the canonical meaning is intended: if a Scan +// method doesn't have a fmt.ScanState as its first argument, +// we let it go. But if it does have a fmt.ScanState, then the +// rest has to match. +var canonicalMethods = map[string]MethodSig{ + // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict + "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter + "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder + "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder + "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler + "MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler + "Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader) + "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader + "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom + "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader + "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner + "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker + "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler + "UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler + "UnreadByte": {[]string{}, []string{"error"}}, + "UnreadRune": {[]string{}, []string{"error"}}, + "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer) + "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo +} + +func checkCanonicalMethod(f *File, node ast.Node) { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Recv != nil { + canonicalMethod(f, n.Name, n.Type) + } + case *ast.InterfaceType: + for _, field := range n.Methods.List { + for _, id := range field.Names { + canonicalMethod(f, id, field.Type.(*ast.FuncType)) + } + } + } +} + +func canonicalMethod(f *File, id *ast.Ident, t *ast.FuncType) { + // Expected input/output. + expect, ok := canonicalMethods[id.Name] + if !ok { + return + } + + // Actual input/output + args := typeFlatten(t.Params.List) + var results []ast.Expr + if t.Results != nil { + results = typeFlatten(t.Results.List) + } + + // Do the =s (if any) all match? + if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") { + return + } + + // Everything must match. + if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") { + expectFmt := id.Name + "(" + argjoin(expect.args) + ")" + if len(expect.results) == 1 { + expectFmt += " " + argjoin(expect.results) + } else if len(expect.results) > 1 { + expectFmt += " (" + argjoin(expect.results) + ")" + } + + f.b.Reset() + if err := printer.Fprint(&f.b, f.fset, t); err != nil { + fmt.Fprintf(&f.b, "<%s>", err) + } + actual := f.b.String() + actual = strings.TrimPrefix(actual, "func") + actual = id.Name + actual + + f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt) + } +} + +func argjoin(x []string) string { + y := make([]string, len(x)) + for i, s := range x { + if s[0] == '=' { + s = s[1:] + } + y[i] = s + } + return strings.Join(y, ", ") +} + +// Turn parameter list into slice of types +// (in the ast, types are Exprs). +// Have to handle f(int, bool) and f(x, y, z int) +// so not a simple 1-to-1 conversion. +func typeFlatten(l []*ast.Field) []ast.Expr { + var t []ast.Expr + for _, f := range l { + if len(f.Names) == 0 { + t = append(t, f.Type) + continue + } + for _ = range f.Names { + t = append(t, f.Type) + } + } + return t +} + +// Does each type in expect with the given prefix match the corresponding type in actual? +func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool { + for i, x := range expect { + if !strings.HasPrefix(x, prefix) { + continue + } + if i >= len(actual) { + return false + } + if !f.matchParamType(x, actual[i]) { + return false + } + } + if prefix == "" && len(actual) > len(expect) { + return false + } + return true +} + +// Does this one type match? +func (f *File) matchParamType(expect string, actual ast.Expr) bool { + if strings.HasPrefix(expect, "=") { + expect = expect[1:] + } + // Strip package name if we're in that package. + if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' { + expect = expect[n+1:] + } + + // Overkill but easy. + f.b.Reset() + printer.Fprint(&f.b, f.fset, actual) + return f.b.String() == expect +} diff --git a/tools/vendor/github.com/dnephin/govet/nilfunc.go b/tools/vendor/github.com/dnephin/govet/nilfunc.go new file mode 100644 index 000000000..bfe05e335 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/nilfunc.go @@ -0,0 +1,67 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for useless function comparisons. +A useless comparison is one like f == nil as opposed to f() == nil. +*/ + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("nilfunc", + "check for comparisons between functions and nil", + checkNilFuncComparison, + binaryExpr) +} + +func checkNilFuncComparison(f *File, node ast.Node) { + e := node.(*ast.BinaryExpr) + + // Only want == or != comparisons. + if e.Op != token.EQL && e.Op != token.NEQ { + return + } + + // Only want comparisons with a nil identifier on one side. + var e2 ast.Expr + switch { + case f.isNil(e.X): + e2 = e.Y + case f.isNil(e.Y): + e2 = e.X + default: + return + } + + // Only want identifiers or selector expressions. + var obj types.Object + switch v := e2.(type) { + case *ast.Ident: + obj = f.pkg.uses[v] + case *ast.SelectorExpr: + obj = f.pkg.uses[v.Sel] + default: + return + } + + // Only want functions. + if _, ok := obj.(*types.Func); !ok { + return + } + + f.Badf(e.Pos(), "comparison of function %v %v nil is always %v", obj.Name(), e.Op, e.Op == token.NEQ) +} + +// isNil reports whether the provided expression is the built-in nil +// identifier. +func (f *File) isNil(e ast.Expr) bool { + return f.pkg.types[e].Type == types.Typ[types.UntypedNil] +} diff --git a/tools/vendor/github.com/dnephin/govet/print.go b/tools/vendor/github.com/dnephin/govet/print.go new file mode 100644 index 000000000..f4b985cfb --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/print.go @@ -0,0 +1,650 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the printf-checker. + +package main + +import ( + "bytes" + "flag" + "go/ast" + "go/constant" + "go/token" + "go/types" + "strconv" + "strings" + "unicode/utf8" +) + +var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") + +func init() { + register("printf", + "check printf-like invocations", + checkFmtPrintfCall, + funcDecl, callExpr) +} + +func initPrintFlags() { + if *printfuncs == "" { + return + } + for _, name := range strings.Split(*printfuncs, ",") { + if len(name) == 0 { + flag.Usage() + } + + // Backwards compatibility: skip optional first argument + // index after the colon. + if colon := strings.LastIndex(name, ":"); colon > 0 { + name = name[:colon] + } + + name = strings.ToLower(name) + if name[len(name)-1] == 'f' { + isFormattedPrint[name] = true + } else { + isPrint[name] = true + } + } +} + +// isFormattedPrint records the formatted-print functions. Names are +// lower-cased so the lookup is case insensitive. +var isFormattedPrint = map[string]bool{ + "errorf": true, + "fatalf": true, + "fprintf": true, + "logf": true, + "panicf": true, + "printf": true, + "sprintf": true, +} + +// isPrint records the unformatted-print functions. Names are lower-cased +// so the lookup is case insensitive. +var isPrint = map[string]bool{ + "error": true, + "fatal": true, + "fprint": true, + "fprintln": true, + "log": true, + "panic": true, + "panicln": true, + "print": true, + "println": true, + "sprint": true, + "sprintln": true, +} + +// formatString returns the format string argument and its index within +// the given printf-like call expression. +// +// The last parameter before variadic arguments is assumed to be +// a format string. +// +// The first string literal or string constant is assumed to be a format string +// if the call's signature cannot be determined. +// +// If it cannot find any format string parameter, it returns ("", -1). +func formatString(f *File, call *ast.CallExpr) (string, int) { + typ := f.pkg.types[call.Fun].Type + if typ != nil { + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions + return "", -1 + } + idx := sig.Params().Len() - 2 + if idx < 0 { + // Skip checking variadic functions without + // fixed arguments. + return "", -1 + } + s, ok := stringLiteralArg(f, call, idx) + if !ok { + // The last argument before variadic args isn't a string + return "", -1 + } + return s, idx + } + } + + // Cannot determine call's signature. Fallback to scanning for the first + // string argument in the call + for idx := range call.Args { + if s, ok := stringLiteralArg(f, call, idx); ok { + return s, idx + } + } + return "", -1 +} + +// stringLiteralArg returns call's string constant argument at the index idx. +// +// ("", false) is returned if call's argument at the index idx isn't a string +// literal. +func stringLiteralArg(f *File, call *ast.CallExpr, idx int) (string, bool) { + if idx >= len(call.Args) { + return "", false + } + arg := call.Args[idx] + lit := f.pkg.types[arg].Value + if lit != nil && lit.Kind() == constant.String { + return constant.StringVal(lit), true + } + return "", false +} + +// checkCall triggers the print-specific checks if the call invokes a print function. +func checkFmtPrintfCall(f *File, node ast.Node) { + if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) { + // Remember we saw this. + if f.stringers == nil { + f.stringers = make(map[*ast.Object]bool) + } + if l := d.Recv.List; len(l) == 1 { + if n := l[0].Names; len(n) == 1 { + f.stringers[n[0].Obj] = true + } + } + return + } + + call, ok := node.(*ast.CallExpr) + if !ok { + return + } + var Name string + switch x := call.Fun.(type) { + case *ast.Ident: + Name = x.Name + case *ast.SelectorExpr: + Name = x.Sel.Name + default: + return + } + + name := strings.ToLower(Name) + if _, ok := isFormattedPrint[name]; ok { + f.checkPrintf(call, Name) + return + } + if _, ok := isPrint[name]; ok { + f.checkPrint(call, Name) + return + } +} + +// isStringer returns true if the provided declaration is a "String() string" +// method, an implementation of fmt.Stringer. +func isStringer(f *File, d *ast.FuncDecl) bool { + return d.Recv != nil && d.Name.Name == "String" && d.Type.Results != nil && + len(d.Type.Params.List) == 0 && len(d.Type.Results.List) == 1 && + f.pkg.types[d.Type.Results.List[0].Type].Type == types.Typ[types.String] +} + +// formatState holds the parsed representation of a printf directive such as "%3.*[4]d". +// It is constructed by parsePrintfVerb. +type formatState struct { + verb rune // the format verb: 'd' for "%d" + format string // the full format directive from % through verb, "%.3d". + name string // Printf, Sprintf etc. + flags []byte // the list of # + etc. + argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call + indexed bool // whether an indexing expression appears: %[1]d. + firstArg int // Index of first argument after the format in the Printf call. + // Used only during parse. + file *File + call *ast.CallExpr + argNum int // Which argument we're expecting to format now. + indexPending bool // Whether we have an indexed argument that has not resolved. + nbytes int // number of bytes of the format string consumed. +} + +// checkPrintf checks a call to a formatted print routine such as Printf. +func (f *File) checkPrintf(call *ast.CallExpr, name string) { + format, idx := formatString(f, call) + if idx < 0 { + if *verbose { + f.Warn(call.Pos(), "can't check non-constant format in call to", name) + } + return + } + + firstArg := idx + 1 // Arguments are immediately after format string. + if !strings.Contains(format, "%") { + if len(call.Args) > firstArg { + f.Badf(call.Pos(), "no formatting directive in %s call", name) + } + return + } + // Hard part: check formats against args. + argNum := firstArg + indexed := false + for i, w := 0, 0; i < len(format); i += w { + w = 1 + if format[i] == '%' { + state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum) + if state == nil { + return + } + w = len(state.format) + if state.indexed { + indexed = true + } + if !f.okPrintfArg(call, state) { // One error per format is enough. + return + } + if len(state.argNums) > 0 { + // Continue with the next sequential argument. + argNum = state.argNums[len(state.argNums)-1] + 1 + } + } + } + // Dotdotdot is hard. + if call.Ellipsis.IsValid() && argNum >= len(call.Args)-1 { + return + } + // If the arguments were direct indexed, we assume the programmer knows what's up. + // Otherwise, there should be no leftover arguments. + if !indexed && argNum != len(call.Args) { + expect := argNum - firstArg + numArgs := len(call.Args) - firstArg + f.Badf(call.Pos(), "wrong number of args for format in %s call: %d needed but %d args", name, expect, numArgs) + } +} + +// parseFlags accepts any printf flags. +func (s *formatState) parseFlags() { + for s.nbytes < len(s.format) { + switch c := s.format[s.nbytes]; c { + case '#', '0', '+', '-', ' ': + s.flags = append(s.flags, c) + s.nbytes++ + default: + return + } + } +} + +// scanNum advances through a decimal number if present. +func (s *formatState) scanNum() { + for ; s.nbytes < len(s.format); s.nbytes++ { + c := s.format[s.nbytes] + if c < '0' || '9' < c { + return + } + } +} + +// parseIndex scans an index expression. It returns false if there is a syntax error. +func (s *formatState) parseIndex() bool { + if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' { + return true + } + // Argument index present. + s.indexed = true + s.nbytes++ // skip '[' + start := s.nbytes + s.scanNum() + if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' { + s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index") + return false + } + arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32) + if err != nil { + s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index: %s", err) + return false + } + s.nbytes++ // skip ']' + arg := int(arg32) + arg += s.firstArg - 1 // We want to zero-index the actual arguments. + s.argNum = arg + s.indexPending = true + return true +} + +// parseNum scans a width or precision (or *). It returns false if there's a bad index expression. +func (s *formatState) parseNum() bool { + if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' { + if s.indexPending { // Absorb it. + s.indexPending = false + } + s.nbytes++ + s.argNums = append(s.argNums, s.argNum) + s.argNum++ + } else { + s.scanNum() + } + return true +} + +// parsePrecision scans for a precision. It returns false if there's a bad index expression. +func (s *formatState) parsePrecision() bool { + // If there's a period, there may be a precision. + if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' { + s.flags = append(s.flags, '.') // Treat precision as a flag. + s.nbytes++ + if !s.parseIndex() { + return false + } + if !s.parseNum() { + return false + } + } + return true +} + +// parsePrintfVerb looks the formatting directive that begins the format string +// and returns a formatState that encodes what the directive wants, without looking +// at the actual arguments present in the call. The result is nil if there is an error. +func (f *File) parsePrintfVerb(call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState { + state := &formatState{ + format: format, + name: name, + flags: make([]byte, 0, 5), + argNum: argNum, + argNums: make([]int, 0, 1), + nbytes: 1, // There's guaranteed to be a percent sign. + indexed: false, + firstArg: firstArg, + file: f, + call: call, + } + // There may be flags. + state.parseFlags() + indexPending := false + // There may be an index. + if !state.parseIndex() { + return nil + } + // There may be a width. + if !state.parseNum() { + return nil + } + // There may be a precision. + if !state.parsePrecision() { + return nil + } + // Now a verb, possibly prefixed by an index (which we may already have). + if !indexPending && !state.parseIndex() { + return nil + } + if state.nbytes == len(state.format) { + f.Badf(call.Pos(), "missing verb at end of format string in %s call", name) + return nil + } + verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:]) + state.verb = verb + state.nbytes += w + if verb != '%' { + state.argNums = append(state.argNums, state.argNum) + } + state.format = state.format[:state.nbytes] + return state +} + +// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask. +type printfArgType int + +const ( + argBool printfArgType = 1 << iota + argInt + argRune + argString + argFloat + argComplex + argPointer + anyType printfArgType = ^0 +) + +type printVerb struct { + verb rune // User may provide verb through Formatter; could be a rune. + flags string // known flags are all ASCII + typ printfArgType +} + +// Common flag sets for printf verbs. +const ( + noFlag = "" + numFlag = " -+.0" + sharpNumFlag = " -+.0#" + allFlags = " -+.0#" +) + +// printVerbs identifies which flags are known to printf for each verb. +// TODO: A type that implements Formatter may do what it wants, and vet +// will complain incorrectly. +var printVerbs = []printVerb{ + // '-' is a width modifier, always valid. + // '.' is a precision for float, max width for strings. + // '+' is required sign for numbers, Go format for %v. + // '#' is alternate format for several verbs. + // ' ' is spacer for numbers + {'%', noFlag, 0}, + {'b', numFlag, argInt | argFloat | argComplex}, + {'c', "-", argRune | argInt}, + {'d', numFlag, argInt}, + {'e', numFlag, argFloat | argComplex}, + {'E', numFlag, argFloat | argComplex}, + {'f', numFlag, argFloat | argComplex}, + {'F', numFlag, argFloat | argComplex}, + {'g', numFlag, argFloat | argComplex}, + {'G', numFlag, argFloat | argComplex}, + {'o', sharpNumFlag, argInt}, + {'p', "-#", argPointer}, + {'q', " -+.0#", argRune | argInt | argString}, + {'s', " -+.0", argString}, + {'t', "-", argBool}, + {'T', "-", anyType}, + {'U', "-#", argRune | argInt}, + {'v', allFlags, anyType}, + {'x', sharpNumFlag, argRune | argInt | argString}, + {'X', sharpNumFlag, argRune | argInt | argString}, +} + +// okPrintfArg compares the formatState to the arguments actually present, +// reporting any discrepancies it can discern. If the final argument is ellipsissed, +// there's little it can do for that. +func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) { + var v printVerb + found := false + // Linear scan is fast enough for a small list. + for _, v = range printVerbs { + if v.verb == state.verb { + found = true + break + } + } + if !found { + f.Badf(call.Pos(), "unrecognized printf verb %q", state.verb) + return false + } + for _, flag := range state.flags { + if !strings.ContainsRune(v.flags, rune(flag)) { + f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", state.verb, flag) + return false + } + } + // Verb is good. If len(state.argNums)>trueArgs, we have something like %.*s and all + // but the final arg must be an integer. + trueArgs := 1 + if state.verb == '%' { + trueArgs = 0 + } + nargs := len(state.argNums) + for i := 0; i < nargs-trueArgs; i++ { + argNum := state.argNums[i] + if !f.argCanBeChecked(call, i, true, state) { + return + } + arg := call.Args[argNum] + if !f.matchArgType(argInt, nil, arg) { + f.Badf(call.Pos(), "arg %s for * in printf format not of type int", f.gofmt(arg)) + return false + } + } + if state.verb == '%' { + return true + } + argNum := state.argNums[len(state.argNums)-1] + if !f.argCanBeChecked(call, len(state.argNums)-1, false, state) { + return false + } + arg := call.Args[argNum] + if f.isFunctionValue(arg) && state.verb != 'p' && state.verb != 'T' { + f.Badf(call.Pos(), "arg %s in printf call is a function value, not a function call", f.gofmt(arg)) + return false + } + if !f.matchArgType(v.typ, nil, arg) { + typeString := "" + if typ := f.pkg.types[arg].Type; typ != nil { + typeString = typ.String() + } + f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", f.gofmt(arg), state.verb, typeString) + return false + } + if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) { + f.Badf(call.Pos(), "arg %s for printf causes recursive call to String method", f.gofmt(arg)) + return false + } + return true +} + +// recursiveStringer reports whether the provided argument is r or &r for the +// fmt.Stringer receiver identifier r. +func (f *File) recursiveStringer(e ast.Expr) bool { + if len(f.stringers) == 0 { + return false + } + var obj *ast.Object + switch e := e.(type) { + case *ast.Ident: + obj = e.Obj + case *ast.UnaryExpr: + if id, ok := e.X.(*ast.Ident); ok && e.Op == token.AND { + obj = id.Obj + } + } + + // It's unlikely to be a recursive stringer if it has a Format method. + if typ := f.pkg.types[e].Type; typ != nil { + // Not a perfect match; see issue 6259. + if f.hasMethod(typ, "Format") { + return false + } + } + + // We compare the underlying Object, which checks that the identifier + // is the one we declared as the receiver for the String method in + // which this printf appears. + return f.stringers[obj] +} + +// isFunctionValue reports whether the expression is a function as opposed to a function call. +// It is almost always a mistake to print a function value. +func (f *File) isFunctionValue(e ast.Expr) bool { + if typ := f.pkg.types[e].Type; typ != nil { + _, ok := typ.(*types.Signature) + return ok + } + return false +} + +// argCanBeChecked reports whether the specified argument is statically present; +// it may be beyond the list of arguments or in a terminal slice... argument, which +// means we can't see it. +func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, isStar bool, state *formatState) bool { + argNum := state.argNums[formatArg] + if argNum < 0 { + // Shouldn't happen, so catch it with prejudice. + panic("negative arg num") + } + if argNum == 0 { + f.Badf(call.Pos(), `index value [0] for %s("%s"); indexes start at 1`, state.name, state.format) + return false + } + if argNum < len(call.Args)-1 { + return true // Always OK. + } + if call.Ellipsis.IsValid() { + return false // We just can't tell; there could be many more arguments. + } + if argNum < len(call.Args) { + return true + } + // There are bad indexes in the format or there are fewer arguments than the format needs. + // This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi". + arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed. + f.Badf(call.Pos(), `missing argument for %s("%s"): format reads arg %d, have only %d args`, state.name, state.format, arg, len(call.Args)-state.firstArg) + return false +} + +// checkPrint checks a call to an unformatted print routine such as Println. +func (f *File) checkPrint(call *ast.CallExpr, name string) { + firstArg := 0 + typ := f.pkg.types[call.Fun].Type + if typ == nil { + // Skip checking functions with unknown type. + return + } + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions. + return + } + params := sig.Params() + firstArg = params.Len() - 1 + + typ := params.At(firstArg).Type() + typ = typ.(*types.Slice).Elem() + it, ok := typ.(*types.Interface) + if !ok || !it.Empty() { + // Skip variadic functions accepting non-interface{} args. + return + } + } + args := call.Args + if len(args) <= firstArg { + // Skip calls without variadic args. + return + } + args = args[firstArg:] + + // check for Println(os.Stderr, ...) + if firstArg == 0 { + if sel, ok := args[0].(*ast.SelectorExpr); ok { + if x, ok := sel.X.(*ast.Ident); ok { + if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") { + f.Badf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name) + } + } + } + } + arg := args[0] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.Contains(lit.Value, "%") { + f.Badf(call.Pos(), "possible formatting directive in %s call", name) + } + } + if strings.HasSuffix(name, "ln") { + // The last item, if a string, should not have a newline. + arg = args[len(args)-1] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.HasSuffix(lit.Value, `\n"`) { + f.Badf(call.Pos(), "%s call ends with newline", name) + } + } + } + for _, arg := range args { + if f.isFunctionValue(arg) { + f.Badf(call.Pos(), "arg %s in %s call is a function value, not a function call", f.gofmt(arg), name) + } + if f.recursiveStringer(arg) { + f.Badf(call.Pos(), "arg %s in %s call causes recursive call to String method", f.gofmt(arg), name) + } + } +} diff --git a/tools/vendor/github.com/dnephin/govet/rangeloop.go b/tools/vendor/github.com/dnephin/govet/rangeloop.go new file mode 100644 index 000000000..e085e21a2 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/rangeloop.go @@ -0,0 +1,74 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check range loop variables bound inside function +literals that are deferred or launched in new goroutines. We only check +instances where the defer or go statement is the last statement in the loop +body, as otherwise we would need whole program analysis. + +For example: + + for i, v := range s { + go func() { + println(i, v) // not what you might expect + }() + } + +See: https://golang.org/doc/go_faq.html#closures_and_goroutines +*/ + +package main + +import "go/ast" + +func init() { + register("rangeloops", + "check that range loop variables are used correctly", + checkRangeLoop, + rangeStmt) +} + +// checkRangeLoop walks the body of the provided range statement, checking if +// its index or value variables are used unsafely inside goroutines or deferred +// function literals. +func checkRangeLoop(f *File, node ast.Node) { + n := node.(*ast.RangeStmt) + key, _ := n.Key.(*ast.Ident) + val, _ := n.Value.(*ast.Ident) + if key == nil && val == nil { + return + } + sl := n.Body.List + if len(sl) == 0 { + return + } + var last *ast.CallExpr + switch s := sl[len(sl)-1].(type) { + case *ast.GoStmt: + last = s.Call + case *ast.DeferStmt: + last = s.Call + default: + return + } + lit, ok := last.Fun.(*ast.FuncLit) + if !ok { + return + } + ast.Inspect(lit.Body, func(n ast.Node) bool { + id, ok := n.(*ast.Ident) + if !ok || id.Obj == nil { + return true + } + if f.pkg.types[id].Type == nil { + // Not referring to a variable + return true + } + if key != nil && id.Obj == key.Obj || val != nil && id.Obj == val.Obj { + f.Bad(id.Pos(), "range variable", id.Name, "captured by func literal") + } + return true + }) +} diff --git a/tools/vendor/github.com/dnephin/govet/shadow.go b/tools/vendor/github.com/dnephin/govet/shadow.go new file mode 100644 index 000000000..29c952fd8 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/shadow.go @@ -0,0 +1,246 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for shadowed variables. +A shadowed variable is a variable declared in an inner scope +with the same name and type as a variable in an outer scope, +and where the outer variable is mentioned after the inner one +is declared. + +(This definition can be refined; the module generates too many +false positives and is not yet enabled by default.) + +For example: + + func BadRead(f *os.File, buf []byte) error { + var err error + for { + n, err := f.Read(buf) // shadows the function variable 'err' + if err != nil { + break // causes return of wrong value + } + foo(buf) + } + return err + } + +*/ + +package main + +import ( + "flag" + "go/ast" + "go/token" + "go/types" +) + +var strictShadowing = flag.Bool("shadowstrict", false, "whether to be strict about shadowing; can be noisy") + +func init() { + register("shadow", + "check for shadowed variables (experimental; must be set explicitly)", + checkShadow, + assignStmt, genDecl) + experimental["shadow"] = true +} + +func checkShadow(f *File, node ast.Node) { + switch n := node.(type) { + case *ast.AssignStmt: + checkShadowAssignment(f, n) + case *ast.GenDecl: + checkShadowDecl(f, n) + } +} + +// Span stores the minimum range of byte positions in the file in which a +// given variable (types.Object) is mentioned. It is lexically defined: it spans +// from the beginning of its first mention to the end of its last mention. +// A variable is considered shadowed (if *strictShadowing is off) only if the +// shadowing variable is declared within the span of the shadowed variable. +// In other words, if a variable is shadowed but not used after the shadowed +// variable is declared, it is inconsequential and not worth complaining about. +// This simple check dramatically reduces the nuisance rate for the shadowing +// check, at least until something cleverer comes along. +// +// One wrinkle: A "naked return" is a silent use of a variable that the Span +// will not capture, but the compilers catch naked returns of shadowed +// variables so we don't need to. +// +// Cases this gets wrong (TODO): +// - If a for loop's continuation statement mentions a variable redeclared in +// the block, we should complain about it but don't. +// - A variable declared inside a function literal can falsely be identified +// as shadowing a variable in the outer function. +// +type Span struct { + min token.Pos + max token.Pos +} + +// contains reports whether the position is inside the span. +func (s Span) contains(pos token.Pos) bool { + return s.min <= pos && pos < s.max +} + +// growSpan expands the span for the object to contain the instance represented +// by the identifier. +func (pkg *Package) growSpan(ident *ast.Ident, obj types.Object) { + if *strictShadowing { + return // No need + } + pos := ident.Pos() + end := ident.End() + span, ok := pkg.spans[obj] + if ok { + if span.min > pos { + span.min = pos + } + if span.max < end { + span.max = end + } + } else { + span = Span{pos, end} + } + pkg.spans[obj] = span +} + +// checkShadowAssignment checks for shadowing in a short variable declaration. +func checkShadowAssignment(f *File, a *ast.AssignStmt) { + if a.Tok != token.DEFINE { + return + } + if f.idiomaticShortRedecl(a) { + return + } + for _, expr := range a.Lhs { + ident, ok := expr.(*ast.Ident) + if !ok { + f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier") + return + } + checkShadowing(f, ident) + } +} + +// idiomaticShortRedecl reports whether this short declaration can be ignored for +// the purposes of shadowing, that is, that any redeclarations it contains are deliberate. +func (f *File) idiomaticShortRedecl(a *ast.AssignStmt) bool { + // Don't complain about deliberate redeclarations of the form + // i := i + // Such constructs are idiomatic in range loops to create a new variable + // for each iteration. Another example is + // switch n := n.(type) + if len(a.Rhs) != len(a.Lhs) { + return false + } + // We know it's an assignment, so the LHS must be all identifiers. (We check anyway.) + for i, expr := range a.Lhs { + lhs, ok := expr.(*ast.Ident) + if !ok { + f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier") + return true // Don't do any more processing. + } + switch rhs := a.Rhs[i].(type) { + case *ast.Ident: + if lhs.Name != rhs.Name { + return false + } + case *ast.TypeAssertExpr: + if id, ok := rhs.X.(*ast.Ident); ok { + if lhs.Name != id.Name { + return false + } + } + default: + return false + } + } + return true +} + +// idiomaticRedecl reports whether this declaration spec can be ignored for +// the purposes of shadowing, that is, that any redeclarations it contains are deliberate. +func (f *File) idiomaticRedecl(d *ast.ValueSpec) bool { + // Don't complain about deliberate redeclarations of the form + // var i, j = i, j + if len(d.Names) != len(d.Values) { + return false + } + for i, lhs := range d.Names { + if rhs, ok := d.Values[i].(*ast.Ident); ok { + if lhs.Name != rhs.Name { + return false + } + } + } + return true +} + +// checkShadowDecl checks for shadowing in a general variable declaration. +func checkShadowDecl(f *File, d *ast.GenDecl) { + if d.Tok != token.VAR { + return + } + for _, spec := range d.Specs { + valueSpec, ok := spec.(*ast.ValueSpec) + if !ok { + f.Badf(spec.Pos(), "invalid AST: var GenDecl not ValueSpec") + return + } + // Don't complain about deliberate redeclarations of the form + // var i = i + if f.idiomaticRedecl(valueSpec) { + return + } + for _, ident := range valueSpec.Names { + checkShadowing(f, ident) + } + } +} + +// checkShadowing checks whether the identifier shadows an identifier in an outer scope. +func checkShadowing(f *File, ident *ast.Ident) { + if ident.Name == "_" { + // Can't shadow the blank identifier. + return + } + obj := f.pkg.defs[ident] + if obj == nil { + return + } + // obj.Parent.Parent is the surrounding scope. If we can find another declaration + // starting from there, we have a shadowed identifier. + _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos()) + if shadowed == nil { + return + } + // Don't complain if it's shadowing a universe-declared identifier; that's fine. + if shadowed.Parent() == types.Universe { + return + } + if *strictShadowing { + // The shadowed identifier must appear before this one to be an instance of shadowing. + if shadowed.Pos() > ident.Pos() { + return + } + } else { + // Don't complain if the span of validity of the shadowed identifier doesn't include + // the shadowing identifier. + span, ok := f.pkg.spans[shadowed] + if !ok { + f.Badf(ident.Pos(), "internal error: no range for %q", ident.Name) + return + } + if !span.contains(ident.Pos()) { + return + } + } + // Don't complain if the types differ: that implies the programmer really wants two different things. + if types.Identical(obj.Type(), shadowed.Type()) { + f.Badf(ident.Pos(), "declaration of %q shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos())) + } +} diff --git a/tools/vendor/github.com/dnephin/govet/shift.go b/tools/vendor/github.com/dnephin/govet/shift.go new file mode 100644 index 000000000..8c038b4bd --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/shift.go @@ -0,0 +1,82 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for suspicious shifts. +*/ + +package main + +import ( + "go/ast" + "go/constant" + "go/token" + "go/types" +) + +func init() { + register("shift", + "check for useless shifts", + checkShift, + binaryExpr, assignStmt) +} + +func checkShift(f *File, node ast.Node) { + switch node := node.(type) { + case *ast.BinaryExpr: + if node.Op == token.SHL || node.Op == token.SHR { + checkLongShift(f, node, node.X, node.Y) + } + case *ast.AssignStmt: + if len(node.Lhs) != 1 || len(node.Rhs) != 1 { + return + } + if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN { + checkLongShift(f, node, node.Lhs[0], node.Rhs[0]) + } + } +} + +// checkLongShift checks if shift or shift-assign operations shift by more than +// the length of the underlying variable. +func checkLongShift(f *File, node ast.Node, x, y ast.Expr) { + v := f.pkg.types[y].Value + if v == nil { + return + } + amt, ok := constant.Int64Val(v) + if !ok { + return + } + t := f.pkg.types[x].Type + if t == nil { + return + } + b, ok := t.Underlying().(*types.Basic) + if !ok { + return + } + var size int64 + var msg string + switch b.Kind() { + case types.Uint8, types.Int8: + size = 8 + case types.Uint16, types.Int16: + size = 16 + case types.Uint32, types.Int32: + size = 32 + case types.Uint64, types.Int64: + size = 64 + case types.Int, types.Uint, types.Uintptr: + // These types may be as small as 32 bits, but no smaller. + size = 32 + msg = "might be " + default: + return + } + if amt >= size { + ident := f.gofmt(x) + f.Badf(node.Pos(), "%s %stoo small for shift of %d", ident, msg, amt) + } +} diff --git a/tools/vendor/github.com/dnephin/govet/structtag.go b/tools/vendor/github.com/dnephin/govet/structtag.go new file mode 100644 index 000000000..abff14fb1 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/structtag.go @@ -0,0 +1,122 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for canonical struct tags. + +package main + +import ( + "errors" + "go/ast" + "reflect" + "strconv" +) + +func init() { + register("structtags", + "check that struct field tags have canonical format and apply to exported fields as needed", + checkCanonicalFieldTag, + field) +} + +// checkCanonicalFieldTag checks a struct field tag. +func checkCanonicalFieldTag(f *File, node ast.Node) { + field := node.(*ast.Field) + if field.Tag == nil { + return + } + + tag, err := strconv.Unquote(field.Tag.Value) + if err != nil { + f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) + return + } + + if err := validateStructTag(tag); err != nil { + f.Badf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get: %s", field.Tag.Value, err) + } + + // Check for use of json or xml tags with unexported fields. + + // Embedded struct. Nothing to do for now, but that + // may change, depending on what happens with issue 7363. + if len(field.Names) == 0 { + return + } + + if field.Names[0].IsExported() { + return + } + + st := reflect.StructTag(tag) + for _, enc := range [...]string{"json", "xml"} { + if st.Get(enc) != "" { + f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc) + return + } + } +} + +var ( + errTagSyntax = errors.New("bad syntax for struct tag pair") + errTagKeySyntax = errors.New("bad syntax for struct tag key") + errTagValueSyntax = errors.New("bad syntax for struct tag value") +) + +// validateStructTag parses the struct tag and returns an error if it is not +// in the canonical format, which is a space-separated list of key:"value" +// settings. The value may contain spaces. +func validateStructTag(tag string) error { + // This code is based on the StructTag.Get code in package reflect. + + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 { + return errTagKeySyntax + } + if i+1 >= len(tag) || tag[i] != ':' { + return errTagSyntax + } + if tag[i+1] != '"' { + return errTagValueSyntax + } + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + return errTagValueSyntax + } + qvalue := tag[:i+1] + tag = tag[i+1:] + + if _, err := strconv.Unquote(qvalue); err != nil { + return errTagValueSyntax + } + } + return nil +} diff --git a/tools/vendor/github.com/dnephin/govet/tests.go b/tools/vendor/github.com/dnephin/govet/tests.go new file mode 100644 index 000000000..8c051f133 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/tests.go @@ -0,0 +1,187 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "go/types" + "strings" + "unicode" + "unicode/utf8" +) + +func init() { + register("tests", + "check for common mistaken usages of tests/documentation examples", + checkTestFunctions, + funcDecl) +} + +func isExampleSuffix(s string) bool { + r, size := utf8.DecodeRuneInString(s) + return size > 0 && unicode.IsLower(r) +} + +func isTestSuffix(name string) bool { + if len(name) == 0 { + // "Test" is ok. + return true + } + r, _ := utf8.DecodeRuneInString(name) + return !unicode.IsLower(r) +} + +func isTestParam(typ ast.Expr, wantType string) bool { + ptr, ok := typ.(*ast.StarExpr) + if !ok { + // Not a pointer. + return false + } + // No easy way of making sure it's a *testing.T or *testing.B: + // ensure the name of the type matches. + if name, ok := ptr.X.(*ast.Ident); ok { + return name.Name == wantType + } + if sel, ok := ptr.X.(*ast.SelectorExpr); ok { + return sel.Sel.Name == wantType + } + return false +} + +func lookup(name string, scopes []*types.Scope) types.Object { + for _, scope := range scopes { + if o := scope.Lookup(name); o != nil { + return o + } + } + return nil +} + +func extendedScope(f *File) []*types.Scope { + scopes := []*types.Scope{f.pkg.typesPkg.Scope()} + if f.basePkg != nil { + scopes = append(scopes, f.basePkg.typesPkg.Scope()) + } else { + // If basePkg is not specified (e.g. when checking a single file) try to + // find it among imports. + pkgName := f.pkg.typesPkg.Name() + if strings.HasSuffix(pkgName, "_test") { + basePkgName := strings.TrimSuffix(pkgName, "_test") + for _, p := range f.pkg.typesPkg.Imports() { + if p.Name() == basePkgName { + scopes = append(scopes, p.Scope()) + break + } + } + } + } + return scopes +} + +func checkExample(fn *ast.FuncDecl, f *File, report reporter) { + fnName := fn.Name.Name + if params := fn.Type.Params; len(params.List) != 0 { + report("%s should be niladic", fnName) + } + if results := fn.Type.Results; results != nil && len(results.List) != 0 { + report("%s should return nothing", fnName) + } + + if filesRun && !includesNonTest { + // The coherence checks between a test and the package it tests + // will report false positives if no non-test files have + // been provided. + return + } + + if fnName == "Example" { + // Nothing more to do. + return + } + + var ( + exName = strings.TrimPrefix(fnName, "Example") + elems = strings.SplitN(exName, "_", 3) + ident = elems[0] + obj = lookup(ident, extendedScope(f)) + ) + if ident != "" && obj == nil { + // Check ExampleFoo and ExampleBadFoo. + report("%s refers to unknown identifier: %s", fnName, ident) + // Abort since obj is absent and no subsequent checks can be performed. + return + } + if len(elems) < 2 { + // Nothing more to do. + return + } + + if ident == "" { + // Check Example_suffix and Example_BadSuffix. + if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { + report("%s has malformed example suffix: %s", fnName, residual) + } + return + } + + mmbr := elems[1] + if !isExampleSuffix(mmbr) { + // Check ExampleFoo_Method and ExampleFoo_BadMethod. + if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { + report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) + } + } + if len(elems) == 3 && !isExampleSuffix(elems[2]) { + // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix. + report("%s has malformed example suffix: %s", fnName, elems[2]) + } +} + +func checkTest(fn *ast.FuncDecl, prefix string, report reporter) { + // Want functions with 0 results and 1 parameter. + if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || + fn.Type.Params == nil || + len(fn.Type.Params.List) != 1 || + len(fn.Type.Params.List[0].Names) > 1 { + return + } + + // The param must look like a *testing.T or *testing.B. + if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) { + return + } + + if !isTestSuffix(fn.Name.Name[len(prefix):]) { + report("%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix) + } +} + +type reporter func(format string, args ...interface{}) + +// checkTestFunctions walks Test, Benchmark and Example functions checking +// malformed names, wrong signatures and examples documenting inexistent +// identifiers. +func checkTestFunctions(f *File, node ast.Node) { + if !strings.HasSuffix(f.name, "_test.go") { + return + } + + fn, ok := node.(*ast.FuncDecl) + if !ok || fn.Recv != nil { + // Ignore non-functions or functions with receivers. + return + } + + report := func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } + + switch { + case strings.HasPrefix(fn.Name.Name, "Example"): + checkExample(fn, f, report) + case strings.HasPrefix(fn.Name.Name, "Test"): + checkTest(fn, "Test", report) + case strings.HasPrefix(fn.Name.Name, "Benchmark"): + checkTest(fn, "Benchmark", report) + } +} diff --git a/tools/vendor/github.com/dnephin/govet/types.go b/tools/vendor/github.com/dnephin/govet/types.go new file mode 100644 index 000000000..4d0e6154b --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/types.go @@ -0,0 +1,281 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the pieces of the tool that use typechecking from the go/types package. + +package main + +import ( + "go/ast" + "go/importer" + "go/token" + "go/types" +) + +// stdImporter is the importer we use to import packages. +// It is created during initialization so that all packages +// are imported by the same importer. +var stdImporter = importer.Default() + +var ( + errorType *types.Interface + stringerType *types.Interface // possibly nil + formatterType *types.Interface // possibly nil +) + +func init() { + errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + + if typ := importType("fmt", "Stringer"); typ != nil { + stringerType = typ.Underlying().(*types.Interface) + } + + if typ := importType("fmt", "Formatter"); typ != nil { + formatterType = typ.Underlying().(*types.Interface) + } +} + +// importType returns the type denoted by the qualified identifier +// path.name, and adds the respective package to the imports map +// as a side effect. In case of an error, importType returns nil. +func importType(path, name string) types.Type { + pkg, err := stdImporter.Import(path) + if err != nil { + // This can happen if the package at path hasn't been compiled yet. + warnf("import failed: %v", err) + return nil + } + if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok { + return obj.Type() + } + warnf("invalid type name %q", name) + return nil +} + +func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error { + pkg.defs = make(map[*ast.Ident]types.Object) + pkg.uses = make(map[*ast.Ident]types.Object) + pkg.selectors = make(map[*ast.SelectorExpr]*types.Selection) + pkg.spans = make(map[types.Object]Span) + pkg.types = make(map[ast.Expr]types.TypeAndValue) + config := types.Config{ + // We use the same importer for all imports to ensure that + // everybody sees identical packages for the given paths. + Importer: stdImporter, + // By providing a Config with our own error function, it will continue + // past the first error. There is no need for that function to do anything. + Error: func(error) {}, + } + info := &types.Info{ + Selections: pkg.selectors, + Types: pkg.types, + Defs: pkg.defs, + Uses: pkg.uses, + } + typesPkg, err := config.Check(pkg.path, fs, astFiles, info) + pkg.typesPkg = typesPkg + // update spans + for id, obj := range pkg.defs { + pkg.growSpan(id, obj) + } + for id, obj := range pkg.uses { + pkg.growSpan(id, obj) + } + return err +} + +// matchArgType reports an error if printf verb t is not appropriate +// for operand arg. +// +// typ is used only for recursive calls; external callers must supply nil. +// +// (Recursion arises from the compound types {map,chan,slice} which +// may be printed with %d etc. if that is appropriate for their element +// types.) +func (f *File) matchArgType(t printfArgType, typ types.Type, arg ast.Expr) bool { + return f.matchArgTypeInternal(t, typ, arg, make(map[types.Type]bool)) +} + +// matchArgTypeInternal is the internal version of matchArgType. It carries a map +// remembering what types are in progress so we don't recur when faced with recursive +// types or mutually recursive types. +func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool { + // %v, %T accept any argument type. + if t == anyType { + return true + } + if typ == nil { + // external call + typ = f.pkg.types[arg].Type + if typ == nil { + return true // probably a type check problem + } + } + // If the type implements fmt.Formatter, we have nothing to check. + // formatterTyp may be nil - be conservative and check for Format method in that case. + if formatterType != nil && types.Implements(typ, formatterType) || f.hasMethod(typ, "Format") { + return true + } + // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? + if t&argString != 0 { + if types.AssertableTo(errorType, typ) || stringerType != nil && types.AssertableTo(stringerType, typ) { + return true + } + } + + typ = typ.Underlying() + if inProgress[typ] { + // We're already looking at this type. The call that started it will take care of it. + return true + } + inProgress[typ] = true + + switch typ := typ.(type) { + case *types.Signature: + return t&argPointer != 0 + + case *types.Map: + // Recur: map[int]int matches %d. + return t&argPointer != 0 || + (f.matchArgTypeInternal(t, typ.Key(), arg, inProgress) && f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress)) + + case *types.Chan: + return t&argPointer != 0 + + case *types.Array: + // Same as slice. + if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { + return true // %s matches []byte + } + // Recur: []int matches %d. + return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem().Underlying(), arg, inProgress) + + case *types.Slice: + // Same as array. + if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { + return true // %s matches []byte + } + // Recur: []int matches %d. But watch out for + // type T []T + // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. + return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress) + + case *types.Pointer: + // Ugly, but dealing with an edge case: a known pointer to an invalid type, + // probably something from a failed import. + if typ.Elem().String() == "invalid type" { + if *verbose { + f.Warnf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", f.gofmt(arg)) + } + return true // special case + } + // If it's actually a pointer with %p, it prints as one. + if t == argPointer { + return true + } + // If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct. + if str, ok := typ.Elem().Underlying().(*types.Struct); ok { + return f.matchStructArgType(t, str, arg, inProgress) + } + // The rest can print with %p as pointers, or as integers with %x etc. + return t&(argInt|argPointer) != 0 + + case *types.Struct: + return f.matchStructArgType(t, typ, arg, inProgress) + + case *types.Interface: + // If the static type of the argument is empty interface, there's little we can do. + // Example: + // func f(x interface{}) { fmt.Printf("%s", x) } + // Whether x is valid for %s depends on the type of the argument to f. One day + // we will be able to do better. For now, we assume that empty interface is OK + // but non-empty interfaces, with Stringer and Error handled above, are errors. + return typ.NumMethods() == 0 + + case *types.Basic: + switch typ.Kind() { + case types.UntypedBool, + types.Bool: + return t&argBool != 0 + + case types.UntypedInt, + types.Int, + types.Int8, + types.Int16, + types.Int32, + types.Int64, + types.Uint, + types.Uint8, + types.Uint16, + types.Uint32, + types.Uint64, + types.Uintptr: + return t&argInt != 0 + + case types.UntypedFloat, + types.Float32, + types.Float64: + return t&argFloat != 0 + + case types.UntypedComplex, + types.Complex64, + types.Complex128: + return t&argComplex != 0 + + case types.UntypedString, + types.String: + return t&argString != 0 + + case types.UnsafePointer: + return t&(argPointer|argInt) != 0 + + case types.UntypedRune: + return t&(argInt|argRune) != 0 + + case types.UntypedNil: + return t&argPointer != 0 // TODO? + + case types.Invalid: + if *verbose { + f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", f.gofmt(arg)) + } + return true // Probably a type check problem. + } + panic("unreachable") + } + + return false +} + +// hasBasicType reports whether x's type is a types.Basic with the given kind. +func (f *File) hasBasicType(x ast.Expr, kind types.BasicKind) bool { + t := f.pkg.types[x].Type + if t != nil { + t = t.Underlying() + } + b, ok := t.(*types.Basic) + return ok && b.Kind() == kind +} + +// matchStructArgType reports whether all the elements of the struct match the expected +// type. For instance, with "%d" all the elements must be printable with the "%d" format. +func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool { + for i := 0; i < typ.NumFields(); i++ { + if !f.matchArgTypeInternal(t, typ.Field(i).Type(), arg, inProgress) { + return false + } + } + return true +} + +// hasMethod reports whether the type contains a method with the given name. +// It is part of the workaround for Formatters and should be deleted when +// that workaround is no longer necessary. +// TODO: This could be better once issue 6259 is fixed. +func (f *File) hasMethod(typ types.Type, name string) bool { + // assume we have an addressable variable of type typ + obj, _, _ := types.LookupFieldOrMethod(typ, true, f.pkg.typesPkg, name) + _, ok := obj.(*types.Func) + return ok +} diff --git a/tools/vendor/github.com/dnephin/govet/unsafeptr.go b/tools/vendor/github.com/dnephin/govet/unsafeptr.go new file mode 100644 index 000000000..a143e4d81 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/unsafeptr.go @@ -0,0 +1,97 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check for invalid uintptr -> unsafe.Pointer conversions. + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("unsafeptr", + "check for misuse of unsafe.Pointer", + checkUnsafePointer, + callExpr) +} + +func checkUnsafePointer(f *File, node ast.Node) { + x := node.(*ast.CallExpr) + if len(x.Args) != 1 { + return + } + if f.hasBasicType(x.Fun, types.UnsafePointer) && f.hasBasicType(x.Args[0], types.Uintptr) && !f.isSafeUintptr(x.Args[0]) { + f.Badf(x.Pos(), "possible misuse of unsafe.Pointer") + } +} + +// isSafeUintptr reports whether x - already known to be a uintptr - +// is safe to convert to unsafe.Pointer. It is safe if x is itself derived +// directly from an unsafe.Pointer via conversion and pointer arithmetic +// or if x is the result of reflect.Value.Pointer or reflect.Value.UnsafeAddr +// or obtained from the Data field of a *reflect.SliceHeader or *reflect.StringHeader. +func (f *File) isSafeUintptr(x ast.Expr) bool { + switch x := x.(type) { + case *ast.ParenExpr: + return f.isSafeUintptr(x.X) + + case *ast.SelectorExpr: + switch x.Sel.Name { + case "Data": + // reflect.SliceHeader and reflect.StringHeader are okay, + // but only if they are pointing at a real slice or string. + // It's not okay to do: + // var x SliceHeader + // x.Data = uintptr(unsafe.Pointer(...)) + // ... use x ... + // p := unsafe.Pointer(x.Data) + // because in the middle the garbage collector doesn't + // see x.Data as a pointer and so x.Data may be dangling + // by the time we get to the conversion at the end. + // For now approximate by saying that *Header is okay + // but Header is not. + pt, ok := f.pkg.types[x.X].Type.(*types.Pointer) + if ok { + t, ok := pt.Elem().(*types.Named) + if ok && t.Obj().Pkg().Path() == "reflect" { + switch t.Obj().Name() { + case "StringHeader", "SliceHeader": + return true + } + } + } + } + + case *ast.CallExpr: + switch len(x.Args) { + case 0: + // maybe call to reflect.Value.Pointer or reflect.Value.UnsafeAddr. + sel, ok := x.Fun.(*ast.SelectorExpr) + if !ok { + break + } + switch sel.Sel.Name { + case "Pointer", "UnsafeAddr": + t, ok := f.pkg.types[sel.X].Type.(*types.Named) + if ok && t.Obj().Pkg().Path() == "reflect" && t.Obj().Name() == "Value" { + return true + } + } + + case 1: + // maybe conversion of uintptr to unsafe.Pointer + return f.hasBasicType(x.Fun, types.Uintptr) && f.hasBasicType(x.Args[0], types.UnsafePointer) + } + + case *ast.BinaryExpr: + switch x.Op { + case token.ADD, token.SUB: + return f.isSafeUintptr(x.X) && !f.isSafeUintptr(x.Y) + } + } + return false +} diff --git a/tools/vendor/github.com/dnephin/govet/unused.go b/tools/vendor/github.com/dnephin/govet/unused.go new file mode 100644 index 000000000..df2317a43 --- /dev/null +++ b/tools/vendor/github.com/dnephin/govet/unused.go @@ -0,0 +1,93 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file defines the check for unused results of calls to certain +// pure functions. + +package main + +import ( + "flag" + "go/ast" + "go/token" + "go/types" + "strings" +) + +var unusedFuncsFlag = flag.String("unusedfuncs", + "errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse", + "comma-separated list of functions whose results must be used") + +var unusedStringMethodsFlag = flag.String("unusedstringmethods", + "Error,String", + "comma-separated list of names of methods of type func() string whose results must be used") + +func init() { + register("unusedresult", + "check for unused result of calls to functions in -unusedfuncs list and methods in -unusedstringmethods list", + checkUnusedResult, + exprStmt) +} + +// func() string +var sigNoArgsStringResult = types.NewSignature(nil, nil, + types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), + false) + +var unusedFuncs = make(map[string]bool) +var unusedStringMethods = make(map[string]bool) + +func initUnusedFlags() { + commaSplit := func(s string, m map[string]bool) { + if s != "" { + for _, name := range strings.Split(s, ",") { + if len(name) == 0 { + flag.Usage() + } + m[name] = true + } + } + } + commaSplit(*unusedFuncsFlag, unusedFuncs) + commaSplit(*unusedStringMethodsFlag, unusedStringMethods) +} + +func checkUnusedResult(f *File, n ast.Node) { + call, ok := unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) + if !ok { + return // not a call statement + } + fun := unparen(call.Fun) + + if f.pkg.types[fun].IsType() { + return // a conversion, not a call + } + + selector, ok := fun.(*ast.SelectorExpr) + if !ok { + return // neither a method call nor a qualified ident + } + + sel, ok := f.pkg.selectors[selector] + if ok && sel.Kind() == types.MethodVal { + // method (e.g. foo.String()) + obj := sel.Obj().(*types.Func) + sig := sel.Type().(*types.Signature) + if types.Identical(sig, sigNoArgsStringResult) { + if unusedStringMethods[obj.Name()] { + f.Badf(call.Lparen, "result of (%s).%s call not used", + sig.Recv().Type(), obj.Name()) + } + } + } else if !ok { + // package-qualified function (e.g. fmt.Errorf) + obj, _ := f.pkg.uses[selector.Sel] + if obj, ok := obj.(*types.Func); ok { + qname := obj.Pkg().Path() + "." + obj.Name() + if unusedFuncs[qname] { + f.Badf(call.Lparen, "result of %v call not used", qname) + } + } + } +} diff --git a/tools/vendor/github.com/gordonklaus/ineffassign/ineffassign.go b/tools/vendor/github.com/gordonklaus/ineffassign/ineffassign.go index 0e882d7d7..202f19370 100644 --- a/tools/vendor/github.com/gordonklaus/ineffassign/ineffassign.go +++ b/tools/vendor/github.com/gordonklaus/ineffassign/ineffassign.go @@ -12,26 +12,36 @@ import ( "strings" ) -var ( - root string - dontRecurseFlag = flag.Bool("n", false, "don't recursively check paths") -) +const invalidArgumentExitCode = 3 + +var dontRecurseFlag = flag.Bool("n", false, "don't recursively check paths") func main() { flag.Parse() - if len(flag.Args()) != 1 { + + if len(flag.Args()) == 0 { fmt.Println("missing argument: filepath") - return + os.Exit(invalidArgumentExitCode) } - var err error - root, err = filepath.Abs(flag.Arg(0)) - if err != nil { - fmt.Printf("Error finding absolute path :%s", err) - return + lintFailed := false + for _, path := range flag.Args() { + root, err := filepath.Abs(path) + if err != nil { + fmt.Printf("Error finding absolute path: %s", err) + os.Exit(invalidArgumentExitCode) + } + if walkPath(root) { + lintFailed = true + } + } + if lintFailed { + os.Exit(1) } +} - errors := false +func walkPath(root string) bool { + lintFailed := false filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { if err != nil { fmt.Printf("Error during filesystem walk: %v\n", err) @@ -51,13 +61,11 @@ func main() { fset, _, ineff := checkPath(path) for _, id := range ineff { fmt.Printf("%s: ineffectual assignment to %s\n", fset.Position(id.Pos()), id.Name) - errors = true + lintFailed = true } return nil }) - if errors { - os.Exit(1) - } + return lintFailed } func checkPath(path string) (*token.FileSet, []*ast.CommentGroup, []*ast.Ident) { @@ -223,15 +231,24 @@ func (bld *builder) Visit(n ast.Node) ast.Visitor { } case *ast.AssignStmt: + if n.Tok == token.QUO_ASSIGN || n.Tok == token.REM_ASSIGN { + bld.maybePanic() + } + for _, x := range n.Rhs { bld.walk(x) } - for _, x := range n.Lhs { + for i, x := range n.Lhs { if id, ok := ident(x); ok { if n.Tok >= token.ADD_ASSIGN && n.Tok <= token.AND_NOT_ASSIGN { bld.use(id) } - bld.assign(id) + // Don't treat explicit initialization to zero as assignment; it is often used as shorthand for a bare declaration. + if n.Tok == token.DEFINE && i < len(n.Rhs) && isZeroLiteral(n.Rhs[i]) { + bld.use(id) + } else { + bld.assign(id) + } } else { bld.walk(x) } @@ -277,7 +294,21 @@ func (bld *builder) Visit(n ast.Node) ast.Visitor { bld.use(id) } } + case *ast.SendStmt: + bld.maybePanic() + return bld + case *ast.BinaryExpr: + if n.Op == token.EQL || n.Op == token.QUO || n.Op == token.REM { + bld.maybePanic() + } + return bld + case *ast.CallExpr: + bld.maybePanic() + return bld + case *ast.IndexExpr: + bld.maybePanic() + return bld case *ast.UnaryExpr: id, ok := ident(n.X) if ix, isIx := n.X.(*ast.IndexExpr); isIx { @@ -291,6 +322,7 @@ func (bld *builder) Visit(n ast.Node) ast.Visitor { } return bld case *ast.SelectorExpr: + bld.maybePanic() // A method call (possibly delayed via a method value) might implicitly take // the address of its receiver, causing it to escape. // We can't do any better here without knowing the variable's type. @@ -301,6 +333,7 @@ func (bld *builder) Visit(n ast.Node) ast.Visitor { } return bld case *ast.SliceExpr: + bld.maybePanic() // We don't care about slicing into slices, but without type information we can do no better. if id, ok := ident(n.X); ok { if v, ok := bld.vars[id.Obj]; ok { @@ -308,6 +341,12 @@ func (bld *builder) Visit(n ast.Node) ast.Visitor { } } return bld + case *ast.StarExpr: + bld.maybePanic() + return bld + case *ast.TypeAssertExpr: + bld.maybePanic() + return bld default: return bld @@ -315,6 +354,18 @@ func (bld *builder) Visit(n ast.Node) ast.Visitor { return nil } +func isZeroLiteral(x ast.Expr) bool { + b, ok := x.(*ast.BasicLit) + if !ok { + return false + } + switch b.Value { + case "0", "0.0", "0.", ".0", `""`: + return true + } + return false +} + func (bld *builder) fun(typ *ast.FuncType, body *ast.BlockStmt) { for _, v := range bld.vars { v.fundept++ @@ -382,6 +433,22 @@ func (bld *builder) swtch(stmt ast.Stmt, cases []ast.Stmt) { bld.breaks.pop() } +// An operation that might panic marks named function results as used. +func (bld *builder) maybePanic() { + if len(bld.results) == 0 { + return + } + res := bld.results[len(bld.results)-1] + if res == nil { + return + } + for _, f := range res.List { + for _, id := range f.Names { + bld.use(id) + } + } +} + func (bld *builder) newBlock(parents ...*block) *block { bld.block = &block{ops: map[*ast.Object][]operation{}} for _, b := range parents { diff --git a/tools/vendor/github.com/kisielk/errcheck/README.md b/tools/vendor/github.com/kisielk/errcheck/README.md index 6a26b34d8..64cf17473 100644 --- a/tools/vendor/github.com/kisielk/errcheck/README.md +++ b/tools/vendor/github.com/kisielk/errcheck/README.md @@ -99,7 +99,7 @@ in the importer. However, you can use errcheck on packages that depend on those which use cgo. In order for this to work you need to `go install` the cgo dependencies before running -errcheck on the dependant packages. +errcheck on the dependent packages. See https://github.com/kisielk/errcheck/issues/16 for more details. diff --git a/tools/vendor/github.com/mattn/goveralls/README.md b/tools/vendor/github.com/mattn/goveralls/README.md index 3b536e646..bc5608b57 100644 --- a/tools/vendor/github.com/mattn/goveralls/README.md +++ b/tools/vendor/github.com/mattn/goveralls/README.md @@ -40,7 +40,7 @@ Enable Travis-CI on your github repository settings. For a **public** github repository put below's `.travis.yml`. -``` +```yml language: go sudo: false go: @@ -48,14 +48,14 @@ go: before_install: - go get github.com/mattn/goveralls script: - - $HOME/gopath/bin/goveralls -service=travis-ci + - $GOPATH/bin/goveralls -service=travis-ci ``` For a **public** github repository, it is not necessary to define your repository key (`COVERALLS_TOKEN`). For a **private** github repository put below's `.travis.yml`. If you use **travis pro**, you need to specify `-service=travis-pro` instead of `-service=travis-ci`. -``` +```yml language: go sudo: false go: @@ -63,7 +63,7 @@ go: before_install: - go get github.com/mattn/goveralls script: - - $HOME/gopath/bin/goveralls -service=travis-pro + - $GOPATH/bin/goveralls -service=travis-pro ``` Store your Coveralls API token in `Environment variables`. @@ -81,7 +81,7 @@ $ travis encrypt COVERALLS_TOKEN=your_token_goes_here --add env.global travis will add `env` block as following example: -``` +```yml env: global: secure: xxxxxxxxxxxxx diff --git a/tools/vendor/github.com/mattn/goveralls/gocover.go b/tools/vendor/github.com/mattn/goveralls/gocover.go index 7e18ba652..dc3ec312c 100644 --- a/tools/vendor/github.com/mattn/goveralls/gocover.go +++ b/tools/vendor/github.com/mattn/goveralls/gocover.go @@ -48,14 +48,13 @@ func mergeProfs(pfss [][]*cover.Profile) []*cover.Profile { ret := make([]*cover.Profile, 0, len(head)) for i, profile := range head { for _, ps := range rest { + // find profiles if len(ps) == 0 { - // no test files continue } else if len(ps) < i+1 { - log.Fatal("Profile length is different") - } - if ps[i].FileName != profile.FileName { - log.Fatal("Profile FileName is different") + continue + } else if ps[i].FileName != profile.FileName { + continue } profile.Blocks = mergeProfBlocks(profile.Blocks, ps[i].Blocks) } diff --git a/tools/vendor/github.com/mattn/goveralls/goveralls.go b/tools/vendor/github.com/mattn/goveralls/goveralls.go index a69507676..3be972db9 100644 --- a/tools/vendor/github.com/mattn/goveralls/goveralls.go +++ b/tools/vendor/github.com/mattn/goveralls/goveralls.go @@ -45,6 +45,7 @@ var ( extraFlags Flags pkg = flag.String("package", "", "Go package") verbose = flag.Bool("v", false, "Pass '-v' argument to 'go test' and output to stdout") + race = flag.Bool("race", false, "Pass '-race' argument to 'go test'") debug = flag.Bool("debug", false, "Enable debug output") coverprof = flag.String("coverprofile", "", "If supplied, use a go cover profile (comma separated)") covermode = flag.String("covermode", "count", "sent as covermode argument to go test") @@ -132,12 +133,18 @@ func getCoverage() ([]*SourceFile, error) { outBuf := new(bytes.Buffer) cmd.Stdout = outBuf cmd.Stderr = outBuf - - args := []string{"go", "test", "-covermode", *covermode, "-coverprofile", f.Name(), coverpkg} + coverm := *covermode + if *race { + coverm = "atomic" + } + args := []string{"go", "test", "-covermode", coverm, "-coverprofile", f.Name(), coverpkg} if *verbose { args = append(args, "-v") cmd.Stdout = os.Stdout } + if *race { + args = append(args, "-race") + } args = append(args, extraFlags...) args = append(args, line) cmd.Args = args diff --git a/tools/vendor/github.com/mdempsky/maligned/LICENSE b/tools/vendor/github.com/mdempsky/maligned/LICENSE new file mode 100644 index 000000000..744875676 --- /dev/null +++ b/tools/vendor/github.com/mdempsky/maligned/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tools/vendor/github.com/mdempsky/maligned/README b/tools/vendor/github.com/mdempsky/maligned/README new file mode 100644 index 000000000..4e57f6eab --- /dev/null +++ b/tools/vendor/github.com/mdempsky/maligned/README @@ -0,0 +1,7 @@ +Install: + + go get github.com/mdempsky/maligned + +Usage: + + maligned cmd/compile/internal/gc cmd/link/internal/ld diff --git a/tools/vendor/github.com/mdempsky/maligned/maligned.go b/tools/vendor/github.com/mdempsky/maligned/maligned.go new file mode 100644 index 000000000..ae3fadf67 --- /dev/null +++ b/tools/vendor/github.com/mdempsky/maligned/maligned.go @@ -0,0 +1,229 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/build" + "go/token" + "go/types" + "log" + "sort" + + "github.com/kisielk/gotool" + "golang.org/x/tools/go/loader" +) + +var fset = token.NewFileSet() + +func main() { + flag.Parse() + + importPaths := gotool.ImportPaths(flag.Args()) + if len(importPaths) == 0 { + return + } + + var conf loader.Config + conf.Fset = fset + for _, importPath := range importPaths { + conf.Import(importPath) + } + prog, err := conf.Load() + if err != nil { + log.Fatal(err) + } + + for _, pkg := range prog.InitialPackages() { + for _, file := range pkg.Files { + ast.Inspect(file, func(node ast.Node) bool { + if s, ok := node.(*ast.StructType); ok { + malign(node.Pos(), pkg.Types[s].Type.(*types.Struct)) + } + return true + }) + } + } +} + +func malign(pos token.Pos, str *types.Struct) { + wordSize := int64(8) + maxAlign := int64(8) + switch build.Default.GOARCH { + case "386", "arm": + wordSize, maxAlign = 4, 4 + case "amd64p32": + wordSize = 4 + } + + s := gcSizes{wordSize, maxAlign} + sz, opt := s.Sizeof(str), optimalSize(str, &s) + if sz != opt { + fmt.Printf("%s: struct of size %d could be %d\n", fset.Position(pos), sz, opt) + } +} + +func optimalSize(str *types.Struct, sizes *gcSizes) int64 { + nf := str.NumFields() + fields := make([]*types.Var, nf) + alignofs := make([]int64, nf) + sizeofs := make([]int64, nf) + for i := 0; i < nf; i++ { + fields[i] = str.Field(i) + ft := fields[i].Type() + alignofs[i] = sizes.Alignof(ft) + sizeofs[i] = sizes.Sizeof(ft) + } + sort.Sort(&byAlignAndSize{fields, alignofs, sizeofs}) + return sizes.Sizeof(types.NewStruct(fields, nil)) +} + +type byAlignAndSize struct { + fields []*types.Var + alignofs []int64 + sizeofs []int64 +} + +func (s *byAlignAndSize) Len() int { return len(s.fields) } +func (s *byAlignAndSize) Swap(i, j int) { + s.fields[i], s.fields[j] = s.fields[j], s.fields[i] + s.alignofs[i], s.alignofs[j] = s.alignofs[j], s.alignofs[i] + s.sizeofs[i], s.sizeofs[j] = s.sizeofs[j], s.sizeofs[i] +} + +func (s *byAlignAndSize) Less(i, j int) bool { + // Place zero sized objects before non-zero sized objects. + if s.sizeofs[i] == 0 && s.sizeofs[j] != 0 { + return true + } + if s.sizeofs[j] == 0 && s.sizeofs[i] != 0 { + return false + } + + // Next, place more tightly aligned objects before less tightly aligned objects. + if s.alignofs[i] != s.alignofs[j] { + return s.alignofs[i] > s.alignofs[j] + } + + // Lastly, order by size. + if s.sizeofs[i] != s.sizeofs[j] { + return s.sizeofs[i] > s.sizeofs[j] + } + + return false +} + +// Code below based on go/types.StdSizes. + +type gcSizes struct { + WordSize int64 + MaxAlign int64 +} + +func (s *gcSizes) Alignof(T types.Type) int64 { + // NOTE: On amd64, complex64 is 8 byte aligned, + // even though float32 is only 4 byte aligned. + + // For arrays and structs, alignment is defined in terms + // of alignment of the elements and fields, respectively. + switch t := T.Underlying().(type) { + case *types.Array: + // spec: "For a variable x of array type: unsafe.Alignof(x) + // is the same as unsafe.Alignof(x[0]), but at least 1." + return s.Alignof(t.Elem()) + case *types.Struct: + // spec: "For a variable x of struct type: unsafe.Alignof(x) + // is the largest of the values unsafe.Alignof(x.f) for each + // field f of x, but at least 1." + max := int64(1) + for i, nf := 0, t.NumFields(); i < nf; i++ { + if a := s.Alignof(t.Field(i).Type()); a > max { + max = a + } + } + return max + } + a := s.Sizeof(T) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + if a > s.MaxAlign { + return s.MaxAlign + } + return a +} + +var basicSizes = [...]byte{ + types.Bool: 1, + types.Int8: 1, + types.Int16: 2, + types.Int32: 4, + types.Int64: 8, + types.Uint8: 1, + types.Uint16: 2, + types.Uint32: 4, + types.Uint64: 8, + types.Float32: 4, + types.Float64: 8, + types.Complex64: 8, + types.Complex128: 16, +} + +func (s *gcSizes) Sizeof(T types.Type) int64 { + switch t := T.Underlying().(type) { + case *types.Basic: + k := t.Kind() + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } + } + if k == types.String { + return s.WordSize * 2 + } + case *types.Array: + n := t.Len() + if n == 0 { + return 0 + } + a := s.Alignof(t.Elem()) + z := s.Sizeof(t.Elem()) + return align(z, a)*(n-1) + z + case *types.Slice: + return s.WordSize * 3 + case *types.Struct: + nf := t.NumFields() + if nf == 0 { + return 0 + } + + var o int64 + max := int64(1) + for i := 0; i < nf; i++ { + ft := t.Field(i).Type() + a, sz := s.Alignof(ft), s.Sizeof(ft) + if a > max { + max = a + } + if i == nf-1 && sz == 0 && o != 0 { + sz = 1 + } + o = align(o, a) + sz + } + return align(o, max) + case *types.Interface: + return s.WordSize * 2 + } + return s.WordSize // catch-all +} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a +} diff --git a/tools/vendor/github.com/mibk/dupl/main.go b/tools/vendor/github.com/mibk/dupl/main.go index f2af56612..dae08e361 100644 --- a/tools/vendor/github.com/mibk/dupl/main.go +++ b/tools/vendor/github.com/mibk/dupl/main.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/mibk/dupl/job" - "github.com/mibk/dupl/output" + "github.com/mibk/dupl/printer" "github.com/mibk/dupl/syntax" ) @@ -20,18 +20,18 @@ const defaultThreshold = 15 var ( paths = []string{"."} - vendor = flag.Bool("vendor", false, "check files in vendor directory") - verbose = flag.Bool("verbose", false, "explain what is being done") - threshold = flag.Int("threshold", defaultThreshold, "minimum token sequence as a clone") - files = flag.Bool("files", false, "files names from stdin") + vendor = flag.Bool("vendor", false, "") + verbose = flag.Bool("verbose", false, "") + threshold = flag.Int("threshold", defaultThreshold, "") + files = flag.Bool("files", false, "") - html = flag.Bool("html", false, "html output") - plumbing = flag.Bool("plumbing", false, "plumbing output for consumption by scripts or tools") + html = flag.Bool("html", false, "") + plumbing = flag.Bool("plumbing", false, "") ) const ( vendorDirPrefix = "vendor" + string(filepath.Separator) - vendorDirInPath = string(filepath.Separator) + "vendor" + string(filepath.Separator) + vendorDirInPath = string(filepath.Separator) + vendorDirPrefix ) func init() { @@ -39,43 +39,6 @@ func init() { flag.IntVar(threshold, "t", defaultThreshold, "alias for -threshold") } -func usage() { - fmt.Fprintln(os.Stderr, `Usage of dupl: - dupl [flags] [paths] - -Paths: - If the given path is a file, dupl will use it regardless of - the file extension. If it is a directory it will recursively - search for *.go files in that directory. - - If no path is given dupl will recursively search for *.go - files in the current directory. - -Flags: - -files - read file names from stdin one at each line - -html - output the results as HTML, including duplicate code fragments - -plumbing - plumbing (easy-to-parse) output for consumption by scripts or tools - -t, -threshold size - minimum token sequence size as a clone (default 15) - -vendor - check files in vendor directory - -v, -verbose - explain what is being done - -Examples: - dupl -t 100 - Search clones in the current directory of size at least - 100 tokens. - dupl $(find app/ -name '*_test.go') - Search for clones in tests in the app directory. - find app/ -name '*_test.go' |dupl -files - The same as above.`) - os.Exit(2) -} - func main() { flag.Usage = usage flag.Parse() @@ -110,7 +73,17 @@ func main() { } close(duplChan) }() - printDupls(duplChan) + + newPrinter := printer.NewText + if *html { + newPrinter = printer.NewHTML + } else if *plumbing { + newPrinter = printer.NewPlumbing + } + p := newPrinter(os.Stdout, ioutil.ReadFile) + if err := printDupls(p, duplChan); err != nil { + log.Fatal(err) + } } func filesFeed() chan string { @@ -120,10 +93,7 @@ func filesFeed() chan string { s := bufio.NewScanner(os.Stdin) for s.Scan() { f := s.Text() - if strings.HasPrefix(f, "./") { - f = f[2:] - } - fchan <- f + fchan <- strings.TrimPrefix(f, "./") } close(fchan) }() @@ -160,7 +130,7 @@ func crawlPaths(paths []string) chan string { return fchan } -func printDupls(duplChan <-chan syntax.Match) { +func printDupls(p printer.Printer, duplChan <-chan syntax.Match) error { groups := make(map[string][][]*syntax.Node) for dupl := range duplChan { groups[dupl.Hash] = append(groups[dupl.Hash], dupl.Frags...) @@ -171,32 +141,18 @@ func printDupls(duplChan <-chan syntax.Match) { } sort.Strings(keys) - p := getPrinter() + if err := p.PrintHeader(); err != nil { + return err + } for _, k := range keys { uniq := unique(groups[k]) if len(uniq) > 1 { - if err := p.Print(uniq); err != nil { - log.Fatal(err) + if err := p.PrintClones(uniq); err != nil { + return err } } } - p.Finish() -} - -func getPrinter() output.Printer { - var fr fileReader - if *html { - return output.NewHTMLPrinter(os.Stdout, fr) - } else if *plumbing { - return output.NewPlumbingPrinter(os.Stdout, fr) - } - return output.NewTextPrinter(os.Stdout, fr) -} - -type fileReader struct{} - -func (fileReader) ReadFile(filename string) ([]byte, error) { - return ioutil.ReadFile(filename) + return p.PrintFooter() } func unique(group [][]*syntax.Node) [][]*syntax.Node { @@ -217,3 +173,39 @@ func unique(group [][]*syntax.Node) [][]*syntax.Node { } return newGroup } + +func usage() { + fmt.Fprintln(os.Stderr, `Usage: dupl [flags] [paths] + +Paths: + If the given path is a file, dupl will use it regardless of + the file extension. If it is a directory, it will recursively + search for *.go files in that directory. + + If no path is given, dupl will recursively search for *.go + files in the current directory. + +Flags: + -files + read file names from stdin one at each line + -html + output the results as HTML, including duplicate code fragments + -plumbing + plumbing (easy-to-parse) output for consumption by scripts or tools + -t, -threshold size + minimum token sequence size as a clone (default 15) + -vendor + check files in vendor directory + -v, -verbose + explain what is being done + +Examples: + dupl -t 100 + Search clones in the current directory of size at least + 100 tokens. + dupl $(find app/ -name '*_test.go') + Search for clones in tests in the app directory. + find app/ -name '*_test.go' |dupl -files + The same as above.`) + os.Exit(2) +} diff --git a/tools/vendor/github.com/mibk/dupl/printer/html.go b/tools/vendor/github.com/mibk/dupl/printer/html.go new file mode 100644 index 000000000..667f0437c --- /dev/null +++ b/tools/vendor/github.com/mibk/dupl/printer/html.go @@ -0,0 +1,120 @@ +package printer + +import ( + "bytes" + "fmt" + "io" + "regexp" + "sort" + + "github.com/mibk/dupl/syntax" +) + +type html struct { + iota int + w io.Writer + ReadFile +} + +func NewHTML(w io.Writer, fread ReadFile) Printer { + return &html{w: w, ReadFile: fread} +} + +func (p *html) PrintHeader() error { + _, err := fmt.Fprint(p.w, ` + +Duplicates + +`) + return err +} + +func (p *html) PrintClones(dups [][]*syntax.Node) error { + p.iota++ + fmt.Fprintf(p.w, "

#%d found %d clones

\n", p.iota, len(dups)) + + clones := make([]clone, len(dups)) + for i, dup := range dups { + cnt := len(dup) + if cnt == 0 { + panic("zero length dup") + } + nstart := dup[0] + nend := dup[cnt-1] + + file, err := p.ReadFile(nstart.Filename) + if err != nil { + return err + } + + lineStart, _ := blockLines(file, nstart.Pos, nend.End) + cl := clone{filename: nstart.Filename, lineStart: lineStart} + start := findLineBeg(file, nstart.Pos) + content := append(toWhitespace(file[start:nstart.Pos]), file[nstart.Pos:nend.End]...) + cl.fragment = deindent(content) + clones[i] = cl + } + + sort.Sort(byNameAndLine(clones)) + for _, cl := range clones { + fmt.Fprintf(p.w, "

%s:%d

\n
%s
\n", cl.filename, cl.lineStart, cl.fragment) + } + return nil +} + +func (*html) PrintFooter() error { return nil } + +func findLineBeg(file []byte, index int) int { + for i := index; i >= 0; i-- { + if file[i] == '\n' { + return i + 1 + } + } + return 0 +} + +func toWhitespace(str []byte) []byte { + var out []byte + for _, c := range bytes.Runes(str) { + if c == '\t' { + out = append(out, '\t') + } else { + out = append(out, ' ') + } + } + return out +} + +func deindent(block []byte) []byte { + const maxVal = 99 + min := maxVal + re := regexp.MustCompile(`(^|\n)(\t*)\S`) + for _, line := range re.FindAllSubmatch(block, -1) { + indent := line[2] + if len(indent) < min { + min = len(indent) + } + } + if min == 0 || min == maxVal { + return block + } + block = block[min:] +Loop: + for i := 0; i < len(block); i++ { + if block[i] == '\n' && i != len(block)-1 { + for j := 0; j < min; j++ { + if block[i+j+1] != '\t' { + continue Loop + } + } + block = append(block[:i+1], block[i+1+min:]...) + } + } + return block +} diff --git a/tools/vendor/github.com/mibk/dupl/printer/plumbing.go b/tools/vendor/github.com/mibk/dupl/printer/plumbing.go new file mode 100644 index 000000000..da77213d8 --- /dev/null +++ b/tools/vendor/github.com/mibk/dupl/printer/plumbing.go @@ -0,0 +1,36 @@ +package printer + +import ( + "fmt" + "io" + "sort" + + "github.com/mibk/dupl/syntax" +) + +type plumbing struct { + w io.Writer + ReadFile +} + +func NewPlumbing(w io.Writer, fread ReadFile) Printer { + return &plumbing{w, fread} +} + +func (p *plumbing) PrintHeader() error { return nil } + +func (p *plumbing) PrintClones(dups [][]*syntax.Node) error { + clones, err := prepareClonesInfo(p.ReadFile, dups) + if err != nil { + return err + } + sort.Sort(byNameAndLine(clones)) + for i, cl := range clones { + nextCl := clones[(i+1)%len(clones)] + fmt.Fprintf(p.w, "%s:%d-%d: duplicate of %s:%d-%d\n", cl.filename, cl.lineStart, cl.lineEnd, + nextCl.filename, nextCl.lineStart, nextCl.lineEnd) + } + return nil +} + +func (p *plumbing) PrintFooter() error { return nil } diff --git a/tools/vendor/github.com/mibk/dupl/printer/printer.go b/tools/vendor/github.com/mibk/dupl/printer/printer.go new file mode 100644 index 000000000..855ca0990 --- /dev/null +++ b/tools/vendor/github.com/mibk/dupl/printer/printer.go @@ -0,0 +1,11 @@ +package printer + +import "github.com/mibk/dupl/syntax" + +type ReadFile func(filename string) ([]byte, error) + +type Printer interface { + PrintHeader() error + PrintClones(dups [][]*syntax.Node) error + PrintFooter() error +} diff --git a/tools/vendor/github.com/mibk/dupl/printer/text.go b/tools/vendor/github.com/mibk/dupl/printer/text.go new file mode 100644 index 000000000..f8a1071b1 --- /dev/null +++ b/tools/vendor/github.com/mibk/dupl/printer/text.go @@ -0,0 +1,100 @@ +package printer + +import ( + "fmt" + "io" + "sort" + + "github.com/mibk/dupl/syntax" +) + +type text struct { + cnt int + w io.Writer + ReadFile +} + +func NewText(w io.Writer, fread ReadFile) Printer { + return &text{w: w, ReadFile: fread} +} + +func (p *text) PrintHeader() error { return nil } + +func (p *text) PrintClones(dups [][]*syntax.Node) error { + p.cnt++ + fmt.Fprintf(p.w, "found %d clones:\n", len(dups)) + clones, err := prepareClonesInfo(p.ReadFile, dups) + if err != nil { + return err + } + sort.Sort(byNameAndLine(clones)) + for _, cl := range clones { + fmt.Fprintf(p.w, " %s:%d,%d\n", cl.filename, cl.lineStart, cl.lineEnd) + } + return nil +} + +func (p *text) PrintFooter() error { + _, err := fmt.Fprintf(p.w, "\nFound total %d clone groups.\n", p.cnt) + return err +} + +func prepareClonesInfo(fread ReadFile, dups [][]*syntax.Node) ([]clone, error) { + clones := make([]clone, len(dups)) + for i, dup := range dups { + cnt := len(dup) + if cnt == 0 { + panic("zero length dup") + } + nstart := dup[0] + nend := dup[cnt-1] + + file, err := fread(nstart.Filename) + if err != nil { + return nil, err + } + + cl := clone{filename: nstart.Filename} + cl.lineStart, cl.lineEnd = blockLines(file, nstart.Pos, nend.End) + clones[i] = cl + } + return clones, nil +} + +func blockLines(file []byte, from, to int) (int, int) { + line := 1 + lineStart, lineEnd := 0, 0 + for offset, b := range file { + if b == '\n' { + line++ + } + if offset == from { + lineStart = line + } + if offset == to-1 { + lineEnd = line + break + } + } + return lineStart, lineEnd +} + +type clone struct { + filename string + lineStart int + lineEnd int + fragment []byte +} + +type byNameAndLine []clone + +func (c byNameAndLine) Len() int { return len(c) } + +func (c byNameAndLine) Swap(i, j int) { c[i], c[j] = c[j], c[i] } + +func (c byNameAndLine) Less(i, j int) bool { + if c[i].filename == c[j].filename { + return c[i].lineStart < c[j].lineStart + } + return c[i].filename < c[j].filename +} diff --git a/tools/vendor/github.com/mvdan/unparam/check/check.go b/tools/vendor/github.com/mvdan/unparam/check/check.go index 56253f3d5..65f7d1846 100644 --- a/tools/vendor/github.com/mvdan/unparam/check/check.go +++ b/tools/vendor/github.com/mvdan/unparam/check/check.go @@ -12,7 +12,6 @@ import ( "go/parser" "go/token" "go/types" - "io" "os" "path/filepath" "regexp" @@ -29,19 +28,15 @@ import ( "github.com/mvdan/lint" ) -func UnusedParams(tests, debug bool, args ...string) ([]string, error) { +func UnusedParams(tests bool, args ...string) ([]string, error) { wd, err := os.Getwd() if err != nil { return nil, err } c := &Checker{ - wd: wd, - tests: tests, + wd: wd, tests: tests, cachedDeclCounts: make(map[string]map[string]int), } - if debug { - c.debugLog = os.Stderr - } return c.lines(args...) } @@ -51,8 +46,7 @@ type Checker struct { wd string - tests bool - debugLog io.Writer + tests bool cachedDeclCounts map[string]map[string]int } @@ -107,12 +101,6 @@ func (c *Checker) ProgramSSA(prog *ssa.Program) { c.prog = prog } -func (c *Checker) debug(format string, a ...interface{}) { - if c.debugLog != nil { - fmt.Fprintf(c.debugLog, format, a...) - } -} - func (c *Checker) Check() ([]lint.Issue, error) { wantPkg := make(map[*types.Package]*loader.PackageInfo) for _, info := range c.lprog.InitialPackages() { @@ -133,9 +121,7 @@ funcLoop: if info == nil { // not part of given pkgs continue } - c.debug("func %s\n", fn.String()) if dummyImpl(fn.Blocks[0]) { // panic implementation - c.debug(" skip - dummy implementation\n") continue } for _, edge := range cg.Nodes[fn].In { @@ -144,29 +130,24 @@ funcLoop: default: // called via a parameter or field, type // is set in stone. - c.debug(" skip - type is required via call\n") continue funcLoop } } if c.multipleImpls(info, fn) { - c.debug(" skip - multiple implementations via build tags\n") continue } for i, par := range fn.Params { if i == 0 && fn.Signature.Recv() != nil { // receiver continue } - c.debug("%s\n", par.String()) switch par.Object().Name() { case "", "_": // unnamed - c.debug(" skip - unnamed\n") continue } reason := "is unused" if cv := receivesSameValue(cg.Nodes[fn].In, par, i); cv != nil { reason = fmt.Sprintf("always receives %v", cv) } else if anyRealUse(par, i) { - c.debug(" skip - used somewhere in the func body\n") continue } issues = append(issues, Issue{ @@ -177,25 +158,15 @@ funcLoop: } // TODO: replace by sort.Slice once we drop Go 1.7 support - sort.Sort(byNamePos{c.prog.Fset, issues}) + sort.Sort(byPos(issues)) return issues, nil } -type byNamePos struct { - fset *token.FileSet - l []lint.Issue -} +type byPos []lint.Issue -func (p byNamePos) Len() int { return len(p.l) } -func (p byNamePos) Swap(i, j int) { p.l[i], p.l[j] = p.l[j], p.l[i] } -func (p byNamePos) Less(i, j int) bool { - p1 := p.fset.Position(p.l[i].Pos()) - p2 := p.fset.Position(p.l[j].Pos()) - if p1.Filename == p2.Filename { - return p1.Offset < p2.Offset - } - return p1.Filename < p2.Filename -} +func (p byPos) Len() int { return len(p) } +func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p byPos) Less(i, j int) bool { return p[i].Pos() < p[j].Pos() } func receivesSameValue(in []*callgraph.Edge, par *ssa.Parameter, pos int) constant.Value { if ast.IsExported(par.Parent().Name()) { @@ -221,47 +192,27 @@ func receivesSameValue(in []*callgraph.Edge, par *ssa.Parameter, pos int) consta func anyRealUse(par *ssa.Parameter, pos int) bool { refLoop: for _, ref := range *par.Referrers() { - switch x := ref.(type) { - case *ssa.Call: - if x.Call.Value != par.Parent() { - return true // not a recursive call - } - for i, arg := range x.Call.Args { - if arg != par { - continue - } - if i == pos { - // reused directly in a recursive call - continue refLoop - } - } + call, ok := ref.(*ssa.Call) + if !ok { return true - case *ssa.Store: - if insertedStore(x) { - continue // inserted by go/ssa, not from the code + } + if call.Call.Value != par.Parent() { + return true // not a recursive call + } + for i, arg := range call.Call.Args { + if arg != par { + continue + } + if i == pos { + // reused directly in a recursive call + continue refLoop } - return true - default: - return true } + return true } return false } -func insertedStore(instr ssa.Instruction) bool { - if instr.Pos() != token.NoPos { - return false - } - store, ok := instr.(*ssa.Store) - if !ok { - return false - } - alloc, ok := store.Addr.(*ssa.Alloc) - // we want exactly one use of this alloc value for it to be - // inserted by ssa and dummy - the alloc instruction itself. - return ok && len(*alloc.Referrers()) == 1 -} - var rxHarmlessCall = regexp.MustCompile(`(?i)\b(log(ger)?|errors)\b|\bf?print`) // dummyImpl reports whether a block is a dummy implementation. This is @@ -270,15 +221,11 @@ var rxHarmlessCall = regexp.MustCompile(`(?i)\b(log(ger)?|errors)\b|\bf?print`) func dummyImpl(blk *ssa.BasicBlock) bool { var ops [8]*ssa.Value for _, instr := range blk.Instrs { - if insertedStore(instr) { - continue // inserted by go/ssa, not from the code - } for _, val := range instr.Operands(ops[:0]) { switch x := (*val).(type) { case nil, *ssa.Const, *ssa.ChangeType, *ssa.Alloc, *ssa.MakeInterface, *ssa.Function, - *ssa.Global, *ssa.IndexAddr, *ssa.Slice, - *ssa.UnOp: + *ssa.Global, *ssa.IndexAddr, *ssa.Slice: case *ssa.Call: if rxHarmlessCall.MatchString(x.Call.Value.String()) { continue diff --git a/tools/vendor/github.com/mvdan/unparam/main.go b/tools/vendor/github.com/mvdan/unparam/main.go index 81f3021a4..801748bbd 100644 --- a/tools/vendor/github.com/mvdan/unparam/main.go +++ b/tools/vendor/github.com/mvdan/unparam/main.go @@ -11,18 +11,11 @@ import ( "github.com/mvdan/unparam/check" ) -var ( - tests = flag.Bool("tests", true, "include tests") - debug = flag.Bool("debug", false, "debug prints") -) +var tests = flag.Bool("tests", true, "include tests") func main() { - flag.Usage = func() { - fmt.Fprintln(os.Stderr, "usage: unparam [flags] [package ...]") - flag.PrintDefaults() - } flag.Parse() - warns, err := check.UnusedParams(*tests, *debug, flag.Args()...) + warns, err := check.UnusedParams(*tests, flag.Args()...) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/tools/vendor/honnef.co/go/tools/cmd/gosimple/README.md b/tools/vendor/honnef.co/go/tools/cmd/gosimple/README.md index 7e9693c2f..599dfd564 100644 --- a/tools/vendor/honnef.co/go/tools/cmd/gosimple/README.md +++ b/tools/vendor/honnef.co/go/tools/cmd/gosimple/README.md @@ -9,123 +9,7 @@ Gosimple requires Go 1.6 or later. go get honnef.co/go/tools/cmd/gosimple -## Usage +## Documentation -Invoke `gosimple` with one or more filenames, a directory, or a package named -by its import path. Gosimple uses the same -[import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as -the `go` command and therefore -also supports relative import paths like `./...`. Additionally the `...` -wildcard can be used as suffix on relative and absolute file paths to recurse -into them. - -The output of this tool is a list of suggestions in Vim quickfix format, -which is accepted by lots of different editors. - -## Purpose - -Gosimple differs from golint in that gosimple focuses on simplifying -code, while golint flags common style issues. Furthermore, gosimple -always targets the latest Go version. If a new Go release adds a -simpler way of doing something, gosimple will suggest that way. - -## Checks - -Gosimple makes the following recommendations for avoiding unsimple -constructs: - -| Check | Description | Suggestion | -|-------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------| -| S1000 | `select{}` with a single case | Use a plain channel send or receive | -| S1001 | A loop copying elements of `s2` to `s1` | `copy(s1, s2)` | -| S1002 | `if b == true` | `if b` | -| S1003 | `strings.Index*(x, y) != -1` | `strings.Contains(x, y)` | -| S1004 | `bytes.Compare(x, y) == 0` | `bytes.Equal(x, y)` | -| S1005 | `for _ = range x` | `for range x` | -| S1006 | `for true {...}` | `for {...}` | -| S1007 | Using double quotes and escaping for regular expressions | Use raw strings | -| S1008 | `if { return }; return ` | `return ` | -| S1009 | Checking a slice against nil and also checking its length against zero | Nil slices are defined to have length zero, the nil check is redundant | -| S1010 | `s[a:len(s)]` | `s[a:]` | -| S1011 | A loop appending each element of `s2` to `s1` | `append(s1, s2...)` | -| S1012 | `time.Now().Sub(x)` | `time.Since(x)` | -| S1013 | `if err != nil { return err }; return nil` | `return err` | -| S1014 | `_ = <-x` | `<-x` | -| S1015 | Using `strconv.FormatInt` when `strconv.Atoi` would be more straightforward | | -| S1016 | Converting two struct types by manually copying each field | A type conversion: `T(v)` | -| S1017 | `if strings.HasPrefix` + string slicing | Call `strings.TrimPrefix` unconditionally | -| S1018 | A loop sliding elements in a slice to the beginning | `copy(s[:n], s[offset:])` | -| S1019 | `make(T, 0)` or `make(T, x, x)` | `make(T)` or `make(T, x)` | -| S1020 | `if _, ok := i.(T); ok && i != nil` | `if _, ok := i.(T); ok` | -| S1021 | `var x uint; x = 1` | `var x uint = 1` | -| S1022 | `x, _ = someMap[key]` | `x = someMap[key]` | -| S1023 | `break` as the final statement of a `case` clause | Go doesn't have automatic fallthrough, making final `break` redundant | -| S1024 | `t.Sub(time.Now())` | `time.Until(t)` | -| S1025 | `fmt.Sprintf("%s", x)` where `x` is already a string | `x` | -| | `fmt.Sprintf("%s", x)` where `x`'s underlying type is a string | `string(x)` | -| | `fmt.Sprintf("%s", x)` where `x` has a String method | `x.String()` | -| S1026 | Copies of strings, like `string([]byte(x))` or `"" + x` | `x` | -| S1027 | `return` as the final statement of a func body with no return values | Functions that don't return anything don't need a final return statement | -| S1028 | `errors.New(fmt.Sprintf(...))` | `fmt.Errorf(...)` | -| S1029 | `for _, r := range []rune(s)` | `for _, r := range s` | -| S1030 | `string(buf.Bytes())` or `[]byte(buf.String())` | Use the appropriate method of `bytes.Buffer` instead | - -## gofmt -r - -Some of these rules can be automatically applied via `gofmt -r`: - -``` -strings.IndexRune(a, b) > -1 -> strings.ContainsRune(a, b) -strings.IndexRune(a, b) >= 0 -> strings.ContainsRune(a, b) -strings.IndexRune(a, b) != -1 -> strings.ContainsRune(a, b) -strings.IndexRune(a, b) == -1 -> !strings.ContainsRune(a, b) -strings.IndexRune(a, b) < 0 -> !strings.ContainsRune(a, b) -strings.IndexAny(a, b) > -1 -> strings.ContainsAny(a, b) -strings.IndexAny(a, b) >= 0 -> strings.ContainsAny(a, b) -strings.IndexAny(a, b) != -1 -> strings.ContainsAny(a, b) -strings.IndexAny(a, b) == -1 -> !strings.ContainsAny(a, b) -strings.IndexAny(a, b) < 0 -> !strings.ContainsAny(a, b) -strings.Index(a, b) > -1 -> strings.Contains(a, b) -strings.Index(a, b) >= 0 -> strings.Contains(a, b) -strings.Index(a, b) != -1 -> strings.Contains(a, b) -strings.Index(a, b) == -1 -> !strings.Contains(a, b) -strings.Index(a, b) < 0 -> !strings.Contains(a, b) -bytes.Index(a, b) > -1 -> bytes.Contains(a, b) -bytes.Index(a, b) >= 0 -> bytes.Contains(a, b) -bytes.Index(a, b) != -1 -> bytes.Contains(a, b) -bytes.Index(a, b) == -1 -> !bytes.Contains(a, b) -bytes.Index(a, b) < 0 -> !bytes.Contains(a, b) -bytes.Compare(a, b) == 0 -> bytes.Equal(a, b) -bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b) - -time.Now().Sub(a) -> time.Since(a) -a.Sub(time.Now()) -> time.Until(a) -``` - -## Ignoring checks - -gosimple allows disabling some or all checks for certain files. The -`-ignore` flag takes a whitespace-separated list of -`glob:check1,check2,...` pairs. `glob` is a glob pattern matching -files in packages, and `check1,check2,...` are checks named by their -IDs. - -For example, to ignore uses of strconv.FormatInt in all test files in the -`os/exec` package, you would write `-ignore -"os/exec/*_test.go:S1015"` - -Additionally, the check IDs support globbing, too. Using a pattern -such as `os/exec/*.gen.go:*` would disable all checks in all -auto-generated files in the os/exec package. - -Any whitespace can be used to separate rules, including newlines. This -allows for a setup like the following: - -``` -$ cat stdlib.ignore -sync/*_test.go:S1000 -testing/benchmark.go:S1016 -runtime/string_test.go:S1005 - -$ gosimple -ignore "$(cat stdlib.ignore)" std -``` +Detailed documentation can be found on +[staticcheck.io](https://staticcheck.io/docs/gosimple). diff --git a/tools/vendor/honnef.co/go/tools/cmd/gosimple/gosimple.go b/tools/vendor/honnef.co/go/tools/cmd/gosimple/gosimple.go index a939a2789..6ea1d79b0 100644 --- a/tools/vendor/honnef.co/go/tools/cmd/gosimple/gosimple.go +++ b/tools/vendor/honnef.co/go/tools/cmd/gosimple/gosimple.go @@ -13,6 +13,9 @@ func main() { fs.Parse(os.Args[1:]) c := simple.NewChecker() c.CheckGenerated = *gen - - lintutil.ProcessFlagSet(c, fs) + cfg := lintutil.CheckerConfig{ + Checker: c, + ExitNonZero: true, + } + lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs) } diff --git a/tools/vendor/honnef.co/go/tools/cmd/megacheck/megacheck.go b/tools/vendor/honnef.co/go/tools/cmd/megacheck/megacheck.go index 8477df65d..4c0b97c80 100644 --- a/tools/vendor/honnef.co/go/tools/cmd/megacheck/megacheck.go +++ b/tools/vendor/honnef.co/go/tools/cmd/megacheck/megacheck.go @@ -4,42 +4,23 @@ package main // import "honnef.co/go/tools/cmd/megacheck" import ( "os" - "honnef.co/go/tools/lint" "honnef.co/go/tools/lint/lintutil" "honnef.co/go/tools/simple" "honnef.co/go/tools/staticcheck" "honnef.co/go/tools/unused" ) -type Checker struct { - Checkers []lint.Checker -} - -func (c *Checker) Init(prog *lint.Program) { - for _, cc := range c.Checkers { - cc.Init(prog) - } -} - -func (c *Checker) Funcs() map[string]lint.Func { - fns := map[string]lint.Func{} - for _, cc := range c.Checkers { - for k, v := range cc.Funcs() { - fns[k] = v - } - } - return fns -} - func main() { var flags struct { staticcheck struct { - enabled bool - generated bool + enabled bool + generated bool + exitNonZero bool } gosimple struct { - enabled bool - generated bool + enabled bool + generated bool + exitNonZero bool } unused struct { enabled bool @@ -51,6 +32,7 @@ func main() { debug string wholeProgram bool reflection bool + exitNonZero bool } } fs := lintutil.FlagSet("megacheck") @@ -58,11 +40,15 @@ func main() { "simple.enabled", true, "Run gosimple") fs.BoolVar(&flags.gosimple.generated, "simple.generated", false, "Check generated code") + fs.BoolVar(&flags.gosimple.exitNonZero, + "simple.exit-non-zero", false, "Exit non-zero if any problems were found") fs.BoolVar(&flags.staticcheck.enabled, "staticcheck.enabled", true, "Run staticcheck") fs.BoolVar(&flags.staticcheck.generated, "staticcheck.generated", false, "Check generated code (only applies to a subset of checks)") + fs.BoolVar(&flags.staticcheck.exitNonZero, + "staticcheck.exit-non-zero", true, "Exit non-zero if any problems were found") fs.BoolVar(&flags.unused.enabled, "unused.enabled", true, "Run unused") @@ -78,22 +64,31 @@ func main() { "unused.vars", true, "Report unused variables") fs.BoolVar(&flags.unused.wholeProgram, "unused.exported", false, "Treat arguments as a program and report unused exported identifiers") - fs.BoolVar(&flags.unused.reflection, "unused.reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection") + fs.BoolVar(&flags.unused.reflection, + "unused.reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection") + fs.BoolVar(&flags.unused.exitNonZero, + "unused.exit-non-zero", true, "Exit non-zero if any problems were found") fs.Parse(os.Args[1:]) - c := &Checker{} + var checkers []lintutil.CheckerConfig if flags.staticcheck.enabled { sac := staticcheck.NewChecker() sac.CheckGenerated = flags.staticcheck.generated - c.Checkers = append(c.Checkers, sac) + checkers = append(checkers, lintutil.CheckerConfig{ + Checker: sac, + ExitNonZero: flags.staticcheck.exitNonZero, + }) } if flags.gosimple.enabled { sc := simple.NewChecker() sc.CheckGenerated = flags.gosimple.generated - c.Checkers = append(c.Checkers, sc) + checkers = append(checkers, lintutil.CheckerConfig{ + Checker: sc, + ExitNonZero: flags.gosimple.exitNonZero, + }) } if flags.unused.enabled { @@ -116,8 +111,12 @@ func main() { uc := unused.NewChecker(mode) uc.WholeProgram = flags.unused.wholeProgram uc.ConsiderReflection = flags.unused.reflection - c.Checkers = append(c.Checkers, unused.NewLintChecker(uc)) + checkers = append(checkers, lintutil.CheckerConfig{ + Checker: unused.NewLintChecker(uc), + ExitNonZero: flags.unused.exitNonZero, + }) + } - lintutil.ProcessFlagSet(c, fs) + lintutil.ProcessFlagSet(checkers, fs) } diff --git a/tools/vendor/honnef.co/go/tools/cmd/staticcheck/README.md.template b/tools/vendor/honnef.co/go/tools/cmd/staticcheck/README.md.template deleted file mode 100644 index 6eee08980..000000000 --- a/tools/vendor/honnef.co/go/tools/cmd/staticcheck/README.md.template +++ /dev/null @@ -1,76 +0,0 @@ -# staticcheck - -_staticcheck_ is `go vet` on steroids, applying a ton of static analysis -checks you might be used to from tools like ReSharper for C#. - -## Installation - -Staticcheck requires Go 1.6 or later. - - go get honnef.co/go/tools/cmd/staticcheck - -## Usage - -Invoke `staticcheck` with one or more filenames, a directory, or a package named -by its import path. Staticcheck uses the same -[import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as -the `go` command and therefore -also supports relative import paths like `./...`. Additionally the `...` -wildcard can be used as suffix on relative and absolute file paths to recurse -into them. - -The output of this tool is a list of suggestions in Vim quickfix format, -which is accepted by lots of different editors. - -## Purpose - -The main purpose of staticcheck is editor integration, or workflow -integration in general. For example, by running staticcheck when -saving a file, one can quickly catch simple bugs without having to run -the whole test suite or the program itself. - -The tool shouldn't report any errors unless there are legitimate -bugs - or very dubious constructs - in the code. - -It is similar in nature to `go vet`, but has more checks that catch -bugs that would also be caught easily at runtime, to reduce the number -of edit, compile and debug cycles. - -## Checks - -The following things are currently checked by staticcheck: - -[CHECKS PLACEHOLDER] - -## Ignoring checks - -staticcheck allows disabling some or all checks for certain files. The -`-ignore` flag takes a whitespace-separated list of -`glob:check1,check2,...` pairs. `glob` is a glob pattern matching -files in packages, and `check1,check2,...` are checks named by their -IDs. - -For example, to ignore assignment to nil maps in all test files in the -`os/exec` package, you would write `-ignore -"os/exec/*_test.go:SA5000"` - -Additionally, the check IDs support globbing, too. Using a pattern -such as `os/exec/*.gen.go:*` would disable all checks in all -auto-generated files in the os/exec package. - -Any whitespace can be used to separate rules, including newlines. This -allows for a setup like the following: - -``` -$ cat stdlib.ignore -sync/*_test.go:SA2001 -testing/benchmark.go:SA3001 -runtime/string_test.go:SA4007 -runtime/proc_test.go:SA5004 -runtime/lfstack_test.go:SA4010 -runtime/append_test.go:SA4010 -errors/errors_test.go:SA4000 -reflect/all_test.go:SA4000 - -$ staticcheck -ignore "$(cat stdlib.ignore)" std -``` diff --git a/tools/vendor/honnef.co/go/tools/cmd/staticcheck/generate_readme.sh b/tools/vendor/honnef.co/go/tools/cmd/staticcheck/generate_readme.sh deleted file mode 100755 index 23bc8421f..000000000 --- a/tools/vendor/honnef.co/go/tools/cmd/staticcheck/generate_readme.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh - -build() { - echo "|Check|Description|" - echo "|---|---|" - for cat in docs/categories/*; do - catname=$(basename "$cat") - catdesc=$(cat "$cat") - echo "|**$catname???**|**$catdesc**|" - - for check in docs/checks/"$catname"*; do - checkname=$(basename "$check") - checktitle=$(head -1 "$check") - checkdesc=$(tail -n +3 "$check") - - if [ -n "$checkdesc" ]; then - echo "|[$checkname](#$checkname)|$checktitle|" - else - echo "|$checkname|$checktitle|" - fi - done - echo "|||" - done - - echo - - for check in docs/checks/*; do - checkname=$(basename "$check") - checktitle=$(head -1 "$check") - checkdesc=$(tail -n +3 "$check") - - if [ -n "$checkdesc" ]; then - echo "### $checkname – $checktitle" - echo - echo "$checkdesc" - fi - done -} - -output=$(build) - -while IFS= read -r line; do - if [ "$line" = "[CHECKS PLACEHOLDER]" ]; then - echo "$output" - else - echo "$line" - fi -done < README.md.template diff --git a/tools/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go b/tools/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go index 46f496c48..5e6d6f9cc 100644 --- a/tools/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go +++ b/tools/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go @@ -15,5 +15,9 @@ func main() { fs.Parse(os.Args[1:]) c := staticcheck.NewChecker() c.CheckGenerated = *gen - lintutil.ProcessFlagSet(c, fs) + cfg := lintutil.CheckerConfig{ + Checker: c, + ExitNonZero: true, + } + lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs) } diff --git a/tools/vendor/honnef.co/go/tools/cmd/unused/main.go b/tools/vendor/honnef.co/go/tools/cmd/unused/main.go index 9d0abfb65..9698db50a 100644 --- a/tools/vendor/honnef.co/go/tools/cmd/unused/main.go +++ b/tools/vendor/honnef.co/go/tools/cmd/unused/main.go @@ -70,5 +70,9 @@ func main() { checker := newChecker(mode) l := unused.NewLintChecker(checker) - lintutil.ProcessFlagSet(l, fs) + cfg := lintutil.CheckerConfig{ + Checker: l, + ExitNonZero: true, + } + lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs) } diff --git a/tools/vendor/honnef.co/go/tools/deprecated/stdlib.go b/tools/vendor/honnef.co/go/tools/deprecated/stdlib.go new file mode 100644 index 000000000..b6b217c3e --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/deprecated/stdlib.go @@ -0,0 +1,54 @@ +package deprecated + +type Deprecation struct { + DeprecatedSince int + AlternativeAvailableSince int +} + +var Stdlib = map[string]Deprecation{ + "image/jpeg.Reader": {4, 0}, + // FIXME(dh): AllowBinary isn't being detected as deprecated + // because the comment has a newline right after "Deprecated:" + "go/build.AllowBinary": {7, 7}, + "(archive/zip.FileHeader).CompressedSize": {1, 1}, + "(archive/zip.FileHeader).UncompressedSize": {1, 1}, + "(go/doc.Package).Bugs": {1, 1}, + "os.SEEK_SET": {7, 7}, + "os.SEEK_CUR": {7, 7}, + "os.SEEK_END": {7, 7}, + "(net.Dialer).Cancel": {7, 7}, + "runtime.CPUProfile": {9, 0}, + "compress/flate.ReadError": {6, 6}, + "compress/flate.WriteError": {6, 6}, + "path/filepath.HasPrefix": {0, 0}, + "(net/http.Transport).Dial": {7, 7}, + "(*net/http.Transport).CancelRequest": {6, 5}, + "net/http.ErrWriteAfterFlush": {7, 0}, + "net/http.ErrHeaderTooLong": {8, 0}, + "net/http.ErrShortBody": {8, 0}, + "net/http.ErrMissingContentLength": {8, 0}, + "net/http/httputil.ErrPersistEOF": {0, 0}, + "net/http/httputil.ErrClosed": {0, 0}, + "net/http/httputil.ErrPipeline": {0, 0}, + "net/http/httputil.ServerConn": {0, 0}, + "net/http/httputil.NewServerConn": {0, 0}, + "net/http/httputil.ClientConn": {0, 0}, + "net/http/httputil.NewClientConn": {0, 0}, + "net/http/httputil.NewProxyClientConn": {0, 0}, + "(net/http.Request).Cancel": {7, 7}, + "(text/template/parse.PipeNode).Line": {1, 1}, + "(text/template/parse.ActionNode).Line": {1, 1}, + "(text/template/parse.BranchNode).Line": {1, 1}, + "(text/template/parse.TemplateNode).Line": {1, 1}, + "database/sql/driver.ColumnConverter": {9, 9}, + "database/sql/driver.Execer": {8, 8}, + "database/sql/driver.Queryer": {8, 8}, + "(database/sql/driver.Conn).Begin": {8, 8}, + "(database/sql/driver.Stmt).Exec": {8, 8}, + "(database/sql/driver.Stmt).Query": {8, 8}, + "syscall.StringByteSlice": {1, 1}, + "syscall.StringBytePtr": {1, 1}, + "syscall.StringSlicePtr": {1, 1}, + "syscall.StringToUTF16": {1, 1}, + "syscall.StringToUTF16Ptr": {1, 1}, +} diff --git a/tools/vendor/honnef.co/go/tools/functions/functions.go b/tools/vendor/honnef.co/go/tools/functions/functions.go index 477f8a5a3..c5fe2d729 100644 --- a/tools/vendor/honnef.co/go/tools/functions/functions.go +++ b/tools/vendor/honnef.co/go/tools/functions/functions.go @@ -11,6 +11,14 @@ import ( ) var stdlibDescs = map[string]Description{ + "errors.New": Description{Pure: true}, + + "fmt.Errorf": Description{Pure: true}, + "fmt.Sprintf": Description{Pure: true}, + "fmt.Sprint": Description{Pure: true}, + + "sort.Reverse": Description{Pure: true}, + "strings.Map": Description{Pure: true}, "strings.Repeat": Description{Pure: true}, "strings.Replace": Description{Pure: true}, @@ -40,6 +48,8 @@ var stdlibDescs = map[string]Description{ type Description struct { // The function is known to be pure Pure bool + // The function is known to be a stub + Stub bool // The function is known to never return (panics notwithstanding) Infinite bool // Variable ranges @@ -82,6 +92,7 @@ func (d *Descriptions) Get(fn *ssa.Function) Description { { fd.result = stdlibDescs[fn.RelString(nil)] fd.result.Pure = fd.result.Pure || d.IsPure(fn) + fd.result.Stub = fd.result.Stub || d.IsStub(fn) fd.result.Infinite = fd.result.Infinite || !terminates(fn) fd.result.Ranges = vrp.BuildGraph(fn).Solve() fd.result.Loops = findLoops(fn) diff --git a/tools/vendor/honnef.co/go/tools/functions/pure.go b/tools/vendor/honnef.co/go/tools/functions/pure.go index 5b99c479c..d1c4d036e 100644 --- a/tools/vendor/honnef.co/go/tools/functions/pure.go +++ b/tools/vendor/honnef.co/go/tools/functions/pure.go @@ -5,9 +5,41 @@ import ( "go/types" "honnef.co/go/tools/callgraph" + "honnef.co/go/tools/lint" "honnef.co/go/tools/ssa" ) +// IsStub reports whether a function is a stub. A function is +// considered a stub if it has no instructions or exactly one +// instruction, which must be either returning only constant values or +// a panic. +func (d *Descriptions) IsStub(fn *ssa.Function) bool { + if len(fn.Blocks) == 0 { + return true + } + if len(fn.Blocks) > 1 { + return false + } + instrs := lint.FilterDebug(fn.Blocks[0].Instrs) + if len(instrs) != 1 { + return false + } + + switch instrs[0].(type) { + case *ssa.Return: + // Since this is the only instruction, the return value must + // be a constant. We consider all constants as stubs, not just + // the zero value. This does not, unfortunately, cover zero + // initialised structs, as these cause additional + // instructions. + return true + case *ssa.Panic: + return true + default: + return false + } +} + func (d *Descriptions) IsPure(fn *ssa.Function) bool { if fn.Signature.Results().Len() == 0 { // A function with no return values is empty or is doing some diff --git a/tools/vendor/honnef.co/go/tools/gcsizes/sizes.go b/tools/vendor/honnef.co/go/tools/gcsizes/sizes.go deleted file mode 100644 index 4857418c9..000000000 --- a/tools/vendor/honnef.co/go/tools/gcsizes/sizes.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package gcsizes provides a types.Sizes implementation that adheres -// to the rules used by the gc compiler. -package gcsizes // import "honnef.co/go/tools/gcsizes" - -import ( - "go/build" - "go/types" -) - -type Sizes struct { - WordSize int64 - MaxAlign int64 -} - -// ForArch returns a correct Sizes for the given architecture. -func ForArch(arch string) *Sizes { - wordSize := int64(8) - maxAlign := int64(8) - switch build.Default.GOARCH { - case "386", "arm": - wordSize, maxAlign = 4, 4 - case "amd64p32": - wordSize = 4 - } - return &Sizes{WordSize: wordSize, MaxAlign: maxAlign} -} - -func (s *Sizes) Alignof(T types.Type) int64 { - switch t := T.Underlying().(type) { - case *types.Array: - return s.Alignof(t.Elem()) - case *types.Struct: - max := int64(1) - n := t.NumFields() - var fields []*types.Var - for i := 0; i < n; i++ { - fields = append(fields, t.Field(i)) - } - for _, f := range fields { - if a := s.Alignof(f.Type()); a > max { - max = a - } - } - return max - } - a := s.Sizeof(T) // may be 0 - if a < 1 { - return 1 - } - if a > s.MaxAlign { - return s.MaxAlign - } - return a -} - -func (s *Sizes) Offsetsof(fields []*types.Var) []int64 { - offsets := make([]int64, len(fields)) - var o int64 - for i, f := range fields { - a := s.Alignof(f.Type()) - o = align(o, a) - offsets[i] = o - o += s.Sizeof(f.Type()) - } - return offsets -} - -var basicSizes = [...]byte{ - types.Bool: 1, - types.Int8: 1, - types.Int16: 2, - types.Int32: 4, - types.Int64: 8, - types.Uint8: 1, - types.Uint16: 2, - types.Uint32: 4, - types.Uint64: 8, - types.Float32: 4, - types.Float64: 8, - types.Complex64: 8, - types.Complex128: 16, -} - -func (s *Sizes) Sizeof(T types.Type) int64 { - switch t := T.Underlying().(type) { - case *types.Basic: - k := t.Kind() - if int(k) < len(basicSizes) { - if s := basicSizes[k]; s > 0 { - return int64(s) - } - } - if k == types.String { - return s.WordSize * 2 - } - case *types.Array: - n := t.Len() - if n == 0 { - return 0 - } - a := s.Alignof(t.Elem()) - z := s.Sizeof(t.Elem()) - return align(z, a)*(n-1) + z - case *types.Slice: - return s.WordSize * 3 - case *types.Struct: - n := t.NumFields() - if n == 0 { - return 0 - } - - var fields []*types.Var - for i := 0; i < n; i++ { - fields = append(fields, t.Field(i)) - } - offsets := s.Offsetsof(fields) - a := s.Alignof(T) - lsz := s.Sizeof(fields[n-1].Type()) - if lsz == 0 { - lsz = 1 - } - z := offsets[n-1] + lsz - return align(z, a) - case *types.Interface: - return s.WordSize * 2 - } - return s.WordSize // catch-all -} - -// align returns the smallest y >= x such that y % a == 0. -func align(x, a int64) int64 { - y := x + a - 1 - return y - y%a -} diff --git a/tools/vendor/honnef.co/go/tools/lint/lint.go b/tools/vendor/honnef.co/go/tools/lint/lint.go index db1f6cddf..9e1582b89 100644 --- a/tools/vendor/honnef.co/go/tools/lint/lint.go +++ b/tools/vendor/honnef.co/go/tools/lint/lint.go @@ -11,6 +11,7 @@ import ( "bytes" "fmt" "go/ast" + "go/build" "go/constant" "go/printer" "go/token" @@ -20,6 +21,7 @@ import ( "sort" "strings" "sync" + "unicode" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/loader" @@ -30,15 +32,85 @@ import ( type Job struct { Program *Program + checker string check string problems []Problem } -type Ignore struct { +type Ignore interface { + Match(p Problem) bool +} + +type LineIgnore struct { + File string + Line int + Checks []string + matched bool + pos token.Pos +} + +func (li *LineIgnore) Match(p Problem) bool { + if p.Position.Filename != li.File || p.Position.Line != li.Line { + return false + } + for _, c := range li.Checks { + if m, _ := filepath.Match(c, p.Check); m { + li.matched = true + return true + } + } + return false +} + +func (li *LineIgnore) String() string { + matched := "not matched" + if li.matched { + matched = "matched" + } + return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched) +} + +type FileIgnore struct { + File string + Checks []string +} + +func (fi *FileIgnore) Match(p Problem) bool { + if p.Position.Filename != fi.File { + return false + } + for _, c := range fi.Checks { + if m, _ := filepath.Match(c, p.Check); m { + return true + } + } + return false +} + +type GlobIgnore struct { Pattern string Checks []string } +func (gi *GlobIgnore) Match(p Problem) bool { + if gi.Pattern != "*" { + pkgpath := p.Package.Path() + if strings.HasSuffix(pkgpath, "_test") { + pkgpath = pkgpath[:len(pkgpath)-len("_test")] + } + name := filepath.Join(pkgpath, filepath.Base(p.Position.Filename)) + if m, _ := filepath.Match(gi.Pattern, name); !m { + return false + } + } + for _, c := range gi.Checks { + if m, _ := filepath.Match(c, p.Check); m { + return true + } + } + return false +} + type Program struct { SSA *ssa.Program Prog *loader.Program @@ -58,51 +130,70 @@ type Func func(*Job) // Problem represents a problem in some source code. type Problem struct { - Position token.Pos // position in source file - Text string // the prose that describes the problem + pos token.Pos + Position token.Position // position in source file + Text string // the prose that describes the problem + Check string + Checker string + Package *types.Package + Ignored bool } func (p *Problem) String() string { - return p.Text + if p.Check == "" { + return p.Text + } + return fmt.Sprintf("%s (%s)", p.Text, p.Check) } type Checker interface { + Name() string + Prefix() string Init(*Program) Funcs() map[string]Func } // A Linter lints Go source code. type Linter struct { - Checker Checker - Ignores []Ignore - GoVersion int -} + Checker Checker + Ignores []Ignore + GoVersion int + ReturnIgnored bool -func (l *Linter) ignore(j *Job, p Problem) bool { - tf := j.Program.SSA.Fset.File(p.Position) - f := j.Program.tokenFileMap[tf] - pkg := j.Program.astFileMap[f].Pkg + automaticIgnores []Ignore +} - for _, ig := range l.Ignores { - pkgpath := pkg.Path() - if strings.HasSuffix(pkgpath, "_test") { - pkgpath = pkgpath[:len(pkgpath)-len("_test")] +func (l *Linter) ignore(p Problem) bool { + ignored := false + for _, ig := range l.automaticIgnores { + // We cannot short-circuit these, as we want to record, for + // each ignore, whether it matched or not. + if ig.Match(p) { + ignored = true } - name := filepath.Join(pkgpath, filepath.Base(tf.Name())) - if m, _ := filepath.Match(ig.Pattern, name); !m { - continue - } - for _, c := range ig.Checks { - if m, _ := filepath.Match(c, j.check); m { - return true - } + } + if ignored { + // no need to execute other ignores if we've already had a + // match. + return true + } + for _, ig := range l.Ignores { + // We can short-circuit here, as we aren't tracking any + // information. + if ig.Match(p) { + return true } } + return false } +func (prog *Program) File(node Positioner) *ast.File { + return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())] +} + func (j *Job) File(node Positioner) *ast.File { - return j.Program.tokenFileMap[j.Program.SSA.Fset.File(node.Pos())] + return j.Program.File(node) } // TODO(dh): switch to sort.Slice when Go 1.9 lands. @@ -116,7 +207,7 @@ func (ps byPosition) Len() int { } func (ps byPosition) Less(i int, j int) bool { - pi, pj := ps.fset.Position(ps.ps[i].Position), ps.fset.Position(ps.ps[j].Position) + pi, pj := ps.ps[i].Position, ps.ps[j].Position if pi.Filename != pj.Filename { return pi.Filename < pj.Filename @@ -135,16 +226,40 @@ func (ps byPosition) Swap(i int, j int) { ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i] } -func (l *Linter) Lint(lprog *loader.Program) []Problem { +func parseDirective(s string) (cmd string, args []string) { + if !strings.HasPrefix(s, "//lint:") { + return "", nil + } + s = strings.TrimPrefix(s, "//lint:") + fields := strings.Split(s, " ") + return fields[0], fields[1:] +} + +func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem { ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ssaprog.Build() pkgMap := map[*ssa.Package]*Pkg{} var pkgs []*Pkg for _, pkginfo := range lprog.InitialPackages() { ssapkg := ssaprog.Package(pkginfo.Pkg) + var bp *build.Package + if len(pkginfo.Files) != 0 { + path := lprog.Fset.Position(pkginfo.Files[0].Pos()).Filename + dir := filepath.Dir(path) + var err error + ctx := conf.Build + if ctx == nil { + ctx = &build.Default + } + bp, err = ctx.ImportDir(dir, 0) + if err != nil { + // shouldn't happen + } + } pkg := &Pkg{ - Package: ssapkg, - Info: pkginfo, + Package: ssapkg, + Info: pkginfo, + BuildPkg: bp, } pkgMap[ssapkg] = pkg pkgs = append(pkgs, pkg) @@ -158,6 +273,7 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem { tokenFileMap: map[*token.File]*ast.File{}, astFileMap: map[*ast.File]*Pkg{}, } + initial := map[*types.Package]struct{}{} for _, pkg := range pkgs { initial[pkg.Info.Pkg] = struct{}{} @@ -176,9 +292,69 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem { ssapkg := ssaprog.Package(pkg.Info.Pkg) for _, f := range pkg.Info.Files { + prog.astFileMap[f] = pkgMap[ssapkg] + } + } + + for _, pkginfo := range lprog.AllPackages { + for _, f := range pkginfo.Files { tf := lprog.Fset.File(f.Pos()) prog.tokenFileMap[tf] = f - prog.astFileMap[f] = pkgMap[ssapkg] + } + } + + var out []Problem + l.automaticIgnores = nil + for _, pkginfo := range lprog.InitialPackages() { + for _, f := range pkginfo.Files { + cm := ast.NewCommentMap(lprog.Fset, f, f.Comments) + for node, cgs := range cm { + for _, cg := range cgs { + for _, c := range cg.List { + if !strings.HasPrefix(c.Text, "//lint:") { + continue + } + cmd, args := parseDirective(c.Text) + switch cmd { + case "ignore", "file-ignore": + if len(args) < 2 { + // FIXME(dh): this causes duplicated warnings when using megacheck + p := Problem{ + pos: c.Pos(), + Position: prog.DisplayPosition(c.Pos()), + Text: "malformed linter directive; missing the required reason field?", + Check: "", + Checker: l.Checker.Name(), + Package: nil, + } + out = append(out, p) + continue + } + default: + // unknown directive, ignore + continue + } + checks := strings.Split(args[0], ",") + pos := prog.DisplayPosition(node.Pos()) + var ig Ignore + switch cmd { + case "ignore": + ig = &LineIgnore{ + File: pos.Filename, + Line: pos.Line, + Checks: checks, + pos: c.Pos(), + } + case "file-ignore": + ig = &FileIgnore{ + File: pos.Filename, + Checks: checks, + } + } + l.automaticIgnores = append(l.automaticIgnores, ig) + } + } + } } } @@ -237,6 +413,7 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem { for _, k := range keys { j := &Job{ Program: prog, + checker: l.Checker.Name(), check: k, } jobs = append(jobs, j) @@ -255,15 +432,47 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem { } wg.Wait() - var out []Problem for _, j := range jobs { for _, p := range j.problems { - if !l.ignore(j, p) { + p.Ignored = l.ignore(p) + if l.ReturnIgnored || !p.Ignored { out = append(out, p) } } } + for _, ig := range l.automaticIgnores { + ig, ok := ig.(*LineIgnore) + if !ok { + continue + } + if ig.matched { + continue + } + for _, c := range ig.Checks { + idx := strings.IndexFunc(c, func(r rune) bool { + return unicode.IsNumber(r) + }) + if idx == -1 { + // malformed check name, backing out + continue + } + if c[:idx] != l.Checker.Prefix() { + // not for this checker + continue + } + p := Problem{ + pos: ig.pos, + Position: prog.DisplayPosition(ig.pos), + Text: "this linter directive didn't match anything; should it be removed?", + Check: "", + Checker: l.Checker.Name(), + Package: nil, + } + out = append(out, p) + } + } + sort.Sort(byPosition{lprog.Fset, out}) return out } @@ -271,7 +480,8 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem { // Pkg represents a package being linted. type Pkg struct { *ssa.Package - Info *loader.PackageInfo + Info *loader.PackageInfo + BuildPkg *build.Package } type packager interface { @@ -309,10 +519,55 @@ type Positioner interface { Pos() token.Pos } +func (prog *Program) DisplayPosition(p token.Pos) token.Position { + // The //line compiler directive can be used to change the file + // name and line numbers associated with code. This can, for + // example, be used by code generation tools. The most prominent + // example is 'go tool cgo', which uses //line directives to refer + // back to the original source code. + // + // In the context of our linters, we need to treat these + // directives differently depending on context. For cgo files, we + // want to honour the directives, so that line numbers are + // adjusted correctly. For all other files, we want to ignore the + // directives, so that problems are reported at their actual + // position and not, for example, a yacc grammar file. This also + // affects the ignore mechanism, since it operates on the position + // information stored within problems. With this implementation, a + // user will ignore foo.go, not foo.y + + pkg := prog.astFileMap[prog.tokenFileMap[prog.Prog.Fset.File(p)]] + bp := pkg.BuildPkg + adjPos := prog.Prog.Fset.Position(p) + if bp == nil { + // couldn't find the package for some reason (deleted? faulty + // file system?) + return adjPos + } + base := filepath.Base(adjPos.Filename) + for _, f := range bp.CgoFiles { + if f == base { + // this is a cgo file, use the adjusted position + return adjPos + } + } + // not a cgo file, ignore //line directives + return prog.Prog.Fset.PositionFor(p, false) +} + func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem { + tf := j.Program.SSA.Fset.File(n.Pos()) + f := j.Program.tokenFileMap[tf] + pkg := j.Program.astFileMap[f].Pkg + + pos := j.Program.DisplayPosition(n.Pos()) problem := Problem{ - Position: n.Pos(), - Text: fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", j.check), + pos: n.Pos(), + Position: pos, + Text: fmt.Sprintf(format, args...), + Check: j.check, + Checker: j.checker, + Package: pkg, } j.problems = append(j.problems, problem) return &j.problems[len(j.problems)-1] @@ -422,6 +677,31 @@ func IsGenerated(f *ast.File) bool { return false } +func Preamble(f *ast.File) string { + cutoff := f.Package + if f.Doc != nil { + cutoff = f.Doc.Pos() + } + var out []string + for _, cmt := range f.Comments { + if cmt.Pos() >= cutoff { + break + } + out = append(out, cmt.Text()) + } + return strings.Join(out, "\n") +} + +func IsPointerLike(T types.Type) bool { + switch T := T.Underlying().(type) { + case *types.Interface, *types.Chan, *types.Map, *types.Pointer: + return true + case *types.Basic: + return T.Kind() == types.UnsafePointer + } + return false +} + func (j *Job) IsGoVersion(minor int) bool { return j.Program.GoVersion >= minor } @@ -448,6 +728,22 @@ func (j *Job) IsCallToAnyAST(node ast.Node, names ...string) bool { return false } +func (j *Job) SelectorName(expr *ast.SelectorExpr) string { + sel := j.Program.Info.Selections[expr] + if sel == nil { + if x, ok := expr.X.(*ast.Ident); ok { + pkg, ok := j.Program.Info.ObjectOf(x).(*types.PkgName) + if !ok { + // This shouldn't happen + return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name) + } + return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name) + } + panic(fmt.Sprintf("unsupported selector: %v", expr)) + } + return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) +} + func CallName(call *ssa.CallCommon) string { if call.IsInvoke() { return "" diff --git a/tools/vendor/honnef.co/go/tools/lint/lintutil/util.go b/tools/vendor/honnef.co/go/tools/lint/lintutil/util.go index 53b9f83a1..0bb142668 100644 --- a/tools/vendor/honnef.co/go/tools/lint/lintutil/util.go +++ b/tools/vendor/honnef.co/go/tools/lint/lintutil/util.go @@ -8,23 +8,70 @@ package lintutil // import "honnef.co/go/tools/lint/lintutil" import ( + "encoding/json" "errors" "flag" "fmt" "go/build" "go/parser" "go/token" + "go/types" + "io" "os" "path/filepath" "strconv" "strings" "honnef.co/go/tools/lint" + "honnef.co/go/tools/version" "github.com/kisielk/gotool" "golang.org/x/tools/go/loader" ) +type OutputFormatter interface { + Format(p lint.Problem) +} + +type TextOutput struct { + w io.Writer +} + +func (o TextOutput) Format(p lint.Problem) { + fmt.Fprintf(o.w, "%v: %s\n", relativePositionString(p.Position), p.String()) +} + +type JSONOutput struct { + w io.Writer +} + +func (o JSONOutput) Format(p lint.Problem) { + type location struct { + File string `json:"file"` + Line int `json:"line"` + Column int `json:"column"` + } + jp := struct { + Checker string `json:"checker"` + Code string `json:"code"` + Severity string `json:"severity,omitempty"` + Location location `json:"location"` + Message string `json:"message"` + Ignored bool `json:"ignored"` + }{ + p.Checker, + p.Check, + "", // TODO(dh): support severity + location{ + p.Position.Filename, + p.Position.Line, + p.Position.Column, + }, + p.Text, + p.Ignored, + } + _ = json.NewEncoder(o.w).Encode(jp) +} func usage(name string, flags *flag.FlagSet) func() { return func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", name) @@ -38,13 +85,14 @@ func usage(name string, flags *flag.FlagSet) func() { } type runner struct { - checker lint.Checker - tags []string - ignores []lint.Ignore - version int + checker lint.Checker + tags []string + ignores []lint.Ignore + version int + returnIgnored bool } -func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err error) { +func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) { if len(importPaths) == 0 { return false, nil } @@ -57,7 +105,7 @@ func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err er return false, err } ctx := build.Default - ctx.BuildTags = runner.tags + ctx.BuildTags = tags for i, path := range importPaths { bpkg, err := ctx.Import(path, wd, build.FindOnly) if err != nil { @@ -80,7 +128,7 @@ func parseIgnore(s string) ([]lint.Ignore, error) { } path := p[0] checks := strings.Split(p[1], ",") - out = append(out, lint.Ignore{Pattern: path, Checks: checks}) + out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks}) } return out, nil } @@ -117,6 +165,9 @@ func FlagSet(name string) *flag.FlagSet { flags.String("tags", "", "List of `build tags`") flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'") flags.Bool("tests", true, "Include tests") + flags.Bool("version", false, "Print version and exit") + flags.Bool("show-ignored", false, "Don't filter ignored problems") + flags.String("f", "text", "Output `format` (valid choices are 'text' and 'json')") tags := build.Default.ReleaseTags v := tags[len(tags)-1][2:] @@ -129,67 +180,105 @@ func FlagSet(name string) *flag.FlagSet { return flags } -func ProcessFlagSet(c lint.Checker, fs *flag.FlagSet) { +type CheckerConfig struct { + Checker lint.Checker + ExitNonZero bool +} + +func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet) { tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string) ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string) tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool) - version := fs.Lookup("go").Value.(flag.Getter).Get().(int) + goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int) + format := fs.Lookup("f").Value.(flag.Getter).Get().(string) + printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool) + showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool) - ps, lprog, err := Lint(c, fs.Args(), &Options{ - Tags: strings.Fields(tags), - LintTests: tests, - Ignores: ignore, - GoVersion: version, + if printVersion { + version.Print() + os.Exit(0) + } + + var cs []lint.Checker + for _, conf := range confs { + cs = append(cs, conf.Checker) + } + pss, err := Lint(cs, fs.Args(), &Options{ + Tags: strings.Fields(tags), + LintTests: tests, + Ignores: ignore, + GoVersion: goVersion, + ReturnIgnored: showIgnored, }) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - unclean := false + + var ps []lint.Problem + for _, p := range pss { + ps = append(ps, p...) + } + + var f OutputFormatter + switch format { + case "text": + f = TextOutput{os.Stdout} + case "json": + f = JSONOutput{os.Stdout} + default: + fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format) + os.Exit(2) + } + for _, p := range ps { - unclean = true - pos := lprog.Fset.Position(p.Position) - fmt.Printf("%v: %s\n", relativePositionString(pos), p.Text) + f.Format(p) } - if unclean { - os.Exit(1) + for i, p := range pss { + if len(p) != 0 && confs[i].ExitNonZero { + os.Exit(1) + } } } type Options struct { - Tags []string - LintTests bool - Ignores string - GoVersion int + Tags []string + LintTests bool + Ignores string + GoVersion int + ReturnIgnored bool } -func Lint(c lint.Checker, pkgs []string, opt *Options) ([]lint.Problem, *loader.Program, error) { - // TODO(dh): Instead of returning the loader.Program, we should - // store token.Position instead of token.Pos in lint.Problem. +func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) { if opt == nil { opt = &Options{} } ignores, err := parseIgnore(opt.Ignores) if err != nil { - return nil, nil, err - } - runner := &runner{ - checker: c, - tags: opt.Tags, - ignores: ignores, - version: opt.GoVersion, + return nil, err } paths := gotool.ImportPaths(pkgs) - goFiles, err := runner.resolveRelative(paths) + goFiles, err := resolveRelative(paths, opt.Tags) if err != nil { - return nil, nil, err + return nil, err } ctx := build.Default - ctx.BuildTags = runner.tags + ctx.BuildTags = opt.Tags + hadError := false conf := &loader.Config{ Build: &ctx, ParserMode: parser.ParseComments, ImportPkgs: map[string]bool{}, + TypeChecker: types.Config{ + Error: func(err error) { + // Only print the first error found + if hadError { + return + } + hadError = true + fmt.Fprintln(os.Stderr, err) + }, + }, } if goFiles { conf.CreateFromFilenames("adhoc", paths...) @@ -200,9 +289,21 @@ func Lint(c lint.Checker, pkgs []string, opt *Options) ([]lint.Problem, *loader. } lprog, err := conf.Load() if err != nil { - return nil, nil, err + return nil, err + } + + var problems [][]lint.Problem + for _, c := range cs { + runner := &runner{ + checker: c, + tags: opt.Tags, + ignores: ignores, + version: opt.GoVersion, + returnIgnored: opt.ReturnIgnored, + } + problems = append(problems, runner.lint(lprog, conf)) } - return runner.lint(lprog), lprog, nil + return problems, nil } func shortPath(path string) string { @@ -230,18 +331,19 @@ func relativePositionString(pos token.Position) string { return s } -func ProcessArgs(name string, c lint.Checker, args []string) { +func ProcessArgs(name string, cs []CheckerConfig, args []string) { flags := FlagSet(name) flags.Parse(args) - ProcessFlagSet(c, flags) + ProcessFlagSet(cs, flags) } -func (runner *runner) lint(lprog *loader.Program) []lint.Problem { +func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem { l := &lint.Linter{ - Checker: runner.checker, - Ignores: runner.ignores, - GoVersion: runner.version, + Checker: runner.checker, + Ignores: runner.ignores, + GoVersion: runner.version, + ReturnIgnored: runner.returnIgnored, } - return l.Lint(lprog) + return l.Lint(lprog, conf) } diff --git a/tools/vendor/honnef.co/go/tools/simple/lint.go b/tools/vendor/honnef.co/go/tools/simple/lint.go index 9ea35d929..b89de14ae 100644 --- a/tools/vendor/honnef.co/go/tools/simple/lint.go +++ b/tools/vendor/honnef.co/go/tools/simple/lint.go @@ -6,9 +6,7 @@ import ( "go/constant" "go/token" "go/types" - "math" "reflect" - "strconv" "strings" "honnef.co/go/tools/internal/sharedcheck" @@ -31,6 +29,9 @@ func NewChecker() *Checker { } } +func (*Checker) Name() string { return "gosimple" } +func (*Checker) Prefix() string { return "S" } + func (c *Checker) Init(prog *lint.Program) { c.nodeFns = lint.NodeFns(prog.Packages) } @@ -42,7 +43,7 @@ func (c *Checker) Funcs() map[string]lint.Func { "S1002": c.LintIfBoolCmp, "S1003": c.LintStringsContains, "S1004": c.LintBytesCompare, - "S1005": c.LintRanges, + "S1005": c.LintUnnecessaryBlank, "S1006": c.LintForTrue, "S1007": c.LintRegexpRaw, "S1008": c.LintIfReturn, @@ -51,22 +52,25 @@ func (c *Checker) Funcs() map[string]lint.Func { "S1011": c.LintLoopAppend, "S1012": c.LintTimeSince, "S1013": c.LintSimplerReturn, - "S1014": c.LintReceiveIntoBlank, - "S1015": c.LintFormatInt, + "S1014": nil, + "S1015": nil, "S1016": c.LintSimplerStructConversion, "S1017": c.LintTrim, "S1018": c.LintLoopSlide, "S1019": c.LintMakeLenCap, "S1020": c.LintAssertNotNil, "S1021": c.LintDeclareAssign, - "S1022": c.LintBlankOK, + "S1022": nil, "S1023": c.LintRedundantBreak, "S1024": c.LintTimeUntil, "S1025": c.LintRedundantSprintf, - "S1026": c.LintStringCopy, - "S1027": c.LintRedundantReturn, + "S1026": nil, + "S1027": nil, "S1028": c.LintErrorsNewSprintf, "S1029": c.LintRangeStringRunes, + "S1030": c.LintBytesBufferConversions, + "S1031": c.LintNilCheckAroundRange, + "S1032": c.LintSortHelpers, } } @@ -247,6 +251,36 @@ func (c *Checker) LintIfBoolCmp(j *lint.Job) { } } +func (c *Checker) LintBytesBufferConversions(j *lint.Job) { + fn := func(node ast.Node) bool { + call, ok := node.(*ast.CallExpr) + if !ok || len(call.Args) != 1 { + return true + } + + argCall, ok := call.Args[0].(*ast.CallExpr) + if !ok { + return true + } + sel, ok := argCall.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + + typ := j.Program.Info.TypeOf(call.Fun) + if typ == types.Universe.Lookup("string").Type() && j.IsCallToAST(call.Args[0], "(*bytes.Buffer).Bytes") { + j.Errorf(call, "should use %v.String() instead of %v", j.Render(sel.X), j.Render(call)) + } else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && j.IsCallToAST(call.Args[0], "(*bytes.Buffer).String") { + j.Errorf(call, "should use %v.Bytes() instead of %v", j.Render(sel.X), j.Render(call)) + } + + return true + } + for _, f := range c.filterGenerated(j.Program.Files) { + ast.Inspect(f, fn) + } +} + func (c *Checker) LintStringsContains(j *lint.Job) { // map of value to token to bool value allowed := map[int64]map[token.Token]bool{ @@ -352,23 +386,6 @@ func (c *Checker) LintBytesCompare(j *lint.Job) { } } -func (c *Checker) LintRanges(j *lint.Job) { - fn := func(node ast.Node) bool { - rs, ok := node.(*ast.RangeStmt) - if !ok { - return true - } - if lint.IsBlank(rs.Key) && (rs.Value == nil || lint.IsBlank(rs.Value)) { - j.Errorf(rs.Key, "should omit values from range; this loop is equivalent to `for range ...`") - } - - return true - } - for _, f := range c.filterGenerated(j.Program.Files) { - ast.Inspect(f, fn) - } -} - func (c *Checker) LintForTrue(j *lint.Job) { fn := func(node ast.Node) bool { loop, ok := node.(*ast.ForStmt) @@ -941,14 +958,44 @@ func (c *Checker) LintSimplerReturn(j *lint.Job) { } } -func (c *Checker) LintReceiveIntoBlank(j *lint.Job) { - fn := func(node ast.Node) bool { +func (c *Checker) LintUnnecessaryBlank(j *lint.Job) { + fn1 := func(node ast.Node) { + assign, ok := node.(*ast.AssignStmt) + if !ok { + return + } + if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 { + return + } + if !lint.IsBlank(assign.Lhs[1]) { + return + } + switch rhs := assign.Rhs[0].(type) { + case *ast.IndexExpr: + // The type-checker should make sure that it's a map, but + // let's be safe. + if _, ok := j.Program.Info.TypeOf(rhs.X).Underlying().(*types.Map); !ok { + return + } + case *ast.UnaryExpr: + if rhs.Op != token.ARROW { + return + } + default: + return + } + cp := *assign + cp.Lhs = cp.Lhs[0:1] + j.Errorf(assign, "should write %s instead of %s", j.Render(&cp), j.Render(assign)) + } + + fn2 := func(node ast.Node) { stmt, ok := node.(*ast.AssignStmt) if !ok { - return true + return } if len(stmt.Lhs) != len(stmt.Rhs) { - return true + return } for i, lh := range stmt.Lhs { rh := stmt.Rhs[i] @@ -964,100 +1011,23 @@ func (c *Checker) LintReceiveIntoBlank(j *lint.Job) { } j.Errorf(lh, "'_ = <-ch' can be simplified to '<-ch'") } - return true } - for _, f := range c.filterGenerated(j.Program.Files) { - ast.Inspect(f, fn) - } -} -func (c *Checker) LintFormatInt(j *lint.Job) { - checkBasic := func(v ast.Expr) bool { - typ, ok := j.Program.Info.TypeOf(v).(*types.Basic) - if !ok { - return false - } - return typ.Kind() == types.Int - } - checkConst := func(v *ast.Ident) bool { - c, ok := j.Program.Info.ObjectOf(v).(*types.Const) + fn3 := func(node ast.Node) { + rs, ok := node.(*ast.RangeStmt) if !ok { - return false + return } - if c.Val().Kind() != constant.Int { - return false - } - i, _ := constant.Int64Val(c.Val()) - return i <= math.MaxInt32 - } - checkConstStrict := func(v *ast.Ident) bool { - if !checkConst(v) { - return false + if lint.IsBlank(rs.Key) && (rs.Value == nil || lint.IsBlank(rs.Value)) { + j.Errorf(rs.Key, "should omit values from range; this loop is equivalent to `for range ...`") } - basic, ok := j.Program.Info.ObjectOf(v).(*types.Const).Type().(*types.Basic) - return ok && basic.Kind() == types.UntypedInt } fn := func(node ast.Node) bool { - call, ok := node.(*ast.CallExpr) - if !ok { - return true - } - if !j.IsCallToAST(call, "strconv.FormatInt") { - return true - } - if len(call.Args) != 2 { - return true - } - if lit, ok := call.Args[1].(*ast.BasicLit); !ok || lit.Value != "10" { - return true - } - - matches := false - switch v := call.Args[0].(type) { - case *ast.CallExpr: - if len(v.Args) != 1 { - return true - } - ident, ok := v.Fun.(*ast.Ident) - if !ok { - return true - } - obj, ok := j.Program.Info.ObjectOf(ident).(*types.TypeName) - if !ok || obj.Parent() != types.Universe || obj.Name() != "int64" { - return true - } - - switch vv := v.Args[0].(type) { - case *ast.BasicLit: - i, _ := strconv.ParseInt(vv.Value, 10, 64) - if i <= math.MaxInt32 { - matches = true - } - case *ast.Ident: - if checkConst(vv) || checkBasic(v.Args[0]) { - matches = true - } - default: - if checkBasic(v.Args[0]) { - matches = true - } - } - case *ast.BasicLit: - if v.Kind != token.INT { - return true - } - i, _ := strconv.ParseInt(v.Value, 10, 64) - if i <= math.MaxInt32 { - matches = true - } - case *ast.Ident: - if checkConstStrict(v) { - matches = true - } - } - if matches { - j.Errorf(call, "should use strconv.Itoa instead of strconv.FormatInt") + fn1(node) + fn2(node) + if j.IsGoVersion(4) { + fn3(node) } return true } @@ -1067,23 +1037,34 @@ func (c *Checker) LintFormatInt(j *lint.Job) { } func (c *Checker) LintSimplerStructConversion(j *lint.Job) { + var skip ast.Node fn := func(node ast.Node) bool { + // Do not suggest type conversion between pointers + if unary, ok := node.(*ast.UnaryExpr); ok && unary.Op == token.AND { + if lit, ok := unary.X.(*ast.CompositeLit); ok { + skip = lit + } + return true + } + + if node == skip { + return true + } + lit, ok := node.(*ast.CompositeLit) if !ok { return true } - typ1 := j.Program.Info.TypeOf(lit.Type) + typ1, _ := j.Program.Info.TypeOf(lit.Type).(*types.Named) if typ1 == nil { return true } - // FIXME support pointer to struct s1, ok := typ1.Underlying().(*types.Struct) if !ok { return true } - n := s1.NumFields() - var typ2 types.Type + var typ2 *types.Named var ident *ast.Ident getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) { sel, ok := expr.(*ast.SelectorExpr) @@ -1100,8 +1081,10 @@ func (c *Checker) LintSimplerStructConversion(j *lint.Job) { if len(lit.Elts) == 0 { return true } + if s1.NumFields() != len(lit.Elts) { + return true + } for i, elt := range lit.Elts { - n-- var t types.Type var id *ast.Ident var ok bool @@ -1129,21 +1112,27 @@ func (c *Checker) LintSimplerStructConversion(j *lint.Job) { if !ok { return true } - if typ2 != nil && typ2 != t { + // All fields must be initialized from the same object + if ident != nil && ident.Obj != id.Obj { return true } - if ident != nil && ident.Obj != id.Obj { + typ2, _ = t.(*types.Named) + if typ2 == nil { return true } - typ2 = t ident = id } - if n != 0 { + if typ2 == nil { return true } - if typ2 == nil { + if typ1.Obj().Pkg() != typ2.Obj().Pkg() { + // Do not suggest type conversions between different + // packages. Types in different packages might only match + // by coincidence. Furthermore, if the dependency ever + // adds more fields to its type, it could break the code + // that relies on the type conversion to work. return true } @@ -1157,7 +1146,8 @@ func (c *Checker) LintSimplerStructConversion(j *lint.Job) { if !structsIdentical(s1, s2) { return true } - j.Errorf(node, "should use type conversion instead of struct literal") + j.Errorf(node, "should convert %s (type %s) to %s instead of using struct literal", + ident.Name, typ2.Obj().Name(), typ1.Obj().Name()) return true } for _, f := range c.filterGenerated(j.Program.Files) { @@ -1598,56 +1588,52 @@ func (c *Checker) LintDeclareAssign(j *lint.Job) { } } -func (c *Checker) LintBlankOK(j *lint.Job) { - fn := func(node ast.Node) bool { - assign, ok := node.(*ast.AssignStmt) - if !ok { - return true - } - if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 { - return true - } - if !lint.IsBlank(assign.Lhs[1]) { - return true - } - switch rhs := assign.Rhs[0].(type) { - case *ast.IndexExpr: - // The type-checker should make sure that it's a map, but - // let's be safe. - if _, ok := j.Program.Info.TypeOf(rhs.X).Underlying().(*types.Map); !ok { - return true - } - case *ast.UnaryExpr: - if rhs.Op != token.ARROW { - return true - } - default: - return true - } - cp := *assign - cp.Lhs = cp.Lhs[0:1] - j.Errorf(assign, "should write %s instead of %s", j.Render(&cp), j.Render(assign)) - return true - } - for _, f := range c.filterGenerated(j.Program.Files) { - ast.Inspect(f, fn) - } -} - func (c *Checker) LintRedundantBreak(j *lint.Job) { - fn := func(node ast.Node) bool { + fn1 := func(node ast.Node) { clause, ok := node.(*ast.CaseClause) if !ok { - return true + return } if len(clause.Body) < 2 { - return true + return } branch, ok := clause.Body[len(clause.Body)-1].(*ast.BranchStmt) if !ok || branch.Tok != token.BREAK || branch.Label != nil { - return true + return } j.Errorf(branch, "redundant break statement") + return + } + fn2 := func(node ast.Node) { + var ret *ast.FieldList + var body *ast.BlockStmt + switch x := node.(type) { + case *ast.FuncDecl: + ret = x.Type.Results + body = x.Body + case *ast.FuncLit: + ret = x.Type.Results + body = x.Body + default: + return + } + // if the func has results, a return can't be redundant. + // similarly, if there are no statements, there can be + // no return. + if ret != nil || body == nil || len(body.List) < 1 { + return + } + rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt) + if !ok { + return + } + // we don't need to check rst.Results as we already + // checked x.Type.Results to be nil. + j.Errorf(rst, "redundant return statement") + } + fn := func(node ast.Node) bool { + fn1(node) + fn2(node) return true } for _, f := range c.filterGenerated(j.Program.Files) { @@ -1722,74 +1708,16 @@ func (c *Checker) LintRedundantSprintf(j *lint.Job) { } } -func (c *Checker) LintStringCopy(j *lint.Job) { - emptyStringLit := func(e ast.Expr) bool { - bl, ok := e.(*ast.BasicLit) - return ok && bl.Value == `""` - } +func (c *Checker) LintErrorsNewSprintf(j *lint.Job) { fn := func(node ast.Node) bool { - switch x := node.(type) { - case *ast.BinaryExpr: // "" + s, s + "" - if x.Op != token.ADD { - break - } - l1 := j.Program.Prog.Fset.Position(x.X.Pos()).Line - l2 := j.Program.Prog.Fset.Position(x.Y.Pos()).Line - if l1 != l2 { - break - } - var want ast.Expr - switch { - case emptyStringLit(x.X): - want = x.Y - case emptyStringLit(x.Y): - want = x.X - default: - return true - } - j.Errorf(x, "should use %s instead of %s", - j.Render(want), j.Render(x)) - case *ast.CallExpr: - if j.IsCallToAST(x, "fmt.Sprint") && len(x.Args) == 1 { - // fmt.Sprint(x) - - argT := j.Program.Info.TypeOf(x.Args[0]) - bt, ok := argT.Underlying().(*types.Basic) - if !ok || bt.Kind() != types.String { - return true - } - if c.Implements(j, argT, "fmt.Stringer") || c.Implements(j, argT, "error") { - return true - } - - j.Errorf(x, "should use %s instead of %s", j.Render(x.Args[0]), j.Render(x)) - return true - } - - // string([]byte(s)) - bt, ok := j.Program.Info.TypeOf(x.Fun).(*types.Basic) - if !ok || bt.Kind() != types.String { - break - } - nested, ok := x.Args[0].(*ast.CallExpr) - if !ok { - break - } - st, ok := j.Program.Info.TypeOf(nested.Fun).(*types.Slice) - if !ok { - break - } - et, ok := st.Elem().(*types.Basic) - if !ok || et.Kind() != types.Byte { - break - } - xt, ok := j.Program.Info.TypeOf(nested.Args[0]).(*types.Basic) - if !ok || xt.Kind() != types.String { - break - } - j.Errorf(x, "should use %s instead of %s", - j.Render(nested.Args[0]), j.Render(x)) + if !j.IsCallToAST(node, "errors.New") { + return true } + call := node.(*ast.CallExpr) + if !j.IsCallToAST(call.Args[0], "fmt.Sprintf") { + return true + } + j.Errorf(node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))") return true } for _, f := range c.filterGenerated(j.Program.Files) { @@ -1797,33 +1725,45 @@ func (c *Checker) LintStringCopy(j *lint.Job) { } } -func (c *Checker) LintRedundantReturn(j *lint.Job) { +func (c *Checker) LintRangeStringRunes(j *lint.Job) { + sharedcheck.CheckRangeStringRunes(c.nodeFns, j) +} + +func (c *Checker) LintNilCheckAroundRange(j *lint.Job) { fn := func(node ast.Node) bool { - var ret *ast.FieldList - var body *ast.BlockStmt - switch x := node.(type) { - case *ast.FuncDecl: - ret = x.Type.Results - body = x.Body - case *ast.FuncLit: - ret = x.Type.Results - body = x.Body - default: + ifstmt, ok := node.(*ast.IfStmt) + if !ok { return true } - // if the func has results, a return can't be redundant. - // similarly, if there are no statements, there can be - // no return. - if ret != nil || body == nil || len(body.List) < 1 { + + cond, ok := ifstmt.Cond.(*ast.BinaryExpr) + if !ok { return true } - rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt) + + if cond.Op != token.NEQ || !j.IsNil(cond.Y) || len(ifstmt.Body.List) != 1 { + return true + } + + loop, ok := ifstmt.Body.List[0].(*ast.RangeStmt) if !ok { return true } - // we don't need to check rst.Results as we already - // checked x.Type.Results to be nil. - j.Errorf(rst, "redundant return statement") + ifXIdent, ok := cond.X.(*ast.Ident) + if !ok { + return true + } + rangeXIdent, ok := loop.X.(*ast.Ident) + if !ok { + return true + } + if ifXIdent.Obj != rangeXIdent.Obj { + return true + } + switch j.Program.Info.TypeOf(rangeXIdent).(type) { + case *types.Slice, *types.Map: + j.Errorf(node, "unnecessary nil check around range") + } return true } for _, f := range c.filterGenerated(j.Program.Files) { @@ -1831,23 +1771,86 @@ func (c *Checker) LintRedundantReturn(j *lint.Job) { } } -func (c *Checker) LintErrorsNewSprintf(j *lint.Job) { - fn := func(node ast.Node) bool { - if !j.IsCallToAST(node, "errors.New") { +func isPermissibleSort(j *lint.Job, node ast.Node) bool { + call := node.(*ast.CallExpr) + typeconv, ok := call.Args[0].(*ast.CallExpr) + if !ok { + return true + } + + sel, ok := typeconv.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + name := j.SelectorName(sel) + switch name { + case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice": + default: + return true + } + + return false +} + +func (c *Checker) LintSortHelpers(j *lint.Job) { + fnFuncs := func(node ast.Node) bool { + var body *ast.BlockStmt + switch node := node.(type) { + case *ast.FuncLit: + body = node.Body + case *ast.FuncDecl: + body = node.Body + default: return true } - call := node.(*ast.CallExpr) - if !j.IsCallToAST(call.Args[0], "fmt.Sprintf") { + if body == nil { return true } - j.Errorf(node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))") - return true + + type Error struct { + node lint.Positioner + msg string + } + var errors []Error + permissible := false + fnSorts := func(node ast.Node) bool { + if permissible { + return false + } + if !j.IsCallToAST(node, "sort.Sort") { + return true + } + if isPermissibleSort(j, node) { + permissible = true + return false + } + call := node.(*ast.CallExpr) + typeconv := call.Args[0].(*ast.CallExpr) + sel := typeconv.Fun.(*ast.SelectorExpr) + name := j.SelectorName(sel) + + switch name { + case "sort.IntSlice": + errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"}) + case "sort.Float64Slice": + errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"}) + case "sort.StringSlice": + errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"}) + } + return true + } + ast.Inspect(body, fnSorts) + + if permissible { + return false + } + for _, err := range errors { + j.Errorf(err.node, "%s", err.msg) + } + return false } + for _, f := range c.filterGenerated(j.Program.Files) { - ast.Inspect(f, fn) + ast.Inspect(f, fnFuncs) } } - -func (c *Checker) LintRangeStringRunes(j *lint.Job) { - sharedcheck.CheckRangeStringRunes(c.nodeFns, j) -} diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/buildtag.go b/tools/vendor/honnef.co/go/tools/staticcheck/buildtag.go new file mode 100644 index 000000000..27c31c099 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/staticcheck/buildtag.go @@ -0,0 +1,21 @@ +package staticcheck + +import ( + "go/ast" + "strings" + + "honnef.co/go/tools/lint" +) + +func buildTags(f *ast.File) [][]string { + var out [][]string + for _, line := range strings.Split(lint.Preamble(f), "\n") { + if !strings.HasPrefix(line, "+build ") { + continue + } + line = strings.TrimSpace(strings.TrimPrefix(line, "+build ")) + fields := strings.Fields(line) + out = append(out, fields) + } + return out +} diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/lint.go b/tools/vendor/honnef.co/go/tools/staticcheck/lint.go index c9ab9a9cc..7fb7c72bd 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/lint.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/lint.go @@ -4,19 +4,20 @@ package staticcheck // import "honnef.co/go/tools/staticcheck" import ( "fmt" "go/ast" - "go/build" "go/constant" "go/token" "go/types" htmltemplate "html/template" "net/http" + "regexp" + "sort" "strconv" "strings" "sync" texttemplate "text/template" + "honnef.co/go/tools/deprecated" "honnef.co/go/tools/functions" - "honnef.co/go/tools/gcsizes" "honnef.co/go/tools/internal/sharedcheck" "honnef.co/go/tools/lint" "honnef.co/go/tools/ssa" @@ -110,14 +111,12 @@ var ( }, } - checkSyncPoolSizeRules = map[string]CallCheck{ + checkSyncPoolValueRules = map[string]CallCheck{ "(*sync.Pool).Put": func(call *Call) { - // TODO(dh): allow users to pass in a custom build environment - sizes := gcsizes.ForArch(build.Default.GOARCH) arg := call.Args[0] typ := arg.Value.Value.Type() - if !types.IsInterface(typ) && sizes.Sizeof(typ) > sizes.WordSize { - arg.Invalid("argument should be one word large or less to avoid allocations") + if !lint.IsPointerLike(typ) { + arg.Invalid("argument should be pointer-like to avoid allocations") } }, } @@ -149,10 +148,11 @@ var ( } checkUnmarshalPointerRules = map[string]CallCheck{ - "encoding/xml.Unmarshal": unmarshalPointer("xml.Unmarshal", 1), - "(*encoding/xml.Decoder).Decode": unmarshalPointer("Decode", 0), - "encoding/json.Unmarshal": unmarshalPointer("json.Unmarshal", 1), - "(*encoding/json.Decoder).Decode": unmarshalPointer("Decode", 0), + "encoding/xml.Unmarshal": unmarshalPointer("xml.Unmarshal", 1), + "(*encoding/xml.Decoder).Decode": unmarshalPointer("Decode", 0), + "(*encoding/xml.Decoder).DecodeElement": unmarshalPointer("DecodeElement", 0), + "encoding/json.Unmarshal": unmarshalPointer("json.Unmarshal", 1), + "(*encoding/json.Decoder).Decode": unmarshalPointer("Decode", 0), } checkUnbufferedSignalChanRules = map[string]CallCheck{ @@ -208,6 +208,9 @@ func NewChecker() *Checker { return &Checker{} } +func (*Checker) Name() string { return "staticcheck" } +func (*Checker) Prefix() string { return "SA" } + func (c *Checker) Funcs() map[string]lint.Func { return map[string]lint.Func{ "SA1000": c.callChecker(checkRegexpRules), @@ -232,7 +235,7 @@ func (c *Checker) Funcs() map[string]lint.Func { "SA1019": c.CheckDeprecated, "SA1020": c.callChecker(checkListenAddressRules), "SA1021": c.callChecker(checkBytesEqualIPRules), - "SA1022": c.CheckFlagUsage, + "SA1022": nil, "SA1023": c.CheckWriterBufferModified, "SA1024": c.callChecker(checkUniqueCutsetRules), @@ -249,7 +252,7 @@ func (c *Checker) Funcs() map[string]lint.Func { "SA4002": c.CheckDiffSizeComparison, "SA4003": c.CheckUnsignedComparison, "SA4004": c.CheckIneffectiveLoop, - "SA4005": c.CheckIneffecitiveFieldAssignments, + "SA4005": c.CheckIneffectiveFieldAssignments, "SA4006": c.CheckUnreadVariableValues, // "SA4007": c.CheckPredeterminedBooleanExprs, "SA4007": nil, @@ -263,6 +266,8 @@ func (c *Checker) Funcs() map[string]lint.Func { "SA4015": c.callChecker(checkMathIntRules), "SA4016": c.CheckSillyBitwiseOps, "SA4017": c.CheckPureFunctions, + "SA4018": c.CheckSelfAssignment, + "SA4019": c.CheckDuplicateBuildConstraints, "SA5000": c.CheckNilMaps, "SA5001": c.CheckEarlyDefer, @@ -275,13 +280,15 @@ func (c *Checker) Funcs() map[string]lint.Func { "SA6000": c.callChecker(checkRegexpMatchLoopRules), "SA6001": c.CheckMapBytesKey, - "SA6002": c.callChecker(checkSyncPoolSizeRules), + "SA6002": c.callChecker(checkSyncPoolValueRules), "SA6003": c.CheckRangeStringRunes, + "SA6004": nil, "SA9000": nil, "SA9001": c.CheckDubiousDeferInChannelRangeLoop, "SA9002": c.CheckNonOctalFileMode, "SA9003": c.CheckEmptyBranch, + "SA9004": c.CheckMissingEnumTypesInDeclaration, } } @@ -298,36 +305,62 @@ func (c *Checker) filterGenerated(files []*ast.File) []*ast.File { return out } -func (c *Checker) Init(prog *lint.Program) { - c.funcDescs = functions.NewDescriptions(prog.SSA) - c.deprecatedObjs = map[types.Object]string{} - c.nodeFns = map[ast.Node]*ssa.Function{} - - for _, fn := range prog.AllFunctions { - if fn.Blocks != nil { - applyStdlibKnowledge(fn) - ssa.OptimizeBlocks(fn) - } +func (c *Checker) deprecateObject(m map[types.Object]string, prog *lint.Program, obj types.Object) { + if obj.Pkg() == nil { + return } - c.nodeFns = lint.NodeFns(prog.Packages) + f := prog.File(obj) + if f == nil { + return + } + msg := c.deprecationMessage(f, prog.Prog.Fset, obj) + if msg != "" { + m[obj] = msg + } +} - deprecated := []map[types.Object]string{} +func (c *Checker) Init(prog *lint.Program) { wg := &sync.WaitGroup{} - for _, pkginfo := range prog.Prog.AllPackages { - pkginfo := pkginfo - scope := pkginfo.Pkg.Scope() - names := scope.Names() - wg.Add(1) - - m := map[types.Object]string{} - deprecated = append(deprecated, m) - go func(m map[types.Object]string) { - for _, name := range names { - obj := scope.Lookup(name) - msg := c.deprecationMessage(pkginfo.Files, prog.SSA.Fset, obj) - if msg != "" { - m[obj] = msg + wg.Add(3) + go func() { + c.funcDescs = functions.NewDescriptions(prog.SSA) + for _, fn := range prog.AllFunctions { + if fn.Blocks != nil { + applyStdlibKnowledge(fn) + ssa.OptimizeBlocks(fn) + } + } + wg.Done() + }() + + go func() { + c.nodeFns = lint.NodeFns(prog.Packages) + wg.Done() + }() + + go func() { + c.deprecatedObjs = map[types.Object]string{} + for _, ssapkg := range prog.SSA.AllPackages() { + ssapkg := ssapkg + for _, member := range ssapkg.Members { + obj := member.Object() + if obj == nil { + continue + } + c.deprecateObject(c.deprecatedObjs, prog, obj) + if typ, ok := obj.Type().(*types.Named); ok { + for i := 0; i < typ.NumMethods(); i++ { + meth := typ.Method(i) + c.deprecateObject(c.deprecatedObjs, prog, meth) + } + + if iface, ok := typ.Underlying().(*types.Interface); ok { + for i := 0; i < iface.NumExplicitMethods(); i++ { + meth := iface.ExplicitMethod(i) + c.deprecateObject(c.deprecatedObjs, prog, meth) + } + } } if typ, ok := obj.Type().Underlying().(*types.Struct); ok { n := typ.NumFields() @@ -335,51 +368,20 @@ func (c *Checker) Init(prog *lint.Program) { // FIXME(dh): This code will not find deprecated // fields in anonymous structs. field := typ.Field(i) - msg := c.deprecationMessage(pkginfo.Files, prog.SSA.Fset, field) - if msg != "" { - m[field] = msg - } + c.deprecateObject(c.deprecatedObjs, prog, field) } } } - wg.Done() - }(m) - } - wg.Wait() - for _, m := range deprecated { - for k, v := range m { - c.deprecatedObjs[k] = v } - } -} - -// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) -func tokenFileContainsPos(f *token.File, pos token.Pos) bool { - p := int(pos) - base := f.Base() - return base <= p && p < base+f.Size() -} + wg.Done() + }() -func pathEnclosingInterval(files []*ast.File, fset *token.FileSet, start, end token.Pos) (path []ast.Node, exact bool) { - for _, f := range files { - if f.Pos() == token.NoPos { - // This can happen if the parser saw - // too many errors and bailed out. - // (Use parser.AllErrors to prevent that.) - continue - } - if !tokenFileContainsPos(fset.File(f.Pos()), start) { - continue - } - if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { - return path, exact - } - } - return nil, false + wg.Wait() } -func (c *Checker) deprecationMessage(files []*ast.File, fset *token.FileSet, obj types.Object) (message string) { - path, _ := pathEnclosingInterval(files, fset, obj.Pos(), obj.Pos()) +func (c *Checker) deprecationMessage(file *ast.File, fset *token.FileSet, obj types.Object) (message string) { + pos := obj.Pos() + path, _ := astutil.PathEnclosingInterval(file, pos, pos) if len(path) <= 2 { return "" } @@ -1137,11 +1139,18 @@ func (c *Checker) CheckEmptyCriticalSection(j *lint.Job) { } } +// cgo produces code like fn(&*_Cvar_kSomeCallbacks) which we don't +// want to flag. +var cgoIdent = regexp.MustCompile(`^_C(func|var)_.+$`) + func (c *Checker) CheckIneffectiveCopy(j *lint.Job) { fn := func(node ast.Node) bool { if unary, ok := node.(*ast.UnaryExpr); ok { - if _, ok := unary.X.(*ast.StarExpr); ok && unary.Op == token.AND { - j.Errorf(unary, "&*x will be simplified to x. It will not copy x.") + if star, ok := unary.X.(*ast.StarExpr); ok && unary.Op == token.AND { + ident, ok := star.X.(*ast.Ident) + if !ok || !cgoIdent.MatchString(ident.Name) { + j.Errorf(unary, "&*x will be simplified to x. It will not copy x.") + } } } @@ -1254,7 +1263,7 @@ func (c *Checker) CheckBenchmarkN(j *lint.Job) { } } -func (c *Checker) CheckIneffecitiveFieldAssignments(j *lint.Job) { +func (c *Checker) CheckIneffectiveFieldAssignments(j *lint.Job) { for _, ssafn := range j.Program.InitialFunctions { // fset := j.Program.SSA.Fset // if fset.File(f.File.Pos()) != fset.File(ssafn.Pos()) { @@ -2055,7 +2064,7 @@ func (c *Checker) CheckCyclicFinalizer(j *lint.Job) { } for _, b := range mc.Bindings { if b == v { - pos := j.Program.SSA.Fset.Position(mc.Fn.Pos()) + pos := j.Program.DisplayPosition(mc.Fn.Pos()) j.Errorf(edge.Site, "the finalizer closes over the object, preventing the finalizer from ever running (at %s)", pos) } } @@ -2156,6 +2165,11 @@ func (c *Checker) CheckInfiniteRecursion(j *lint.Job) { if edge.Callee != node { continue } + if _, ok := edge.Site.(*ssa.Go); ok { + // Recursively spawning goroutines doesn't consume + // stack space infinitely, so don't flag it. + continue + } block := edge.Site.Block() canReturn := false @@ -2427,7 +2441,7 @@ fnLoop: if callee == nil { continue } - if c.funcDescs.Get(callee).Pure { + if c.funcDescs.Get(callee).Pure && !c.funcDescs.Get(callee).Stub { j.Errorf(ins, "%s is a pure function but its return value is ignored", callee.Name()) continue } @@ -2436,22 +2450,6 @@ fnLoop: } } -func enclosingFunction(j *lint.Job, node ast.Node) *ast.FuncDecl { - f := j.File(node) - path, _ := astutil.PathEnclosingInterval(f, node.Pos(), node.Pos()) - for _, e := range path { - fn, ok := e.(*ast.FuncDecl) - if !ok { - continue - } - if fn.Name == nil { - continue - } - return fn - } - return nil -} - func (c *Checker) isDeprecated(j *lint.Job, ident *ast.Ident) (bool, string) { obj := j.Program.Info.ObjectOf(ident) if obj.Pkg() == nil { @@ -2461,19 +2459,23 @@ func (c *Checker) isDeprecated(j *lint.Job, ident *ast.Ident) (bool, string) { return alt != "", alt } +func (c *Checker) enclosingFunc(sel *ast.SelectorExpr) *ssa.Function { + fn := c.nodeFns[sel] + if fn == nil { + return nil + } + for fn.Parent() != nil { + fn = fn.Parent() + } + return fn +} + func (c *Checker) CheckDeprecated(j *lint.Job) { fn := func(node ast.Node) bool { sel, ok := node.(*ast.SelectorExpr) if !ok { return true } - if fn := enclosingFunction(j, sel); fn != nil { - if ok, _ := c.isDeprecated(j, fn.Name); ok { - // functions that are deprecated may use deprecated - // symbols - return true - } - } obj := j.Program.Info.ObjectOf(sel.Sel) if obj.Pkg() == nil { @@ -2485,6 +2487,24 @@ func (c *Checker) CheckDeprecated(j *lint.Job) { return true } if ok, alt := c.isDeprecated(j, sel.Sel); ok { + // Look for the first available alternative, not the first + // version something was deprecated in. If a function was + // deprecated in Go 1.6, an alternative has been available + // already in 1.0, and we're targetting 1.2, it still + // makes sense to use the alternative from 1.0, to be + // future-proof. + minVersion := deprecated.Stdlib[j.SelectorName(sel)].AlternativeAvailableSince + if !j.IsGoVersion(minVersion) { + return true + } + + if fn := c.enclosingFunc(sel); fn != nil { + if _, ok := c.deprecatedObjs[fn.Object()]; ok { + // functions that are deprecated may use deprecated + // symbols + return true + } + } j.Errorf(sel, "%s is deprecated: %s", j.Render(sel), alt) return true } @@ -2558,46 +2578,6 @@ func (c *Checker) checkCalls(j *lint.Job, rules map[string]CallCheck) { } } -func (c *Checker) CheckFlagUsage(j *lint.Job) { - for _, ssafn := range j.Program.InitialFunctions { - for _, block := range ssafn.Blocks { - for _, ins := range block.Instrs { - store, ok := ins.(*ssa.Store) - if !ok { - continue - } - switch addr := store.Addr.(type) { - case *ssa.FieldAddr: - typ := addr.X.Type() - st := deref(typ).Underlying().(*types.Struct) - if types.TypeString(typ, nil) != "*flag.FlagSet" { - continue - } - if st.Field(addr.Field).Name() != "Usage" { - continue - } - case *ssa.Global: - if addr.Pkg.Pkg.Path() != "flag" || addr.Name() != "Usage" { - continue - } - default: - continue - } - fn := unwrapFunction(store.Val) - if fn == nil { - continue - } - for _, oblock := range fn.Blocks { - if hasCallTo(oblock, "os.Exit") { - j.Errorf(store, "the function assigned to Usage shouldn't call os.Exit, but it does") - break - } - } - } - } - } -} - func unwrapFunction(val ssa.Value) *ssa.Function { switch val := val.(type) { case *ssa.Function: @@ -2791,3 +2771,116 @@ func (c *Checker) CheckMapBytesKey(j *lint.Job) { func (c *Checker) CheckRangeStringRunes(j *lint.Job) { sharedcheck.CheckRangeStringRunes(c.nodeFns, j) } + +func (c *Checker) CheckSelfAssignment(j *lint.Job) { + fn := func(node ast.Node) bool { + assign, ok := node.(*ast.AssignStmt) + if !ok { + return true + } + if assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) { + return true + } + for i, stmt := range assign.Lhs { + rlh := j.Render(stmt) + rrh := j.Render(assign.Rhs[i]) + if rlh == rrh { + j.Errorf(assign, "self-assignment of %s to %s", rrh, rlh) + } + } + return true + } + for _, f := range c.filterGenerated(j.Program.Files) { + ast.Inspect(f, fn) + } +} + +func buildTagsIdentical(s1, s2 []string) bool { + if len(s1) != len(s2) { + return false + } + s1s := make([]string, len(s1)) + copy(s1s, s1) + sort.Strings(s1s) + s2s := make([]string, len(s2)) + copy(s2s, s2) + sort.Strings(s2s) + for i, s := range s1s { + if s != s2s[i] { + return false + } + } + return true +} + +func (c *Checker) CheckDuplicateBuildConstraints(job *lint.Job) { + for _, f := range c.filterGenerated(job.Program.Files) { + constraints := buildTags(f) + for i, constraint1 := range constraints { + for j, constraint2 := range constraints { + if i >= j { + continue + } + if buildTagsIdentical(constraint1, constraint2) { + job.Errorf(f, "identical build constraints %q and %q", + strings.Join(constraint1, " "), + strings.Join(constraint2, " ")) + } + } + } + } +} + +func (c *Checker) CheckMissingEnumTypesInDeclaration(j *lint.Job) { + fn := func(node ast.Node) bool { + decl, ok := node.(*ast.GenDecl) + if !ok { + return true + } + if !decl.Lparen.IsValid() { + // not a parenthesised gendecl + // + // TODO(dh): do we need this check, considering we require + // decl.Specs to contain 2+ elements? + return true + } + if decl.Tok != token.CONST { + return true + } + if len(decl.Specs) < 2 { + return true + } + if decl.Specs[0].(*ast.ValueSpec).Type == nil { + // first constant doesn't have a type + return true + } + for i, spec := range decl.Specs { + spec := spec.(*ast.ValueSpec) + if len(spec.Names) != 1 || len(spec.Values) != 1 { + return true + } + switch v := spec.Values[0].(type) { + case *ast.BasicLit: + case *ast.UnaryExpr: + if _, ok := v.X.(*ast.BasicLit); !ok { + return true + } + default: + // if it's not a literal it might be typed, such as + // time.Microsecond = 1000 * Nanosecond + return true + } + if i == 0 { + continue + } + if spec.Type != nil { + return true + } + } + j.Errorf(decl, "only the first constant has an explicit type") + return true + } + for _, f := range j.Program.Files { + ast.Inspect(f, fn) + } +} diff --git a/tools/vendor/honnef.co/go/tools/unused/unused.go b/tools/vendor/honnef.co/go/tools/unused/unused.go index 55d7549ef..c0a71e4e3 100644 --- a/tools/vendor/honnef.co/go/tools/unused/unused.go +++ b/tools/vendor/honnef.co/go/tools/unused/unused.go @@ -26,6 +26,9 @@ type LintChecker struct { c *Checker } +func (*LintChecker) Name() string { return "unused" } +func (*LintChecker) Prefix() string { return "U" } + func (l *LintChecker) Init(*lint.Program) {} func (l *LintChecker) Funcs() map[string]lint.Func { return map[string]lint.Func{ @@ -275,6 +278,51 @@ func (c *Checker) Check(lprog *loader.Program) []Unused { return unused } +// isNoCopyType reports whether a type represents the NoCopy sentinel +// type. The NoCopy type is a named struct with no fields and exactly +// one method `func Lock()` that is empty. +// +// FIXME(dh): currently we're not checking that the function body is +// empty. +func isNoCopyType(typ types.Type) bool { + st, ok := typ.Underlying().(*types.Struct) + if !ok { + return false + } + if st.NumFields() != 0 { + return false + } + + named, ok := typ.(*types.Named) + if !ok { + return false + } + if named.NumMethods() != 1 { + return false + } + meth := named.Method(0) + if meth.Name() != "Lock" { + return false + } + sig := meth.Type().(*types.Signature) + if sig.Params().Len() != 0 || sig.Results().Len() != 0 { + return false + } + return true +} + +func (c *Checker) useNoCopyFields(typ types.Type) { + if st, ok := typ.Underlying().(*types.Struct); ok { + n := st.NumFields() + for i := 0; i < n; i++ { + field := st.Field(i) + if isNoCopyType(field.Type()) { + c.graph.markUsedBy(field, typ) + } + } + } +} + func (c *Checker) useExportedFields(typ types.Type) { if st, ok := typ.Underlying().(*types.Struct); ok { n := st.NumFields() @@ -485,6 +533,7 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) { interfaces = append(interfaces, obj) } case *types.Struct: + c.useNoCopyFields(obj) if pkg.Pkg.Name() != "main" && !c.WholeProgram { c.useExportedFields(obj) } diff --git a/tools/vendor/honnef.co/go/tools/version/version.go b/tools/vendor/honnef.co/go/tools/version/version.go new file mode 100644 index 000000000..232cf7e74 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/version/version.go @@ -0,0 +1,17 @@ +package version + +import ( + "fmt" + "os" + "path/filepath" +) + +const Version = "devel" + +func Print() { + if Version == "devel" { + fmt.Printf("%s (no version)\n", filepath.Base(os.Args[0])) + } else { + fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), Version) + } +} diff --git a/tools/vendor/mvdan.cc/interfacer/LICENSE b/tools/vendor/mvdan.cc/interfacer/LICENSE new file mode 100644 index 000000000..7d71d51a5 --- /dev/null +++ b/tools/vendor/mvdan.cc/interfacer/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Daniel Martí. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tools/vendor/mvdan.cc/interfacer/README.md b/tools/vendor/mvdan.cc/interfacer/README.md new file mode 100644 index 000000000..bafa40b00 --- /dev/null +++ b/tools/vendor/mvdan.cc/interfacer/README.md @@ -0,0 +1,58 @@ +# interfacer + +[![GoDoc](https://godoc.org/mvdan.cc/interfacer?status.svg)](https://godoc.org/mvdan.cc/interfacer) +[![Build Status](https://travis-ci.org/mvdan/interfacer.svg?branch=master)](https://travis-ci.org/mvdan/interfacer) + +A linter that suggests interface types. In other words, it warns about +the usage of types that are more specific than necessary. + + go get -u mvdan.cc/interfacer + +### Usage + +```go +func ProcessInput(f *os.File) error { + b, err := ioutil.ReadAll(f) + if err != nil { + return err + } + return processBytes(b) +} +``` + +```sh +$ interfacer $(go list ./... | grep -v /vendor/) +foo.go:10:19: f can be io.Reader +``` + +### Basic idea + +This tool inspects the parameters of your functions to see if they fit +an interface type that is less specific than the current type. + +The example above illustrates this point. Overly specific interfaces +also trigger a warning - if `f` were an `io.ReadCloser`, the same +message would appear. + +It suggests interface types defined both in the func's package and the +package's imports (two levels; direct imports and their direct imports). + +### False positives + +To avoid false positives, it never does any suggestions on functions +that may be implementing an interface method or a named function type. + +It also skips parameters passed by value (excluding pointers and +interfaces) on unexported functions, since that would introduce extra +allocations where they are usually not worth the tradeoff. + +### Suppressing warnings + +If a suggestion is technically correct but doesn't make sense, you can +still suppress the warning by mentioning the type in the function name: + +```go +func ProcessInputFile(f *os.File) error { + // use as an io.Reader +} +``` diff --git a/tools/vendor/mvdan.cc/interfacer/check/cache.go b/tools/vendor/mvdan.cc/interfacer/check/cache.go new file mode 100644 index 000000000..757eca55e --- /dev/null +++ b/tools/vendor/mvdan.cc/interfacer/check/cache.go @@ -0,0 +1,50 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package check + +import ( + "go/ast" + "go/types" +) + +type pkgTypes struct { + ifaces map[string]string + funcSigns map[string]bool +} + +func (p *pkgTypes) getTypes(pkg *types.Package) { + p.ifaces = make(map[string]string) + p.funcSigns = make(map[string]bool) + done := make(map[*types.Package]bool) + addTypes := func(pkg *types.Package, top bool) { + if done[pkg] { + return + } + done[pkg] = true + ifs, funs := fromScope(pkg.Scope()) + fullName := func(name string) string { + if !top { + return pkg.Path() + "." + name + } + return name + } + for iftype, name := range ifs { + // only suggest exported interfaces + if ast.IsExported(name) { + p.ifaces[iftype] = fullName(name) + } + } + for ftype := range funs { + // ignore non-exported func signatures too + p.funcSigns[ftype] = true + } + } + for _, imp := range pkg.Imports() { + addTypes(imp, false) + for _, imp2 := range imp.Imports() { + addTypes(imp2, false) + } + } + addTypes(pkg, true) +} diff --git a/tools/vendor/mvdan.cc/interfacer/check/check.go b/tools/vendor/mvdan.cc/interfacer/check/check.go new file mode 100644 index 000000000..f4d3b4037 --- /dev/null +++ b/tools/vendor/mvdan.cc/interfacer/check/check.go @@ -0,0 +1,462 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package check // import "mvdan.cc/interfacer/check" + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + "strings" + + "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + + "github.com/kisielk/gotool" + "mvdan.cc/lint" +) + +func toDiscard(usage *varUsage) bool { + if usage.discard { + return true + } + for to := range usage.assigned { + if toDiscard(to) { + return true + } + } + return false +} + +func allCalls(usage *varUsage, all, ftypes map[string]string) { + for fname := range usage.calls { + all[fname] = ftypes[fname] + } + for to := range usage.assigned { + allCalls(to, all, ftypes) + } +} + +func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) { + if toDiscard(usage) { + return "", "" + } + ftypes := typeFuncMap(param.Type()) + called := make(map[string]string, len(usage.calls)) + allCalls(usage, called, ftypes) + s := funcMapString(called) + return c.ifaces[s], s +} + +type varUsage struct { + calls map[string]struct{} + discard bool + + assigned map[*varUsage]struct{} +} + +type funcDecl struct { + astDecl *ast.FuncDecl + ssaFn *ssa.Function +} + +// CheckArgs checks the packages specified by their import paths in +// args. +func CheckArgs(args []string) ([]string, error) { + paths := gotool.ImportPaths(args) + conf := loader.Config{} + conf.AllowErrors = true + rest, err := conf.FromArgs(paths, false) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, fmt.Errorf("unwanted extra args: %v", rest) + } + lprog, err := conf.Load() + if err != nil { + return nil, err + } + prog := ssautil.CreateProgram(lprog, 0) + prog.Build() + c := new(Checker) + c.Program(lprog) + c.ProgramSSA(prog) + issues, err := c.Check() + if err != nil { + return nil, err + } + wd, err := os.Getwd() + if err != nil { + return nil, err + } + lines := make([]string, len(issues)) + for i, issue := range issues { + fpos := prog.Fset.Position(issue.Pos()).String() + if strings.HasPrefix(fpos, wd) { + fpos = fpos[len(wd)+1:] + } + lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message()) + } + return lines, nil +} + +type Checker struct { + lprog *loader.Program + prog *ssa.Program + + pkgTypes + *loader.PackageInfo + + funcs []*funcDecl + + ssaByPos map[token.Pos]*ssa.Function + + discardFuncs map[*types.Signature]struct{} + + vars map[*types.Var]*varUsage +} + +var ( + _ lint.Checker = (*Checker)(nil) + _ lint.WithSSA = (*Checker)(nil) +) + +func (c *Checker) Program(lprog *loader.Program) { + c.lprog = lprog +} + +func (c *Checker) ProgramSSA(prog *ssa.Program) { + c.prog = prog +} + +func (c *Checker) Check() ([]lint.Issue, error) { + var total []lint.Issue + c.ssaByPos = make(map[token.Pos]*ssa.Function) + wantPkg := make(map[*types.Package]bool) + for _, pinfo := range c.lprog.InitialPackages() { + wantPkg[pinfo.Pkg] = true + } + for fn := range ssautil.AllFunctions(c.prog) { + if fn.Pkg == nil { // builtin? + continue + } + if len(fn.Blocks) == 0 { // stub + continue + } + if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs + continue + } + c.ssaByPos[fn.Pos()] = fn + } + for _, pinfo := range c.lprog.InitialPackages() { + pkg := pinfo.Pkg + c.getTypes(pkg) + c.PackageInfo = c.lprog.AllPackages[pkg] + total = append(total, c.checkPkg()...) + } + return total, nil +} + +func (c *Checker) checkPkg() []lint.Issue { + c.discardFuncs = make(map[*types.Signature]struct{}) + c.vars = make(map[*types.Var]*varUsage) + c.funcs = c.funcs[:0] + findFuncs := func(node ast.Node) bool { + decl, ok := node.(*ast.FuncDecl) + if !ok { + return true + } + ssaFn := c.ssaByPos[decl.Name.Pos()] + if ssaFn == nil { + return true + } + fd := &funcDecl{ + astDecl: decl, + ssaFn: ssaFn, + } + if c.funcSigns[signString(fd.ssaFn.Signature)] { + // implements interface + return true + } + c.funcs = append(c.funcs, fd) + ast.Walk(c, decl.Body) + return true + } + for _, f := range c.Files { + ast.Inspect(f, findFuncs) + } + return c.packageIssues() +} + +func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) { + params := sign.Params() + extra := sign.Variadic() && i >= params.Len()-1 + if !extra { + if i >= params.Len() { + // builtins with multiple signatures + return nil, nil + } + vr := params.At(i) + return vr, vr.Type() + } + last := params.At(params.Len() - 1) + switch x := last.Type().(type) { + case *types.Slice: + return nil, x.Elem() + default: + return nil, x + } +} + +func (c *Checker) varUsage(e ast.Expr) *varUsage { + id, ok := e.(*ast.Ident) + if !ok { + return nil + } + param, ok := c.ObjectOf(id).(*types.Var) + if !ok { + // not a variable + return nil + } + if usage, e := c.vars[param]; e { + return usage + } + if !interesting(param.Type()) { + return nil + } + usage := &varUsage{ + calls: make(map[string]struct{}), + assigned: make(map[*varUsage]struct{}), + } + c.vars[param] = usage + return usage +} + +func (c *Checker) addUsed(e ast.Expr, as types.Type) { + if as == nil { + return + } + if usage := c.varUsage(e); usage != nil { + // using variable + iface, ok := as.Underlying().(*types.Interface) + if !ok { + usage.discard = true + return + } + for i := 0; i < iface.NumMethods(); i++ { + m := iface.Method(i) + usage.calls[m.Name()] = struct{}{} + } + } else if t, ok := c.TypeOf(e).(*types.Signature); ok { + // using func + c.discardFuncs[t] = struct{}{} + } +} + +func (c *Checker) addAssign(to, from ast.Expr) { + pto := c.varUsage(to) + pfrom := c.varUsage(from) + if pto == nil || pfrom == nil { + // either isn't interesting + return + } + pfrom.assigned[pto] = struct{}{} +} + +func (c *Checker) discard(e ast.Expr) { + if usage := c.varUsage(e); usage != nil { + usage.discard = true + } +} + +func (c *Checker) comparedWith(e, with ast.Expr) { + if _, ok := with.(*ast.BasicLit); ok { + c.discard(e) + } +} + +func (c *Checker) Visit(node ast.Node) ast.Visitor { + switch x := node.(type) { + case *ast.SelectorExpr: + if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok { + c.discard(x.X) + } + case *ast.StarExpr: + c.discard(x.X) + case *ast.UnaryExpr: + c.discard(x.X) + case *ast.IndexExpr: + c.discard(x.X) + case *ast.IncDecStmt: + c.discard(x.X) + case *ast.BinaryExpr: + switch x.Op { + case token.EQL, token.NEQ: + c.comparedWith(x.X, x.Y) + c.comparedWith(x.Y, x.X) + default: + c.discard(x.X) + c.discard(x.Y) + } + case *ast.ValueSpec: + for _, val := range x.Values { + c.addUsed(val, c.TypeOf(x.Type)) + } + case *ast.AssignStmt: + for i, val := range x.Rhs { + left := x.Lhs[i] + if x.Tok == token.ASSIGN { + c.addUsed(val, c.TypeOf(left)) + } + c.addAssign(left, val) + } + case *ast.CompositeLit: + for i, e := range x.Elts { + switch y := e.(type) { + case *ast.KeyValueExpr: + c.addUsed(y.Key, c.TypeOf(y.Value)) + c.addUsed(y.Value, c.TypeOf(y.Key)) + case *ast.Ident: + c.addUsed(y, compositeIdentType(c.TypeOf(x), i)) + } + } + case *ast.CallExpr: + switch y := c.TypeOf(x.Fun).Underlying().(type) { + case *types.Signature: + c.onMethodCall(x, y) + default: + // type conversion + if len(x.Args) == 1 { + c.addUsed(x.Args[0], y) + } + } + } + return c +} + +func compositeIdentType(t types.Type, i int) types.Type { + switch x := t.(type) { + case *types.Named: + return compositeIdentType(x.Underlying(), i) + case *types.Struct: + return x.Field(i).Type() + case *types.Array: + return x.Elem() + case *types.Slice: + return x.Elem() + } + return nil +} + +func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) { + for i, e := range ce.Args { + paramObj, t := paramVarAndType(sign, i) + // Don't if this is a parameter being re-used as itself + // in a recursive call + if id, ok := e.(*ast.Ident); ok { + if paramObj == c.ObjectOf(id) { + continue + } + } + c.addUsed(e, t) + } + sel, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return + } + // receiver func call on the left side + if usage := c.varUsage(sel.X); usage != nil { + usage.calls[sel.Sel.Name] = struct{}{} + } +} + +func (fd *funcDecl) paramGroups() [][]*types.Var { + astList := fd.astDecl.Type.Params.List + groups := make([][]*types.Var, len(astList)) + signIndex := 0 + for i, field := range astList { + group := make([]*types.Var, len(field.Names)) + for j := range field.Names { + group[j] = fd.ssaFn.Signature.Params().At(signIndex) + signIndex++ + } + groups[i] = group + } + return groups +} + +func (c *Checker) packageIssues() []lint.Issue { + var issues []lint.Issue + for _, fd := range c.funcs { + if _, e := c.discardFuncs[fd.ssaFn.Signature]; e { + continue + } + for _, group := range fd.paramGroups() { + issues = append(issues, c.groupIssues(fd, group)...) + } + } + return issues +} + +type Issue struct { + pos token.Pos + msg string +} + +func (i Issue) Pos() token.Pos { return i.pos } +func (i Issue) Message() string { return i.msg } + +func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue { + var issues []lint.Issue + for _, param := range group { + usage := c.vars[param] + if usage == nil { + return nil + } + newType := c.paramNewType(fd.astDecl.Name.Name, param, usage) + if newType == "" { + return nil + } + issues = append(issues, Issue{ + pos: param.Pos(), + msg: fmt.Sprintf("%s can be %s", param.Name(), newType), + }) + } + return issues +} + +func willAddAllocation(t types.Type) bool { + switch t.Underlying().(type) { + case *types.Pointer, *types.Interface: + return false + } + return true +} + +func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string { + t := param.Type() + if !ast.IsExported(funcName) && willAddAllocation(t) { + return "" + } + if named := typeNamed(t); named != nil { + tname := named.Obj().Name() + vname := param.Name() + if mentionsName(funcName, tname) || mentionsName(funcName, vname) { + return "" + } + } + ifname, iftype := c.interfaceMatching(param, usage) + if ifname == "" { + return "" + } + if types.IsInterface(t.Underlying()) { + if have := funcMapString(typeFuncMap(t)); have == iftype { + return "" + } + } + return ifname +} diff --git a/tools/vendor/mvdan.cc/interfacer/check/types.go b/tools/vendor/mvdan.cc/interfacer/check/types.go new file mode 100644 index 000000000..393bb0b9f --- /dev/null +++ b/tools/vendor/mvdan.cc/interfacer/check/types.go @@ -0,0 +1,170 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package check + +import ( + "bytes" + "fmt" + "go/types" + "sort" + "strings" +) + +type methoder interface { + NumMethods() int + Method(int) *types.Func +} + +func methoderFuncMap(m methoder, skip bool) map[string]string { + ifuncs := make(map[string]string, m.NumMethods()) + for i := 0; i < m.NumMethods(); i++ { + f := m.Method(i) + if !f.Exported() { + if skip { + continue + } + return nil + } + sign := f.Type().(*types.Signature) + ifuncs[f.Name()] = signString(sign) + } + return ifuncs +} + +func typeFuncMap(t types.Type) map[string]string { + switch x := t.(type) { + case *types.Pointer: + return typeFuncMap(x.Elem()) + case *types.Named: + u := x.Underlying() + if types.IsInterface(u) { + return typeFuncMap(u) + } + return methoderFuncMap(x, true) + case *types.Interface: + return methoderFuncMap(x, false) + default: + return nil + } +} + +func funcMapString(iface map[string]string) string { + fnames := make([]string, 0, len(iface)) + for fname := range iface { + fnames = append(fnames, fname) + } + sort.Strings(fnames) + var b bytes.Buffer + for i, fname := range fnames { + if i > 0 { + fmt.Fprint(&b, "; ") + } + fmt.Fprint(&b, fname, iface[fname]) + } + return b.String() +} + +func tupleJoin(buf *bytes.Buffer, t *types.Tuple) { + buf.WriteByte('(') + for i := 0; i < t.Len(); i++ { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(t.At(i).Type().String()) + } + buf.WriteByte(')') +} + +// signString is similar to Signature.String(), but it ignores +// param/result names. +func signString(sign *types.Signature) string { + var buf bytes.Buffer + tupleJoin(&buf, sign.Params()) + tupleJoin(&buf, sign.Results()) + return buf.String() +} + +func interesting(t types.Type) bool { + switch x := t.(type) { + case *types.Interface: + return x.NumMethods() > 1 + case *types.Named: + if u := x.Underlying(); types.IsInterface(u) { + return interesting(u) + } + return x.NumMethods() >= 1 + case *types.Pointer: + return interesting(x.Elem()) + default: + return false + } +} + +func anyInteresting(params *types.Tuple) bool { + for i := 0; i < params.Len(); i++ { + t := params.At(i).Type() + if interesting(t) { + return true + } + } + return false +} + +func fromScope(scope *types.Scope) (ifaces map[string]string, funcs map[string]bool) { + ifaces = make(map[string]string) + funcs = make(map[string]bool) + for _, name := range scope.Names() { + tn, ok := scope.Lookup(name).(*types.TypeName) + if !ok { + continue + } + switch x := tn.Type().Underlying().(type) { + case *types.Interface: + iface := methoderFuncMap(x, false) + if len(iface) == 0 { + continue + } + for i := 0; i < x.NumMethods(); i++ { + f := x.Method(i) + sign := f.Type().(*types.Signature) + if !anyInteresting(sign.Params()) { + continue + } + funcs[signString(sign)] = true + } + s := funcMapString(iface) + if _, e := ifaces[s]; !e { + ifaces[s] = tn.Name() + } + case *types.Signature: + if !anyInteresting(x.Params()) { + continue + } + funcs[signString(x)] = true + } + } + return ifaces, funcs +} + +func mentionsName(fname, name string) bool { + if len(name) < 2 { + return false + } + capit := strings.ToUpper(name[:1]) + name[1:] + lower := strings.ToLower(name) + return strings.Contains(fname, capit) || strings.HasPrefix(fname, lower) +} + +func typeNamed(t types.Type) *types.Named { + for { + switch x := t.(type) { + case *types.Named: + return x + case *types.Pointer: + t = x.Elem() + default: + return nil + } + } +} diff --git a/tools/vendor/mvdan.cc/interfacer/main.go b/tools/vendor/mvdan.cc/interfacer/main.go new file mode 100644 index 000000000..bc86977a1 --- /dev/null +++ b/tools/vendor/mvdan.cc/interfacer/main.go @@ -0,0 +1,26 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package main // import "mvdan.cc/interfacer" + +import ( + "flag" + "fmt" + "os" + + "mvdan.cc/interfacer/check" +) + +var _ = flag.Bool("v", false, "print the names of packages as they are checked") + +func main() { + flag.Parse() + lines, err := check.CheckArgs(flag.Args()) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + for _, line := range lines { + fmt.Println(line) + } +} diff --git a/tools/vendor/mvdan.cc/lint/LICENSE b/tools/vendor/mvdan.cc/lint/LICENSE new file mode 100644 index 000000000..a06c5ebfc --- /dev/null +++ b/tools/vendor/mvdan.cc/lint/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017, Daniel Martí. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tools/vendor/mvdan.cc/lint/README.md b/tools/vendor/mvdan.cc/lint/README.md new file mode 100644 index 000000000..8a9c8b51c --- /dev/null +++ b/tools/vendor/mvdan.cc/lint/README.md @@ -0,0 +1,27 @@ +# lint + +[![GoDoc](https://godoc.org/mvdan.cc/lint?status.svg)](https://godoc.org/mvdan.cc/lint) +[![Build Status](https://travis-ci.org/mvdan/lint.svg?branch=master)](https://travis-ci.org/mvdan/lint) + +Work in progress. Its API might change before the 1.0 release. + +This package intends to define simple interfaces that Go code checkers +can implement. This would simplify calling them from Go code, as well as +running multiple linters while sharing initial loading work. + +### metalint + + go get -u mvdan.cc/lint/cmd/metalint + +The start of a linter that runs many linters leveraging the common +interface. Not stable yet. + +Linters included: + +* [unparam](https://mvdan.cc/unparam) +* [interfacer](https://github.com/mvdan/interfacer) + +### Related projects + +* [golinters](https://github.com/thomasheller/golinters) - Report on + linter support diff --git a/tools/vendor/mvdan.cc/lint/lint.go b/tools/vendor/mvdan.cc/lint/lint.go new file mode 100644 index 000000000..a16789fad --- /dev/null +++ b/tools/vendor/mvdan.cc/lint/lint.go @@ -0,0 +1,28 @@ +// Copyright (c) 2017, Daniel Martí +// See LICENSE for licensing information + +// Package lint defines common interfaces for Go code checkers. +package lint // import "mvdan.cc/lint" + +import ( + "go/token" + + "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/ssa" +) + +// A Checker points out issues in a program. +type Checker interface { + Program(*loader.Program) + Check() ([]Issue, error) +} + +type WithSSA interface { + ProgramSSA(*ssa.Program) +} + +// Issue represents an issue somewhere in a source code file. +type Issue interface { + Pos() token.Pos + Message() string +} diff --git a/tools/vendor/mvdan.cc/unparam/LICENSE b/tools/vendor/mvdan.cc/unparam/LICENSE new file mode 100644 index 000000000..a06c5ebfc --- /dev/null +++ b/tools/vendor/mvdan.cc/unparam/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017, Daniel Martí. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tools/vendor/mvdan.cc/unparam/README.md b/tools/vendor/mvdan.cc/unparam/README.md new file mode 100644 index 000000000..bfa25a014 --- /dev/null +++ b/tools/vendor/mvdan.cc/unparam/README.md @@ -0,0 +1,24 @@ +# unparam + +[![Build Status](https://travis-ci.org/mvdan/unparam.svg?branch=master)](https://travis-ci.org/mvdan/unparam) + + go get -u mvdan.cc/unparam + +Reports unused function parameters and results in your code. + +To minimise false positives, it ignores certain cases such as: + +* Exported functions (by default, see `-exported`) +* Unnamed and underscore parameters +* Funcs that may satisfy an interface +* Funcs that may satisfy a function signature +* Funcs that are stubs (empty, only error, immediately return, etc) +* Funcs that have multiple implementations via build tags + +It also reports results that always return the same value, parameters +that always receive the same value, and results that are never used. In +the last two cases, a minimum number of calls is required to ensure that +the warnings are useful. + +False positives can still occur by design. The aim of the tool is to be +as precise as possible - if you find any mistakes, file a bug. diff --git a/tools/vendor/mvdan.cc/unparam/check/check.go b/tools/vendor/mvdan.cc/unparam/check/check.go new file mode 100644 index 000000000..33cc3e15d --- /dev/null +++ b/tools/vendor/mvdan.cc/unparam/check/check.go @@ -0,0 +1,603 @@ +// Copyright (c) 2017, Daniel Martí +// See LICENSE for licensing information + +// Package check implements the unparam linter. Note that its API is not +// stable. +package check // import "mvdan.cc/unparam/check" + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/parser" + "go/printer" + "go/token" + "go/types" + "io" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + + "golang.org/x/tools/go/callgraph" + "golang.org/x/tools/go/callgraph/cha" + "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + + "github.com/kisielk/gotool" + "mvdan.cc/lint" +) + +func UnusedParams(tests, exported, debug bool, args ...string) ([]string, error) { + wd, err := os.Getwd() + if err != nil { + return nil, err + } + c := &Checker{ + wd: wd, + tests: tests, + exported: exported, + } + if debug { + c.debugLog = os.Stderr + } + return c.lines(args...) +} + +type Checker struct { + lprog *loader.Program + prog *ssa.Program + + wd string + + tests bool + exported bool + debugLog io.Writer + + cachedDeclCounts map[string]map[string]int + + callByPos map[token.Pos]*ast.CallExpr +} + +var ( + _ lint.Checker = (*Checker)(nil) + _ lint.WithSSA = (*Checker)(nil) + + errorType = types.Universe.Lookup("error").Type() +) + +func (c *Checker) lines(args ...string) ([]string, error) { + paths := gotool.ImportPaths(args) + conf := loader.Config{ + ParserMode: parser.ParseComments, + } + if _, err := conf.FromArgs(paths, c.tests); err != nil { + return nil, err + } + lprog, err := conf.Load() + if err != nil { + return nil, err + } + prog := ssautil.CreateProgram(lprog, 0) + prog.Build() + c.Program(lprog) + c.ProgramSSA(prog) + issues, err := c.Check() + if err != nil { + return nil, err + } + lines := make([]string, len(issues)) + for i, issue := range issues { + fpos := prog.Fset.Position(issue.Pos()).String() + if strings.HasPrefix(fpos, c.wd) { + fpos = fpos[len(c.wd)+1:] + } + lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message()) + } + return lines, nil +} + +type Issue struct { + pos token.Pos + fname string + msg string +} + +func (i Issue) Pos() token.Pos { return i.pos } +func (i Issue) Message() string { return i.fname + " - " + i.msg } + +func (c *Checker) Program(lprog *loader.Program) { + c.lprog = lprog +} + +func (c *Checker) ProgramSSA(prog *ssa.Program) { + c.prog = prog +} + +func (c *Checker) debug(format string, a ...interface{}) { + if c.debugLog != nil { + fmt.Fprintf(c.debugLog, format, a...) + } +} + +func generatedDoc(text string) bool { + return strings.Contains(text, "Code generated") || + strings.Contains(text, "DO NOT EDIT") +} + +var stdSizes = types.SizesFor("gc", "amd64") + +func (c *Checker) Check() ([]lint.Issue, error) { + c.cachedDeclCounts = make(map[string]map[string]int) + c.callByPos = make(map[token.Pos]*ast.CallExpr) + wantPkg := make(map[*types.Package]*loader.PackageInfo) + genFiles := make(map[string]bool) + for _, info := range c.lprog.InitialPackages() { + wantPkg[info.Pkg] = info + for _, f := range info.Files { + if len(f.Comments) > 0 && generatedDoc(f.Comments[0].Text()) { + fname := c.prog.Fset.Position(f.Pos()).Filename + genFiles[fname] = true + } + ast.Inspect(f, func(node ast.Node) bool { + if ce, ok := node.(*ast.CallExpr); ok { + c.callByPos[ce.Lparen] = ce + } + return true + }) + } + } + cg := cha.CallGraph(c.prog) + + var issues []lint.Issue +funcLoop: + for fn := range ssautil.AllFunctions(c.prog) { + if fn.Pkg == nil { // builtin? + continue + } + if len(fn.Blocks) == 0 { // stub + continue + } + info := wantPkg[fn.Pkg.Pkg] + if info == nil { // not part of given pkgs + continue + } + if c.exported || fn.Pkg.Pkg.Name() == "main" { + // we want exported funcs, or this is a main + // package so nothing is exported + } else if strings.Contains(fn.Name(), "$") { + // anonymous function + } else if ast.IsExported(fn.Name()) { + continue // user doesn't want to change signatures here + } + fname := c.prog.Fset.Position(fn.Pos()).Filename + if genFiles[fname] { + continue // generated file + } + c.debug("func %s\n", fn.RelString(fn.Package().Pkg)) + if dummyImpl(fn.Blocks[0]) { // panic implementation + c.debug(" skip - dummy implementation\n") + continue + } + for _, edge := range cg.Nodes[fn].In { + call := edge.Site.Value() + if receivesExtractedArgs(fn.Signature, call) { + // called via function(results()) + c.debug(" skip - type is required via call\n") + continue funcLoop + } + caller := edge.Caller.Func + switch { + case len(caller.FreeVars) == 1 && strings.HasSuffix(caller.Name(), "$bound"): + // passing method via someFunc(type.method) + fallthrough + case len(caller.FreeVars) == 0 && strings.HasSuffix(caller.Name(), "$thunk"): + // passing method via someFunc(recv.method) + c.debug(" skip - type is required via call\n") + continue funcLoop + } + switch edge.Site.Common().Value.(type) { + case *ssa.Function: + default: + // called via a parameter or field, type + // is set in stone. + c.debug(" skip - type is required via call\n") + continue funcLoop + } + } + if c.multipleImpls(info, fn) { + c.debug(" skip - multiple implementations via build tags\n") + continue + } + + results := fn.Signature.Results() + seenConsts := make([]constant.Value, results.Len()) + seenParams := make([]*ssa.Parameter, results.Len()) + numRets := 0 + allRetsExtracting := true + for _, block := range fn.Blocks { + last := block.Instrs[len(block.Instrs)-1] + ret, ok := last.(*ssa.Return) + if !ok { + continue + } + for i, val := range ret.Results { + switch x := val.(type) { + case *ssa.Const: + allRetsExtracting = false + seenParams[i] = nil + switch { + case numRets == 0: + seenConsts[i] = x.Value + case seenConsts[i] == nil: + case !constant.Compare(seenConsts[i], token.EQL, x.Value): + seenConsts[i] = nil + } + case *ssa.Parameter: + allRetsExtracting = false + seenConsts[i] = nil + switch { + case numRets == 0: + seenParams[i] = x + case seenParams[i] == nil: + case seenParams[i] != x: + seenParams[i] = nil + } + case *ssa.Extract: + seenConsts[i] = nil + seenParams[i] = nil + default: + allRetsExtracting = false + seenConsts[i] = nil + seenParams[i] = nil + } + } + numRets++ + } + for i, val := range seenConsts { + if val == nil || numRets < 2 { + continue + } + res := results.At(i) + name := paramDesc(i, res) + issues = append(issues, Issue{ + pos: res.Pos(), + fname: fn.RelString(fn.Package().Pkg), + msg: fmt.Sprintf("result %s is always %s", name, val.String()), + }) + } + + callers := cg.Nodes[fn].In + resLoop: + for i := 0; i < results.Len(); i++ { + if allRetsExtracting { + continue + } + res := results.At(i) + if res.Type() == errorType { + // "error is never unused" is less + // useful, and it's up to tools like + // errcheck anyway. + continue + } + count := 0 + for _, edge := range callers { + val := edge.Site.Value() + if val == nil { // e.g. go statement + count++ + continue + } + for _, instr := range *val.Referrers() { + extract, ok := instr.(*ssa.Extract) + if !ok { + continue resLoop // direct, real use + } + if extract.Index != i { + continue // not the same result param + } + if len(*extract.Referrers()) > 0 { + continue resLoop // real use after extraction + } + } + count++ + } + if count < 2 { + continue // require ignoring at least twice + } + name := paramDesc(i, res) + issues = append(issues, Issue{ + pos: res.Pos(), + fname: fn.RelString(fn.Package().Pkg), + msg: fmt.Sprintf("result %s is never used", name), + }) + } + + for i, par := range fn.Params { + if i == 0 && fn.Signature.Recv() != nil { // receiver + continue + } + c.debug("%s\n", par.String()) + switch par.Object().Name() { + case "", "_": // unnamed + c.debug(" skip - unnamed\n") + continue + } + if stdSizes.Sizeof(par.Type()) == 0 { + c.debug(" skip - zero size\n") + continue + } + reason := "is unused" + if valStr := c.receivesSameValues(cg.Nodes[fn].In, par, i); valStr != "" { + reason = fmt.Sprintf("always receives %s", valStr) + } else if anyRealUse(par, i) { + c.debug(" skip - used somewhere in the func body\n") + continue + } + issues = append(issues, Issue{ + pos: par.Pos(), + fname: fn.RelString(fn.Package().Pkg), + msg: fmt.Sprintf("%s %s", par.Name(), reason), + }) + } + + } + sort.Slice(issues, func(i, j int) bool { + p1 := c.prog.Fset.Position(issues[i].Pos()) + p2 := c.prog.Fset.Position(issues[j].Pos()) + if p1.Filename == p2.Filename { + return p1.Offset < p2.Offset + } + return p1.Filename < p2.Filename + }) + return issues, nil +} + +func nodeStr(node ast.Node) string { + var buf bytes.Buffer + fset := token.NewFileSet() + if err := printer.Fprint(&buf, fset, node); err != nil { + panic(err) + } + return buf.String() +} + +func (c *Checker) receivesSameValues(in []*callgraph.Edge, par *ssa.Parameter, pos int) string { + if ast.IsExported(par.Parent().Name()) { + // we might not have all call sites for an exported func + return "" + } + var seen constant.Value + origPos := pos + if par.Parent().Signature.Recv() != nil { + // go/ast's CallExpr.Args does not include the receiver, + // but go/ssa's equivalent does. + origPos-- + } + seenOrig := "" + count := 0 + for _, edge := range in { + call := edge.Site.Common() + cnst, ok := call.Args[pos].(*ssa.Const) + if !ok { + return "" // not a constant + } + origArg := "" + origCall := c.callByPos[call.Pos()] + if origPos >= len(origCall.Args) { + // variadic parameter that wasn't given + } else { + origArg = nodeStr(origCall.Args[origPos]) + } + if seen == nil { + seen = cnst.Value // first constant + seenOrig = origArg + count = 1 + } else if !constant.Compare(seen, token.EQL, cnst.Value) { + return "" // different constants + } else { + count++ + if origArg != seenOrig { + seenOrig = "" + } + } + } + if count < 4 { + return "" // not enough times, likely false positive + } + if seenOrig != "" && seenOrig != seen.String() { + return fmt.Sprintf("%s (%v)", seenOrig, seen) + } + return seen.String() +} + +func anyRealUse(par *ssa.Parameter, pos int) bool { +refLoop: + for _, ref := range *par.Referrers() { + switch x := ref.(type) { + case *ssa.Call: + if x.Call.Value != par.Parent() { + return true // not a recursive call + } + for i, arg := range x.Call.Args { + if arg != par { + continue + } + if i == pos { + // reused directly in a recursive call + continue refLoop + } + } + return true + case *ssa.Store: + if insertedStore(x) { + continue // inserted by go/ssa, not from the code + } + return true + default: + return true + } + } + return false +} + +func insertedStore(instr ssa.Instruction) bool { + if instr.Pos() != token.NoPos { + return false + } + store, ok := instr.(*ssa.Store) + if !ok { + return false + } + alloc, ok := store.Addr.(*ssa.Alloc) + // we want exactly one use of this alloc value for it to be + // inserted by ssa and dummy - the alloc instruction itself. + return ok && len(*alloc.Referrers()) == 1 +} + +var rxHarmlessCall = regexp.MustCompile(`(?i)\b(log(ger)?|errors)\b|\bf?print`) + +// dummyImpl reports whether a block is a dummy implementation. This is +// true if the block will almost immediately panic, throw or return +// constants only. +func dummyImpl(blk *ssa.BasicBlock) bool { + var ops [8]*ssa.Value + for _, instr := range blk.Instrs { + if insertedStore(instr) { + continue // inserted by go/ssa, not from the code + } + for _, val := range instr.Operands(ops[:0]) { + switch x := (*val).(type) { + case nil, *ssa.Const, *ssa.ChangeType, *ssa.Alloc, + *ssa.MakeInterface, *ssa.Function, + *ssa.Global, *ssa.IndexAddr, *ssa.Slice, + *ssa.UnOp, *ssa.Parameter: + case *ssa.Call: + if rxHarmlessCall.MatchString(x.Call.Value.String()) { + continue + } + default: + return false + } + } + switch x := instr.(type) { + case *ssa.Alloc, *ssa.Store, *ssa.UnOp, *ssa.BinOp, + *ssa.MakeInterface, *ssa.MakeMap, *ssa.Extract, + *ssa.IndexAddr, *ssa.FieldAddr, *ssa.Slice, + *ssa.Lookup, *ssa.ChangeType, *ssa.TypeAssert, + *ssa.Convert, *ssa.ChangeInterface: + // non-trivial expressions in panic/log/print + // calls + case *ssa.Return, *ssa.Panic: + return true + case *ssa.Call: + if rxHarmlessCall.MatchString(x.Call.Value.String()) { + continue + } + return x.Call.Value.Name() == "throw" // runtime's panic + default: + return false + } + } + return false +} + +func (c *Checker) declCounts(pkgDir string, pkgName string) map[string]int { + if m := c.cachedDeclCounts[pkgDir]; m != nil { + return m + } + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, pkgDir, nil, 0) + if err != nil { + println(err.Error()) + c.cachedDeclCounts[pkgDir] = map[string]int{} + return map[string]int{} + } + pkg := pkgs[pkgName] + count := make(map[string]int) + for _, file := range pkg.Files { + for _, decl := range file.Decls { + fd, _ := decl.(*ast.FuncDecl) + if fd == nil { + continue + } + name := astPrefix(fd.Recv) + fd.Name.Name + count[name]++ + } + } + c.cachedDeclCounts[pkgDir] = count + return count +} + +func astPrefix(recv *ast.FieldList) string { + if recv == nil { + return "" + } + expr := recv.List[0].Type + for { + star, _ := expr.(*ast.StarExpr) + if star == nil { + break + } + expr = star.X + } + id := expr.(*ast.Ident) + return id.Name + "." +} + +func (c *Checker) multipleImpls(info *loader.PackageInfo, fn *ssa.Function) bool { + if fn.Parent() != nil { // nested func + return false + } + path := c.prog.Fset.Position(fn.Pos()).Filename + if path == "" { // generated func, like init + return false + } + count := c.declCounts(filepath.Dir(path), info.Pkg.Name()) + name := fn.Name() + if fn.Signature.Recv() != nil { + tp := fn.Params[0].Type() + for { + point, _ := tp.(*types.Pointer) + if point == nil { + break + } + tp = point.Elem() + } + named := tp.(*types.Named) + name = named.Obj().Name() + "." + name + } + return count[name] > 1 +} + +func receivesExtractedArgs(sign *types.Signature, call *ssa.Call) bool { + if call == nil { + return false + } + if sign.Params().Len() < 2 { + return false // extracting into one param is ok + } + args := call.Operands(nil) + for i, arg := range args { + if i == 0 { + continue // *ssa.Function, func itself + } + if i == 1 && sign.Recv() != nil { + continue // method receiver + } + if _, ok := (*arg).(*ssa.Extract); !ok { + return false + } + } + return true +} + +func paramDesc(i int, v *types.Var) string { + name := v.Name() + if name != "" { + return name + } + return fmt.Sprintf("%d (%s)", i, v.Type().String()) +} diff --git a/tools/vendor/mvdan.cc/unparam/main.go b/tools/vendor/mvdan.cc/unparam/main.go new file mode 100644 index 000000000..09102c746 --- /dev/null +++ b/tools/vendor/mvdan.cc/unparam/main.go @@ -0,0 +1,34 @@ +// Copyright (c) 2017, Daniel Martí +// See LICENSE for licensing information + +package main // import "mvdan.cc/unparam" + +import ( + "flag" + "fmt" + "os" + + "mvdan.cc/unparam/check" +) + +var ( + tests = flag.Bool("tests", true, "include tests") + exported = flag.Bool("exported", false, "inspect exported functions") + debug = flag.Bool("debug", false, "debug prints") +) + +func main() { + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "usage: unparam [flags] [package ...]") + flag.PrintDefaults() + } + flag.Parse() + warns, err := check.UnusedParams(*tests, *exported, *debug, flag.Args()...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + for _, warn := range warns { + fmt.Println(warn) + } +} diff --git a/tools/vendor/vendor.json b/tools/vendor/vendor.json index 89d90addd..c30494bf8 100644 --- a/tools/vendor/vendor.json +++ b/tools/vendor/vendor.json @@ -9,10 +9,10 @@ "revisionTime": "2017-02-19T07:16:37Z" }, { - "checksumSHA1": "HORezzSZjiTLlEG6RV5e3VoBMTI=", + "checksumSHA1": "IL9TI69eihi/XUfY/k/uuBRI8WY=", "path": "github.com/GoASTScanner/gas", - "revision": "1beec25f7754273c9672a3368ea7048d4e73138e", - "revisionTime": "2017-04-11T19:38:53Z" + "revision": "6de76c92610b387855cdfdd53c99b149928916f7", + "revisionTime": "2017-10-04T14:01:47Z" }, { "checksumSHA1": "sK1dOo48F424xLCvE+ic8tRk7i8=", @@ -39,10 +39,10 @@ "revisionTime": "2015-02-08T22:17:26Z" }, { - "checksumSHA1": "dGfv3nwAfhSJ+20gR0yYgUwjPVY=", + "checksumSHA1": "HL3Dyr4dmbtBo+V3ULfRJMWAyoA=", "path": "github.com/alecthomas/gometalinter", - "revision": "258ea75208e542b8a18a08e053ec10379a9f50f9", - "revisionTime": "2017-08-02T01:03:20Z" + "revision": "212b1b91e362ea0b0e441c9b53ce31e81405c240", + "revisionTime": "2017-11-26T10:02:12Z" }, { "checksumSHA1": "fCc3grA7vIxfBru7R3SqjcW+oLI=", @@ -62,6 +62,12 @@ "revision": "e80c3b7ed292b052c7083b6fd7154a8422c33f65", "revisionTime": "2017-02-16T02:04:25Z" }, + { + "checksumSHA1": "rDM1YOCSZE4BLxZoBJV56/VmZSo=", + "path": "github.com/alexkohler/nakedret", + "revision": "c0e305a4f690fed163d47628bcc06a6d5655bf92", + "revisionTime": "2017-11-06T22:32:15Z" + }, { "checksumSHA1": "z6mKUmWeXRT0k+xrXxA5CLKOWiE=", "path": "github.com/client9/misspell", @@ -69,10 +75,10 @@ "revisionTime": "2017-05-30T22:15:07Z" }, { - "checksumSHA1": "YdtKuQqs3pDJhMxOruVxLAD/JSo=", + "checksumSHA1": "3Lbx+qNi8brwKa9dU41O6SEOW6c=", "path": "github.com/client9/misspell/cmd/misspell", - "revision": "e1f24e3e0b6b2c8bc98584370d382ae095b13c94", - "revisionTime": "2017-06-21T02:16:58Z" + "revision": "9ce5d979ffdaca6385988d7ad1079a33ec942d20", + "revisionTime": "2017-09-28T00:02:06Z" }, { "checksumSHA1": "ULnk7ggN82JFO0ZdBCmSsQH3Vh8=", @@ -80,6 +86,30 @@ "revision": "855e8d98f1852d48dde521e0522408d1fe7e836a", "revisionTime": "2015-10-22T10:31:44Z" }, + { + "checksumSHA1": "aLXmB5i+PFfM/dVqfHxoB3a8v/g=", + "path": "github.com/dnephin/govet", + "revision": "4a96d43e39d340b63daa8bc5576985aa599885f6", + "revisionTime": "2017-10-12T18:51:37Z", + "version": "fork", + "versionExact": "fork" + }, + { + "checksumSHA1": "Ba6cj2wCpDZcjE0kZ4Q32PJW4fg=", + "path": "github.com/dnephin/govet/internal/cfg", + "revision": "4a96d43e39d340b63daa8bc5576985aa599885f6", + "revisionTime": "2017-10-12T18:51:37Z", + "version": "fork", + "versionExact": "fork" + }, + { + "checksumSHA1": "ttQiZmni3k7Tbfi4/CX3JG6NjAw=", + "path": "github.com/dnephin/govet/internal/whitelist", + "revision": "4a96d43e39d340b63daa8bc5576985aa599885f6", + "revisionTime": "2017-10-12T18:51:37Z", + "version": "fork", + "versionExact": "fork" + }, { "checksumSHA1": "+U50xksZS8g53vruOP7px7cLilE=", "path": "github.com/golang/lint", @@ -89,8 +119,8 @@ { "checksumSHA1": "REm9ddVDL+Pm1OPxjVMxxpIjyw4=", "path": "github.com/golang/lint/golint", - "revision": "c5fb716d6688a859aae56d26d3e6070808df29f7", - "revisionTime": "2017-06-02T23:41:31Z" + "revision": "6aaf7c34af0f4c36a57e0c429bace4d706d8e931", + "revisionTime": "2017-10-05T22:33:36Z" }, { "checksumSHA1": "e/Kc2UOy1lKAy31xWlK37M1r2e8=", @@ -99,10 +129,10 @@ "revisionTime": "2015-01-27T13:39:51Z" }, { - "checksumSHA1": "F4OFLBCfPosjmNN+mpLtdbmlg2g=", + "checksumSHA1": "TKaX+8YCZQwYv7Kiy2o/E86V0aE=", "path": "github.com/gordonklaus/ineffassign", - "revision": "08cd83b3f513abe575f40d23acf2ae79088128bb", - "revisionTime": "2017-06-26T19:28:42Z" + "revision": "7bae11eba15a3285c75e388f77eb6357a2d73ee2", + "revisionTime": "2017-11-18T19:06:32Z" }, { "checksumSHA1": "DbSCKltce7IrgpDUF8+C7J+z+GU=", @@ -119,8 +149,8 @@ { "checksumSHA1": "NKvKUGq0lp/GjLS7Ffp7BAjcoTg=", "path": "github.com/kardianos/govendor", - "revision": "b6d23590f46ec4816cb726e013bae44954d58972", - "revisionTime": "2017-07-28T15:58:28Z" + "revision": "d644d03c4cc00cf7c88a365de8508c12ce56c392", + "revisionTime": "2017-11-27T22:34:05Z" }, { "checksumSHA1": "m24kWw3bFoAkKVvTjmxSLsywdHY=", @@ -195,10 +225,10 @@ "revisionTime": "2017-05-06T05:20:04Z" }, { - "checksumSHA1": "eva6MIhWu7k6BWB8G7OOYTJnpeA=", + "checksumSHA1": "GkLbM8KFqtVcp4kzc40+VinQukY=", "path": "github.com/kisielk/errcheck", - "revision": "23699b7e2cbfdb89481023524954ba2aeff6be90", - "revisionTime": "2017-03-17T17:34:29Z" + "revision": "b1445a9dd8285a50c6d1661d16f0a9ceb08125f7", + "revisionTime": "2017-09-18T09:31:01Z" }, { "checksumSHA1": "GP25rgIPshJh0tpiBg3Z8Dexqj4=", @@ -213,10 +243,16 @@ "revisionTime": "2016-11-30T08:01:11Z" }, { - "checksumSHA1": "18oHyXPLzfVDaXtMYIUyK23+slg=", + "checksumSHA1": "+ArBDwAK19OC1iDy1yEbXOC9sgQ=", "path": "github.com/mattn/goveralls", - "revision": "6efce81852ad1b7567c17ad71b03aeccc9dd9ae0", - "revisionTime": "2017-07-18T16:42:45Z" + "revision": "b71a1e4855f87991aff01c2c833a75a07059c61c", + "revisionTime": "2017-11-14T04:29:57Z" + }, + { + "checksumSHA1": "dk0ehYSmMaGLWFQPND3cVgk744I=", + "path": "github.com/mdempsky/maligned", + "revision": "08c8e9db1bce03f1af283686c0943fcb75f0109e", + "revisionTime": "2016-08-25T09:47:39Z" }, { "checksumSHA1": "90pFJb64MwgYvN5AmPVaWl87ZyU=", @@ -225,10 +261,10 @@ "revisionTime": "2016-08-03T23:01:54Z" }, { - "checksumSHA1": "aJa6VmPp5WZZ2Z705/TdhqQG/k4=", + "checksumSHA1": "k3eGAQ+pCIffVpgvoBrPuLK6Yz8=", "path": "github.com/mibk/dupl", - "revision": "3447d9b0cb5a3e7dccb1a9f1c975f35683f304e6", - "revisionTime": "2017-02-27T22:14:17Z" + "revision": "72dc2d83bec70e053e9294378aacb1a032f51a31", + "revisionTime": "2017-11-19T16:48:37Z" }, { "checksumSHA1": "sHi3Qhc2/0XMcIXB31NLXkbIoz0=", @@ -242,6 +278,12 @@ "revision": "3447d9b0cb5a3e7dccb1a9f1c975f35683f304e6", "revisionTime": "2017-02-27T22:14:17Z" }, + { + "checksumSHA1": "mGQ3tVbY9uLwfwoeQjvBBBm7yRw=", + "path": "github.com/mibk/dupl/printer", + "revision": "72dc2d83bec70e053e9294378aacb1a032f51a31", + "revisionTime": "2017-11-19T16:48:37Z" + }, { "checksumSHA1": "o9BJwhna5BuCTYWBGBo9VA+Ez/M=", "path": "github.com/mibk/dupl/suffixtree", @@ -269,8 +311,8 @@ { "checksumSHA1": "zD/VW+BRbOjxk1xq5bmdigi0cp8=", "path": "github.com/mvdan/interfacer/cmd/interfacer", - "revision": "0b82697b33395e082e4010e4122abb213eadfb5e", - "revisionTime": "2017-08-01T09:14:01Z" + "revision": "22c51662ff476dfd97944f74db1b263ed920ee83", + "revisionTime": "2017-04-06T16:05:15Z" }, { "checksumSHA1": "18GDIJCo0vo+mmQDIYmyb2JSWqo=", @@ -279,16 +321,16 @@ "revisionTime": "2017-04-06T10:09:31Z" }, { - "checksumSHA1": "nV5rJzg1w+8RG7lgDtuMXvtgJ6g=", + "checksumSHA1": "5LiZtu67exUdRJ0/QQvU/epG9no=", "path": "github.com/mvdan/unparam", - "revision": "4f8ea7ae6525529da4e3c90bda033935b80d709a", - "revisionTime": "2017-08-02T23:35:07Z" + "revision": "d647bb803b10a6777ee4c6a176416b91fa14713e", + "revisionTime": "2017-05-30T08:59:07Z" }, { - "checksumSHA1": "VE/ZFPAtX2obu4EFt1ajO8RydfU=", + "checksumSHA1": "tuOLCrGa9DjfXheKkMXtHtQu3bs=", "path": "github.com/mvdan/unparam/check", - "revision": "4f8ea7ae6525529da4e3c90bda033935b80d709a", - "revisionTime": "2017-08-02T23:35:07Z" + "revision": "d647bb803b10a6777ee4c6a176416b91fa14713e", + "revisionTime": "2017-05-30T08:59:07Z" }, { "checksumSHA1": "DP8R0Q7TDlHbhz9Livyj8RkRKvU=", @@ -461,14 +503,14 @@ { "checksumSHA1": "V4M/6A62nVBzPFxPbN+EAatCrVs=", "path": "golang.org/x/tools/cmd/goimports", - "revision": "4e70a1b26a7875f00ca1916637a876b5ffaeec59", - "revisionTime": "2017-08-02T20:37:59Z" + "revision": "36c7af3342056179a831c19ec142f2763b310f7b", + "revisionTime": "2017-11-29T19:51:57Z" }, { "checksumSHA1": "V6/A1ZOZ2GUOZcRWcXegtci2FoU=", "path": "golang.org/x/tools/cmd/gotype", - "revision": "4e70a1b26a7875f00ca1916637a876b5ffaeec59", - "revisionTime": "2017-08-02T20:37:59Z" + "revision": "36c7af3342056179a831c19ec142f2763b310f7b", + "revisionTime": "2017-11-29T19:51:57Z" }, { "checksumSHA1": "nD89PLkMqA5CakR8SoDuj3iQz1M=", @@ -575,44 +617,50 @@ { "checksumSHA1": "FG8LnaSRTHBnrPHwa0zW4zX9K7M=", "path": "honnef.co/go/tools/callgraph", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { "checksumSHA1": "fR7Q7BVwKHUEsUNGn6Q2zygAvTU=", "path": "honnef.co/go/tools/callgraph/static", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "tZUNdNOtkUpEB27J1ob00AFjsW4=", + "checksumSHA1": "YL/UCzWYvDXeFInLOLC1buYve6w=", "path": "honnef.co/go/tools/cmd/gosimple", - "revision": "ae0caf6437a82200bc1c349a4f761b4106409f0b", - "revisionTime": "2017-07-31T15:06:36Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "pHczeoSy3ltbM5mEQzrG845aiAk=", + "checksumSHA1": "84jyAI0Uv1PQ3fN3Ufi0T7/IpOw=", "path": "honnef.co/go/tools/cmd/megacheck", - "revision": "ae0caf6437a82200bc1c349a4f761b4106409f0b", - "revisionTime": "2017-07-31T15:06:36Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "fCBb/czTIH/xy9Ot7sA95uRDPxE=", + "checksumSHA1": "dP4Ft0yiZSTZOzzNho1Gg5b7o2w=", "path": "honnef.co/go/tools/cmd/staticcheck", - "revision": "ae0caf6437a82200bc1c349a4f761b4106409f0b", - "revisionTime": "2017-07-31T15:06:36Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "TXSOAGnX/YpKr2hBk9EiE3OlODI=", + "checksumSHA1": "Qipy1/3Z8n4UnoWF9X0sQ/VC5JI=", "path": "honnef.co/go/tools/cmd/unused", - "revision": "ae0caf6437a82200bc1c349a4f761b4106409f0b", - "revisionTime": "2017-07-31T15:06:36Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" + }, + { + "checksumSHA1": "smQXvyCgi0lsTRk7edZNx/z44rc=", + "path": "honnef.co/go/tools/deprecated", + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "3oKDy41034t1Hh/cEp2zvhMTU0w=", + "checksumSHA1": "ZQAEQCc18o76M9Cyncm1W5cczJ8=", "path": "honnef.co/go/tools/functions", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { "checksumSHA1": "NPXYxmyCQTv53OmGTCiHvbfIct4=", @@ -623,57 +671,97 @@ { "checksumSHA1": "ZWtH73AO33mmXmK2RfGwld1/00I=", "path": "honnef.co/go/tools/internal/sharedcheck", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "ALFoG0EDHSENgXXKVakOwdwXUSU=", + "checksumSHA1": "44oONKG61hcaBAPaA2jNhBgYLmE=", "path": "honnef.co/go/tools/lint", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "jevSfIBFuPrD5LPY/jF8EQWUDa8=", + "checksumSHA1": "gKJKwlFyfVebwzqA3P/N3HJIq/0=", "path": "honnef.co/go/tools/lint/lintutil", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "Qws66gYHUhxTO8ddXJTdr0Wl9d8=", + "checksumSHA1": "kkVszwWx3L3erU3QkMDIppFv34o=", "path": "honnef.co/go/tools/simple", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { "checksumSHA1": "OB5QjdkxC9rYXruXUuoYSsxK+VY=", "path": "honnef.co/go/tools/ssa", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { "checksumSHA1": "jgNTrcXg52qlqjkb/R2vKxtcDu4=", "path": "honnef.co/go/tools/ssa/ssautil", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "/z2xUUyR/MlzlPdU06U2g12P2oU=", + "checksumSHA1": "9kqdoLEm2gHS9QVE1OXWBCtRqhI=", "path": "honnef.co/go/tools/staticcheck", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { "checksumSHA1": "9u74dwwwi+tg9eBr86by4i4CMNM=", "path": "honnef.co/go/tools/staticcheck/vrp", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" }, { - "checksumSHA1": "4rxcUL+C0+sbSNRQvkWeCJ4J0Hg=", + "checksumSHA1": "tDBL3athXaJ9JoiY75NktH+OTjQ=", "path": "honnef.co/go/tools/unused", - "revision": "e94d1c1a34c6b61d8d06c7793b8f22cd0dfcdd90", - "revisionTime": "2017-05-22T19:09:05Z" + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" + }, + { + "checksumSHA1": "RY0sZkXnDI/MxBauBD28dwuulSs=", + "path": "honnef.co/go/tools/version", + "revision": "376b3b58b9e4def403181ee2fd3d4cc7de8375ae", + "revisionTime": "2017-11-25T07:40:24Z" + }, + { + "path": "local/numcpus", + "revision": "" + }, + { + "checksumSHA1": "uKQMTzjTI15hy1sTGgsZ/b8PImA=", + "path": "mvdan.cc/interfacer", + "revision": "d7e7372184a059b8fd99d96a593e3811bf989d75", + "revisionTime": "2017-09-08T18:13:45Z" + }, + { + "checksumSHA1": "0+bmt/m62xZSbyATqBbp1MTy6ZI=", + "path": "mvdan.cc/interfacer/check", + "revision": "d7e7372184a059b8fd99d96a593e3811bf989d75", + "revisionTime": "2017-09-08T18:13:45Z" + }, + { + "checksumSHA1": "pCQUv3qVciM9V98kVNkOw1JWKzs=", + "path": "mvdan.cc/lint", + "revision": "adc824a0674b99099789b6188a058d485eaf61c0", + "revisionTime": "2017-09-08T18:12:59Z" + }, + { + "checksumSHA1": "TCj3HvSF+NLYG4SPv7Mv4GQ09xE=", + "path": "mvdan.cc/unparam", + "revision": "644240604b3c6d719b0f981ef28cd1168962efb9", + "revisionTime": "2017-11-25T20:11:03Z" + }, + { + "checksumSHA1": "Cdz2FebEWti1vA3eW36lypbYn9w=", + "path": "mvdan.cc/unparam/check", + "revision": "644240604b3c6d719b0f981ef28cd1168962efb9", + "revisionTime": "2017-11-25T20:11:03Z" } ], - "rootPath": "github.com/wrouesnel/postgres_exporter/tools" + "rootPath": "github.com/wrouesnel/self-contained-go-project/tools" }