golang.org/x/arch v0.0.0-20190815191158-8a70ba74b3a1
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 // indirect
- golang.org/x/tools v0.0.0-20190611154301-25a4f137592f
+ golang.org/x/tools v0.0.0-20190925211824-e4ea94538f5b
)
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190611154301-25a4f137592f h1:6awn5JC4pwVI5HiBqs7MDtRxnwV9PpO5iSA9v6P09pA=
-golang.org/x/tools v0.0.0-20190611154301-25a4f137592f/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190925211824-e4ea94538f5b h1:gyG4T6EqWG9fqSgT0VbHhzp8bHbFux5mvlgz1gUkEaQ=
+golang.org/x/tools v0.0.0-20190925211824-e4ea94538f5b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
// See comments for ExportObjectFact.
ExportPackageFact func(fact Fact)
- // AllPackageFacts returns a new slice containing all package facts in unspecified order.
+ // AllPackageFacts returns a new slice containing all package facts of the analysis's FactTypes
+ // in unspecified order.
// WARNING: This is an experimental API and may change in the future.
AllPackageFacts func() []PackageFact
- // AllObjectFacts returns a new slice containing all object facts in unspecified order.
+ // AllObjectFacts returns a new slice containing all object facts of the analysis's FactTypes
+ // in unspecified order.
// WARNING: This is an experimental API and may change in the future.
AllObjectFacts func() []ObjectFact
type Fact interface {
AFact() // dummy method to avoid type errors
}
-
-// A Diagnostic is a message associated with a source location or range.
-//
-// An Analyzer may return a variety of diagnostics; the optional Category,
-// which should be a constant, may be used to classify them.
-// It is primarily intended to make it easy to look up documentation.
-//
-// If End is provided, the diagnostic is specified to apply to the range between
-// Pos and End.
-type Diagnostic struct {
- Pos token.Pos
- End token.Pos // optional
- Category string // optional
- Message string
-}
--- /dev/null
+package analysis
+
+import "go/token"
+
+// A Diagnostic is a message associated with a source location or range.
+//
+// An Analyzer may return a variety of diagnostics; the optional Category,
+// which should be a constant, may be used to classify them.
+// It is primarily intended to make it easy to look up documentation.
+//
+// If End is provided, the diagnostic is specified to apply to the range between
+// Pos and End.
+type Diagnostic struct {
+ Pos token.Pos
+ End token.Pos // optional
+ Category string // optional
+ Message string
+
+ // SuggestedFixes contains suggested fixes for a diagnostic which can be used to perform
+ // edits to a file that address the diagnostic.
+ // TODO(matloob): Should multiple SuggestedFixes be allowed for a diagnostic?
+ // Diagnostics should not contain SuggestedFixes that overlap.
+ // Experimental: This API is experimental and may change in the future.
+ SuggestedFixes []SuggestedFix // optional
+}
+
+// A SuggestedFix is a code change associated with a Diagnostic that a user can choose
+// to apply to their code. Usually the SuggestedFix is meant to fix the issue flagged
+// by the diagnostic.
+// TextEdits for a SuggestedFix should not overlap. TextEdits for a SuggestedFix
+// should not contain edits for other packages.
+// Experimental: This API is experimental and may change in the future.
+type SuggestedFix struct {
+ // A description for this suggested fix to be shown to a user deciding
+ // whether to accept it.
+ Message string
+ TextEdits []TextEdit
+}
+
+// A TextEdit represents the replacement of the code between Pos and End with the new text.
+// Each TextEdit should apply to a single file. End should not be earlier in the file than Pos.
+// Experimental: This API is experimental and may change in the future.
+type TextEdit struct {
+ // For a pure insertion, End can either be set to Pos or token.NoPos.
+ Pos token.Pos
+ End token.Pos
+ NewText []byte
+}
}
A driver may use the name, flags, and documentation to provide on-line
-help that describes the analyses its performs.
+help that describes the analyses it performs.
The doc comment contains a brief one-line summary,
optionally followed by paragraphs of explanation.
The vet command, shown below, is an example of a driver that runs
The OtherFiles field provides the names, but not the contents, of non-Go
files such as assembly that are part of this package. See the "asmdecl"
-or "buildtags" analyzers for examples of loading non-Go files and report
+or "buildtags" analyzers for examples of loading non-Go files and reporting
diagnostics against them.
The ResultOf field provides the results computed by the analyzers
vet's printf checker infers whether a function has the "printf wrapper"
type, and it applies stricter checks to calls of such functions. In
addition, it records which functions are printf wrappers for use by
-later analysis units to identify other printf wrappers by induction.
+later analysis passes to identify other printf wrappers by induction.
A result such as “f is a printf wrapper” that is not interesting by
itself but serves as a stepping stone to an interesting result (such as
a diagnostic) is called a "fact".
type isWrapper struct{} // => *types.Func f “is a printf wrapper”
-A driver program ensures that facts for a pass’s dependencies are
-generated before analyzing the pass and are responsible for propagating
-facts between from one pass to another, possibly across address spaces.
+The driver program ensures that facts for a pass’s dependencies are
+generated before analyzing the package and is responsible for propagating
+facts from one package to another, possibly across address spaces.
Consequently, Facts must be serializable. The API requires that drivers
use the gob encoding, an efficient, robust, self-describing binary
protocol. A fact type may implement the GobEncoder/GobDecoder interfaces
Therefore, for best results, analyzer authors should not rely on
analysis facts being available for standard packages.
For example, although the printf checker is capable of deducing during
-analysis of the log package that log.Printf is a printf-wrapper,
+analysis of the log package that log.Printf is a printf wrapper,
this fact is built in to the analyzer so that it correctly checks
calls to log.Printf even when run in a driver that does not apply
-it to standard packages. We plan to remove this limitation in future.
+it to standard packages. We would like to remove this limitation in future.
Testing an Analyzer
var flags []jsonFlag = nil
flag.VisitAll(func(f *flag.Flag) {
// Don't report {single,multi}checker debugging
- // flags as these have no effect on unitchecker
+ // flags or fix as these have no effect on unitchecker
// (as invoked by 'go vet').
switch f.Name {
- case "debug", "cpuprofile", "memprofile", "trace":
+ case "debug", "cpuprofile", "memprofile", "trace", "fix":
return
}
log.Fatalf("unsupported flag value: -V=%s", s)
}
- // This replicates the miminal subset of
+ // This replicates the minimal subset of
// cmd/internal/objabi.AddVersionFlag, which is private to the
// go tool yet forms part of our command-line interface.
// TODO(adonovan): clarify the contract.
// The notion of "exportedness" that matters here is that of the
// compiler. According to the language spec, a method pkg.T.f is
// unexported simply because its name starts with lowercase. But the
-// compiler must nonethless export f so that downstream compilations can
+// compiler must nonetheless export f so that downstream compilations can
// accurately ascertain whether pkg.T implements an interface pkg.I
// defined as interface{f()}. Exported thus means "described in export
// data".
s.mu.Unlock()
}
+func (s *Set) AllObjectFacts(filter map[reflect.Type]bool) []analysis.ObjectFact {
+ var facts []analysis.ObjectFact
+ for k, v := range s.m {
+ if k.obj != nil && filter[k.t] {
+ facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: v})
+ }
+ }
+ return facts
+}
+
// ImportPackageFact implements analysis.Pass.ImportPackageFact.
func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool {
if pkg == nil {
s.mu.Unlock()
}
+func (s *Set) AllPackageFacts(filter map[reflect.Type]bool) []analysis.PackageFact {
+ var facts []analysis.PackageFact
+ for k, v := range s.m {
+ if k.obj == nil && filter[k.t] {
+ facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: v})
+ }
+ }
+ return facts
+}
+
// gobFact is the Gob declaration of a serialized fact.
type gobFact struct {
PkgPath string // path of package
// methods that are on T instead of *T.
import (
+ "fmt"
"go/ast"
"go/token"
"reflect"
le := analysisutil.Format(pass.Fset, lhs)
re := analysisutil.Format(pass.Fset, rhs)
if le == re {
- pass.Reportf(stmt.Pos(), "self-assignment of %s to %s", re, le)
+ pass.Report(analysis.Diagnostic{
+ Pos: stmt.Pos(), Message: fmt.Sprintf("self-assignment of %s to %s", re, le),
+ SuggestedFixes: []analysis.SuggestedFix{
+ {Message: "Remove", TextEdits: []analysis.TextEdit{
+ {Pos: stmt.Pos(), End: stmt.End(), NewText: []byte{}},
+ }},
+ },
+ })
}
}
})
// cgo files of a package (those that import "C"). Such files are not
// Go, so there may be gaps in type information around C.f references.
//
-// This checker was initially written in vet to inpect raw cgo source
+// This checker was initially written in vet to inspect raw cgo source
// files using partial type information. However, Analyzers in the new
// analysis API are presented with the type-checked, "cooked" Go ASTs
// resulting from cgo-processing files, so we must choose between
// func (T) f(int) string { ... }
//
// we synthesize a new ast.File, shown below, that dot-imports the
-// orginal "cooked" package using a special name ("·this·"), so that all
+// original "cooked" package using a special name ("·this·"), so that all
// references to package members resolve correctly. (References to
// unexported names cause an "unexported" error, which we ignore.)
//
"image.Uniform": true,
"unicode.Range16": true,
+ "unicode.Range32": true,
// These three structs are used in generated test main files,
// but the generator can be trusted.
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.FuncDecl:
- fn := pass.TypesInfo.Defs[n.Name].(*types.Func)
- funcDecls[fn] = &declInfo{decl: n}
- decls = append(decls, fn)
-
+ // Type information may be incomplete.
+ if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
+ funcDecls[fn] = &declInfo{decl: n}
+ decls = append(decls, fn)
+ }
case *ast.FuncLit:
funcLits[n] = new(litInfo)
lits = append(lits, n)
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// The errorsas package defines an Analyzer that checks that the second arugment to
+// The errorsas package defines an Analyzer that checks that the second argument to
// errors.As is a pointer to a type implementing error.
package errorsas
`
// isWrapper is a fact indicating that a function is a print or printf wrapper.
-type isWrapper struct{ Printf bool }
+type isWrapper struct{ Kind funcKind }
func (f *isWrapper) AFact() {}
func (f *isWrapper) String() string {
- if f.Printf {
+ switch f.Kind {
+ case kindPrintf:
return "printfWrapper"
- } else {
+ case kindPrint:
return "printWrapper"
+ case kindErrorf:
+ return "errorfWrapper"
+ default:
+ return "unknownWrapper"
}
}
if !ok || fdecl.Body == nil {
return nil
}
- fn := info.Defs[fdecl.Name].(*types.Func)
+ fn, ok := info.Defs[fdecl.Name].(*types.Func)
+ // Type information may be incomplete.
+ if !ok {
+ return nil
+ }
sig := fn.Type().(*types.Signature)
if !sig.Variadic() {
return ok && info.ObjectOf(id) == param
}
+type funcKind int
+
const (
- kindPrintf = 1
- kindPrint = 2
+ kindUnknown funcKind = iota
+ kindPrintf = iota
+ kindPrint
+ kindErrorf
)
// checkPrintfFwd checks that a printf-forwarding wrapper is forwarding correctly.
// It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...).
-func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind int) {
+func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind funcKind) {
matched := kind == kindPrint ||
- kind == kindPrintf && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format)
+ kind != kindUnknown && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format)
if !matched {
return
}
fn := w.obj
var fact isWrapper
if !pass.ImportObjectFact(fn, &fact) {
- fact.Printf = kind == kindPrintf
+ fact.Kind = kind
pass.ExportObjectFact(fn, &fact)
for _, caller := range w.callers {
checkPrintfFwd(pass, caller.w, caller.call, kind)
call := n.(*ast.CallExpr)
fn, kind := printfNameAndKind(pass, call)
switch kind {
- case kindPrintf:
- checkPrintf(pass, call, fn)
+ case kindPrintf, kindErrorf:
+ checkPrintf(pass, kind, call, fn)
case kindPrint:
checkPrint(pass, call, fn)
}
})
}
-func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind int) {
+func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind funcKind) {
fn, _ = typeutil.Callee(pass.TypesInfo, call).(*types.Func)
if fn == nil {
return nil, 0
}
- var fact isWrapper
- if pass.ImportObjectFact(fn, &fact) {
- if fact.Printf {
- return fn, kindPrintf
- } else {
- return fn, kindPrint
- }
- }
-
_, ok := isPrint[fn.FullName()]
if !ok {
// Next look up just "printf", for use with -printf.funcs.
_, ok = isPrint[strings.ToLower(fn.Name())]
}
if ok {
- if strings.HasSuffix(fn.Name(), "f") {
+ if fn.Name() == "Errorf" {
+ kind = kindErrorf
+ } else if strings.HasSuffix(fn.Name(), "f") {
kind = kindPrintf
} else {
kind = kindPrint
}
+ return fn, kind
+ }
+
+ var fact isWrapper
+ if pass.ImportObjectFact(fn, &fact) {
+ return fn, fact.Kind
}
- return fn, kind
+
+ return fn, kindUnknown
}
// isFormatter reports whether t satisfies fmt.Formatter.
}
// checkPrintf checks a call to a formatted print routine such as Printf.
-func checkPrintf(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
+func checkPrintf(pass *analysis.Pass, kind funcKind, call *ast.CallExpr, fn *types.Func) {
format, idx := formatString(pass, call)
if idx < 0 {
if false {
argNum := firstArg
maxArgNum := firstArg
anyIndex := false
+ anyW := false
for i, w := 0, 0; i < len(format); i += w {
w = 1
if format[i] != '%' {
if state.hasIndex {
anyIndex = true
}
+ if state.verb == 'w' {
+ if kind != kindErrorf {
+ pass.Reportf(call.Pos(), "%s call has error-wrapping directive %%w", state.name)
+ return
+ }
+ if anyW {
+ pass.Reportf(call.Pos(), "%s call has more than one error-wrapping directive %%w", state.name)
+ return
+ }
+ anyW = true
+ }
if len(state.argNums) > 0 {
// Continue with the next sequential argument.
argNum = state.argNums[len(state.argNums)-1] + 1
argFloat
argComplex
argPointer
+ argError
anyType printfArgType = ^0
)
{'T', "-", anyType},
{'U', "-#", argRune | argInt},
{'v', allFlags, anyType},
- {'w', noFlag, anyType},
+ {'w', allFlags, argError},
{'x', sharpNumFlag, argRune | argInt | argString | argPointer},
{'X', sharpNumFlag, argRune | argInt | argString | argPointer},
}
return true // probably a type check problem
}
}
+
+ // %w accepts only errors.
+ if t == argError {
+ return types.ConvertibleTo(typ, errorType)
+ }
+
// If the type implements fmt.Formatter, we have nothing to check.
if isFormatter(typ) {
return true
return false
}
if t&argString != 0 && !typf.Exported() && isConvertibleToString(pass, typf.Type()) {
- // Issue #17798: unexported Stringer or error cannot be properly fomatted.
+ // Issue #17798: unexported Stringer or error cannot be properly formatted.
return false
}
}
(*ast.StructType)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
- styp := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
+ styp, ok := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
+ // Type information may be incomplete.
+ if !ok {
+ return
+ }
var seen namesSeen
for i := 0; i < styp.NumFields(); i++ {
field := styp.Field(i)
"log"
"os"
"path/filepath"
+ "reflect"
"sort"
"strings"
"sync"
return
}
+ factFilter := make(map[reflect.Type]bool)
+ for _, f := range a.FactTypes {
+ factFilter[reflect.TypeOf(f)] = true
+ }
+
pass := &analysis.Pass{
Analyzer: a,
Fset: fset,
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
ImportObjectFact: facts.ImportObjectFact,
ExportObjectFact: facts.ExportObjectFact,
+ AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
ImportPackageFact: facts.ImportPackageFact,
ExportPackageFact: facts.ExportPackageFact,
+ AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
}
t0 := time.Now()
// Checks include:
// that the name is a valid identifier;
// that analyzer names are unique;
-// that the Requires graph is acylic;
+// that the Requires graph is acyclic;
// that analyzer fact types are unique;
// that each fact type is a pointer.
func Validate(analyzers []*Analyzer) error {
}
case token.FALLTHROUGH:
- for t := b.targets; t != nil; t = t.tail {
+ for t := b.targets; t != nil && block == nil; t = t.tail {
block = t._fallthrough
}
return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj)
}
- // abtraction of *types.{Pointer,Slice,Array,Chan,Map}
+ // abstraction of *types.{Pointer,Slice,Array,Chan,Map}
type hasElem interface {
Elem() types.Type
}
# golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82
golang.org/x/sys/unix
golang.org/x/sys/windows
-# golang.org/x/tools v0.0.0-20190611154301-25a4f137592f
+# golang.org/x/tools v0.0.0-20190925211824-e4ea94538f5b
golang.org/x/tools/go/analysis
golang.org/x/tools/go/analysis/internal/analysisflags
golang.org/x/tools/go/analysis/internal/facts
)
func TestErrorf(t *testing.T) {
+ // noVetErrorf is an alias for fmt.Errorf that does not trigger vet warnings for
+ // %w format strings.
+ noVetErrorf := fmt.Errorf
+
wrapped := errors.New("inner error")
for _, test := range []struct {
err error
err: fmt.Errorf("%v with added context", wrapped),
wantText: "inner error with added context",
}, {
- err: fmt.Errorf("%w is not an error", "not-an-error"),
+ err: noVetErrorf("%w is not an error", "not-an-error"),
wantText: "%!w(string=not-an-error) is not an error",
}, {
- err: fmt.Errorf("wrapped two errors: %w %w", errString("1"), errString("2")),
+ err: noVetErrorf("wrapped two errors: %w %w", errString("1"), errString("2")),
wantText: "wrapped two errors: 1 %!w(fmt_test.errString=2)",
}, {
- err: fmt.Errorf("wrapped three errors: %w %w %w", errString("1"), errString("2"), errString("3")),
+ err: noVetErrorf("wrapped three errors: %w %w %w", errString("1"), errString("2"), errString("3")),
wantText: "wrapped three errors: 1 %!w(fmt_test.errString=2) %!w(fmt_test.errString=3)",
}, {
err: fmt.Errorf("%w", nil),