Commit 89a79174 authored by Alan Donovan's avatar Alan Donovan

cmd/vendor/golang.org/x/tools: update to 7d6b83ca

Also, add a script for future updates.

Change-Id: I2565d1f26532b9dd7cf9d8ce198ba08fb3d53407
Reviewed-on: https://go-review.googlesource.com/c/149604
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: default avatarMichael Matloob <matloob@golang.org>
parent bfd9b940
// The vet-lite command is a driver for static checkers conforming to // The vet-lite command is a driver for static checkers conforming to
// the golang.org/x/tools/go/analysis API. It must be run by go vet: // the golang.org/x/tools/go/analysis API. It must be run by go vet:
// //
// $ GOVETTOOL=$(which vet-lite) go vet // $ go vet -vettool=$(which vet-lite)
// //
// For a checker also capable of running standalone, use multichecker. // For a checker also capable of running standalone, use multichecker.
package main package main
import ( import (
"flag" "flag"
"fmt"
"log" "log"
"os"
"strings" "strings"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
...@@ -33,13 +35,13 @@ import ( ...@@ -33,13 +35,13 @@ import (
"golang.org/x/tools/go/analysis/passes/stdmethods" "golang.org/x/tools/go/analysis/passes/stdmethods"
"golang.org/x/tools/go/analysis/passes/structtag" "golang.org/x/tools/go/analysis/passes/structtag"
"golang.org/x/tools/go/analysis/passes/tests" "golang.org/x/tools/go/analysis/passes/tests"
"golang.org/x/tools/go/analysis/passes/unmarshal"
"golang.org/x/tools/go/analysis/passes/unreachable" "golang.org/x/tools/go/analysis/passes/unreachable"
"golang.org/x/tools/go/analysis/passes/unsafeptr" "golang.org/x/tools/go/analysis/passes/unsafeptr"
"golang.org/x/tools/go/analysis/passes/unusedresult" "golang.org/x/tools/go/analysis/passes/unusedresult"
) )
var analyzers = []*analysis.Analyzer{ var analyzers = []*analysis.Analyzer{
// For now, just the traditional vet suite:
asmdecl.Analyzer, asmdecl.Analyzer,
assign.Analyzer, assign.Analyzer,
atomic.Analyzer, atomic.Analyzer,
...@@ -54,11 +56,11 @@ var analyzers = []*analysis.Analyzer{ ...@@ -54,11 +56,11 @@ var analyzers = []*analysis.Analyzer{
nilfunc.Analyzer, nilfunc.Analyzer,
pkgfact.Analyzer, pkgfact.Analyzer,
printf.Analyzer, printf.Analyzer,
// shadow.Analyzer, // experimental; not enabled by default
shift.Analyzer, shift.Analyzer,
stdmethods.Analyzer, stdmethods.Analyzer,
structtag.Analyzer, structtag.Analyzer,
tests.Analyzer, tests.Analyzer,
unmarshal.Analyzer,
unreachable.Analyzer, unreachable.Analyzer,
unsafeptr.Analyzer, unsafeptr.Analyzer,
unusedresult.Analyzer, unusedresult.Analyzer,
...@@ -72,9 +74,48 @@ func main() { ...@@ -72,9 +74,48 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
// Flags for legacy vet compatibility.
//
// These flags, plus the shims in analysisflags, enable
// existing scripts that run vet to continue to work.
//
// Legacy vet had the concept of "experimental" checkers. There
// was exactly one, shadow, and it had to be explicitly enabled
// by the -shadow flag, which would of course disable all the
// other tristate flags, requiring the -all flag to reenable them.
// (By itself, -all did not enable all checkers.)
// The -all flag is no longer needed, so it is a no-op.
//
// The shadow analyzer has been removed from the suite,
// but can be run using these additional commands:
// $ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
// $ go vet -vettool=$(which shadow)
// Alternatively, one could build a multichecker containing all
// the desired checks (vet's suite + shadow) and run it in a
// single "go vet" command.
for _, name := range []string{"source", "v", "all"} {
_ = flag.Bool(name, false, "no effect (deprecated)")
}
flag.Usage = func() {
fmt.Fprintln(os.Stderr, `Usage of vet:
vet unit.cfg # execute analysis specified by config file
vet help # general help
vet help name # help on specific analyzer and its flags`)
flag.PrintDefaults()
os.Exit(1)
}
analyzers = analysisflags.Parse(analyzers, true) analyzers = analysisflags.Parse(analyzers, true)
args := flag.Args() args := flag.Args()
if len(args) == 0 {
flag.Usage()
}
if args[0] == "help" {
analysisflags.Help("vet", analyzers, args[1:])
os.Exit(0)
}
if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") { if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
log.Fatalf("invalid command: want .cfg file (this reduced version of vet is intended to be run only by the 'go vet' command)") log.Fatalf("invalid command: want .cfg file (this reduced version of vet is intended to be run only by the 'go vet' command)")
} }
......
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
) )
// Parse creates a flag for each of the analyzer's flags, // Parse creates a flag for each of the analyzer's flags,
// including (in multi mode) an --analysis.enable flag, // including (in multi mode) a flag named after the analyzer,
// parses the flags, then filters and returns the list of // parses the flags, then filters and returns the list of
// analyzers enabled by flags. // analyzers enabled by flags.
func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer { func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
...@@ -36,16 +36,15 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer { ...@@ -36,16 +36,15 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
for _, a := range analyzers { for _, a := range analyzers {
var prefix string var prefix string
// Add -analysis.enable flag. // Add -NAME flag to enable it.
if multi { if multi {
prefix = a.Name + "." prefix = a.Name + "."
enable := new(triState) enable := new(triState)
enableName := prefix + "enable"
enableUsage := "enable " + a.Name + " analysis" enableUsage := "enable " + a.Name + " analysis"
flag.Var(enable, enableName, enableUsage) flag.Var(enable, a.Name, enableUsage)
enabled[a] = enable enabled[a] = enable
analysisFlags = append(analysisFlags, analysisFlag{enableName, true, enableUsage}) analysisFlags = append(analysisFlags, analysisFlag{a.Name, true, enableUsage})
} }
a.Flags.VisitAll(func(f *flag.Flag) { a.Flags.VisitAll(func(f *flag.Flag) {
...@@ -69,6 +68,14 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer { ...@@ -69,6 +68,14 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
printflags := flag.Bool("flags", false, "print analyzer flags in JSON") printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
addVersionFlag() addVersionFlag()
// Add shims for legacy vet flags.
for old, new := range vetLegacyFlags {
newFlag := flag.Lookup(new)
if newFlag != nil && flag.Lookup(old) == nil {
flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
}
}
flag.Parse() // (ExitOnError) flag.Parse() // (ExitOnError)
// -flags: print flags so that go vet knows which ones are legitimate. // -flags: print flags so that go vet knows which ones are legitimate.
...@@ -81,8 +88,8 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer { ...@@ -81,8 +88,8 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
os.Exit(0) os.Exit(0)
} }
// If any --foo.enable flag is true, run only those analyzers. Otherwise, // If any -NAME flag is true, run only those analyzers. Otherwise,
// if any --foo.enable flag is false, run all but those analyzers. // if any -NAME flag is false, run all but those analyzers.
if multi { if multi {
var hasTrue, hasFalse bool var hasTrue, hasFalse bool
for _, ts := range enabled { for _, ts := range enabled {
...@@ -195,7 +202,7 @@ func (ts *triState) Set(value string) error { ...@@ -195,7 +202,7 @@ func (ts *triState) Set(value string) error {
b, err := strconv.ParseBool(value) b, err := strconv.ParseBool(value)
if err != nil { if err != nil {
// This error message looks poor but package "flag" adds // This error message looks poor but package "flag" adds
// "invalid boolean value %q for -foo.enable: %s" // "invalid boolean value %q for -NAME: %s"
return fmt.Errorf("want true or false") return fmt.Errorf("want true or false")
} }
if b { if b {
...@@ -221,3 +228,22 @@ func (ts *triState) String() string { ...@@ -221,3 +228,22 @@ func (ts *triState) String() string {
func (ts triState) IsBoolFlag() bool { func (ts triState) IsBoolFlag() bool {
return true return true
} }
// Legacy flag support
// vetLegacyFlags maps flags used by legacy vet to their corresponding
// new names. The old names will continue to work.
var vetLegacyFlags = map[string]string{
// Analyzer name changes
"bool": "bools",
"buildtags": "buildtag",
"methods": "stdmethods",
"rangeloops": "loopclosure",
// Analyzer flags
"compositewhitelist": "composites.whitelist",
"printfuncs": "printf.funcs",
"shadowstrict": "shadow.strict",
"unusedfuncs": "unusedresult.funcs",
"unusedstringmethods": "unusedresult.stringmethods",
}
package analysisflags
import (
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"sort"
"strings"
"golang.org/x/tools/go/analysis"
)
const usage = `PROGNAME is a tool for static analysis of Go programs.
PROGNAME examines Go source code and reports suspicious constructs, such as Printf
calls whose arguments do not align with the format string. It uses heuristics
that do not guarantee all reports are genuine problems, but it can find errors
not caught by the compilers.
Usage: PROGNAME [-flag] [package]
`
// PrintUsage prints the usage message to stderr.
func PrintUsage(out io.Writer) {
progname := filepath.Base(os.Args[0])
fmt.Fprintln(out, strings.Replace(usage, "PROGNAME", progname, -1))
}
// Help implements the help subcommand for a multichecker or vet-lite
// style command. The optional args specify the analyzers to describe.
// Help calls log.Fatal if no such analyzer exists.
func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
// No args: show summary of all analyzers.
if len(args) == 0 {
PrintUsage(os.Stdout)
fmt.Println("Registered analyzers:")
fmt.Println()
sort.Slice(analyzers, func(i, j int) bool {
return analyzers[i].Name < analyzers[j].Name
})
for _, a := range analyzers {
title := strings.Split(a.Doc, "\n\n")[0]
fmt.Printf(" %-12s %s\n", a.Name, title)
}
fmt.Println("\nBy default all analyzers are run.")
fmt.Println("To select specific analyzers, use the -NAME flag for each one,")
fmt.Println(" or -NAME=false to run all analyzers not explicitly disabled.")
// Show only the core command-line flags.
fmt.Println("\nCore flags:")
fmt.Println()
fs := flag.NewFlagSet("", flag.ExitOnError)
flag.VisitAll(func(f *flag.Flag) {
if !strings.Contains(f.Name, ".") {
fs.Var(f.Value, f.Name, f.Usage)
}
})
fs.PrintDefaults()
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname)
return
}
// Show help on specific analyzer(s).
outer:
for _, arg := range args {
for _, a := range analyzers {
if a.Name == arg {
paras := strings.Split(a.Doc, "\n\n")
title := paras[0]
fmt.Printf("%s: %s\n", a.Name, title)
// Show only the flags relating to this analysis,
// properly prefixed.
first := true
fs := flag.NewFlagSet(a.Name, flag.ExitOnError)
a.Flags.VisitAll(func(f *flag.Flag) {
if first {
first = false
fmt.Println("\nAnalyzer flags:")
fmt.Println()
}
fs.Var(f.Value, a.Name+"."+f.Name, f.Usage)
})
fs.PrintDefaults()
if len(paras) > 1 {
fmt.Printf("\n%s\n", strings.Join(paras[1:], "\n\n"))
}
continue outer
}
}
log.Fatalf("Analyzer %q not registered", arg)
}
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// driver that analyzes a single compilation unit during a build. // driver that analyzes a single compilation unit during a build.
// It is invoked by a build system such as "go vet": // It is invoked by a build system such as "go vet":
// //
// $ GOVETTOOL=$(which vet) go vet // $ go vet -vettool=$(which vet)
// //
// It supports the following command-line protocol: // It supports the following command-line protocol:
// //
......
...@@ -243,16 +243,17 @@ Files: ...@@ -243,16 +243,17 @@ Files:
} }
} }
if arch == "" { if arch == "" {
badf("%s: cannot determine architecture for assembly file") log.Printf("%s: cannot determine architecture for assembly file", fname)
continue Files continue Files
} }
} }
fnName = m[2] fnName = m[2]
if pkgName := strings.TrimSpace(m[1]); pkgName != "" { if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" {
pathParts := strings.Split(pkgName, "∕") // The assembler uses Unicode division slash within
pkgName = pathParts[len(pathParts)-1] // identifiers to represent the directory separator.
if pkgName != pass.Pkg.Path() { pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
badf("[%s] cannot check cross-package assembly function: %s is in package %s", arch, fnName, pkgName) if pkgPath != pass.Pkg.Path() {
log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
fn = nil fn = nil
fnName = "" fnName = ""
continue continue
......
...@@ -9,18 +9,22 @@ package cgocall ...@@ -9,18 +9,22 @@ package cgocall
import ( import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/build"
"go/format"
"go/parser"
"go/token" "go/token"
"go/types" "go/types"
"log" "log"
"strings" "os"
"strconv"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
) )
const Doc = `detect some violations of the cgo pointer passing rules const debug = false
const doc = `detect some violations of the cgo pointer passing rules
Check for invalid cgo pointer passing. Check for invalid cgo pointer passing.
This looks for code that uses cgo to call C code passing values This looks for code that uses cgo to call C code passing values
...@@ -31,24 +35,41 @@ or slice to C, either directly, or via a pointer, array, or struct.` ...@@ -31,24 +35,41 @@ or slice to C, either directly, or via a pointer, array, or struct.`
var Analyzer = &analysis.Analyzer{ var Analyzer = &analysis.Analyzer{
Name: "cgocall", Name: "cgocall",
Doc: Doc, Doc: doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true, RunDespiteErrors: true,
Run: run, Run: run,
} }
func run(pass *analysis.Pass) (interface{}, error) { func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) if imports(pass.Pkg, "runtime/cgo") == nil {
return nil, nil // doesn't use cgo
}
nodeFilter := []ast.Node{ cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo)
(*ast.CallExpr)(nil), if err != nil {
return nil, err
}
for _, f := range cgofiles {
checkCgo(pass.Fset, f, info, pass.Reportf)
} }
inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool { return nil, nil
if !push { }
func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) {
ast.Inspect(f, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true return true
} }
call, name := findCall(pass.Fset, stack)
if call == nil { // Is this a C.f() call?
var name string
if sel, ok := analysisutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
name = sel.Sel.Name
}
}
if name == "" {
return true // not a call we need to check return true // not a call we need to check
} }
...@@ -57,92 +78,210 @@ func run(pass *analysis.Pass) (interface{}, error) { ...@@ -57,92 +78,210 @@ func run(pass *analysis.Pass) (interface{}, error) {
return true return true
} }
if false { if debug {
fmt.Printf("%s: inner call to C.%s\n", pass.Fset.Position(n.Pos()), name) log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name)
fmt.Printf("%s: outer call to C.%s\n", pass.Fset.Position(call.Lparen), name)
} }
for _, arg := range call.Args { for _, arg := range call.Args {
if !typeOKForCgoCall(cgoBaseType(pass.TypesInfo, arg), make(map[types.Type]bool)) { if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) {
pass.Reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C") reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
break break
} }
// Check for passing the address of a bad type. // Check for passing the address of a bad type.
if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
isUnsafePointer(pass.TypesInfo, conv.Fun) { isUnsafePointer(info, conv.Fun) {
arg = conv.Args[0] arg = conv.Args[0]
} }
if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND { if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
if !typeOKForCgoCall(cgoBaseType(pass.TypesInfo, u.X), make(map[types.Type]bool)) { if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) {
pass.Reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C") reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
break break
} }
} }
} }
return true return true
}) })
return nil, nil
} }
// findCall returns the CallExpr that we need to check, which may not be // typeCheckCgoSourceFiles returns type-checked syntax trees for the raw
// the same as the one we're currently visiting, due to code generation. // cgo files of a package (those that import "C"). Such files are not
// It also returns the name of the function, such as "f" for C.f(...). // Go, so there may be gaps in type information around C.f references.
//
// This checker was initially written in vet to inpect unprocessed cgo
// source files using partial type information. However, Analyzers in
// the new analysis API are presented with the type-checked, processed
// Go ASTs resulting from cgo processing files, so we must choose
// between:
// //
// a) locating the cgo file (e.g. from //line directives) // This checker was initially written in vet to inpect raw cgo source
// and working with that, or // files using partial type information. However, Analyzers in the new
// b) working with the file generated by cgo. // analysis API are presented with the type-checked, "cooked" Go ASTs
// resulting from cgo-processing files, so we must choose between
// working with the cooked file generated by cgo (which was tried but
// proved fragile) or locating the raw cgo file (e.g. from //line
// directives) and working with that, as we now do.
// //
// We cannot use (a) because it does not provide type information, which // Specifically, we must type-check the raw cgo source files (or at
// the analyzer needs, and it is infeasible for the analyzer to run the // least the subtrees needed for this analyzer) in an environment that
// type checker on this file. Thus we choose (b), which is fragile, // simulates the rest of the already type-checked package.
// because the checker may need to change each time the cgo processor
// changes.
// //
// Consider a cgo source file containing this header: // For example, for each raw cgo source file in the original package,
// such as this one:
// //
// /* void f(void *x, *y); */ // package p
// import "C" // import "C"
// import "fmt"
// type T int
// const k = 3
// var x, y = fmt.Println()
// func f() { ... }
// func g() { ... C.malloc(k) ... }
// func (T) f(int) string { ... }
// //
// The cgo tool expands a call such as: // we synthesize a new ast.File, shown below, that dot-imports the
// orginal "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.)
// //
// C.f(x, y) // To avoid shadowing names imported from the cooked package,
// package-level declarations in the new source file are modified so
// that they do not declare any names.
// (The cgocall analysis is concerned with uses, not declarations.)
// Specifically, type declarations are discarded;
// all names in each var and const declaration are blanked out;
// each method is turned into a regular function by turning
// the receiver into the first parameter;
// and all functions are renamed to "_".
// //
// to this: // package p
// import . "·this·" // declares T, k, x, y, f, g, T.f
// import "C"
// import "fmt"
// const _ = 3
// var _, _ = fmt.Println()
// func _() { ... }
// func _() { ... C.malloc(k) ... }
// func _(T, int) string { ... }
// //
// 1 func(param0, param1 unsafe.Pointer) { // In this way, the raw function bodies and const/var initializer
// 2 ... various checks on params ... // expressions are preserved but refer to the "cooked" objects imported
// 3 (_Cfunc_f)(param0, param1) // from "·this·", and none of the transformed package-level declarations
// 4 }(x, y) // actually declares anything. In the example above, the reference to k
// in the argument of the call to C.malloc resolves to "·this·".k, which
// has an accurate type.
// //
// We first locate the _Cfunc_f call on line 3, then // This approach could in principle be generalized to more complex
// walk up the stack of enclosing nodes until we find // analyses on raw cgo files. One could synthesize a "C" package so that
// the call on line 4. // C.f would resolve to "·this·"._C_func_f, for example. But we have
// limited ourselves here to preserving function bodies and initializer
// expressions since that is all that the cgocall analyzer needs.
// //
func findCall(fset *token.FileSet, stack []ast.Node) (*ast.CallExpr, string) { func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info) ([]*ast.File, *types.Info, error) {
last := len(stack) - 1 const thispkg = "·this·"
call := stack[last].(*ast.CallExpr)
if id, ok := analysisutil.Unparen(call.Fun).(*ast.Ident); ok { // Which files are cgo files?
if name := strings.TrimPrefix(id.Name, "_Cfunc_"); name != id.Name { var cgoFiles []*ast.File
// Find the outer call with the arguments (x, y) we want to check. importMap := map[string]*types.Package{thispkg: pkg}
for i := last - 1; i >= 0; i-- { for _, raw := range files {
if outer, ok := stack[i].(*ast.CallExpr); ok { // If f is a cgo-generated file, Position reports
return outer, name // the original file, honoring //line directives.
filename := fset.Position(raw.Pos()).Filename
f, err := parser.ParseFile(fset, filename, nil, parser.Mode(0))
if err != nil {
return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)
}
found := false
for _, spec := range f.Imports {
if spec.Path.Value == `"C"` {
found = true
break
}
}
if !found {
continue // not a cgo file
} }
// Record the original import map.
for _, spec := range raw.Imports {
path, _ := strconv.Unquote(spec.Path.Value)
importMap[path] = imported(info, spec)
}
// Add special dot-import declaration:
// import . "·this·"
var decls []ast.Decl
decls = append(decls, &ast.GenDecl{
Tok: token.IMPORT,
Specs: []ast.Spec{
&ast.ImportSpec{
Name: &ast.Ident{Name: "."},
Path: &ast.BasicLit{
Kind: token.STRING,
Value: strconv.Quote(thispkg),
},
},
},
})
// Transform declarations from the raw cgo file.
for _, decl := range f.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE:
// Discard type declarations.
continue
case token.IMPORT:
// Keep imports.
case token.VAR, token.CONST:
// Blank the declared var/const names.
for _, spec := range decl.Specs {
spec := spec.(*ast.ValueSpec)
for i := range spec.Names {
spec.Names[i].Name = "_"
} }
// This shouldn't happen.
// Perhaps the code generator has changed?
log.Printf("%s: can't find outer call for C.%s(...)",
fset.Position(call.Lparen), name)
} }
} }
return nil, "" case *ast.FuncDecl:
// Blank the declared func name.
decl.Name.Name = "_"
// Turn a method receiver: func (T) f(P) R {...}
// into regular parameter: func _(T, P) R {...}
if decl.Recv != nil {
var params []*ast.Field
params = append(params, decl.Recv.List...)
params = append(params, decl.Type.Params.List...)
decl.Type.Params.List = params
decl.Recv = nil
}
}
decls = append(decls, decl)
}
f.Decls = decls
if debug {
format.Node(os.Stderr, fset, f) // debugging
}
cgoFiles = append(cgoFiles, f)
}
if cgoFiles == nil {
return nil, nil, nil // nothing to do (can't happen?)
}
// Type-check the synthetic files.
tc := &types.Config{
FakeImportC: true,
Importer: importerFunc(func(path string) (*types.Package, error) {
return importMap[path], nil
}),
// TODO(adonovan): Sizes should probably be provided by analysis.Pass.
Sizes: types.SizesFor("gc", build.Default.GOARCH),
Error: func(error) {}, // ignore errors (e.g. unused import)
}
// It's tempting to record the new types in the
// existing pass.TypesInfo, but we don't own it.
altInfo := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
}
tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
return cgoFiles, altInfo, nil
} }
// cgoBaseType tries to look through type conversions involving // cgoBaseType tries to look through type conversions involving
...@@ -224,3 +363,28 @@ func isUnsafePointer(info *types.Info, e ast.Expr) bool { ...@@ -224,3 +363,28 @@ func isUnsafePointer(info *types.Info, e ast.Expr) bool {
t := info.Types[e].Type t := info.Types[e].Type
return t != nil && t.Underlying() == types.Typ[types.UnsafePointer] return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
} }
type importerFunc func(path string) (*types.Package, error)
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
// TODO(adonovan): make this a library function or method of Info.
func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
obj, ok := info.Implicits[spec]
if !ok {
obj = info.Defs[spec.Name] // renaming import
}
return obj.(*types.PkgName).Imported()
}
// imports reports whether pkg has path among its direct imports.
// It returns the imported package if so, or nil if not.
// TODO(adonovan): move to analysisutil.
func imports(pkg *types.Package, path string) *types.Package {
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return imp
}
}
return nil
}
...@@ -132,11 +132,17 @@ func runFunc(pass *analysis.Pass, node ast.Node) { ...@@ -132,11 +132,17 @@ func runFunc(pass *analysis.Pass, node ast.Node) {
var sig *types.Signature var sig *types.Signature
switch node := node.(type) { switch node := node.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
g = cfgs.FuncDecl(node)
sig, _ = pass.TypesInfo.Defs[node.Name].Type().(*types.Signature) sig, _ = pass.TypesInfo.Defs[node.Name].Type().(*types.Signature)
if node.Name.Name == "main" && sig.Recv() == nil && pass.Pkg.Name() == "main" {
// Returning from main.main terminates the process,
// so there's no need to cancel contexts.
return
}
g = cfgs.FuncDecl(node)
case *ast.FuncLit: case *ast.FuncLit:
g = cfgs.FuncLit(node)
sig, _ = pass.TypesInfo.Types[node.Type].Type.(*types.Signature) sig, _ = pass.TypesInfo.Types[node.Type].Type.(*types.Signature)
g = cfgs.FuncLit(node)
} }
if sig == nil { if sig == nil {
return // missing type information return // missing type information
......
...@@ -277,18 +277,56 @@ func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, k ...@@ -277,18 +277,56 @@ func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, k
// or case-insensitive identifiers such as "errorf". // or case-insensitive identifiers such as "errorf".
// //
// The -funcs flag adds to this set. // The -funcs flag adds to this set.
//
// The set below includes facts for many important standard library
// functions, even though the analysis is capable of deducing that, for
// example, fmt.Printf forwards to fmt.Fprintf. We avoid relying on the
// driver applying analyzers to standard packages because "go vet" does
// not do so with gccgo, and nor do some other build systems.
// TODO(adonovan): eliminate the redundant facts once this restriction
// is lifted.
//
var isPrint = stringSet{ var isPrint = stringSet{
"fmt.Errorf": true, "fmt.Errorf": true,
"fmt.Fprint": true, "fmt.Fprint": true,
"fmt.Fprintf": true, "fmt.Fprintf": true,
"fmt.Fprintln": true, "fmt.Fprintln": true,
"fmt.Print": true, // technically these three "fmt.Print": true,
"fmt.Printf": true, // are redundant because they "fmt.Printf": true,
"fmt.Println": true, // forward to Fprint{,f,ln} "fmt.Println": true,
"fmt.Sprint": true, "fmt.Sprint": true,
"fmt.Sprintf": true, "fmt.Sprintf": true,
"fmt.Sprintln": true, "fmt.Sprintln": true,
"runtime/trace.Logf": true,
"log.Print": true,
"log.Printf": true,
"log.Println": true,
"log.Fatal": true,
"log.Fatalf": true,
"log.Fatalln": true,
"log.Panic": true,
"log.Panicf": true,
"log.Panicln": true,
"(*log.Logger).Fatal": true,
"(*log.Logger).Fatalf": true,
"(*log.Logger).Fatalln": true,
"(*log.Logger).Panic": true,
"(*log.Logger).Panicf": true,
"(*log.Logger).Panicln": true,
"(*log.Logger).Print": true,
"(*log.Logger).Printf": true,
"(*log.Logger).Println": true,
"(*testing.common).Error": true,
"(*testing.common).Errorf": true,
"(*testing.common).Fatal": true,
"(*testing.common).Fatalf": true,
"(*testing.common).Log": true,
"(*testing.common).Logf": true,
"(*testing.common).Skip": true,
"(*testing.common).Skipf": true,
// *testing.T and B are detected by induction, but testing.TB is // *testing.T and B are detected by induction, but testing.TB is
// an interface and the inference can't follow dynamic calls. // an interface and the inference can't follow dynamic calls.
"(testing.TB).Error": true, "(testing.TB).Error": true,
......
...@@ -97,12 +97,25 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, ...@@ -97,12 +97,25 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
if t == argPointer { if t == argPointer {
return true return true
} }
// If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct.
if str, ok := typ.Elem().Underlying().(*types.Struct); ok { under := typ.Elem().Underlying()
return matchStructArgType(pass, t, str, arg, inProgress) switch under.(type) {
} case *types.Struct: // see below
case *types.Array: // see below
case *types.Slice: // see below
case *types.Map: // see below
default:
// Check whether the rest can print pointers. // Check whether the rest can print pointers.
return t&argPointer != 0 return t&argPointer != 0
}
// If it's a top-level pointer to a struct, array, slice, or
// map, that's equivalent in our analysis to whether we can
// print the type being pointed to. Pointers in nested levels
// are not supported to minimize fmt running into loops.
if len(inProgress) > 1 {
return false
}
return matchArgTypeInternal(pass, t, under, arg, inProgress)
case *types.Struct: case *types.Struct:
return matchStructArgType(pass, t, typ, arg, inProgress) return matchStructArgType(pass, t, typ, arg, inProgress)
......
...@@ -7,10 +7,7 @@ ...@@ -7,10 +7,7 @@
package stdmethods package stdmethods
import ( import (
"bytes"
"fmt"
"go/ast" "go/ast"
"go/printer"
"go/token" "go/token"
"go/types" "go/types"
"strings" "strings"
...@@ -95,12 +92,12 @@ func run(pass *analysis.Pass) (interface{}, error) { ...@@ -95,12 +92,12 @@ func run(pass *analysis.Pass) (interface{}, error) {
switch n := n.(type) { switch n := n.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if n.Recv != nil { if n.Recv != nil {
canonicalMethod(pass, n.Name, n.Type) canonicalMethod(pass, n.Name)
} }
case *ast.InterfaceType: case *ast.InterfaceType:
for _, field := range n.Methods.List { for _, field := range n.Methods.List {
for _, id := range field.Names { for _, id := range field.Names {
canonicalMethod(pass, id, field.Type.(*ast.FuncType)) canonicalMethod(pass, id)
} }
} }
} }
...@@ -108,7 +105,7 @@ func run(pass *analysis.Pass) (interface{}, error) { ...@@ -108,7 +105,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
return nil, nil return nil, nil
} }
func canonicalMethod(pass *analysis.Pass, id *ast.Ident, t *ast.FuncType) { func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
// Expected input/output. // Expected input/output.
expect, ok := canonicalMethods[id.Name] expect, ok := canonicalMethods[id.Name]
if !ok { if !ok {
...@@ -116,11 +113,9 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident, t *ast.FuncType) { ...@@ -116,11 +113,9 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident, t *ast.FuncType) {
} }
// Actual input/output // Actual input/output
args := typeFlatten(t.Params.List) sign := pass.TypesInfo.Defs[id].Type().(*types.Signature)
var results []ast.Expr args := sign.Params()
if t.Results != nil { results := sign.Results()
results = typeFlatten(t.Results.List)
}
// Do the =s (if any) all match? // Do the =s (if any) all match?
if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") { if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
...@@ -136,11 +131,7 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident, t *ast.FuncType) { ...@@ -136,11 +131,7 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident, t *ast.FuncType) {
expectFmt += " (" + argjoin(expect.results) + ")" expectFmt += " (" + argjoin(expect.results) + ")"
} }
var buf bytes.Buffer actual := types.TypeString(sign, (*types.Package).Name)
if err := printer.Fprint(&buf, pass.Fset, t); err != nil {
fmt.Fprintf(&buf, "<%s>", err)
}
actual := buf.String()
actual = strings.TrimPrefix(actual, "func") actual = strings.TrimPrefix(actual, "func")
actual = id.Name + actual actual = id.Name + actual
...@@ -159,45 +150,27 @@ func argjoin(x []string) string { ...@@ -159,45 +150,27 @@ func argjoin(x []string) string {
return strings.Join(y, ", ") return strings.Join(y, ", ")
} }
// Turn parameter list into slice of types
// (in the ast, types are Exprs).
// Have to handle f(int, bool) and f(x, y, z int)
// so not a simple 1-to-1 conversion.
func typeFlatten(l []*ast.Field) []ast.Expr {
var t []ast.Expr
for _, f := range l {
if len(f.Names) == 0 {
t = append(t, f.Type)
continue
}
for range f.Names {
t = append(t, f.Type)
}
}
return t
}
// Does each type in expect with the given prefix match the corresponding type in actual? // Does each type in expect with the given prefix match the corresponding type in actual?
func matchParams(pass *analysis.Pass, expect []string, actual []ast.Expr, prefix string) bool { func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, prefix string) bool {
for i, x := range expect { for i, x := range expect {
if !strings.HasPrefix(x, prefix) { if !strings.HasPrefix(x, prefix) {
continue continue
} }
if i >= len(actual) { if i >= actual.Len() {
return false return false
} }
if !matchParamType(pass.Fset, pass.Pkg, x, actual[i]) { if !matchParamType(pass.Fset, pass.Pkg, x, actual.At(i).Type()) {
return false return false
} }
} }
if prefix == "" && len(actual) > len(expect) { if prefix == "" && actual.Len() > len(expect) {
return false return false
} }
return true return true
} }
// Does this one type match? // Does this one type match?
func matchParamType(fset *token.FileSet, pkg *types.Package, expect string, actual ast.Expr) bool { func matchParamType(fset *token.FileSet, pkg *types.Package, expect string, actual types.Type) bool {
expect = strings.TrimPrefix(expect, "=") expect = strings.TrimPrefix(expect, "=")
// Strip package name if we're in that package. // Strip package name if we're in that package.
if n := len(pkg.Name()); len(expect) > n && expect[:n] == pkg.Name() && expect[n] == '.' { if n := len(pkg.Name()); len(expect) > n && expect[:n] == pkg.Name() && expect[n] == '.' {
...@@ -205,7 +178,5 @@ func matchParamType(fset *token.FileSet, pkg *types.Package, expect string, actu ...@@ -205,7 +178,5 @@ func matchParamType(fset *token.FileSet, pkg *types.Package, expect string, actu
} }
// Overkill but easy. // Overkill but easy.
var buf bytes.Buffer return actual.String() == expect
printer.Fprint(&buf, fset, actual)
return buf.String() == expect
} }
// Copyright 2018 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.
// The unmarshal package defines an Analyzer that checks for passing
// non-pointer or non-interface types to unmarshal and decode functions.
package unmarshal
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
const doc = `report passing non-pointer or non-interface values to unmarshal
The unmarshal analysis reports calls to functions such as json.Unmarshal
in which the argument type is not a pointer or an interface.`
var Analyzer = &analysis.Analyzer{
Name: "unmarshal",
Doc: doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
fn := typeutil.StaticCallee(pass.TypesInfo, call)
if fn == nil {
return // not a static call
}
// Classify the callee (without allocating memory).
argidx := -1
recv := fn.Type().(*types.Signature).Recv()
if fn.Name() == "Unmarshal" && recv == nil {
// "encoding/json".Unmarshal
// "encoding/xml".Unmarshal
switch fn.Pkg().Path() {
case "encoding/json", "encoding/xml":
argidx = 1 // func([]byte, interface{})
}
} else if fn.Name() == "Decode" && recv != nil {
// (*"encoding/json".Decoder).Decode
// (* "encoding/gob".Decoder).Decode
// (* "encoding/xml".Decoder).Decode
t := recv.Type()
if ptr, ok := t.(*types.Pointer); ok {
t = ptr.Elem()
}
tname := t.(*types.Named).Obj()
if tname.Name() == "Decoder" {
switch tname.Pkg().Path() {
case "encoding/json", "encoding/xml", "encoding/gob":
argidx = 0 // func(interface{})
}
}
}
if argidx < 0 {
return // not a function we are interested in
}
if len(call.Args) < argidx+1 {
return // not enough arguments, e.g. called with return values of another function
}
t := pass.TypesInfo.Types[call.Args[argidx]].Type
switch t.Underlying().(type) {
case *types.Pointer, *types.Interface:
return
}
switch argidx {
case 0:
pass.Reportf(call.Lparen, "call of %s passes non-pointer", fn.Name())
case 1:
pass.Reportf(call.Lparen, "call of %s passes non-pointer as second argument", fn.Name())
}
})
return nil, nil
}
...@@ -14,26 +14,26 @@ import ( ...@@ -14,26 +14,26 @@ import (
) )
// AddImport adds the import path to the file f, if absent. // AddImport adds the import path to the file f, if absent.
func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) { func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) {
return AddNamedImport(fset, f, "", ipath) return AddNamedImport(fset, f, "", path)
} }
// AddNamedImport adds the import path to the file f, if absent. // AddNamedImport adds the import with the given name and path to the file f, if absent.
// If name is not empty, it is used to rename the import. // If name is not empty, it is used to rename the import.
// //
// For example, calling // For example, calling
// AddNamedImport(fset, f, "pathpkg", "path") // AddNamedImport(fset, f, "pathpkg", "path")
// adds // adds
// import pathpkg "path" // import pathpkg "path"
func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) { func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) {
if imports(f, ipath) { if imports(f, name, path) {
return false return false
} }
newImport := &ast.ImportSpec{ newImport := &ast.ImportSpec{
Path: &ast.BasicLit{ Path: &ast.BasicLit{
Kind: token.STRING, Kind: token.STRING,
Value: strconv.Quote(ipath), Value: strconv.Quote(path),
}, },
} }
if name != "" { if name != "" {
...@@ -43,14 +43,14 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added ...@@ -43,14 +43,14 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added
// Find an import decl to add to. // Find an import decl to add to.
// The goal is to find an existing import // The goal is to find an existing import
// whose import path has the longest shared // whose import path has the longest shared
// prefix with ipath. // prefix with path.
var ( var (
bestMatch = -1 // length of longest shared prefix bestMatch = -1 // length of longest shared prefix
lastImport = -1 // index in f.Decls of the file's final import decl lastImport = -1 // index in f.Decls of the file's final import decl
impDecl *ast.GenDecl // import decl containing the best match impDecl *ast.GenDecl // import decl containing the best match
impIndex = -1 // spec index in impDecl containing the best match impIndex = -1 // spec index in impDecl containing the best match
isThirdPartyPath = isThirdParty(ipath) isThirdPartyPath = isThirdParty(path)
) )
for i, decl := range f.Decls { for i, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl) gen, ok := decl.(*ast.GenDecl)
...@@ -81,7 +81,7 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added ...@@ -81,7 +81,7 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added
for j, spec := range gen.Specs { for j, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec) impspec := spec.(*ast.ImportSpec)
p := importPath(impspec) p := importPath(impspec)
n := matchLen(p, ipath) n := matchLen(p, path)
if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
bestMatch = n bestMatch = n
impDecl = gen impDecl = gen
...@@ -197,11 +197,13 @@ func isThirdParty(importPath string) bool { ...@@ -197,11 +197,13 @@ func isThirdParty(importPath string) bool {
} }
// DeleteImport deletes the import path from the file f, if present. // DeleteImport deletes the import path from the file f, if present.
// If there are duplicate import declarations, all matching ones are deleted.
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
return DeleteNamedImport(fset, f, "", path) return DeleteNamedImport(fset, f, "", path)
} }
// DeleteNamedImport deletes the import with the given name and path from the file f, if present. // DeleteNamedImport deletes the import with the given name and path from the file f, if present.
// If there are duplicate import declarations, all matching ones are deleted.
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) {
var delspecs []*ast.ImportSpec var delspecs []*ast.ImportSpec
var delcomments []*ast.CommentGroup var delcomments []*ast.CommentGroup
...@@ -216,13 +218,7 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del ...@@ -216,13 +218,7 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del
for j := 0; j < len(gen.Specs); j++ { for j := 0; j < len(gen.Specs); j++ {
spec := gen.Specs[j] spec := gen.Specs[j]
impspec := spec.(*ast.ImportSpec) impspec := spec.(*ast.ImportSpec)
if impspec.Name == nil && name != "" { if importName(impspec) != name || importPath(impspec) != path {
continue
}
if impspec.Name != nil && impspec.Name.Name != name {
continue
}
if importPath(impspec) != path {
continue continue
} }
...@@ -383,9 +379,14 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor { ...@@ -383,9 +379,14 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor {
return fn return fn
} }
// imports returns true if f imports path. // imports reports whether f has an import with the specified name and path.
func imports(f *ast.File, path string) bool { func imports(f *ast.File, name, path string) bool {
return importSpec(f, path) != nil for _, s := range f.Imports {
if importName(s) == name && importPath(s) == path {
return true
}
}
return false
} }
// importSpec returns the import spec if f imports path, // importSpec returns the import spec if f imports path,
...@@ -399,14 +400,23 @@ func importSpec(f *ast.File, path string) *ast.ImportSpec { ...@@ -399,14 +400,23 @@ func importSpec(f *ast.File, path string) *ast.ImportSpec {
return nil return nil
} }
// importName returns the name of s,
// or "" if the import is not named.
func importName(s *ast.ImportSpec) string {
if s.Name == nil {
return ""
}
return s.Name.Name
}
// importPath returns the unquoted import path of s, // importPath returns the unquoted import path of s,
// or "" if the path is not properly quoted. // or "" if the path is not properly quoted.
func importPath(s *ast.ImportSpec) string { func importPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value) t, err := strconv.Unquote(s.Path.Value)
if err == nil { if err != nil {
return t
}
return "" return ""
}
return t
} }
// declImports reports whether gen contains an import of path. // declImports reports whether gen contains an import of path.
......
#!/bin/sh
#
# update-xtools.sh: idempotently update the vendored
# copy of the x/tools repository used by vet-lite.
set -u
analysis=$(go list -f {{.Dir}} golang.org/x/tools/go/analysis) ||
{ echo "Add golang.org/x/tools to your GOPATH"; exit 1; } >&2
xtools=$(dirname $(dirname $analysis))
vendor=$(dirname $0)
go list -f '{{.ImportPath}} {{.Dir}}' -deps golang.org/x/tools/go/analysis/cmd/vet-lite |
grep golang.org/x/tools |
while read path dir
do
mkdir -p $vendor/$path
cp $dir/* -t $vendor/$path 2>/dev/null # ignore errors from subdirectories
rm -f $vendor/$path/*_test.go
git add $vendor/$path
done
echo "Copied $xtools@$(cd $analysis && git rev-parse --short HEAD) to $vendor" >&2
go build -o /dev/null ./golang.org/x/tools/go/analysis/cmd/vet-lite ||
{ echo "Failed to build vet-lite"; exit 1; } >&2
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment