diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2020-07-04 11:12:55 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-07-04 15:05:30 +0200 |
| commit | c7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch) | |
| tree | 0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/golangci/unconvert | |
| parent | 9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff) | |
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/golangci/unconvert')
| -rw-r--r-- | vendor/github.com/golangci/unconvert/LICENSE | 27 | ||||
| -rw-r--r-- | vendor/github.com/golangci/unconvert/README | 36 | ||||
| -rw-r--r-- | vendor/github.com/golangci/unconvert/unconvert.go | 665 |
3 files changed, 728 insertions, 0 deletions
diff --git a/vendor/github.com/golangci/unconvert/LICENSE b/vendor/github.com/golangci/unconvert/LICENSE new file mode 100644 index 000000000..744875676 --- /dev/null +++ b/vendor/github.com/golangci/unconvert/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/golangci/unconvert/README b/vendor/github.com/golangci/unconvert/README new file mode 100644 index 000000000..dbaea4f57 --- /dev/null +++ b/vendor/github.com/golangci/unconvert/README @@ -0,0 +1,36 @@ +About: + +The unconvert program analyzes Go packages to identify unnecessary +type conversions; i.e., expressions T(x) where x already has type T. + +Install: + + $ go get github.com/mdempsky/unconvert + +Usage: + + $ unconvert -v bytes fmt + GOROOT/src/bytes/reader.go:117:14: unnecessary conversion + abs = int64(r.i) + offset + ^ + GOROOT/src/fmt/print.go:411:21: unnecessary conversion + p.fmt.integer(int64(v), 16, unsigned, udigits) + ^ + +Flags: + +Using the -v flag, unconvert will also print the source line and a +caret to indicate the unnecessary conversion's position therein. + +Using the -apply flag, unconvert will rewrite the Go source files +without the unnecessary type conversions. + +Using the -all flag, unconvert will analyze the Go packages under all +possible GOOS/GOARCH combinations, and only identify conversions that +are unnecessary in all cases. + +E.g., syscall.Timespec's Sec and Nsec fields are int64 under +linux/amd64 but int32 under linux/386. An int64(ts.Sec) conversion +that appears in a linux/amd64-only file will be identified as +unnecessary, but it will be preserved if it occurs in a file that's +compiled for both linux/amd64 and linux/386. diff --git a/vendor/github.com/golangci/unconvert/unconvert.go b/vendor/github.com/golangci/unconvert/unconvert.go new file mode 100644 index 000000000..38737d39f --- /dev/null +++ b/vendor/github.com/golangci/unconvert/unconvert.go @@ -0,0 +1,665 @@ +// 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. + +// Unconvert removes redundant type conversions from Go packages. +package unconvert + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/build" + "go/format" + "go/parser" + "go/token" + "go/types" + "io/ioutil" + "log" + "os" + "reflect" + "runtime/pprof" + "sort" + "sync" + "unicode" + + "github.com/kisielk/gotool" + "golang.org/x/text/width" + "golang.org/x/tools/go/loader" +) + +// Unnecessary conversions are identified by the position +// of their left parenthesis within a source file. + +type editSet map[token.Position]struct{} + +type fileToEditSet map[string]editSet + +func apply(file string, edits editSet) { + if len(edits) == 0 { + return + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, file, nil, parser.ParseComments) + if err != nil { + log.Fatal(err) + } + + // Note: We modify edits during the walk. + v := editor{edits: edits, file: fset.File(f.Package)} + ast.Walk(&v, f) + if len(edits) != 0 { + log.Printf("%s: missing edits %s", file, edits) + } + + // TODO(mdempsky): Write to temporary file and rename. + var buf bytes.Buffer + err = format.Node(&buf, fset, f) + if err != nil { + log.Fatal(err) + } + + err = ioutil.WriteFile(file, buf.Bytes(), 0) + if err != nil { + log.Fatal(err) + } +} + +type editor struct { + edits editSet + file *token.File +} + +func (e *editor) Visit(n ast.Node) ast.Visitor { + if n == nil { + return nil + } + v := reflect.ValueOf(n).Elem() + for i, n := 0, v.NumField(); i < n; i++ { + switch f := v.Field(i).Addr().Interface().(type) { + case *ast.Expr: + e.rewrite(f) + case *[]ast.Expr: + for i := range *f { + e.rewrite(&(*f)[i]) + } + } + } + return e +} + +func (e *editor) rewrite(f *ast.Expr) { + call, ok := (*f).(*ast.CallExpr) + if !ok { + return + } + + pos := e.file.Position(call.Lparen) + if _, ok := e.edits[pos]; !ok { + return + } + *f = call.Args[0] + delete(e.edits, pos) +} + +var ( + cr = []byte{'\r'} + nl = []byte{'\n'} +) + +func print(conversions []token.Position) { + var file string + var lines [][]byte + + for _, pos := range conversions { + fmt.Printf("%s:%d:%d: unnecessary conversion\n", pos.Filename, pos.Line, pos.Column) + if *flagV { + if pos.Filename != file { + buf, err := ioutil.ReadFile(pos.Filename) + if err != nil { + log.Fatal(err) + } + file = pos.Filename + lines = bytes.Split(buf, nl) + } + + line := bytes.TrimSuffix(lines[pos.Line-1], cr) + fmt.Printf("%s\n", line) + + // For files processed by cgo, Column is the + // column location after cgo processing, which + // may be different than the source column + // that we want here. In lieu of a better + // heuristic for detecting this case, at least + // avoid panicking if column is out of bounds. + if pos.Column <= len(line) { + fmt.Printf("%s^\n", rub(line[:pos.Column-1])) + } + } + } +} + +// Rub returns a copy of buf with all non-whitespace characters replaced +// by spaces (like rubbing them out with white out). +func rub(buf []byte) []byte { + // TODO(mdempsky): Handle combining characters? + var res bytes.Buffer + for _, r := range string(buf) { + if unicode.IsSpace(r) { + res.WriteRune(r) + continue + } + switch width.LookupRune(r).Kind() { + case width.EastAsianWide, width.EastAsianFullwidth: + res.WriteString(" ") + default: + res.WriteByte(' ') + } + } + return res.Bytes() +} + +var ( + flagAll = flag.Bool("unconvert.all", false, "type check all GOOS and GOARCH combinations") + flagApply = flag.Bool("unconvert.apply", false, "apply edits to source files") + flagCPUProfile = flag.String("unconvert.cpuprofile", "", "write CPU profile to file") + // TODO(mdempsky): Better description and maybe flag name. + flagSafe = flag.Bool("unconvert.safe", false, "be more conservative (experimental)") + flagV = flag.Bool("unconvert.v", false, "verbose output") +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: unconvert [flags] [package ...]\n") + flag.PrintDefaults() +} + +func nomain() { + flag.Usage = usage + flag.Parse() + + if *flagCPUProfile != "" { + f, err := os.Create(*flagCPUProfile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + importPaths := gotool.ImportPaths(flag.Args()) + if len(importPaths) == 0 { + return + } + + var m fileToEditSet + if *flagAll { + m = mergeEdits(importPaths) + } else { + m = computeEdits(importPaths, build.Default.GOOS, build.Default.GOARCH, build.Default.CgoEnabled) + } + + if *flagApply { + var wg sync.WaitGroup + for f, e := range m { + wg.Add(1) + f, e := f, e + go func() { + defer wg.Done() + apply(f, e) + }() + } + wg.Wait() + } else { + var conversions []token.Position + for _, positions := range m { + for pos := range positions { + conversions = append(conversions, pos) + } + } + sort.Sort(byPosition(conversions)) + print(conversions) + if len(conversions) > 0 { + os.Exit(1) + } + } +} + +func Run(prog *loader.Program) []token.Position { + m := computeEditsFromProg(prog) + var conversions []token.Position + for _, positions := range m { + for pos := range positions { + conversions = append(conversions, pos) + } + } + return conversions +} + +var plats = [...]struct { + goos, goarch string +}{ + // TODO(mdempsky): buildall.bash also builds linux-386-387 and linux-arm-arm5. + {"android", "386"}, + {"android", "amd64"}, + {"android", "arm"}, + {"android", "arm64"}, + {"darwin", "386"}, + {"darwin", "amd64"}, + {"darwin", "arm"}, + {"darwin", "arm64"}, + {"dragonfly", "amd64"}, + {"freebsd", "386"}, + {"freebsd", "amd64"}, + {"freebsd", "arm"}, + {"linux", "386"}, + {"linux", "amd64"}, + {"linux", "arm"}, + {"linux", "arm64"}, + {"linux", "mips64"}, + {"linux", "mips64le"}, + {"linux", "ppc64"}, + {"linux", "ppc64le"}, + {"linux", "s390x"}, + {"nacl", "386"}, + {"nacl", "amd64p32"}, + {"nacl", "arm"}, + {"netbsd", "386"}, + {"netbsd", "amd64"}, + {"netbsd", "arm"}, + {"openbsd", "386"}, + {"openbsd", "amd64"}, + {"openbsd", "arm"}, + {"plan9", "386"}, + {"plan9", "amd64"}, + {"plan9", "arm"}, + {"solaris", "amd64"}, + {"windows", "386"}, + {"windows", "amd64"}, +} + +func mergeEdits(importPaths []string) fileToEditSet { + m := make(fileToEditSet) + for _, plat := range plats { + for f, e := range computeEdits(importPaths, plat.goos, plat.goarch, false) { + if e0, ok := m[f]; ok { + for k := range e0 { + if _, ok := e[k]; !ok { + delete(e0, k) + } + } + } else { + m[f] = e + } + } + } + return m +} + +type noImporter struct{} + +func (noImporter) Import(path string) (*types.Package, error) { + panic("golang.org/x/tools/go/loader said this wouldn't be called") +} + +func computeEdits(importPaths []string, os, arch string, cgoEnabled bool) fileToEditSet { + ctxt := build.Default + ctxt.GOOS = os + ctxt.GOARCH = arch + ctxt.CgoEnabled = cgoEnabled + + var conf loader.Config + conf.Build = &ctxt + conf.TypeChecker.Importer = noImporter{} + for _, importPath := range importPaths { + conf.Import(importPath) + } + prog, err := conf.Load() + if err != nil { + log.Fatal(err) + } + + return computeEditsFromProg(prog) +} + +func computeEditsFromProg(prog *loader.Program) fileToEditSet { + type res struct { + file string + edits editSet + } + ch := make(chan res) + var wg sync.WaitGroup + for _, pkg := range prog.InitialPackages() { + for _, file := range pkg.Files { + pkg, file := pkg, file + wg.Add(1) + go func() { + defer wg.Done() + v := visitor{pkg: pkg, file: prog.Fset.File(file.Package), edits: make(editSet)} + ast.Walk(&v, file) + ch <- res{v.file.Name(), v.edits} + }() + } + } + go func() { + wg.Wait() + close(ch) + }() + + m := make(fileToEditSet) + for r := range ch { + m[r.file] = r.edits + } + return m +} + +type step struct { + n ast.Node + i int +} + +type visitor struct { + pkg *loader.PackageInfo + file *token.File + edits editSet + path []step +} + +func (v *visitor) Visit(node ast.Node) ast.Visitor { + if node != nil { + v.path = append(v.path, step{n: node}) + } else { + n := len(v.path) + v.path = v.path[:n-1] + if n >= 2 { + v.path[n-2].i++ + } + } + + if call, ok := node.(*ast.CallExpr); ok { + v.unconvert(call) + } + return v +} + +func (v *visitor) unconvert(call *ast.CallExpr) { + // TODO(mdempsky): Handle useless multi-conversions. + + // Conversions have exactly one argument. + if len(call.Args) != 1 || call.Ellipsis != token.NoPos { + return + } + ft, ok := v.pkg.Types[call.Fun] + if !ok { + fmt.Println("Missing type for function") + return + } + if !ft.IsType() { + // Function call; not a conversion. + return + } + at, ok := v.pkg.Types[call.Args[0]] + if !ok { + fmt.Println("Missing type for argument") + return + } + if !types.Identical(ft.Type, at.Type) { + // A real conversion. + return + } + if isUntypedValue(call.Args[0], &v.pkg.Info) { + // Workaround golang.org/issue/13061. + return + } + if *flagSafe && !v.isSafeContext(at.Type) { + // TODO(mdempsky): Remove this message. + fmt.Println("Skipped a possible type conversion because of -safe at", v.file.Position(call.Pos())) + return + } + if v.isCgoCheckPointerContext() { + // cmd/cgo generates explicit type conversions that + // are often redundant when introducing + // _cgoCheckPointer calls (issue #16). Users can't do + // anything about these, so skip over them. + return + } + + v.edits[v.file.Position(call.Lparen)] = struct{}{} +} + +func (v *visitor) isCgoCheckPointerContext() bool { + ctxt := &v.path[len(v.path)-2] + if ctxt.i != 1 { + return false + } + call, ok := ctxt.n.(*ast.CallExpr) + if !ok { + return false + } + ident, ok := call.Fun.(*ast.Ident) + if !ok { + return false + } + return ident.Name == "_cgoCheckPointer" +} + +// isSafeContext reports whether the current context requires +// an expression of type t. +// +// TODO(mdempsky): That's a bad explanation. +func (v *visitor) isSafeContext(t types.Type) bool { + ctxt := &v.path[len(v.path)-2] + switch n := ctxt.n.(type) { + case *ast.AssignStmt: + pos := ctxt.i - len(n.Lhs) + if pos < 0 { + fmt.Println("Type conversion on LHS of assignment?") + return false + } + if n.Tok == token.DEFINE { + // Skip := assignments. + return true + } + // We're a conversion in the pos'th element of n.Rhs. + // Check that the corresponding element of n.Lhs is of type t. + lt, ok := v.pkg.Types[n.Lhs[pos]] + if !ok { + fmt.Println("Missing type for LHS expression") + return false + } + return types.Identical(t, lt.Type) + case *ast.BinaryExpr: + if n.Op == token.SHL || n.Op == token.SHR { + if ctxt.i == 1 { + // RHS of a shift is always safe. + return true + } + // For the LHS, we should inspect up another level. + fmt.Println("TODO(mdempsky): Handle LHS of shift expressions") + return true + } + var other ast.Expr + if ctxt.i == 0 { + other = n.Y + } else { + other = n.X + } + ot, ok := v.pkg.Types[other] + if !ok { + fmt.Println("Missing type for other binop subexpr") + return false + } + return types.Identical(t, ot.Type) + case *ast.CallExpr: + pos := ctxt.i - 1 + if pos < 0 { + // Type conversion in the function subexpr is okay. + return true + } + ft, ok := v.pkg.Types[n.Fun] + if !ok { + fmt.Println("Missing type for function expression") + return false + } + sig, ok := ft.Type.(*types.Signature) + if !ok { + // "Function" is either a type conversion (ok) or a builtin (ok?). + return true + } + params := sig.Params() + var pt types.Type + if sig.Variadic() && n.Ellipsis == token.NoPos && pos >= params.Len()-1 { + pt = params.At(params.Len() - 1).Type().(*types.Slice).Elem() + } else { + pt = params.At(pos).Type() + } + return types.Identical(t, pt) + case *ast.CompositeLit, *ast.KeyValueExpr: + fmt.Println("TODO(mdempsky): Compare against value type of composite literal type at", v.file.Position(n.Pos())) + return true + case *ast.ReturnStmt: + // TODO(mdempsky): Is there a better way to get the corresponding + // return parameter type? + var funcType *ast.FuncType + for i := len(v.path) - 1; funcType == nil && i >= 0; i-- { + switch f := v.path[i].n.(type) { + case *ast.FuncDecl: + funcType = f.Type + case *ast.FuncLit: + funcType = f.Type + } + } + var typeExpr ast.Expr + for i, j := ctxt.i, 0; j < len(funcType.Results.List); j++ { + f := funcType.Results.List[j] + if len(f.Names) == 0 { + if i >= 1 { + i-- + continue + } + } else { + if i >= len(f.Names) { + i -= len(f.Names) + continue + } + } + typeExpr = f.Type + break + } + if typeExpr == nil { + fmt.Println(ctxt) + } + pt, ok := v.pkg.Types[typeExpr] + if !ok { + fmt.Println("Missing type for return parameter at", v.file.Position(n.Pos())) + return false + } + return types.Identical(t, pt.Type) + case *ast.StarExpr, *ast.UnaryExpr: + // TODO(mdempsky): I think these are always safe. + return true + case *ast.SwitchStmt: + // TODO(mdempsky): I think this is always safe? + return true + default: + // TODO(mdempsky): When can this happen? + fmt.Printf("... huh, %T at %v\n", n, v.file.Position(n.Pos())) + return true + } +} + +func isUntypedValue(n ast.Expr, info *types.Info) (res bool) { + switch n := n.(type) { + case *ast.BinaryExpr: + switch n.Op { + case token.SHL, token.SHR: + // Shifts yield an untyped value if their LHS is untyped. + return isUntypedValue(n.X, info) + case token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ: + // Comparisons yield an untyped boolean value. + return true + case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, + token.AND, token.OR, token.XOR, token.AND_NOT, + token.LAND, token.LOR: + return isUntypedValue(n.X, info) && isUntypedValue(n.Y, info) + } + case *ast.UnaryExpr: + switch n.Op { + case token.ADD, token.SUB, token.NOT, token.XOR: + return isUntypedValue(n.X, info) + } + case *ast.BasicLit: + // Basic literals are always untyped. + return true + case *ast.ParenExpr: + return isUntypedValue(n.X, info) + case *ast.SelectorExpr: + return isUntypedValue(n.Sel, info) + case *ast.Ident: + if obj, ok := info.Uses[n]; ok { + if obj.Pkg() == nil && obj.Name() == "nil" { + // The universal untyped zero value. + return true + } + if b, ok := obj.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { + // Reference to an untyped constant. + return true + } + } + case *ast.CallExpr: + if b, ok := asBuiltin(n.Fun, info); ok { + switch b.Name() { + case "real", "imag": + return isUntypedValue(n.Args[0], info) + case "complex": + return isUntypedValue(n.Args[0], info) && isUntypedValue(n.Args[1], info) + } + } + } + + return false +} + +func asBuiltin(n ast.Expr, info *types.Info) (*types.Builtin, bool) { + for { + paren, ok := n.(*ast.ParenExpr) + if !ok { + break + } + n = paren.X + } + + ident, ok := n.(*ast.Ident) + if !ok { + return nil, false + } + + obj, ok := info.Uses[ident] + if !ok { + return nil, false + } + + b, ok := obj.(*types.Builtin) + return b, ok +} + +type byPosition []token.Position + +func (p byPosition) Len() int { + return len(p) +} + +func (p byPosition) Less(i, j int) bool { + if p[i].Filename != p[j].Filename { + return p[i].Filename < p[j].Filename + } + if p[i].Line != p[j].Line { + return p[i].Line < p[j].Line + } + return p[i].Column < p[j].Column +} + +func (p byPosition) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} |
