mirror of
https://github.com/golang/go
synced 2024-11-18 18:44:42 -07:00
go/analysis/passes/vet: delete
All the passes have been moved into their own packages. The README file has been saved for the new cmd/analyze command, which will shortly be renamed to vet. Change-Id: I68c765a4da2f8d5a2b0161b462bd81483b5ceed5 Reviewed-on: https://go-review.googlesource.com/c/143301 Reviewed-by: Michael Matloob <matloob@golang.org> Run-TryBot: Michael Matloob <matloob@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
19100bfbe9
commit
9f76e5c58b
@ -1 +0,0 @@
|
|||||||
cmd/vet@31d19c0
|
|
@ -1,221 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
// 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 fmt.Print, fmt.Printf, and related functions.
|
|
||||||
The check applies to known functions (for example, those in package fmt)
|
|
||||||
as well as any detected wrappers of known functions.
|
|
||||||
|
|
||||||
The -printfuncs flag specifies a comma-separated list of names of
|
|
||||||
additional known formatting functions. Each name can be of the form
|
|
||||||
pkg.Name or pkg.Type.Name, where pkg is a complete import path,
|
|
||||||
or else can be a case-insensitive unqualified identifier like "errorf".
|
|
||||||
If a listed name ends in f, the function is assumed to be Printf-like,
|
|
||||||
taking a format string before the argument list. Otherwise it is
|
|
||||||
assumed to be Print-like, taking a list of arguments with no format string.
|
|
||||||
|
|
||||||
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 main
|
|
@ -1,753 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Vet is a simple checker for static errors in Go source code.
|
|
||||||
// See doc.go for more information.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/importer"
|
|
||||||
"go/parser"
|
|
||||||
"go/printer"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"cmd/internal/objabi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
pkgCheckers = make(map[string]func(*Package))
|
|
||||||
exporters = make(map[string]func() interface{})
|
|
||||||
)
|
|
||||||
|
|
||||||
// The exporters data as written to the vetx output file.
|
|
||||||
type vetxExport struct {
|
|
||||||
Name string
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vet can provide its own "export information"
|
|
||||||
// about package A to future invocations of vet
|
|
||||||
// on packages importing A. If B imports A,
|
|
||||||
// then running "go vet B" actually invokes vet twice:
|
|
||||||
// first, it runs vet on A, in "vetx-only" mode, which
|
|
||||||
// skips most checks and only computes export data
|
|
||||||
// describing A. Then it runs vet on B, making A's vetx
|
|
||||||
// data available for consultation. The vet of B
|
|
||||||
// computes vetx data for B in addition to its
|
|
||||||
// usual vet checks.
|
|
||||||
|
|
||||||
// register registers the named check function,
|
|
||||||
// to be called with AST nodes of the given types.
|
|
||||||
// The registered functions are not called in vetx-only mode.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerPkgCheck registers a package-level checking function,
|
|
||||||
// to be invoked with the whole package being vetted
|
|
||||||
// before any of the per-node handlers.
|
|
||||||
// The registered function fn is called even in vetx-only mode
|
|
||||||
// (see comment above), so fn must take care not to report
|
|
||||||
// errors when vcfg.VetxOnly is true.
|
|
||||||
func registerPkgCheck(name string, fn func(*Package)) {
|
|
||||||
pkgCheckers[name] = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerExport registers a function to return vetx export data
|
|
||||||
// that should be saved and provided to future invocations of vet
|
|
||||||
// when checking packages importing this one.
|
|
||||||
// The value returned by fn should be nil or else valid to encode using gob.
|
|
||||||
// Typically a registerExport call is paired with a call to gob.Register.
|
|
||||||
func registerExport(name string, fn func() interface{}) {
|
|
||||||
exporters[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() {
|
|
||||||
objabi.AddVersionFlag()
|
|
||||||
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.ReplaceAll(*tags, ",", " "))
|
|
||||||
|
|
||||||
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
|
|
||||||
Standard map[string]bool
|
|
||||||
PackageVetx map[string]string // map from import path to vetx data file
|
|
||||||
VetxOnly bool // only compute vetx output; don't run ordinary checks
|
|
||||||
VetxOutput string // file where vetx output should be written
|
|
||||||
|
|
||||||
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] == "" {
|
|
||||||
if v.Compiler == "gccgo" && v.Standard[path] {
|
|
||||||
// gccgo doesn't have sources for standard library packages,
|
|
||||||
// but the importer will do the right thing.
|
|
||||||
return v.imp.Import(path)
|
|
||||||
}
|
|
||||||
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 == "" {
|
|
||||||
if v.Compiler == "gccgo" && v.Standard[path] {
|
|
||||||
// The importer knows how to handle this.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
if vcfg.VetxOutput != "" {
|
|
||||||
out := make([]vetxExport, 0, len(exporters))
|
|
||||||
for name, fn := range exporters {
|
|
||||||
out = append(out, vetxExport{
|
|
||||||
Name: name,
|
|
||||||
Data: fn(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Sort the data so that it is consistent across builds.
|
|
||||||
sort.Slice(out, func(i, j int) bool {
|
|
||||||
return out[i].Name < out[j].Name
|
|
||||||
})
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := gob.NewEncoder(&buf).Encode(out); err != nil {
|
|
||||||
errorf("encoding vet output: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(vcfg.VetxOutput, buf.Bytes(), 0666); err != nil {
|
|
||||||
errorf("saving vet output: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
implicits map[ast.Node]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
|
|
||||||
}
|
|
||||||
var parsedFile *ast.File
|
|
||||||
if strings.HasSuffix(name, ".go") {
|
|
||||||
parsedFile, err = parser.ParseFile(fs, name, data, parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
warnf("%s: %s", name, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
astFiles = append(astFiles, parsedFile)
|
|
||||||
}
|
|
||||||
file := &File{
|
|
||||||
fset: fs,
|
|
||||||
content: data,
|
|
||||||
name: name,
|
|
||||||
file: parsedFile,
|
|
||||||
dead: make(map[ast.Node]bool),
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
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 vcfg.SucceedOnTypecheckFailure {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
for _, file := range files {
|
|
||||||
file.pkg = pkg
|
|
||||||
file.basePkg = basePkg
|
|
||||||
}
|
|
||||||
for name, fn := range pkgCheckers {
|
|
||||||
if vet(name) {
|
|
||||||
fn(pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vcfg.VetxOnly {
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
checkBuildTag(file)
|
|
||||||
file.checkers = chk
|
|
||||||
if file.file != nil {
|
|
||||||
file.walkFile(file.name, file.file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func visit(path string, f os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
warnf("walk error: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// One package per directory. Ignore the files themselves.
|
|
||||||
if !f.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
doPackageDir(path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkg *Package) hasFileWithSuffix(suffix string) bool {
|
|
||||||
for _, f := range pkg.files {
|
|
||||||
if strings.HasSuffix(f.name, suffix) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// walkDir recursively walks the tree looking for Go packages.
|
|
||||||
func walkDir(root string) {
|
|
||||||
filepath.Walk(root, visit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorf formats the error to standard error, adding program
|
|
||||||
// identification and a newline, and exits.
|
|
||||||
func errorf(format string, args ...interface{}) {
|
|
||||||
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// warnf formats the error to standard error, adding program
|
|
||||||
// identification and a newline, but does not exit.
|
|
||||||
func warnf(format string, args ...interface{}) {
|
|
||||||
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
|
|
||||||
setExit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is fmt.Println guarded by -v.
|
|
||||||
func Println(args ...interface{}) {
|
|
||||||
if !*verbose {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is fmt.Printf guarded by -v.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
if !*verbose {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf(format+"\n", args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bad reports an error and sets the exit code..
|
|
||||||
func (f *File) Bad(pos token.Pos, args ...interface{}) {
|
|
||||||
f.Warn(pos, args...)
|
|
||||||
setExit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Badf reports a formatted error and sets the exit code.
|
|
||||||
func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
|
|
||||||
f.Warnf(pos, format, args...)
|
|
||||||
setExit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loc returns a formatted representation of the position.
|
|
||||||
func (f *File) loc(pos token.Pos) string {
|
|
||||||
if pos == token.NoPos {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// Do not print columns. Because the pos often points to the start of an
|
|
||||||
// expression instead of the inner part with the actual error, the
|
|
||||||
// precision can mislead.
|
|
||||||
posn := f.fset.Position(pos)
|
|
||||||
return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// imported[path][key] is previously written export data.
|
|
||||||
var imported = make(map[string]map[string]interface{})
|
|
||||||
|
|
||||||
// readVetx reads export data written by a previous
|
|
||||||
// invocation of vet on an imported package (path).
|
|
||||||
// The key is the name passed to registerExport
|
|
||||||
// when the data was originally generated.
|
|
||||||
// readVetx returns nil if the data is unavailable.
|
|
||||||
func readVetx(path, key string) interface{} {
|
|
||||||
if path == "unsafe" || vcfg.ImportPath == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m := imported[path]
|
|
||||||
if m == nil {
|
|
||||||
file := vcfg.PackageVetx[path]
|
|
||||||
if file == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var out []vetxExport
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(data)).Decode(&out)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m = make(map[string]interface{})
|
|
||||||
for _, x := range out {
|
|
||||||
m[x.Name] = x.Data
|
|
||||||
}
|
|
||||||
imported[path] = m
|
|
||||||
}
|
|
||||||
return m[key]
|
|
||||||
}
|
|
10
go/analysis/passes/vet/testdata/tagtest/file1.go
vendored
10
go/analysis/passes/vet/testdata/tagtest/file1.go
vendored
@ -1,10 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
// +build testtag
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
}
|
|
10
go/analysis/passes/vet/testdata/tagtest/file2.go
vendored
10
go/analysis/passes/vet/testdata/tagtest/file2.go
vendored
@ -1,10 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
// +build !testtag
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
func ignore() {
|
|
||||||
}
|
|
@ -1,441 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"internal/testenv"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dataDir = "testdata"
|
|
||||||
binary = "./testvet.exe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// We implement TestMain so remove the test binary when all is done.
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
result := m.Run()
|
|
||||||
os.Remove(binary)
|
|
||||||
os.Exit(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
buildMu sync.Mutex // guards following
|
|
||||||
built = false // We have built the binary.
|
|
||||||
failed = false // We have failed to build the binary, don't try again.
|
|
||||||
)
|
|
||||||
|
|
||||||
func Build(t *testing.T) {
|
|
||||||
buildMu.Lock()
|
|
||||||
defer buildMu.Unlock()
|
|
||||||
if built {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
t.Skip("cannot run on this environment")
|
|
||||||
}
|
|
||||||
testenv.MustHaveGoBuild(t)
|
|
||||||
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", binary)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
failed = true
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", output)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
built = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func Vet(t *testing.T, files []string) {
|
|
||||||
flags := []string{
|
|
||||||
"-printfuncs=Warn:1,Warnf:1",
|
|
||||||
"-all",
|
|
||||||
"-shadow",
|
|
||||||
}
|
|
||||||
cmd := exec.Command(binary, append(flags, files...)...)
|
|
||||||
errchk(cmd, files, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestVet is equivalent to running this:
|
|
||||||
// go build -o ./testvet
|
|
||||||
// errorCheck the output of ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s
|
|
||||||
// rm ./testvet
|
|
||||||
//
|
|
||||||
|
|
||||||
// TestVet tests self-contained files in testdata/*.go.
|
|
||||||
//
|
|
||||||
// If a file contains assembly or has inter-dependencies, it should be
|
|
||||||
// in its own test, like TestVetAsm, TestDivergentPackagesExamples,
|
|
||||||
// etc below.
|
|
||||||
func TestVet(t *testing.T) {
|
|
||||||
Build(t)
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
gos, err := filepath.Glob(filepath.Join(dataDir, "*.go"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wide := runtime.GOMAXPROCS(0)
|
|
||||||
if wide > len(gos) {
|
|
||||||
wide = len(gos)
|
|
||||||
}
|
|
||||||
batch := make([][]string, wide)
|
|
||||||
for i, file := range gos {
|
|
||||||
// The print.go test is run by TestVetPrint.
|
|
||||||
if strings.HasSuffix(file, "print.go") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
batch[i%wide] = append(batch[i%wide], file)
|
|
||||||
}
|
|
||||||
for i, files := range batch {
|
|
||||||
if len(files) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files := files
|
|
||||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
t.Logf("files: %q", files)
|
|
||||||
Vet(t, files)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVetPrint(t *testing.T) {
|
|
||||||
Build(t)
|
|
||||||
file := filepath.Join("testdata", "print.go")
|
|
||||||
cmd := exec.Command(
|
|
||||||
"go", "vet", "-vettool="+binary,
|
|
||||||
"-printf",
|
|
||||||
"-printfuncs=Warn:1,Warnf:1",
|
|
||||||
file,
|
|
||||||
)
|
|
||||||
errchk(cmd, []string{file}, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVetAsm(t *testing.T) {
|
|
||||||
Build(t)
|
|
||||||
|
|
||||||
asmDir := filepath.Join(dataDir, "asm")
|
|
||||||
gos, err := filepath.Glob(filepath.Join(asmDir, "*.go"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
asms, err := filepath.Glob(filepath.Join(asmDir, "*.s"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
Vet(t, append(gos, asms...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVetDirs(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
Build(t)
|
|
||||||
for _, dir := range []string{
|
|
||||||
"testingpkg",
|
|
||||||
"divergent",
|
|
||||||
"buildtag",
|
|
||||||
"incomplete", // incomplete examples
|
|
||||||
"cgo",
|
|
||||||
} {
|
|
||||||
dir := dir
|
|
||||||
t.Run(dir, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
gos, err := filepath.Glob(filepath.Join("testdata", dir, "*.go"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
Vet(t, gos)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func errchk(c *exec.Cmd, files []string, t *testing.T) {
|
|
||||||
output, err := c.CombinedOutput()
|
|
||||||
if _, ok := err.(*exec.ExitError); !ok {
|
|
||||||
t.Logf("vet output:\n%s", output)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fullshort := make([]string, 0, len(files)*2)
|
|
||||||
for _, f := range files {
|
|
||||||
fullshort = append(fullshort, f, filepath.Base(f))
|
|
||||||
}
|
|
||||||
err = errorCheck(string(output), false, fullshort...)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error check failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestTags verifies that the -tags argument controls which files to check.
|
|
||||||
func TestTags(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
Build(t)
|
|
||||||
for _, tag := range []string{"testtag", "x testtag y", "x,testtag,y"} {
|
|
||||||
tag := tag
|
|
||||||
t.Run(tag, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
t.Logf("-tags=%s", tag)
|
|
||||||
args := []string{
|
|
||||||
"-tags=" + tag,
|
|
||||||
"-v", // We're going to look at the files it examines.
|
|
||||||
"testdata/tagtest",
|
|
||||||
}
|
|
||||||
cmd := exec.Command(binary, args...)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// file1 has testtag and file2 has !testtag.
|
|
||||||
if !bytes.Contains(output, []byte(filepath.Join("tagtest", "file1.go"))) {
|
|
||||||
t.Error("file1 was excluded, should be included")
|
|
||||||
}
|
|
||||||
if bytes.Contains(output, []byte(filepath.Join("tagtest", "file2.go"))) {
|
|
||||||
t.Error("file2 was included, should be excluded")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue #21188.
|
|
||||||
func TestVetVerbose(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
Build(t)
|
|
||||||
cmd := exec.Command(binary, "-v", "-all", "testdata/cgo/cgo3.go")
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.Logf("%s", out)
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All declarations below were adapted from test/run.go.
|
|
||||||
|
|
||||||
// errorCheck matches errors in outStr against comments in source files.
|
|
||||||
// For each line of the source files which should generate an error,
|
|
||||||
// there should be a comment of the form // ERROR "regexp".
|
|
||||||
// If outStr has an error for a line which has no such comment,
|
|
||||||
// this function will report an error.
|
|
||||||
// Likewise if outStr does not have an error for a line which has a comment,
|
|
||||||
// or if the error message does not match the <regexp>.
|
|
||||||
// The <regexp> syntax is Perl but its best to stick to egrep.
|
|
||||||
//
|
|
||||||
// Sources files are supplied as fullshort slice.
|
|
||||||
// It consists of pairs: full path to source file and it's base name.
|
|
||||||
func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
|
|
||||||
var errs []error
|
|
||||||
out := splitOutput(outStr, wantAuto)
|
|
||||||
// Cut directory name.
|
|
||||||
for i := range out {
|
|
||||||
for j := 0; j < len(fullshort); j += 2 {
|
|
||||||
full, short := fullshort[j], fullshort[j+1]
|
|
||||||
out[i] = strings.ReplaceAll(out[i], full, short)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var want []wantedError
|
|
||||||
for j := 0; j < len(fullshort); j += 2 {
|
|
||||||
full, short := fullshort[j], fullshort[j+1]
|
|
||||||
want = append(want, wantedErrors(full, short)...)
|
|
||||||
}
|
|
||||||
for _, we := range want {
|
|
||||||
var errmsgs []string
|
|
||||||
if we.auto {
|
|
||||||
errmsgs, out = partitionStrings("<autogenerated>", out)
|
|
||||||
} else {
|
|
||||||
errmsgs, out = partitionStrings(we.prefix, out)
|
|
||||||
}
|
|
||||||
if len(errmsgs) == 0 {
|
|
||||||
errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matched := false
|
|
||||||
n := len(out)
|
|
||||||
for _, errmsg := range errmsgs {
|
|
||||||
// Assume errmsg says "file:line: foo".
|
|
||||||
// Cut leading "file:line: " to avoid accidental matching of file name instead of message.
|
|
||||||
text := errmsg
|
|
||||||
if i := strings.Index(text, " "); i >= 0 {
|
|
||||||
text = text[i+1:]
|
|
||||||
}
|
|
||||||
if we.re.MatchString(text) {
|
|
||||||
matched = true
|
|
||||||
} else {
|
|
||||||
out = append(out, errmsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !matched {
|
|
||||||
errs = append(errs, fmt.Errorf("%s:%d: no match for %#q in:\n\t%s", we.file, we.lineNum, we.reStr, strings.Join(out[n:], "\n\t")))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(out) > 0 {
|
|
||||||
errs = append(errs, fmt.Errorf("Unmatched Errors:"))
|
|
||||||
for _, errLine := range out {
|
|
||||||
errs = append(errs, fmt.Errorf("%s", errLine))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(errs) == 1 {
|
|
||||||
return errs[0]
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintf(&buf, "\n")
|
|
||||||
for _, err := range errs {
|
|
||||||
fmt.Fprintf(&buf, "%s\n", err.Error())
|
|
||||||
}
|
|
||||||
return errors.New(buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitOutput(out string, wantAuto bool) []string {
|
|
||||||
// gc error messages continue onto additional lines with leading tabs.
|
|
||||||
// Split the output at the beginning of each line that doesn't begin with a tab.
|
|
||||||
// <autogenerated> lines are impossible to match so those are filtered out.
|
|
||||||
var res []string
|
|
||||||
for _, line := range strings.Split(out, "\n") {
|
|
||||||
line = strings.TrimSuffix(line, "\r") // normalize Windows output
|
|
||||||
if strings.HasPrefix(line, "\t") {
|
|
||||||
res[len(res)-1] += "\n" + line
|
|
||||||
} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
|
|
||||||
continue
|
|
||||||
} else if strings.TrimSpace(line) != "" {
|
|
||||||
res = append(res, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchPrefix reports whether s starts with file name prefix followed by a :,
|
|
||||||
// and possibly preceded by a directory name.
|
|
||||||
func matchPrefix(s, prefix string) bool {
|
|
||||||
i := strings.Index(s, ":")
|
|
||||||
if i < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
j := strings.LastIndex(s[:i], "/")
|
|
||||||
s = s[j+1:]
|
|
||||||
if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[len(prefix)] == ':' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
|
|
||||||
for _, s := range strs {
|
|
||||||
if matchPrefix(s, prefix) {
|
|
||||||
matched = append(matched, s)
|
|
||||||
} else {
|
|
||||||
unmatched = append(unmatched, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type wantedError struct {
|
|
||||||
reStr string
|
|
||||||
re *regexp.Regexp
|
|
||||||
lineNum int
|
|
||||||
auto bool // match <autogenerated> line
|
|
||||||
file string
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
|
|
||||||
errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
|
|
||||||
errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
|
|
||||||
lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// wantedErrors parses expected errors from comments in a file.
|
|
||||||
func wantedErrors(file, short string) (errs []wantedError) {
|
|
||||||
cache := make(map[string]*regexp.Regexp)
|
|
||||||
|
|
||||||
src, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for i, line := range strings.Split(string(src), "\n") {
|
|
||||||
lineNum := i + 1
|
|
||||||
if strings.Contains(line, "////") {
|
|
||||||
// double comment disables ERROR
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var auto bool
|
|
||||||
m := errAutoRx.FindStringSubmatch(line)
|
|
||||||
if m != nil {
|
|
||||||
auto = true
|
|
||||||
} else {
|
|
||||||
m = errRx.FindStringSubmatch(line)
|
|
||||||
}
|
|
||||||
if m == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
all := m[1]
|
|
||||||
mm := errQuotesRx.FindAllStringSubmatch(all, -1)
|
|
||||||
if mm == nil {
|
|
||||||
log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
|
|
||||||
}
|
|
||||||
for _, m := range mm {
|
|
||||||
replacedOnce := false
|
|
||||||
rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
|
|
||||||
if replacedOnce {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
replacedOnce = true
|
|
||||||
n := lineNum
|
|
||||||
if strings.HasPrefix(m, "LINE+") {
|
|
||||||
delta, _ := strconv.Atoi(m[5:])
|
|
||||||
n += delta
|
|
||||||
} else if strings.HasPrefix(m, "LINE-") {
|
|
||||||
delta, _ := strconv.Atoi(m[5:])
|
|
||||||
n -= delta
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%d", short, n)
|
|
||||||
})
|
|
||||||
re := cache[rx]
|
|
||||||
if re == nil {
|
|
||||||
var err error
|
|
||||||
re, err = regexp.Compile(rx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
|
|
||||||
}
|
|
||||||
cache[rx] = re
|
|
||||||
}
|
|
||||||
prefix := fmt.Sprintf("%s:%d", short, lineNum)
|
|
||||||
errs = append(errs, wantedError{
|
|
||||||
reStr: rx,
|
|
||||||
re: re,
|
|
||||||
prefix: prefix,
|
|
||||||
auto: auto,
|
|
||||||
lineNum: lineNum,
|
|
||||||
file: short,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user