gitee.com/lonely0422/gometalinter.git@v3.0.1-0.20190307123442-32416ab75314+incompatible/_linters/src/github.com/mdempsky/unconvert/unconvert.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Unconvert removes redundant type conversions from Go packages.
     6  package main
     7  
     8  import (
     9  	"bytes"
    10  	"flag"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/build"
    14  	"go/format"
    15  	"go/parser"
    16  	"go/token"
    17  	"go/types"
    18  	"io/ioutil"
    19  	"log"
    20  	"os"
    21  	"reflect"
    22  	"runtime/pprof"
    23  	"sort"
    24  	"sync"
    25  	"unicode"
    26  
    27  	"github.com/kisielk/gotool"
    28  	"golang.org/x/text/width"
    29  	"golang.org/x/tools/go/loader"
    30  )
    31  
    32  // Unnecessary conversions are identified by the position
    33  // of their left parenthesis within a source file.
    34  
    35  type editSet map[token.Position]struct{}
    36  
    37  type fileToEditSet map[string]editSet
    38  
    39  func apply(file string, edits editSet) {
    40  	if len(edits) == 0 {
    41  		return
    42  	}
    43  
    44  	fset := token.NewFileSet()
    45  	f, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
    46  	if err != nil {
    47  		log.Fatal(err)
    48  	}
    49  
    50  	// Note: We modify edits during the walk.
    51  	v := editor{edits: edits, file: fset.File(f.Package)}
    52  	ast.Walk(&v, f)
    53  	if len(edits) != 0 {
    54  		log.Printf("%s: missing edits %s", file, edits)
    55  	}
    56  
    57  	// TODO(mdempsky): Write to temporary file and rename.
    58  	var buf bytes.Buffer
    59  	err = format.Node(&buf, fset, f)
    60  	if err != nil {
    61  		log.Fatal(err)
    62  	}
    63  
    64  	err = ioutil.WriteFile(file, buf.Bytes(), 0)
    65  	if err != nil {
    66  		log.Fatal(err)
    67  	}
    68  }
    69  
    70  type editor struct {
    71  	edits editSet
    72  	file  *token.File
    73  }
    74  
    75  func (e *editor) Visit(n ast.Node) ast.Visitor {
    76  	if n == nil {
    77  		return nil
    78  	}
    79  	v := reflect.ValueOf(n).Elem()
    80  	for i, n := 0, v.NumField(); i < n; i++ {
    81  		switch f := v.Field(i).Addr().Interface().(type) {
    82  		case *ast.Expr:
    83  			e.rewrite(f)
    84  		case *[]ast.Expr:
    85  			for i := range *f {
    86  				e.rewrite(&(*f)[i])
    87  			}
    88  		}
    89  	}
    90  	return e
    91  }
    92  
    93  func (e *editor) rewrite(f *ast.Expr) {
    94  	call, ok := (*f).(*ast.CallExpr)
    95  	if !ok {
    96  		return
    97  	}
    98  
    99  	pos := e.file.Position(call.Lparen)
   100  	if _, ok := e.edits[pos]; !ok {
   101  		return
   102  	}
   103  	*f = call.Args[0]
   104  	delete(e.edits, pos)
   105  }
   106  
   107  var (
   108  	cr = []byte{'\r'}
   109  	nl = []byte{'\n'}
   110  )
   111  
   112  func print(conversions []token.Position) {
   113  	var file string
   114  	var lines [][]byte
   115  
   116  	for _, pos := range conversions {
   117  		fmt.Printf("%s:%d:%d: unnecessary conversion\n", pos.Filename, pos.Line, pos.Column)
   118  		if *flagV {
   119  			if pos.Filename != file {
   120  				buf, err := ioutil.ReadFile(pos.Filename)
   121  				if err != nil {
   122  					log.Fatal(err)
   123  				}
   124  				file = pos.Filename
   125  				lines = bytes.Split(buf, nl)
   126  			}
   127  
   128  			line := bytes.TrimSuffix(lines[pos.Line-1], cr)
   129  			fmt.Printf("%s\n", line)
   130  
   131  			// For files processed by cgo, Column is the
   132  			// column location after cgo processing, which
   133  			// may be different than the source column
   134  			// that we want here. In lieu of a better
   135  			// heuristic for detecting this case, at least
   136  			// avoid panicking if column is out of bounds.
   137  			if pos.Column <= len(line) {
   138  				fmt.Printf("%s^\n", rub(line[:pos.Column-1]))
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  // Rub returns a copy of buf with all non-whitespace characters replaced
   145  // by spaces (like rubbing them out with white out).
   146  func rub(buf []byte) []byte {
   147  	// TODO(mdempsky): Handle combining characters?
   148  	var res bytes.Buffer
   149  	for _, r := range string(buf) {
   150  		if unicode.IsSpace(r) {
   151  			res.WriteRune(r)
   152  			continue
   153  		}
   154  		switch width.LookupRune(r).Kind() {
   155  		case width.EastAsianWide, width.EastAsianFullwidth:
   156  			res.WriteString("  ")
   157  		default:
   158  			res.WriteByte(' ')
   159  		}
   160  	}
   161  	return res.Bytes()
   162  }
   163  
   164  var (
   165  	flagAll        = flag.Bool("all", false, "type check all GOOS and GOARCH combinations")
   166  	flagApply      = flag.Bool("apply", false, "apply edits to source files")
   167  	flagCPUProfile = flag.String("cpuprofile", "", "write CPU profile to file")
   168  	// TODO(mdempsky): Better description and maybe flag name.
   169  	flagSafe = flag.Bool("safe", false, "be more conservative (experimental)")
   170  	flagV    = flag.Bool("v", false, "verbose output")
   171  )
   172  
   173  func usage() {
   174  	fmt.Fprintf(os.Stderr, "usage: unconvert [flags] [package ...]\n")
   175  	flag.PrintDefaults()
   176  }
   177  
   178  func main() {
   179  	flag.Usage = usage
   180  	flag.Parse()
   181  
   182  	if *flagCPUProfile != "" {
   183  		f, err := os.Create(*flagCPUProfile)
   184  		if err != nil {
   185  			log.Fatal(err)
   186  		}
   187  		pprof.StartCPUProfile(f)
   188  		defer pprof.StopCPUProfile()
   189  	}
   190  
   191  	importPaths := gotool.ImportPaths(flag.Args())
   192  	if len(importPaths) == 0 {
   193  		return
   194  	}
   195  
   196  	var m fileToEditSet
   197  	if *flagAll {
   198  		m = mergeEdits(importPaths)
   199  	} else {
   200  		m = computeEdits(importPaths, build.Default.GOOS, build.Default.GOARCH, build.Default.CgoEnabled)
   201  	}
   202  
   203  	if *flagApply {
   204  		var wg sync.WaitGroup
   205  		for f, e := range m {
   206  			wg.Add(1)
   207  			f, e := f, e
   208  			go func() {
   209  				defer wg.Done()
   210  				apply(f, e)
   211  			}()
   212  		}
   213  		wg.Wait()
   214  	} else {
   215  		var conversions []token.Position
   216  		for _, positions := range m {
   217  			for pos := range positions {
   218  				conversions = append(conversions, pos)
   219  			}
   220  		}
   221  		sort.Sort(byPosition(conversions))
   222  		print(conversions)
   223  		if len(conversions) > 0 {
   224  			os.Exit(1)
   225  		}
   226  	}
   227  }
   228  
   229  var plats = [...]struct {
   230  	goos, goarch string
   231  }{
   232  	// TODO(mdempsky): buildall.bash also builds linux-386-387 and linux-arm-arm5.
   233  	{"android", "386"},
   234  	{"android", "amd64"},
   235  	{"android", "arm"},
   236  	{"android", "arm64"},
   237  	{"darwin", "386"},
   238  	{"darwin", "amd64"},
   239  	{"darwin", "arm"},
   240  	{"darwin", "arm64"},
   241  	{"dragonfly", "amd64"},
   242  	{"freebsd", "386"},
   243  	{"freebsd", "amd64"},
   244  	{"freebsd", "arm"},
   245  	{"linux", "386"},
   246  	{"linux", "amd64"},
   247  	{"linux", "arm"},
   248  	{"linux", "arm64"},
   249  	{"linux", "mips64"},
   250  	{"linux", "mips64le"},
   251  	{"linux", "ppc64"},
   252  	{"linux", "ppc64le"},
   253  	{"linux", "s390x"},
   254  	{"nacl", "386"},
   255  	{"nacl", "amd64p32"},
   256  	{"nacl", "arm"},
   257  	{"netbsd", "386"},
   258  	{"netbsd", "amd64"},
   259  	{"netbsd", "arm"},
   260  	{"openbsd", "386"},
   261  	{"openbsd", "amd64"},
   262  	{"openbsd", "arm"},
   263  	{"plan9", "386"},
   264  	{"plan9", "amd64"},
   265  	{"plan9", "arm"},
   266  	{"solaris", "amd64"},
   267  	{"windows", "386"},
   268  	{"windows", "amd64"},
   269  }
   270  
   271  func mergeEdits(importPaths []string) fileToEditSet {
   272  	m := make(fileToEditSet)
   273  	for _, plat := range plats {
   274  		for f, e := range computeEdits(importPaths, plat.goos, plat.goarch, false) {
   275  			if e0, ok := m[f]; ok {
   276  				for k := range e0 {
   277  					if _, ok := e[k]; !ok {
   278  						delete(e0, k)
   279  					}
   280  				}
   281  			} else {
   282  				m[f] = e
   283  			}
   284  		}
   285  	}
   286  	return m
   287  }
   288  
   289  type noImporter struct{}
   290  
   291  func (noImporter) Import(path string) (*types.Package, error) {
   292  	panic("golang.org/x/tools/go/loader said this wouldn't be called")
   293  }
   294  
   295  func computeEdits(importPaths []string, os, arch string, cgoEnabled bool) fileToEditSet {
   296  	ctxt := build.Default
   297  	ctxt.GOOS = os
   298  	ctxt.GOARCH = arch
   299  	ctxt.CgoEnabled = cgoEnabled
   300  
   301  	var conf loader.Config
   302  	conf.Build = &ctxt
   303  	conf.TypeChecker.Importer = noImporter{}
   304  	for _, importPath := range importPaths {
   305  		conf.Import(importPath)
   306  	}
   307  	prog, err := conf.Load()
   308  	if err != nil {
   309  		log.Fatal(err)
   310  	}
   311  
   312  	type res struct {
   313  		file  string
   314  		edits editSet
   315  	}
   316  	ch := make(chan res)
   317  	var wg sync.WaitGroup
   318  	for _, pkg := range prog.InitialPackages() {
   319  		for _, file := range pkg.Files {
   320  			pkg, file := pkg, file
   321  			wg.Add(1)
   322  			go func() {
   323  				defer wg.Done()
   324  				v := visitor{pkg: pkg, file: conf.Fset.File(file.Package), edits: make(editSet)}
   325  				ast.Walk(&v, file)
   326  				ch <- res{v.file.Name(), v.edits}
   327  			}()
   328  		}
   329  	}
   330  	go func() {
   331  		wg.Wait()
   332  		close(ch)
   333  	}()
   334  
   335  	m := make(fileToEditSet)
   336  	for r := range ch {
   337  		m[r.file] = r.edits
   338  	}
   339  	return m
   340  }
   341  
   342  type step struct {
   343  	n ast.Node
   344  	i int
   345  }
   346  
   347  type visitor struct {
   348  	pkg   *loader.PackageInfo
   349  	file  *token.File
   350  	edits editSet
   351  	path  []step
   352  }
   353  
   354  func (v *visitor) Visit(node ast.Node) ast.Visitor {
   355  	if node != nil {
   356  		v.path = append(v.path, step{n: node})
   357  	} else {
   358  		n := len(v.path)
   359  		v.path = v.path[:n-1]
   360  		if n >= 2 {
   361  			v.path[n-2].i++
   362  		}
   363  	}
   364  
   365  	if call, ok := node.(*ast.CallExpr); ok {
   366  		v.unconvert(call)
   367  	}
   368  	return v
   369  }
   370  
   371  func (v *visitor) unconvert(call *ast.CallExpr) {
   372  	// TODO(mdempsky): Handle useless multi-conversions.
   373  
   374  	// Conversions have exactly one argument.
   375  	if len(call.Args) != 1 || call.Ellipsis != token.NoPos {
   376  		return
   377  	}
   378  	ft, ok := v.pkg.Types[call.Fun]
   379  	if !ok {
   380  		fmt.Println("Missing type for function")
   381  		return
   382  	}
   383  	if !ft.IsType() {
   384  		// Function call; not a conversion.
   385  		return
   386  	}
   387  	at, ok := v.pkg.Types[call.Args[0]]
   388  	if !ok {
   389  		fmt.Println("Missing type for argument")
   390  		return
   391  	}
   392  	if !types.Identical(ft.Type, at.Type) {
   393  		// A real conversion.
   394  		return
   395  	}
   396  	if isUntypedValue(call.Args[0], &v.pkg.Info) {
   397  		// Workaround golang.org/issue/13061.
   398  		return
   399  	}
   400  	if *flagSafe && !v.isSafeContext(at.Type) {
   401  		// TODO(mdempsky): Remove this message.
   402  		fmt.Println("Skipped a possible type conversion because of -safe at", v.file.Position(call.Pos()))
   403  		return
   404  	}
   405  	if v.isCgoCheckPointerContext() {
   406  		// cmd/cgo generates explicit type conversions that
   407  		// are often redundant when introducing
   408  		// _cgoCheckPointer calls (issue #16).  Users can't do
   409  		// anything about these, so skip over them.
   410  		return
   411  	}
   412  
   413  	v.edits[v.file.Position(call.Lparen)] = struct{}{}
   414  }
   415  
   416  func (v *visitor) isCgoCheckPointerContext() bool {
   417  	ctxt := &v.path[len(v.path)-2]
   418  	if ctxt.i != 1 {
   419  		return false
   420  	}
   421  	call, ok := ctxt.n.(*ast.CallExpr)
   422  	if !ok {
   423  		return false
   424  	}
   425  	ident, ok := call.Fun.(*ast.Ident)
   426  	if !ok {
   427  		return false
   428  	}
   429  	return ident.Name == "_cgoCheckPointer"
   430  }
   431  
   432  // isSafeContext reports whether the current context requires
   433  // an expression of type t.
   434  //
   435  // TODO(mdempsky): That's a bad explanation.
   436  func (v *visitor) isSafeContext(t types.Type) bool {
   437  	ctxt := &v.path[len(v.path)-2]
   438  	switch n := ctxt.n.(type) {
   439  	case *ast.AssignStmt:
   440  		pos := ctxt.i - len(n.Lhs)
   441  		if pos < 0 {
   442  			fmt.Println("Type conversion on LHS of assignment?")
   443  			return false
   444  		}
   445  		if n.Tok == token.DEFINE {
   446  			// Skip := assignments.
   447  			return true
   448  		}
   449  		// We're a conversion in the pos'th element of n.Rhs.
   450  		// Check that the corresponding element of n.Lhs is of type t.
   451  		lt, ok := v.pkg.Types[n.Lhs[pos]]
   452  		if !ok {
   453  			fmt.Println("Missing type for LHS expression")
   454  			return false
   455  		}
   456  		return types.Identical(t, lt.Type)
   457  	case *ast.BinaryExpr:
   458  		if n.Op == token.SHL || n.Op == token.SHR {
   459  			if ctxt.i == 1 {
   460  				// RHS of a shift is always safe.
   461  				return true
   462  			}
   463  			// For the LHS, we should inspect up another level.
   464  			fmt.Println("TODO(mdempsky): Handle LHS of shift expressions")
   465  			return true
   466  		}
   467  		var other ast.Expr
   468  		if ctxt.i == 0 {
   469  			other = n.Y
   470  		} else {
   471  			other = n.X
   472  		}
   473  		ot, ok := v.pkg.Types[other]
   474  		if !ok {
   475  			fmt.Println("Missing type for other binop subexpr")
   476  			return false
   477  		}
   478  		return types.Identical(t, ot.Type)
   479  	case *ast.CallExpr:
   480  		pos := ctxt.i - 1
   481  		if pos < 0 {
   482  			// Type conversion in the function subexpr is okay.
   483  			return true
   484  		}
   485  		ft, ok := v.pkg.Types[n.Fun]
   486  		if !ok {
   487  			fmt.Println("Missing type for function expression")
   488  			return false
   489  		}
   490  		sig, ok := ft.Type.(*types.Signature)
   491  		if !ok {
   492  			// "Function" is either a type conversion (ok) or a builtin (ok?).
   493  			return true
   494  		}
   495  		params := sig.Params()
   496  		var pt types.Type
   497  		if sig.Variadic() && n.Ellipsis == token.NoPos && pos >= params.Len()-1 {
   498  			pt = params.At(params.Len() - 1).Type().(*types.Slice).Elem()
   499  		} else {
   500  			pt = params.At(pos).Type()
   501  		}
   502  		return types.Identical(t, pt)
   503  	case *ast.CompositeLit, *ast.KeyValueExpr:
   504  		fmt.Println("TODO(mdempsky): Compare against value type of composite literal type at", v.file.Position(n.Pos()))
   505  		return true
   506  	case *ast.ReturnStmt:
   507  		// TODO(mdempsky): Is there a better way to get the corresponding
   508  		// return parameter type?
   509  		var funcType *ast.FuncType
   510  		for i := len(v.path) - 1; funcType == nil && i >= 0; i-- {
   511  			switch f := v.path[i].n.(type) {
   512  			case *ast.FuncDecl:
   513  				funcType = f.Type
   514  			case *ast.FuncLit:
   515  				funcType = f.Type
   516  			}
   517  		}
   518  		var typeExpr ast.Expr
   519  		for i, j := ctxt.i, 0; j < len(funcType.Results.List); j++ {
   520  			f := funcType.Results.List[j]
   521  			if len(f.Names) == 0 {
   522  				if i >= 1 {
   523  					i--
   524  					continue
   525  				}
   526  			} else {
   527  				if i >= len(f.Names) {
   528  					i -= len(f.Names)
   529  					continue
   530  				}
   531  			}
   532  			typeExpr = f.Type
   533  			break
   534  		}
   535  		if typeExpr == nil {
   536  			fmt.Println(ctxt)
   537  		}
   538  		pt, ok := v.pkg.Types[typeExpr]
   539  		if !ok {
   540  			fmt.Println("Missing type for return parameter at", v.file.Position(n.Pos()))
   541  			return false
   542  		}
   543  		return types.Identical(t, pt.Type)
   544  	case *ast.StarExpr, *ast.UnaryExpr:
   545  		// TODO(mdempsky): I think these are always safe.
   546  		return true
   547  	case *ast.SwitchStmt:
   548  		// TODO(mdempsky): I think this is always safe?
   549  		return true
   550  	default:
   551  		// TODO(mdempsky): When can this happen?
   552  		fmt.Printf("... huh, %T at %v\n", n, v.file.Position(n.Pos()))
   553  		return true
   554  	}
   555  }
   556  
   557  func isUntypedValue(n ast.Expr, info *types.Info) (res bool) {
   558  	switch n := n.(type) {
   559  	case *ast.BinaryExpr:
   560  		switch n.Op {
   561  		case token.SHL, token.SHR:
   562  			// Shifts yield an untyped value if their LHS is untyped.
   563  			return isUntypedValue(n.X, info)
   564  		case token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ:
   565  			// Comparisons yield an untyped boolean value.
   566  			return true
   567  		case token.ADD, token.SUB, token.MUL, token.QUO, token.REM,
   568  			token.AND, token.OR, token.XOR, token.AND_NOT,
   569  			token.LAND, token.LOR:
   570  			return isUntypedValue(n.X, info) && isUntypedValue(n.Y, info)
   571  		}
   572  	case *ast.UnaryExpr:
   573  		switch n.Op {
   574  		case token.ADD, token.SUB, token.NOT, token.XOR:
   575  			return isUntypedValue(n.X, info)
   576  		}
   577  	case *ast.BasicLit:
   578  		// Basic literals are always untyped.
   579  		return true
   580  	case *ast.ParenExpr:
   581  		return isUntypedValue(n.X, info)
   582  	case *ast.SelectorExpr:
   583  		return isUntypedValue(n.Sel, info)
   584  	case *ast.Ident:
   585  		if obj, ok := info.Uses[n]; ok {
   586  			if obj.Pkg() == nil && obj.Name() == "nil" {
   587  				// The universal untyped zero value.
   588  				return true
   589  			}
   590  			if b, ok := obj.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
   591  				// Reference to an untyped constant.
   592  				return true
   593  			}
   594  		}
   595  	case *ast.CallExpr:
   596  		if b, ok := asBuiltin(n.Fun, info); ok {
   597  			switch b.Name() {
   598  			case "real", "imag":
   599  				return isUntypedValue(n.Args[0], info)
   600  			case "complex":
   601  				return isUntypedValue(n.Args[0], info) && isUntypedValue(n.Args[1], info)
   602  			}
   603  		}
   604  	}
   605  
   606  	return false
   607  }
   608  
   609  func asBuiltin(n ast.Expr, info *types.Info) (*types.Builtin, bool) {
   610  	for {
   611  		paren, ok := n.(*ast.ParenExpr)
   612  		if !ok {
   613  			break
   614  		}
   615  		n = paren.X
   616  	}
   617  
   618  	ident, ok := n.(*ast.Ident)
   619  	if !ok {
   620  		return nil, false
   621  	}
   622  
   623  	obj, ok := info.Uses[ident]
   624  	if !ok {
   625  		return nil, false
   626  	}
   627  
   628  	b, ok := obj.(*types.Builtin)
   629  	return b, ok
   630  }
   631  
   632  type byPosition []token.Position
   633  
   634  func (p byPosition) Len() int {
   635  	return len(p)
   636  }
   637  
   638  func (p byPosition) Less(i, j int) bool {
   639  	if p[i].Filename != p[j].Filename {
   640  		return p[i].Filename < p[j].Filename
   641  	}
   642  	if p[i].Line != p[j].Line {
   643  		return p[i].Line < p[j].Line
   644  	}
   645  	return p[i].Column < p[j].Column
   646  }
   647  
   648  func (p byPosition) Swap(i, j int) {
   649  	p[i], p[j] = p[j], p[i]
   650  }