mirror of
https://github.com/golang/go
synced 2024-10-01 01:28:32 -06: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