mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-08-28 22:28:43 -04:00
support go vet
This commit is contained in:
parent
3f1787bf89
commit
0a5924afbc
41 changed files with 6652 additions and 28 deletions
13
Gopkg.lock
generated
13
Gopkg.lock
generated
|
@ -46,6 +46,17 @@
|
|||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/go"
|
||||
packages = [
|
||||
".",
|
||||
"lib/cfg",
|
||||
"lib/whitelist"
|
||||
]
|
||||
revision = "c5b798e3c473354ef919e8fe4e0a11b4a9e36d18"
|
||||
source = "github.com/golangci/govet"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/mock"
|
||||
packages = ["gomock"]
|
||||
|
@ -201,6 +212,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "51ff4a5cc3ccf4539cdef62ac5a53cf5c57180d76d7ac9179380922d45b618c1"
|
||||
inputs-digest = "6d7156f4c2a3467b6a312e9ddb07dc6f07385f9637c29e42a386afded9bb6ef3"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -50,6 +50,11 @@
|
|||
branch = "master"
|
||||
source = "github.com/golangci/errcheck"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/golang/go"
|
||||
branch = "master"
|
||||
source = "github.com/golangci/govet"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
|
|
@ -37,6 +37,8 @@ func (e *Executor) initRun() {
|
|||
runCmd.Flags().BoolVar(&rc.Errcheck.CheckClose, "errcheck.check-close", false, "Check missed error checks on .Close() calls")
|
||||
runCmd.Flags().BoolVar(&rc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Check for ignored type assertion results")
|
||||
runCmd.Flags().BoolVar(&rc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Check for errors assigned to blank identifier: _ = errFunc()")
|
||||
|
||||
runCmd.Flags().BoolVar(&rc.Govet.CheckShadowing, "govet.check-shadowing", true, "Check for shadowed variables")
|
||||
}
|
||||
|
||||
func (e Executor) executeRun(cmd *cobra.Command, args []string) {
|
||||
|
@ -76,7 +78,7 @@ func (e Executor) executeRun(cmd *cobra.Command, args []string) {
|
|||
runner := pkg.SimpleRunner{
|
||||
Processors: []processors.Processor{
|
||||
processors.MaxLinterIssuesPerFile{},
|
||||
processors.UniqByLineProcessor{},
|
||||
//processors.UniqByLineProcessor{},
|
||||
processors.NewPathPrettifier(),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ type Run struct {
|
|||
CheckTypeAssertions bool
|
||||
CheckAssignToBlank bool
|
||||
}
|
||||
Govet struct {
|
||||
CheckShadowing bool
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
|
34
pkg/golinters/govet.go
Normal file
34
pkg/golinters/govet.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
govetAPI "github.com/golang/go"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
"github.com/golangci/golangci-shared/pkg/executors"
|
||||
)
|
||||
|
||||
type govet struct{}
|
||||
|
||||
func (govet) Name() string {
|
||||
return "govet"
|
||||
}
|
||||
|
||||
func (g govet) Run(ctx context.Context, exec executors.Executor, cfg *config.Run) (*result.Result, error) {
|
||||
issues, err := govetAPI.Run(cfg.Paths, cfg.BuildTags, cfg.Govet.CheckShadowing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &result.Result{}
|
||||
for _, i := range issues {
|
||||
res.Issues = append(res.Issues, result.Issue{
|
||||
File: i.Pos.Filename,
|
||||
LineNumber: i.Pos.Line,
|
||||
Text: i.Message,
|
||||
FromLinter: g.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
func TestGovetSimple(t *testing.T) {
|
||||
const source = `package p
|
||||
|
||||
import "os"
|
||||
|
||||
func f() error {
|
||||
return &os.PathError{"first", "path", os.ErrNotExist}
|
||||
}
|
||||
`
|
||||
|
||||
ExpectIssues(t, govet, source, []result.Issue{
|
||||
NewIssue("govet", "os.PathError composite literal uses unkeyed fields", 6),
|
||||
})
|
||||
}
|
|
@ -6,8 +6,7 @@ const pathLineColMessage = `^(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P
|
|||
const pathLineMessage = `^(?P<path>.*?\.go):(?P<line>\d+):\s*(?P<message>.*)$`
|
||||
|
||||
var golint = newLinter("golint", newLinterConfig("", pathLineColMessage, ""))
|
||||
var govet = newLinter("govet", newLinterConfig("", pathLineMessage, "", "--no-recurse"))
|
||||
|
||||
func GetSupportedLinters() []pkg.Linter {
|
||||
return []pkg.Linter{errcheck{}}
|
||||
return []pkg.Linter{govet{}, errcheck{}}
|
||||
}
|
||||
|
|
2
pkg/golinters/testdata/errcheck.go
vendored
2
pkg/golinters/testdata/errcheck.go
vendored
|
@ -1,4 +1,4 @@
|
|||
package p
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
|
19
pkg/golinters/testdata/govet.go
vendored
Normal file
19
pkg/golinters/testdata/govet.go
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
package testdata
|
||||
|
||||
import "os"
|
||||
|
||||
func govet() error {
|
||||
return &os.PathError{"first", "path", os.ErrNotExist} // ERROR "os.PathError composite literal uses unkeyed fields"
|
||||
}
|
||||
|
||||
func govetShadow(f *os.File, buf []byte) (err error) {
|
||||
if f != nil {
|
||||
_, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at testdata/govet.go:9"
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Use variable to trigger shadowing error
|
||||
_ = err
|
||||
return
|
||||
}
|
|
@ -3,6 +3,7 @@ package pkg
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
|
@ -21,8 +22,16 @@ type SimpleRunner struct {
|
|||
|
||||
func (r SimpleRunner) Run(ctx context.Context, linters []Linter, exec executors.Executor, cfg *config.Run) ([]result.Issue, error) {
|
||||
results := []result.Result{}
|
||||
savedStdout, savedStderr := os.Stdout, os.Stderr
|
||||
devNull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open null device %q: %s", os.DevNull, err)
|
||||
}
|
||||
|
||||
for _, linter := range linters {
|
||||
os.Stdout, os.Stderr = devNull, devNull
|
||||
res, err := linter.Run(ctx, exec, cfg)
|
||||
os.Stdout, os.Stderr = savedStdout, savedStderr
|
||||
if err != nil {
|
||||
analytics.Log(ctx).Warnf("Can't run linter %+v: %s", linter, err)
|
||||
continue
|
||||
|
@ -35,7 +44,7 @@ func (r SimpleRunner) Run(ctx context.Context, linters []Linter, exec executors.
|
|||
results = append(results, *res)
|
||||
}
|
||||
|
||||
results, err := r.processResults(results)
|
||||
results, err = r.processResults(results)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't process results: %s", err)
|
||||
}
|
||||
|
|
27
vendor/github.com/golang/go/LICENSE
generated
vendored
Normal file
27
vendor/github.com/golang/go/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 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.
|
33
vendor/github.com/golang/go/README
generated
vendored
Normal file
33
vendor/github.com/golang/go/README
generated
vendored
Normal file
|
@ -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 and 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 checks 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.
|
730
vendor/github.com/golang/go/asmdecl.go
generated
vendored
Normal file
730
vendor/github.com/golang/go/asmdecl.go
generated
vendored
Normal file
|
@ -0,0 +1,730 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"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
|
||||
asmArray
|
||||
asmInterface
|
||||
asmEmptyInterface
|
||||
asmStruct
|
||||
asmComplex
|
||||
)
|
||||
|
||||
// An asmArch describes assembly parameters for an architecture
|
||||
type asmArch struct {
|
||||
name string
|
||||
bigEndian bool
|
||||
stack string
|
||||
lr bool
|
||||
// calculated during initialization
|
||||
sizes types.Sizes
|
||||
intSize int
|
||||
ptrSize int
|
||||
maxAlign int
|
||||
}
|
||||
|
||||
// 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{name: "386", bigEndian: false, stack: "SP", lr: false}
|
||||
asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
|
||||
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true}
|
||||
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false}
|
||||
asmArchAmd64p32 = asmArch{name: "amd64p32", bigEndian: false, stack: "SP", lr: false}
|
||||
asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
|
||||
asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
|
||||
asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
|
||||
asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
|
||||
asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true}
|
||||
asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true}
|
||||
asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
|
||||
|
||||
arches = []*asmArch{
|
||||
&asmArch386,
|
||||
&asmArchArm,
|
||||
&asmArchArm64,
|
||||
&asmArchAmd64,
|
||||
&asmArchAmd64p32,
|
||||
&asmArchMips,
|
||||
&asmArchMipsLE,
|
||||
&asmArchMips64,
|
||||
&asmArchMips64LE,
|
||||
&asmArchPpc64,
|
||||
&asmArchPpc64LE,
|
||||
&asmArchS390X,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, arch := range arches {
|
||||
arch.sizes = types.SizesFor("gc", arch.name)
|
||||
if arch.sizes == nil {
|
||||
panic("missing SizesFor for gc/" + arch.name)
|
||||
}
|
||||
arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int]))
|
||||
arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer]))
|
||||
arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64]))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// There can be multiple architectures in a single +build line,
|
||||
// so accumulate them all and then prefer the one that
|
||||
// matches build.Default.GOARCH.
|
||||
var archCandidates []*asmArch
|
||||
for _, fld := range strings.Fields(m[1]) {
|
||||
for _, a := range arches {
|
||||
if a.name == fld {
|
||||
archCandidates = append(archCandidates, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, a := range archCandidates {
|
||||
if a.name == build.Default.GOARCH {
|
||||
archCandidates = []*asmArch{a}
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(archCandidates) > 0 {
|
||||
arch = archCandidates[0].name
|
||||
archDef = archCandidates[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m := asmTEXT.FindStringSubmatch(line); m != nil {
|
||||
flushRet()
|
||||
if arch == "" {
|
||||
// Arch not specified by filename or build tags.
|
||||
// Fall back to build.Default.GOARCH.
|
||||
for _, a := range arches {
|
||||
if a.name == build.Default.GOARCH {
|
||||
arch = a.name
|
||||
archDef = a
|
||||
break
|
||||
}
|
||||
}
|
||||
if arch == "" {
|
||||
f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name)
|
||||
continue Files
|
||||
}
|
||||
}
|
||||
fnName = m[2]
|
||||
if pkgName := strings.TrimSpace(m[1]); pkgName != "" {
|
||||
pathParts := strings.Split(pkgName, "∕")
|
||||
pkgName = pathParts[len(pathParts)-1]
|
||||
if pkgName != f.pkg.path {
|
||||
f.Warnf(token.NoPos, "%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", f.name, lineno, arch, fnName, pkgName)
|
||||
fn = nil
|
||||
fnName = ""
|
||||
continue
|
||||
}
|
||||
}
|
||||
flag := m[3]
|
||||
fn = knownFunc[fnName][arch]
|
||||
if fn != nil {
|
||||
size, _ := strconv.Atoi(m[5])
|
||||
if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
|
||||
badf("wrong argument size %d; expected $...-%d", size, fn.size)
|
||||
}
|
||||
}
|
||||
localSize, _ = strconv.Atoi(m[4])
|
||||
localSize += archDef.intSize
|
||||
if archDef.lr && !strings.Contains(flag, "NOFRAME") {
|
||||
// Account for caller's saved LR
|
||||
localSize += archDef.intSize
|
||||
}
|
||||
argSize, _ = strconv.Atoi(m[5])
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func asmKindForType(t types.Type, size int) asmKind {
|
||||
switch t := t.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch t.Kind() {
|
||||
case types.String:
|
||||
return asmString
|
||||
case types.Complex64, types.Complex128:
|
||||
return asmComplex
|
||||
}
|
||||
return asmKind(size)
|
||||
case *types.Pointer, *types.Chan, *types.Map, *types.Signature:
|
||||
return asmKind(size)
|
||||
case *types.Struct:
|
||||
return asmStruct
|
||||
case *types.Interface:
|
||||
if t.Empty() {
|
||||
return asmEmptyInterface
|
||||
}
|
||||
return asmInterface
|
||||
case *types.Array:
|
||||
return asmArray
|
||||
case *types.Slice:
|
||||
return asmSlice
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// A component is an assembly-addressable component of a composite type,
|
||||
// or a composite type itself.
|
||||
type component struct {
|
||||
size int
|
||||
offset int
|
||||
kind asmKind
|
||||
typ string
|
||||
suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
|
||||
outer string // The suffix for immediately containing composite type.
|
||||
}
|
||||
|
||||
func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component {
|
||||
return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer}
|
||||
}
|
||||
|
||||
// componentsOfType generates a list of components of type t.
|
||||
// For example, given string, the components are the string itself, the base, and the length.
|
||||
func componentsOfType(arch *asmArch, t types.Type) []component {
|
||||
return appendComponentsRecursive(arch, t, nil, "", 0)
|
||||
}
|
||||
|
||||
// appendComponentsRecursive implements componentsOfType.
|
||||
// Recursion is required to correct handle structs and arrays,
|
||||
// which can contain arbitrary other types.
|
||||
func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component {
|
||||
s := t.String()
|
||||
size := int(arch.sizes.Sizeof(t))
|
||||
kind := asmKindForType(t, size)
|
||||
cc = append(cc, newComponent(suffix, kind, s, off, size, suffix))
|
||||
|
||||
switch kind {
|
||||
case 8:
|
||||
if arch.ptrSize == 4 {
|
||||
w1, w2 := "lo", "hi"
|
||||
if arch.bigEndian {
|
||||
w1, w2 = w2, w1
|
||||
}
|
||||
cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix))
|
||||
}
|
||||
|
||||
case asmEmptyInterface:
|
||||
cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
|
||||
|
||||
case asmInterface:
|
||||
cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
|
||||
|
||||
case asmSlice:
|
||||
cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix))
|
||||
|
||||
case asmString:
|
||||
cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix))
|
||||
|
||||
case asmComplex:
|
||||
fsize := size / 2
|
||||
cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix))
|
||||
cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix))
|
||||
|
||||
case asmStruct:
|
||||
tu := t.Underlying().(*types.Struct)
|
||||
fields := make([]*types.Var, tu.NumFields())
|
||||
for i := 0; i < tu.NumFields(); i++ {
|
||||
fields[i] = tu.Field(i)
|
||||
}
|
||||
offsets := arch.sizes.Offsetsof(fields)
|
||||
for i, f := range fields {
|
||||
cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
|
||||
}
|
||||
|
||||
case asmArray:
|
||||
tu := t.Underlying().(*types.Array)
|
||||
elem := tu.Elem()
|
||||
// Calculate offset of each element array.
|
||||
fields := []*types.Var{
|
||||
types.NewVar(token.NoPos, nil, "fake0", elem),
|
||||
types.NewVar(token.NoPos, nil, "fake1", elem),
|
||||
}
|
||||
offsets := arch.sizes.Offsetsof(fields)
|
||||
elemoff := int(offsets[1])
|
||||
for i := 0; i < int(tu.Len()); i++ {
|
||||
cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), i*elemoff)
|
||||
}
|
||||
}
|
||||
|
||||
return cc
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// addParams adds asmVars for each of the parameters in list.
|
||||
// isret indicates whether the list are the arguments or the return values.
|
||||
addParams := func(list []*ast.Field, isret bool) {
|
||||
argnum := 0
|
||||
for _, fld := range list {
|
||||
t := f.pkg.types[fld.Type].Type
|
||||
align := int(arch.sizes.Alignof(t))
|
||||
size := int(arch.sizes.Sizeof(t))
|
||||
offset += -offset & (align - 1)
|
||||
cc := componentsOfType(arch, t)
|
||||
|
||||
// names is the list of names with this type.
|
||||
names := fld.Names
|
||||
if len(names) == 0 {
|
||||
// Anonymous args will be called arg, arg1, arg2, ...
|
||||
// Similarly so for return values: ret, ret1, ret2, ...
|
||||
name := "arg"
|
||||
if isret {
|
||||
name = "ret"
|
||||
}
|
||||
if argnum > 0 {
|
||||
name += strconv.Itoa(argnum)
|
||||
}
|
||||
names = []*ast.Ident{ast.NewIdent(name)}
|
||||
}
|
||||
argnum += len(names)
|
||||
|
||||
// Create variable for each name.
|
||||
for _, id := range names {
|
||||
name := id.Name
|
||||
for _, c := range cc {
|
||||
outer := name + c.outer
|
||||
v := asmVar{
|
||||
name: name + c.suffix,
|
||||
kind: c.kind,
|
||||
typ: c.typ,
|
||||
off: offset + c.offset,
|
||||
size: c.size,
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
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, false)
|
||||
if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
|
||||
offset += -offset & (arch.maxAlign - 1)
|
||||
addParams(decl.Type.Results.List, true)
|
||||
}
|
||||
fn.size = offset
|
||||
m[arch.name] = fn
|
||||
}
|
||||
|
||||
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 "mips", "mipsle", "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
|
||||
}
|
||||
case "s390x":
|
||||
switch op {
|
||||
case "MOVB", "MOVBZ":
|
||||
src = 1
|
||||
case "MOVH", "MOVHZ":
|
||||
src = 2
|
||||
case "MOVW", "MOVWZ", "FMOVS":
|
||||
src = 4
|
||||
case "MOVD", "FMOVD":
|
||||
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
|
||||
vs := v.size
|
||||
vt := v.typ
|
||||
switch vk {
|
||||
case asmInterface, asmEmptyInterface, asmString, asmSlice:
|
||||
// allow reference to first word (pointer)
|
||||
vk = v.inner[0].kind
|
||||
vs = v.inner[0].size
|
||||
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, vs, inner.String())
|
||||
}
|
||||
}
|
52
vendor/github.com/golang/go/assign.go
generated
vendored
Normal file
52
vendor/github.com/golang/go/assign.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
// 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 govet
|
||||
|
||||
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 "<expr> = <expr>".
|
||||
// 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 hasSideEffects(f, lhs) || hasSideEffects(f, rhs) {
|
||||
continue // expressions may not be equal
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
71
vendor/github.com/golang/go/atomic.go
generated
vendored
Normal file
71
vendor/github.com/golang/go/atomic.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
pkgIdent, _ := sel.X.(*ast.Ident)
|
||||
pkgName, ok := f.pkg.uses[pkgIdent].(*types.PkgName)
|
||||
if !ok || pkgName.Imported().Path() != "sync/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")
|
||||
}
|
||||
}
|
197
vendor/github.com/golang/go/bool.go
generated
vendored
Normal file
197
vendor/github.com/golang/go/bool.go
generated
vendored
Normal file
|
@ -0,0 +1,197 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
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(f, 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(f *File, 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(f, 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(f *File, e ast.Expr) bool {
|
||||
safe := true
|
||||
ast.Inspect(e, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.CallExpr:
|
||||
// Don't call Type.Underlying(), since its lack
|
||||
// lets us see the NamedFuncType(x) type
|
||||
// conversion as a *types.Named.
|
||||
_, ok := f.pkg.types[n.Fun].Type.(*types.Signature)
|
||||
if ok {
|
||||
// Conservatively assume that all function and
|
||||
// method calls have side effects for
|
||||
// now. This will include func type
|
||||
// conversions, but it's ok given that
|
||||
// this is the conservative side.
|
||||
safe = false
|
||||
return false
|
||||
}
|
||||
// It's a type conversion, which cannot
|
||||
// have side effects.
|
||||
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
|
||||
}
|
||||
}
|
91
vendor/github.com/golang/go/buildtag.go
generated
vendored
Normal file
91
vendor/github.com/golang/go/buildtag.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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)
|
||||
setExit(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
|
||||
}
|
||||
elem = strings.TrimPrefix(elem, "!")
|
||||
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)
|
||||
setExit(1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
141
vendor/github.com/golang/go/cgo.go
generated
vendored
Normal file
141
vendor/github.com/golang/go/cgo.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
// 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 govet
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
pkgname, ok := f.pkg.uses[id].(*types.PkgName)
|
||||
if !ok || pkgname.Imported().Path() != "C" {
|
||||
return
|
||||
}
|
||||
|
||||
// A call to C.CBytes passes a pointer but is always safe.
|
||||
if sel.Sel.Name == "CBytes" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, arg := range x.Args {
|
||||
if !typeOKForCgoCall(cgoBaseType(f, arg), make(map[types.Type]bool)) {
|
||||
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), make(map[types.Type]bool)) {
|
||||
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 reports whether the type of arg is OK to pass to a
|
||||
// C function using cgo. This is not true for Go types with embedded
|
||||
// pointers. m is used to avoid infinite recursion on recursive types.
|
||||
func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
|
||||
if t == nil || m[t] {
|
||||
return true
|
||||
}
|
||||
m[t] = true
|
||||
switch t := t.Underlying().(type) {
|
||||
case *types.Chan, *types.Map, *types.Signature, *types.Slice:
|
||||
return false
|
||||
case *types.Pointer:
|
||||
return typeOKForCgoCall(t.Elem(), m)
|
||||
case *types.Array:
|
||||
return typeOKForCgoCall(t.Elem(), m)
|
||||
case *types.Struct:
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if !typeOKForCgoCall(t.Field(i).Type(), m) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
87
vendor/github.com/golang/go/composite.go
generated
vendored
Normal file
87
vendor/github.com/golang/go/composite.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/go/lib/whitelist"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
under := typ.Underlying()
|
||||
for {
|
||||
ptr, ok := under.(*types.Pointer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
under = ptr.Elem().Underlying()
|
||||
}
|
||||
if _, ok := under.(*types.Struct); !ok {
|
||||
// skip non-struct composite literals
|
||||
return
|
||||
}
|
||||
if isLocalType(f, typ) {
|
||||
// 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, typ types.Type) bool {
|
||||
switch x := typ.(type) {
|
||||
case *types.Struct:
|
||||
// struct literals are local types
|
||||
return true
|
||||
case *types.Pointer:
|
||||
return isLocalType(f, x.Elem())
|
||||
case *types.Named:
|
||||
// names in package foo are local to foo_test too
|
||||
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(f.pkg.path, "_test")
|
||||
}
|
||||
return false
|
||||
}
|
256
vendor/github.com/golang/go/copylock.go
generated
vendored
Normal file
256
vendor/github.com/golang/go/copylock.go
generated
vendored
Normal file
|
@ -0,0 +1,256 @@
|
|||
// 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 govet
|
||||
|
||||
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) {
|
||||
var id *ast.Ident
|
||||
switch fun := ce.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
id = fun
|
||||
case *ast.SelectorExpr:
|
||||
id = fun.Sel
|
||||
}
|
||||
if fun, ok := f.pkg.uses[id].(*types.Builtin); ok {
|
||||
switch fun.Name() {
|
||||
case "new", "len", "cap", "Sizeof":
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, x := range ce.Args {
|
||||
if path := lockPathRhs(f, x); path != nil {
|
||||
f.Badf(x.Pos(), "call of %s copies lock value: %v", f.gofmt(ce.Fun), 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
|
||||
}
|
||||
|
||||
for {
|
||||
atyp, ok := typ.Underlying().(*types.Array)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
typ = atyp.Elem()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
108
vendor/github.com/golang/go/dead.go
generated
vendored
Normal file
108
vendor/github.com/golang/go/dead.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2017 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.
|
||||
//
|
||||
// Simplified dead code detector. Used for skipping certain checks
|
||||
// on unreachable code (for instance, shift checks on arch-specific code).
|
||||
|
||||
package govet
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
)
|
||||
|
||||
// updateDead puts unreachable "if" and "case" nodes into f.dead.
|
||||
func (f *File) updateDead(node ast.Node) {
|
||||
if f.dead[node] {
|
||||
// The node is already marked as dead.
|
||||
return
|
||||
}
|
||||
|
||||
switch stmt := node.(type) {
|
||||
case *ast.IfStmt:
|
||||
// "if" branch is dead if its condition evaluates
|
||||
// to constant false.
|
||||
v := f.pkg.types[stmt.Cond].Value
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if !constant.BoolVal(v) {
|
||||
f.setDead(stmt.Body)
|
||||
return
|
||||
}
|
||||
f.setDead(stmt.Else)
|
||||
case *ast.SwitchStmt:
|
||||
// Case clause with empty switch tag is dead if it evaluates
|
||||
// to constant false.
|
||||
if stmt.Tag == nil {
|
||||
BodyLoopBool:
|
||||
for _, stmt := range stmt.Body.List {
|
||||
cc := stmt.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
// Skip default case.
|
||||
continue
|
||||
}
|
||||
for _, expr := range cc.List {
|
||||
v := f.pkg.types[expr].Value
|
||||
if v == nil || v.Kind() != constant.Bool || constant.BoolVal(v) {
|
||||
continue BodyLoopBool
|
||||
}
|
||||
}
|
||||
f.setDead(cc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Case clause is dead if its constant value doesn't match
|
||||
// the constant value from the switch tag.
|
||||
// TODO: This handles integer comparisons only.
|
||||
v := f.pkg.types[stmt.Tag].Value
|
||||
if v == nil || v.Kind() != constant.Int {
|
||||
return
|
||||
}
|
||||
tagN, ok := constant.Uint64Val(v)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
BodyLoopInt:
|
||||
for _, x := range stmt.Body.List {
|
||||
cc := x.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
// Skip default case.
|
||||
continue
|
||||
}
|
||||
for _, expr := range cc.List {
|
||||
v := f.pkg.types[expr].Value
|
||||
if v == nil {
|
||||
continue BodyLoopInt
|
||||
}
|
||||
n, ok := constant.Uint64Val(v)
|
||||
if !ok || tagN == n {
|
||||
continue BodyLoopInt
|
||||
}
|
||||
}
|
||||
f.setDead(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setDead marks the node and all the children as dead.
|
||||
func (f *File) setDead(node ast.Node) {
|
||||
dv := deadVisitor{
|
||||
f: f,
|
||||
}
|
||||
ast.Walk(dv, node)
|
||||
}
|
||||
|
||||
type deadVisitor struct {
|
||||
f *File
|
||||
}
|
||||
|
||||
func (dv deadVisitor) Visit(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
dv.f.dead[node] = true
|
||||
return dv
|
||||
}
|
298
vendor/github.com/golang/go/deadcode.go
generated
vendored
Normal file
298
vendor/github.com/golang/go/deadcode.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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
|
||||
}
|
||||
}
|
224
vendor/github.com/golang/go/doc.go
generated
vendored
Normal file
224
vendor/github.com/golang/go/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,224 @@
|
|||
// 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.
|
||||
|
||||
Vet is normally invoked using the go command by running "go vet":
|
||||
|
||||
go vet
|
||||
vets the package in the current directory.
|
||||
|
||||
go vet package/path/name
|
||||
vets the package whose path is provided.
|
||||
|
||||
Use "go help packages" to see other ways of specifying which packages to vet.
|
||||
|
||||
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.
|
||||
|
||||
By default vet uses the object files generated by 'go install some/pkg' to typecheck the code.
|
||||
If the -source flag is provided, vet uses only source code.
|
||||
|
||||
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.
|
||||
|
||||
HTTP responses used incorrectly
|
||||
|
||||
Flag: -httpresponse
|
||||
|
||||
Mistakes deferring a function call on an HTTP response before
|
||||
checking whether the error returned with the response was nil.
|
||||
|
||||
Failure to call the cancelation function returned by 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.
|
||||
|
||||
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.
|
||||
|
||||
Struct tags
|
||||
|
||||
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.
|
||||
|
||||
Tests 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.
|
||||
|
||||
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.
|
||||
|
||||
Using vet directly
|
||||
|
||||
For testing and debugging vet can be run directly by invoking
|
||||
"go tool vet" or just running the binary. Run this way, vet might not
|
||||
have up to date information for imported packages.
|
||||
|
||||
go tool vet source/directory/*.go
|
||||
vets the files named, all of which must be in the same package.
|
||||
|
||||
go tool vet source/directory
|
||||
recursively descends the directory, vetting each package it finds.
|
||||
|
||||
*/
|
||||
package govet
|
64
vendor/github.com/golang/go/golangci.go
generated
vendored
Normal file
64
vendor/github.com/golang/go/golangci.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package govet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Issue struct {
|
||||
Pos token.Position
|
||||
Message string
|
||||
}
|
||||
|
||||
var foundIssues []Issue
|
||||
|
||||
func Run(paths, buildTags []string, checkShadowing bool) ([]Issue, error) {
|
||||
foundIssues = nil
|
||||
|
||||
if checkShadowing {
|
||||
experimental["shadow"] = false
|
||||
}
|
||||
for name, setting := range report {
|
||||
if *setting == unset && !experimental[name] {
|
||||
*setting = setTrue
|
||||
}
|
||||
}
|
||||
|
||||
tagList = buildTags
|
||||
|
||||
initPrintFlags()
|
||||
initUnusedFlags()
|
||||
|
||||
for _, name := range paths {
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("can't mix dirs and files")
|
||||
}
|
||||
if dirsRun {
|
||||
for _, name := range paths {
|
||||
walkDir(name)
|
||||
}
|
||||
return foundIssues, nil
|
||||
}
|
||||
if doPackage(paths, nil) == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return foundIssues, nil
|
||||
}
|
137
vendor/github.com/golang/go/httpresponse.go
generated
vendored
Normal file
137
vendor/github.com/golang/go/httpresponse.go
generated
vendored
Normal file
|
@ -0,0 +1,137 @@
|
|||
// 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 file contains the check for http.Response values being used before
|
||||
// checking for errors.
|
||||
|
||||
package govet
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("httpresponse",
|
||||
"check errors are checked before using an http Response",
|
||||
checkHTTPResponse, callExpr)
|
||||
}
|
||||
|
||||
func checkHTTPResponse(f *File, node ast.Node) {
|
||||
call := node.(*ast.CallExpr)
|
||||
if !isHTTPFuncOrMethodOnClient(f, call) {
|
||||
return // the function call is not related to this check.
|
||||
}
|
||||
|
||||
finder := &blockStmtFinder{node: call}
|
||||
ast.Walk(finder, f.file)
|
||||
stmts := finder.stmts()
|
||||
if len(stmts) < 2 {
|
||||
return // the call to the http function is the last statement of the block.
|
||||
}
|
||||
|
||||
asg, ok := stmts[0].(*ast.AssignStmt)
|
||||
if !ok {
|
||||
return // the first statement is not assignment.
|
||||
}
|
||||
resp := rootIdent(asg.Lhs[0])
|
||||
if resp == nil {
|
||||
return // could not find the http.Response in the assignment.
|
||||
}
|
||||
|
||||
def, ok := stmts[1].(*ast.DeferStmt)
|
||||
if !ok {
|
||||
return // the following statement is not a defer.
|
||||
}
|
||||
root := rootIdent(def.Call.Fun)
|
||||
if root == nil {
|
||||
return // could not find the receiver of the defer call.
|
||||
}
|
||||
|
||||
if resp.Obj == root.Obj {
|
||||
f.Badf(root.Pos(), "using %s before checking for errors", resp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// isHTTPFuncOrMethodOnClient checks whether the given call expression is on
|
||||
// either a function of the net/http package or a method of http.Client that
|
||||
// returns (*http.Response, error).
|
||||
func isHTTPFuncOrMethodOnClient(f *File, expr *ast.CallExpr) bool {
|
||||
fun, _ := expr.Fun.(*ast.SelectorExpr)
|
||||
sig, _ := f.pkg.types[fun].Type.(*types.Signature)
|
||||
if sig == nil {
|
||||
return false // the call is not on of the form x.f()
|
||||
}
|
||||
|
||||
res := sig.Results()
|
||||
if res.Len() != 2 {
|
||||
return false // the function called does not return two values.
|
||||
}
|
||||
if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") {
|
||||
return false // the first return type is not *http.Response.
|
||||
}
|
||||
if !types.Identical(res.At(1).Type().Underlying(), errorType) {
|
||||
return false // the second return type is not error
|
||||
}
|
||||
|
||||
typ := f.pkg.types[fun.X].Type
|
||||
if typ == nil {
|
||||
id, ok := fun.X.(*ast.Ident)
|
||||
return ok && id.Name == "http" // function in net/http package.
|
||||
}
|
||||
|
||||
if isNamedType(typ, "net/http", "Client") {
|
||||
return true // method on http.Client.
|
||||
}
|
||||
ptr, ok := typ.(*types.Pointer)
|
||||
return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
|
||||
}
|
||||
|
||||
// blockStmtFinder is an ast.Visitor that given any ast node can find the
|
||||
// statement containing it and its succeeding statements in the same block.
|
||||
type blockStmtFinder struct {
|
||||
node ast.Node // target of search
|
||||
stmt ast.Stmt // innermost statement enclosing argument to Visit
|
||||
block *ast.BlockStmt // innermost block enclosing argument to Visit.
|
||||
}
|
||||
|
||||
// Visit finds f.node performing a search down the ast tree.
|
||||
// It keeps the last block statement and statement seen for later use.
|
||||
func (f *blockStmtFinder) Visit(node ast.Node) ast.Visitor {
|
||||
if node == nil || f.node.Pos() < node.Pos() || f.node.End() > node.End() {
|
||||
return nil // not here
|
||||
}
|
||||
switch n := node.(type) {
|
||||
case *ast.BlockStmt:
|
||||
f.block = n
|
||||
case ast.Stmt:
|
||||
f.stmt = n
|
||||
}
|
||||
if f.node.Pos() == node.Pos() && f.node.End() == node.End() {
|
||||
return nil // found
|
||||
}
|
||||
return f // keep looking
|
||||
}
|
||||
|
||||
// stmts returns the statements of f.block starting from the one including f.node.
|
||||
func (f *blockStmtFinder) stmts() []ast.Stmt {
|
||||
for i, v := range f.block.List {
|
||||
if f.stmt == v {
|
||||
return f.block.List[i:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
|
||||
func rootIdent(n ast.Node) *ast.Ident {
|
||||
switch n := n.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
return rootIdent(n.X)
|
||||
case *ast.Ident:
|
||||
return n
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
512
vendor/github.com/golang/go/lib/cfg/builder.go
generated
vendored
Normal file
512
vendor/github.com/golang/go/lib/cfg/builder.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
142
vendor/github.com/golang/go/lib/cfg/cfg.go
generated
vendored
Normal file
142
vendor/github.com/golang/go/lib/cfg/cfg.go
generated
vendored
Normal file
|
@ -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))
|
||||
}
|
28
vendor/github.com/golang/go/lib/whitelist/whitelist.go
generated
vendored
Normal file
28
vendor/github.com/golang/go/lib/whitelist/whitelist.go
generated
vendored
Normal file
|
@ -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,
|
||||
}
|
323
vendor/github.com/golang/go/lostcancel.go
generated
vendored
Normal file
323
vendor/github.com/golang/go/lostcancel.go
generated
vendored
Normal file
|
@ -0,0 +1,323 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/go/lib/cfg"
|
||||
)
|
||||
|
||||
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:
|
||||
obj := f.pkg.defs[node.Name]
|
||||
if obj == nil {
|
||||
return // type error (e.g. duplicate function declaration)
|
||||
}
|
||||
sig, _ = obj.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 ""
|
||||
}
|
625
vendor/github.com/golang/go/main.go
generated
vendored
Normal file
625
vendor/github.com/golang/go/main.go
generated
vendored
Normal file
|
@ -0,0 +1,625 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go.
|
||||
|
||||
var (
|
||||
verbose = flag.Bool("v", false, "verbose")
|
||||
source = flag.Bool("source", false, "import from source instead of compiled object files")
|
||||
tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing")
|
||||
tagList = []string{} // exploded version of tags flag; set in main
|
||||
|
||||
vcfg vetConfig
|
||||
mustTypecheck bool
|
||||
)
|
||||
|
||||
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
|
||||
forStmt *ast.ForStmt
|
||||
funcDecl *ast.FuncDecl
|
||||
funcLit *ast.FuncLit
|
||||
genDecl *ast.GenDecl
|
||||
interfaceType *ast.InterfaceType
|
||||
rangeStmt *ast.RangeStmt
|
||||
returnStmt *ast.ReturnStmt
|
||||
structType *ast.StructType
|
||||
|
||||
// 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 vet:\n")
|
||||
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 keys are the objects that are receivers of a "String()
|
||||
// string" method. The value reports whether the method has a
|
||||
// pointer receiver.
|
||||
// This is used by the recursiveStringer method in print.go.
|
||||
stringerPtrs map[*ast.Object]bool
|
||||
|
||||
// Registered checkers to run.
|
||||
checkers map[ast.Node][]func(*File, ast.Node)
|
||||
|
||||
// Unreachable nodes; can be ignored in shift check.
|
||||
dead map[ast.Node]bool
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accept space-separated tags because that matches
|
||||
// the go command's other subcommands.
|
||||
// Accept commas because go tool vet traditionally has.
|
||||
tagList = strings.Fields(strings.Replace(*tags, ",", " ", -1))
|
||||
|
||||
initPrintFlags()
|
||||
initUnusedFlags()
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
Usage()
|
||||
}
|
||||
|
||||
// Special case for "go vet" passing an explicit configuration:
|
||||
// single argument ending in vet.cfg.
|
||||
// Once we have a more general mechanism for obtaining this
|
||||
// information from build tools like the go command,
|
||||
// vet should be changed to use it. This vet.cfg hack is an
|
||||
// experiment to learn about what form that information should take.
|
||||
if flag.NArg() == 1 && strings.HasSuffix(flag.Arg(0), "vet.cfg") {
|
||||
doPackageCfg(flag.Arg(0))
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vetConfig is the JSON config struct prepared by the Go command.
|
||||
type vetConfig struct {
|
||||
Compiler string
|
||||
Dir string
|
||||
ImportPath string
|
||||
GoFiles []string
|
||||
ImportMap map[string]string
|
||||
PackageFile map[string]string
|
||||
|
||||
SucceedOnTypecheckFailure bool
|
||||
|
||||
imp types.Importer
|
||||
}
|
||||
|
||||
func (v *vetConfig) Import(path string) (*types.Package, error) {
|
||||
if v.imp == nil {
|
||||
v.imp = importer.For(v.Compiler, v.openPackageFile)
|
||||
}
|
||||
if path == "unsafe" {
|
||||
return v.imp.Import("unsafe")
|
||||
}
|
||||
p := v.ImportMap[path]
|
||||
if p == "" {
|
||||
return nil, fmt.Errorf("unknown import path %q", path)
|
||||
}
|
||||
if v.PackageFile[p] == "" {
|
||||
return nil, fmt.Errorf("unknown package file for import %q", path)
|
||||
}
|
||||
return v.imp.Import(p)
|
||||
}
|
||||
|
||||
func (v *vetConfig) openPackageFile(path string) (io.ReadCloser, error) {
|
||||
file := v.PackageFile[path]
|
||||
if file == "" {
|
||||
// Note that path here has been translated via v.ImportMap,
|
||||
// unlike in the error in Import above. We prefer the error in
|
||||
// Import, but it's worth diagnosing this one too, just in case.
|
||||
return nil, fmt.Errorf("unknown package file for %q", path)
|
||||
}
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// doPackageCfg analyzes a single package described in a config file.
|
||||
func doPackageCfg(cfgFile string) {
|
||||
js, err := ioutil.ReadFile(cfgFile)
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
}
|
||||
if err := json.Unmarshal(js, &vcfg); err != nil {
|
||||
errorf("parsing vet config %s: %v", cfgFile, err)
|
||||
}
|
||||
stdImporter = &vcfg
|
||||
inittypes()
|
||||
mustTypecheck = true
|
||||
doPackage(vcfg.GoFiles, nil)
|
||||
}
|
||||
|
||||
// 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(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(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(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,
|
||||
dead: make(map[ast.Node]bool),
|
||||
})
|
||||
}
|
||||
if len(astFiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
pkg := new(Package)
|
||||
pkg.path = astFiles[0].Name.Name
|
||||
pkg.files = files
|
||||
// Type check the package.
|
||||
errs := pkg.check(fs, astFiles)
|
||||
if errs != nil {
|
||||
if *verbose || mustTypecheck {
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
}
|
||||
if mustTypecheck {
|
||||
// This message could be silenced, and we could just exit,
|
||||
// but it might be helpful at least at first to make clear that the
|
||||
// above errors are coming from vet and not the compiler
|
||||
// (they often look like compiler errors, such as "declared but not used").
|
||||
errorf("typecheck failures")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
foundIssues = append(foundIssues, Issue{
|
||||
Pos: f.fset.Position(pos),
|
||||
Message: fmt.Sprint(args...),
|
||||
})
|
||||
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{}) {
|
||||
foundIssues = append(foundIssues, Issue{
|
||||
Pos: f.fset.Position(pos),
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
// locPrefix returns a formatted representation of the position for use as a line prefix.
|
||||
func (f *File) locPrefix(pos token.Pos) string {
|
||||
if pos == token.NoPos {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s: ", f.loc(pos))
|
||||
}
|
||||
|
||||
// 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.locPrefix(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.locPrefix(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 {
|
||||
f.updateDead(node)
|
||||
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.ForStmt:
|
||||
key = forStmt
|
||||
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
|
||||
case *ast.StructType:
|
||||
key = structType
|
||||
}
|
||||
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()
|
||||
}
|
179
vendor/github.com/golang/go/method.go
generated
vendored
Normal file
179
vendor/github.com/golang/go/method.go
generated
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
// 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 govet
|
||||
|
||||
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
|
||||
"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 {
|
||||
expect = strings.TrimPrefix(expect, "=")
|
||||
// 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
|
||||
}
|
67
vendor/github.com/golang/go/nilfunc.go
generated
vendored
Normal file
67
vendor/github.com/golang/go/nilfunc.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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]
|
||||
}
|
807
vendor/github.com/golang/go/print.go
generated
vendored
Normal file
807
vendor/github.com/golang/go/print.go
generated
vendored
Normal file
|
@ -0,0 +1,807 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"regexp"
|
||||
"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]
|
||||
}
|
||||
|
||||
isPrint[strings.ToLower(name)] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(rsc): Incorporate user-defined printf wrappers again.
|
||||
// The general plan is to allow vet of one package P to output
|
||||
// additional information to supply to later vets of packages
|
||||
// importing P. Then vet of P can record a list of printf wrappers
|
||||
// and the later vet using P.Printf will find it in the list and check it.
|
||||
// That's not ready for Go 1.10.
|
||||
// When that does happen, uncomment the user-defined printf
|
||||
// wrapper tests in testdata/print.go.
|
||||
|
||||
// isPrint records the print functions.
|
||||
// If a key ends in 'f' then it is assumed to be a formatted print.
|
||||
var isPrint = map[string]bool{
|
||||
"fmt.Errorf": true,
|
||||
"fmt.Fprint": true,
|
||||
"fmt.Fprintf": true,
|
||||
"fmt.Fprintln": true,
|
||||
"fmt.Print": true,
|
||||
"fmt.Printf": true,
|
||||
"fmt.Println": true,
|
||||
"fmt.Sprint": true,
|
||||
"fmt.Sprintf": true,
|
||||
"fmt.Sprintln": true,
|
||||
"log.Fatal": true,
|
||||
"log.Fatalf": true,
|
||||
"log.Fatalln": true,
|
||||
"log.Logger.Fatal": true,
|
||||
"log.Logger.Fatalf": true,
|
||||
"log.Logger.Fatalln": true,
|
||||
"log.Logger.Panic": true,
|
||||
"log.Logger.Panicf": true,
|
||||
"log.Logger.Panicln": true,
|
||||
"log.Logger.Printf": true,
|
||||
"log.Logger.Println": true,
|
||||
"log.Panic": true,
|
||||
"log.Panicf": true,
|
||||
"log.Panicln": true,
|
||||
"log.Print": true,
|
||||
"log.Printf": true,
|
||||
"log.Println": true,
|
||||
"testing.B.Error": true,
|
||||
"testing.B.Errorf": true,
|
||||
"testing.B.Fatal": true,
|
||||
"testing.B.Fatalf": true,
|
||||
"testing.B.Log": true,
|
||||
"testing.B.Logf": true,
|
||||
"testing.B.Skip": true,
|
||||
"testing.B.Skipf": true,
|
||||
"testing.T.Error": true,
|
||||
"testing.T.Errorf": true,
|
||||
"testing.T.Fatal": true,
|
||||
"testing.T.Fatalf": true,
|
||||
"testing.T.Log": true,
|
||||
"testing.T.Logf": true,
|
||||
"testing.T.Skip": true,
|
||||
"testing.T.Skipf": true,
|
||||
"testing.TB.Error": true,
|
||||
"testing.TB.Errorf": true,
|
||||
"testing.TB.Fatal": true,
|
||||
"testing.TB.Fatalf": true,
|
||||
"testing.TB.Log": true,
|
||||
"testing.TB.Logf": true,
|
||||
"testing.TB.Skip": true,
|
||||
"testing.TB.Skipf": 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) (format string, idx 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 := stringConstantArg(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. Fall back to scanning for the first
|
||||
// string constant in the call.
|
||||
for idx := range call.Args {
|
||||
if s, ok := stringConstantArg(f, call, idx); ok {
|
||||
return s, idx
|
||||
}
|
||||
if f.pkg.types[call.Args[idx]].Type == types.Typ[types.String] {
|
||||
// Skip checking a call with a non-constant format
|
||||
// string argument, since its contents are unavailable
|
||||
// for validation.
|
||||
return "", -1
|
||||
}
|
||||
}
|
||||
return "", -1
|
||||
}
|
||||
|
||||
// stringConstantArg 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
|
||||
// constant.
|
||||
func stringConstantArg(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 f.pkg.typesPkg == nil {
|
||||
// This check now requires type information.
|
||||
return
|
||||
}
|
||||
|
||||
if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) {
|
||||
// Remember we saw this.
|
||||
if f.stringerPtrs == nil {
|
||||
f.stringerPtrs = make(map[*ast.Object]bool)
|
||||
}
|
||||
if l := d.Recv.List; len(l) == 1 {
|
||||
if n := l[0].Names; len(n) == 1 {
|
||||
typ := f.pkg.types[l[0].Type]
|
||||
_, ptrRecv := typ.Type.(*types.Pointer)
|
||||
f.stringerPtrs[n[0].Obj] = ptrRecv
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Construct name like pkg.Printf or pkg.Type.Printf for lookup.
|
||||
var name string
|
||||
switch x := call.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
if fn, ok := f.pkg.uses[x].(*types.Func); ok {
|
||||
var pkg string
|
||||
if fn.Pkg() == nil || fn.Pkg() == f.pkg.typesPkg {
|
||||
pkg = vcfg.ImportPath
|
||||
} else {
|
||||
pkg = fn.Pkg().Path()
|
||||
}
|
||||
name = pkg + "." + x.Name
|
||||
break
|
||||
}
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// Check for "fmt.Printf".
|
||||
if id, ok := x.X.(*ast.Ident); ok {
|
||||
if pkgName, ok := f.pkg.uses[id].(*types.PkgName); ok {
|
||||
name = pkgName.Imported().Path() + "." + x.Sel.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check for t.Logf where t is a *testing.T.
|
||||
if sel := f.pkg.selectors[x]; sel != nil {
|
||||
recv := sel.Recv()
|
||||
if p, ok := recv.(*types.Pointer); ok {
|
||||
recv = p.Elem()
|
||||
}
|
||||
if named, ok := recv.(*types.Named); ok {
|
||||
obj := named.Obj()
|
||||
var pkg string
|
||||
if obj.Pkg() == nil || obj.Pkg() == f.pkg.typesPkg {
|
||||
pkg = vcfg.ImportPath
|
||||
} else {
|
||||
pkg = obj.Pkg().Path()
|
||||
}
|
||||
name = pkg + "." + obj.Name() + "." + x.Sel.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
shortName := name[strings.LastIndex(name, ".")+1:]
|
||||
|
||||
_, ok = isPrint[name]
|
||||
if !ok {
|
||||
// Next look up just "printf", for use with -printfuncs.
|
||||
_, ok = isPrint[strings.ToLower(shortName)]
|
||||
}
|
||||
if ok {
|
||||
if strings.HasSuffix(name, "f") {
|
||||
f.checkPrintf(call, shortName)
|
||||
} else {
|
||||
f.checkPrint(call, shortName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
||||
|
||||
// isFormatter reports whether t satisfies fmt.Formatter.
|
||||
// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt.
|
||||
func (f *File) isFormatter(t types.Type) bool {
|
||||
return formatterType != nil && types.Implements(t, formatterType)
|
||||
}
|
||||
|
||||
// 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
|
||||
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.
|
||||
hasIndex bool // Whether the argument is indexed.
|
||||
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(), "%s call has arguments but no formatting directives", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Hard part: check formats against args.
|
||||
argNum := firstArg
|
||||
maxArgNum := firstArg
|
||||
anyIndex := false
|
||||
for i, w := 0, 0; i < len(format); i += w {
|
||||
w = 1
|
||||
if format[i] != '%' {
|
||||
continue
|
||||
}
|
||||
state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum)
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
w = len(state.format)
|
||||
if !f.okPrintfArg(call, state) { // One error per format is enough.
|
||||
return
|
||||
}
|
||||
if state.hasIndex {
|
||||
anyIndex = true
|
||||
}
|
||||
if len(state.argNums) > 0 {
|
||||
// Continue with the next sequential argument.
|
||||
argNum = state.argNums[len(state.argNums)-1] + 1
|
||||
}
|
||||
for _, n := range state.argNums {
|
||||
if n >= maxArgNum {
|
||||
maxArgNum = n + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dotdotdot is hard.
|
||||
if call.Ellipsis.IsValid() && maxArgNum >= len(call.Args)-1 {
|
||||
return
|
||||
}
|
||||
// If any formats are indexed, extra arguments are ignored.
|
||||
if anyIndex {
|
||||
return
|
||||
}
|
||||
// There should be no leftover arguments.
|
||||
if maxArgNum != len(call.Args) {
|
||||
expect := maxArgNum - firstArg
|
||||
numArgs := len(call.Args) - firstArg
|
||||
f.Badf(call.Pos(), "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg"))
|
||||
}
|
||||
}
|
||||
|
||||
// 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.nbytes++ // skip '['
|
||||
start := s.nbytes
|
||||
s.scanNum()
|
||||
ok := true
|
||||
if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
|
||||
ok = false
|
||||
s.nbytes = strings.Index(s.format, "]")
|
||||
if s.nbytes < 0 {
|
||||
s.file.Badf(s.call.Pos(), "%s format %s is missing closing ]", s.name, s.format)
|
||||
return false
|
||||
}
|
||||
}
|
||||
arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
|
||||
if err != nil || !ok || arg32 <= 0 || arg32 > int64(len(s.call.Args)-s.firstArg) {
|
||||
s.file.Badf(s.call.Pos(), "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes])
|
||||
return false
|
||||
}
|
||||
s.nbytes++ // skip ']'
|
||||
arg := int(arg32)
|
||||
arg += s.firstArg - 1 // We want to zero-index the actual arguments.
|
||||
s.argNum = arg
|
||||
s.hasIndex = true
|
||||
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.
|
||||
firstArg: firstArg,
|
||||
file: f,
|
||||
call: call,
|
||||
}
|
||||
// There may be flags.
|
||||
state.parseFlags()
|
||||
// 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 !state.indexPending && !state.parseIndex() {
|
||||
return nil
|
||||
}
|
||||
if state.nbytes == len(state.format) {
|
||||
f.Badf(call.Pos(), "%s format %s is missing verb at end of string", name, state.format)
|
||||
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.
|
||||
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 | argPointer},
|
||||
{'e', sharpNumFlag, argFloat | argComplex},
|
||||
{'E', sharpNumFlag, argFloat | argComplex},
|
||||
{'f', sharpNumFlag, argFloat | argComplex},
|
||||
{'F', sharpNumFlag, argFloat | argComplex},
|
||||
{'g', sharpNumFlag, argFloat | argComplex},
|
||||
{'G', sharpNumFlag, 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 | argPointer},
|
||||
{'X', sharpNumFlag, argRune | argInt | argString | argPointer},
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Does current arg implement fmt.Formatter?
|
||||
formatter := false
|
||||
if state.argNum < len(call.Args) {
|
||||
if tv, ok := f.pkg.types[call.Args[state.argNum]]; ok {
|
||||
formatter = f.isFormatter(tv.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if !formatter {
|
||||
if !found {
|
||||
f.Badf(call.Pos(), "%s format %s has unknown verb %c", state.name, state.format, state.verb)
|
||||
return false
|
||||
}
|
||||
for _, flag := range state.flags {
|
||||
// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11.
|
||||
// See issues 23598 and 23605.
|
||||
if flag == '0' {
|
||||
continue
|
||||
}
|
||||
if !strings.ContainsRune(v.flags, rune(flag)) {
|
||||
f.Badf(call.Pos(), "%s format %s has unrecognized flag %c", state.name, state.format, 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, state) {
|
||||
return
|
||||
}
|
||||
arg := call.Args[argNum]
|
||||
if !f.matchArgType(argInt, nil, arg) {
|
||||
f.Badf(call.Pos(), "%s format %s uses non-int %s as argument of *", state.name, state.format, f.gofmt(arg))
|
||||
return false
|
||||
}
|
||||
}
|
||||
if state.verb == '%' || formatter {
|
||||
return true
|
||||
}
|
||||
argNum := state.argNums[len(state.argNums)-1]
|
||||
if !f.argCanBeChecked(call, len(state.argNums)-1, state) {
|
||||
return false
|
||||
}
|
||||
arg := call.Args[argNum]
|
||||
if f.isFunctionValue(arg) && state.verb != 'p' && state.verb != 'T' {
|
||||
f.Badf(call.Pos(), "%s format %s arg %s is a func value, not called", state.name, state.format, 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(), "%s format %s has arg %s of wrong type %s", state.name, state.format, f.gofmt(arg), typeString)
|
||||
return false
|
||||
}
|
||||
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) {
|
||||
f.Badf(call.Pos(), "%s format %s with arg %s causes recursive String method call", state.name, state.format, 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.stringerPtrs) == 0 {
|
||||
return false
|
||||
}
|
||||
ptr := 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
|
||||
ptr = true
|
||||
}
|
||||
}
|
||||
|
||||
// It's unlikely to be a recursive stringer if it has a Format method.
|
||||
if typ := f.pkg.types[e].Type; typ != nil {
|
||||
if f.isFormatter(typ) {
|
||||
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.
|
||||
ptrRecv, exist := f.stringerPtrs[obj]
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
// We also need to check that using &t when we declared String
|
||||
// on (t *T) is ok; in such a case, the address is printed.
|
||||
if ptr && ptrRecv {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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, state *formatState) bool {
|
||||
argNum := state.argNums[formatArg]
|
||||
if argNum <= 0 {
|
||||
// Shouldn't happen, so catch it with prejudice.
|
||||
panic("negative arg num")
|
||||
}
|
||||
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(), "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg"))
|
||||
return false
|
||||
}
|
||||
|
||||
// printFormatRE is the regexp we match and report as a possible format string
|
||||
// in the first argument to unformatted prints like fmt.Print.
|
||||
// We exclude the space flag, so that printing a string like "x % y" is not reported as a format.
|
||||
var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE)
|
||||
|
||||
const (
|
||||
flagsRE = `[+\-#]*`
|
||||
indexOptRE = `(\[[0-9]+\])?`
|
||||
numOptRE = `([0-9]+|` + indexOptRE + `\*)?`
|
||||
verbRE = `[bcdefgopqstvxEFGTUX]`
|
||||
)
|
||||
|
||||
// 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:]
|
||||
|
||||
if firstArg == 0 {
|
||||
if sel, ok := call.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(), "%s does not take io.Writer but has first arg %s", name, f.gofmt(call.Args[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||
// Ignore trailing % character in lit.Value.
|
||||
// The % in "abc 0.0%" couldn't be a formatting directive.
|
||||
s := strings.TrimSuffix(lit.Value, `%"`)
|
||||
if strings.Contains(s, "%") {
|
||||
m := printFormatRE.FindStringSubmatch(s)
|
||||
if m != nil {
|
||||
f.Badf(call.Pos(), "%s call has possible formatting directive %s", name, m[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
str, _ := strconv.Unquote(lit.Value)
|
||||
if strings.HasSuffix(str, "\n") {
|
||||
f.Badf(call.Pos(), "%s arg list ends with redundant newline", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, arg := range args {
|
||||
if f.isFunctionValue(arg) {
|
||||
f.Badf(call.Pos(), "%s arg %s is a func value, not called", name, f.gofmt(arg))
|
||||
}
|
||||
if f.recursiveStringer(arg) {
|
||||
f.Badf(call.Pos(), "%s arg %s causes recursive call to String method", name, f.gofmt(arg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// count(n, what) returns "1 what" or "N whats"
|
||||
// (assuming the plural of what is whats).
|
||||
func count(n int, what string) string {
|
||||
if n == 1 {
|
||||
return "1 " + what
|
||||
}
|
||||
return fmt.Sprintf("%d %ss", n, what)
|
||||
}
|
105
vendor/github.com/golang/go/rangeloop.go
generated
vendored
Normal file
105
vendor/github.com/golang/go/rangeloop.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
// 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 govet
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func init() {
|
||||
register("rangeloops",
|
||||
"check that loop variables are used correctly",
|
||||
checkLoop,
|
||||
rangeStmt, forStmt)
|
||||
}
|
||||
|
||||
// checkLoop walks the body of the provided loop statement, checking whether
|
||||
// its index or value variables are used unsafely inside goroutines or deferred
|
||||
// function literals.
|
||||
func checkLoop(f *File, node ast.Node) {
|
||||
// Find the variables updated by the loop statement.
|
||||
var vars []*ast.Ident
|
||||
addVar := func(expr ast.Expr) {
|
||||
if id, ok := expr.(*ast.Ident); ok {
|
||||
vars = append(vars, id)
|
||||
}
|
||||
}
|
||||
var body *ast.BlockStmt
|
||||
switch n := node.(type) {
|
||||
case *ast.RangeStmt:
|
||||
body = n.Body
|
||||
addVar(n.Key)
|
||||
addVar(n.Value)
|
||||
case *ast.ForStmt:
|
||||
body = n.Body
|
||||
switch post := n.Post.(type) {
|
||||
case *ast.AssignStmt:
|
||||
// e.g. for p = head; p != nil; p = p.next
|
||||
for _, lhs := range post.Lhs {
|
||||
addVar(lhs)
|
||||
}
|
||||
case *ast.IncDecStmt:
|
||||
// e.g. for i := 0; i < n; i++
|
||||
addVar(post.X)
|
||||
}
|
||||
}
|
||||
if vars == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Inspect a go or defer statement
|
||||
// if it's the last one in the loop body.
|
||||
// (We give up if there are following statements,
|
||||
// because it's hard to prove go isn't followed by wait,
|
||||
// or defer by return.)
|
||||
if len(body.List) == 0 {
|
||||
return
|
||||
}
|
||||
var last *ast.CallExpr
|
||||
switch s := body.List[len(body.List)-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 (e.g. struct field name)
|
||||
return true
|
||||
}
|
||||
for _, v := range vars {
|
||||
if v.Obj == id.Obj {
|
||||
f.Badf(id.Pos(), "loop variable %s captured by func literal",
|
||||
id.Name)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
246
vendor/github.com/golang/go/shadow.go
generated
vendored
Normal file
246
vendor/github.com/golang/go/shadow.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
98
vendor/github.com/golang/go/shift.go
generated
vendored
Normal file
98
vendor/github.com/golang/go/shift.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
// 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 govet
|
||||
|
||||
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) {
|
||||
if f.dead[node] {
|
||||
// Skip shift checks on unreachable nodes.
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
if f.pkg.types[x].Value != nil {
|
||||
// Ignore shifts of constants.
|
||||
// These are frequently used for bit-twiddling tricks
|
||||
// like ^uint(0) >> 63 for 32/64 bit detection and compatibility.
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
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:
|
||||
size = uintBitSize
|
||||
case types.Uintptr:
|
||||
size = uintptrBitSize
|
||||
default:
|
||||
return
|
||||
}
|
||||
if amt >= size {
|
||||
ident := f.gofmt(x)
|
||||
f.Badf(node.Pos(), "%s (%d bits) too small for shift of %d", ident, size, amt)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
uintBitSize = 8 * archSizes.Sizeof(types.Typ[types.Uint])
|
||||
uintptrBitSize = 8 * archSizes.Sizeof(types.Typ[types.Uintptr])
|
||||
)
|
226
vendor/github.com/golang/go/structtag.go
generated
vendored
Normal file
226
vendor/github.com/golang/go/structtag.go
generated
vendored
Normal file
|
@ -0,0 +1,226 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("structtags",
|
||||
"check that struct field tags have canonical format and apply to exported fields as needed",
|
||||
checkStructFieldTags,
|
||||
structType)
|
||||
}
|
||||
|
||||
// checkStructFieldTags checks all the field tags of a struct, including checking for duplicates.
|
||||
func checkStructFieldTags(f *File, node ast.Node) {
|
||||
var seen map[[2]string]token.Pos
|
||||
for _, field := range node.(*ast.StructType).Fields.List {
|
||||
checkCanonicalFieldTag(f, field, &seen)
|
||||
}
|
||||
}
|
||||
|
||||
var checkTagDups = []string{"json", "xml"}
|
||||
var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
|
||||
|
||||
// checkCanonicalFieldTag checks a single struct field tag.
|
||||
func checkCanonicalFieldTag(f *File, field *ast.Field, seen *map[[2]string]token.Pos) {
|
||||
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 {
|
||||
raw, _ := strconv.Unquote(field.Tag.Value) // field.Tag.Value is known to be a quoted string
|
||||
f.Badf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", raw, err)
|
||||
}
|
||||
|
||||
for _, key := range checkTagDups {
|
||||
val := reflect.StructTag(tag).Get(key)
|
||||
if val == "" || val == "-" || val[0] == ',' {
|
||||
continue
|
||||
}
|
||||
if key == "xml" && len(field.Names) > 0 && field.Names[0].Name == "XMLName" {
|
||||
// XMLName defines the XML element name of the struct being
|
||||
// checked. That name cannot collide with element or attribute
|
||||
// names defined on other fields of the struct. Vet does not have a
|
||||
// check for untagged fields of type struct defining their own name
|
||||
// by containing a field named XMLName; see issue 18256.
|
||||
continue
|
||||
}
|
||||
if i := strings.Index(val, ","); i >= 0 {
|
||||
if key == "xml" {
|
||||
// Use a separate namespace for XML attributes.
|
||||
for _, opt := range strings.Split(val[i:], ",") {
|
||||
if opt == "attr" {
|
||||
key += " attribute" // Key is part of the error message.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
val = val[:i]
|
||||
}
|
||||
if *seen == nil {
|
||||
*seen = map[[2]string]token.Pos{}
|
||||
}
|
||||
if pos, ok := (*seen)[[2]string{key, val}]; ok {
|
||||
var name string
|
||||
if len(field.Names) > 0 {
|
||||
name = field.Names[0].Name
|
||||
} else {
|
||||
name = field.Type.(*ast.Ident).Name
|
||||
}
|
||||
f.Badf(field.Pos(), "struct field %s repeats %s tag %q also at %s", name, key, val, f.loc(pos))
|
||||
} else {
|
||||
(*seen)[[2]string{key, val}] = field.Pos()
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
for _, enc := range [...]string{"json", "xml"} {
|
||||
if reflect.StructTag(tag).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")
|
||||
errTagValueSpace = errors.New("suspicious space in struct tag value")
|
||||
errTagSpace = errors.New("key:\"value\" pairs not separated by spaces")
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
||||
n := 0
|
||||
for ; tag != ""; n++ {
|
||||
if n > 0 && tag != "" && tag[0] != ' ' {
|
||||
// More restrictive than reflect, but catches likely mistakes
|
||||
// like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
|
||||
return errTagSpace
|
||||
}
|
||||
// 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
|
||||
}
|
||||
key := tag[:i]
|
||||
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:]
|
||||
|
||||
value, err := strconv.Unquote(qvalue)
|
||||
if err != nil {
|
||||
return errTagValueSyntax
|
||||
}
|
||||
|
||||
if !checkTagSpaces[key] {
|
||||
continue
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "xml":
|
||||
// If the first or last character in the XML tag is a space, it is
|
||||
// suspicious.
|
||||
if strings.Trim(value, " ") != value {
|
||||
return errTagValueSpace
|
||||
}
|
||||
|
||||
// If there are multiple spaces, they are suspicious.
|
||||
if strings.Count(value, " ") > 1 {
|
||||
return errTagValueSpace
|
||||
}
|
||||
|
||||
// If there is no comma, skip the rest of the checks.
|
||||
comma := strings.IndexRune(value, ',')
|
||||
if comma < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the character before a comma is a space, this is suspicious.
|
||||
if comma > 0 && value[comma-1] == ' ' {
|
||||
return errTagValueSpace
|
||||
}
|
||||
value = value[comma+1:]
|
||||
case "json":
|
||||
// JSON allows using spaces in the name, so skip it.
|
||||
comma := strings.IndexRune(value, ',')
|
||||
if comma < 0 {
|
||||
continue
|
||||
}
|
||||
value = value[comma+1:]
|
||||
}
|
||||
|
||||
if strings.IndexByte(value, ' ') >= 0 {
|
||||
return errTagValueSpace
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
187
vendor/github.com/golang/go/tests.go
generated
vendored
Normal file
187
vendor/github.com/golang/go/tests.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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 nonexistent
|
||||
// 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)
|
||||
}
|
||||
}
|
313
vendor/github.com/golang/go/types.go
generated
vendored
Normal file
313
vendor/github.com/golang/go/types.go
generated
vendored
Normal file
|
@ -0,0 +1,313 @@
|
|||
// 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 govet
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// stdImporter is the importer we use to import packages.
|
||||
// It is shared so that all packages are imported by the same importer.
|
||||
var stdImporter types.Importer
|
||||
|
||||
var (
|
||||
errorType *types.Interface
|
||||
stringerType *types.Interface // possibly nil
|
||||
formatterType *types.Interface // possibly nil
|
||||
)
|
||||
|
||||
func inittypes() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// isNamedType reports whether t is the named type path.name.
|
||||
func isNamedType(t types.Type, path, name string) bool {
|
||||
n, ok := t.(*types.Named)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
obj := n.Obj()
|
||||
return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if stdImporter == nil {
|
||||
if *source {
|
||||
stdImporter = importer.For("source", nil)
|
||||
} else {
|
||||
stdImporter = importer.Default()
|
||||
}
|
||||
inittypes()
|
||||
}
|
||||
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)
|
||||
|
||||
var allErrors []error
|
||||
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. We collect them all for printing later.
|
||||
Error: func(e error) {
|
||||
allErrors = append(allErrors, e)
|
||||
},
|
||||
|
||||
Sizes: archSizes,
|
||||
}
|
||||
info := &types.Info{
|
||||
Selections: pkg.selectors,
|
||||
Types: pkg.types,
|
||||
Defs: pkg.defs,
|
||||
Uses: pkg.uses,
|
||||
}
|
||||
typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
|
||||
if len(allErrors) == 0 && err != nil {
|
||||
allErrors = append(allErrors, err)
|
||||
}
|
||||
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 allErrors
|
||||
}
|
||||
|
||||
// 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.
|
||||
if f.isFormatter(typ) {
|
||||
return true
|
||||
}
|
||||
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
|
||||
if t&argString != 0 && isConvertibleToString(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(), 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)
|
||||
}
|
||||
// Check whether the rest can print pointers.
|
||||
return t&argPointer != 0
|
||||
|
||||
case *types.Struct:
|
||||
return f.matchStructArgType(t, typ, arg, inProgress)
|
||||
|
||||
case *types.Interface:
|
||||
// There's little we can do.
|
||||
// Whether any particular verb is valid depends on the argument.
|
||||
// The user may have reasonable prior knowledge of the contents of the interface.
|
||||
return true
|
||||
|
||||
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 false
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func isConvertibleToString(typ types.Type) bool {
|
||||
if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
|
||||
// We explicitly don't want untyped nil, which is
|
||||
// convertible to both of the interfaces below, as it
|
||||
// would just panic anyway.
|
||||
return false
|
||||
}
|
||||
if types.ConvertibleTo(typ, errorType) {
|
||||
return true // via .Error()
|
||||
}
|
||||
if stringerType != nil && types.ConvertibleTo(typ, stringerType) {
|
||||
return true // via .String()
|
||||
}
|
||||
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++ {
|
||||
typf := typ.Field(i)
|
||||
if !f.matchArgTypeInternal(t, typf.Type(), arg, inProgress) {
|
||||
return false
|
||||
}
|
||||
if t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) {
|
||||
// Issue #17798: unexported Stringer or error cannot be properly fomatted.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var archSizes = types.SizesFor("gc", build.Default.GOARCH)
|
97
vendor/github.com/golang/go/unsafeptr.go
generated
vendored
Normal file
97
vendor/github.com/golang/go/unsafeptr.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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, token.AND_NOT:
|
||||
return f.isSafeUintptr(x.X) && !f.isSafeUintptr(x.Y)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
93
vendor/github.com/golang/go/unused.go
generated
vendored
Normal file
93
vendor/github.com/golang/go/unused.go
generated
vendored
Normal file
|
@ -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 govet
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue