// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "go/ast" "go/parser" "go/token" "os" "path" "reflect" "strconv" "strings" ) type fix struct { name string date string // date that fix was introduced, in YYYY-MM-DD format f func(*ast.File) bool desc string disabled bool // whether this fix should be disabled by default } // main runs sort.Sort(byName(fixes)) before printing list of fixes. type byName []fix func (f byName) Len() int { return len(f) } func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func (f byName) Less(i, j int) bool { return f[i].name < f[j].name } // main runs sort.Sort(byDate(fixes)) before applying fixes. type byDate []fix func (f byDate) Len() int { return len(f) } func (f byDate) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func (f byDate) Less(i, j int) bool { return f[i].date < f[j].date } var fixes []fix func register(f fix) { fixes = append(fixes, f) } // walk traverses the AST x, calling visit(y) for each node y in the tree but // also with a pointer to each ast.Expr, ast.Stmt, and *ast.BlockStmt, // in a bottom-up traversal. func walk(x interface{}, visit func(interface{})) { walkBeforeAfter(x, nop, visit) } func nop(interface{}) {} // walkBeforeAfter is like walk but calls before(x) before traversing // x's children and after(x) afterward. func walkBeforeAfter(x interface{}, before, after func(interface{})) { before(x) switch n := x.(type) { default: panic(fmt.Errorf("unexpected type %T in walkBeforeAfter", x)) case nil: // pointers to interfaces case *ast.Decl: walkBeforeAfter(*n, before, after) case *ast.Expr: walkBeforeAfter(*n, before, after) case *ast.Spec: walkBeforeAfter(*n, before, after) case *ast.Stmt: walkBeforeAfter(*n, before, after) // pointers to struct pointers case **ast.BlockStmt: walkBeforeAfter(*n, before, after) case **ast.CallExpr: walkBeforeAfter(*n, before, after) case **ast.FieldList: walkBeforeAfter(*n, before, after) case **ast.FuncType: walkBeforeAfter(*n, before, after) case **ast.Ident: walkBeforeAfter(*n, before, after) case **ast.BasicLit: walkBeforeAfter(*n, before, after) // pointers to slices case *[]ast.Decl: walkBeforeAfter(*n, before, after) case *[]ast.Expr: walkBeforeAfter(*n, before, after) case *[]*ast.File: walkBeforeAfter(*n, before, after) case *[]*ast.Ident: walkBeforeAfter(*n, before, after) case *[]ast.Spec: walkBeforeAfter(*n, before, after) case *[]ast.Stmt: walkBeforeAfter(*n, before, after) // These are ordered and grouped to match ../../go/ast/ast.go case *ast.Field: walkBeforeAfter(&n.Names, before, after) walkBeforeAfter(&n.Type, before, after) walkBeforeAfter(&n.Tag, before, after) case *ast.FieldList: for _, field := range n.List { walkBeforeAfter(field, before, after) } case *ast.BadExpr: case *ast.Ident: case *ast.Ellipsis: walkBeforeAfter(&n.Elt, before, after) case *ast.BasicLit: case *ast.FuncLit: walkBeforeAfter(&n.Type, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.CompositeLit: walkBeforeAfter(&n.Type, before, after) walkBeforeAfter(&n.Elts, before, after) case *ast.ParenExpr: walkBeforeAfter(&n.X, before, after) case *ast.SelectorExpr: walkBeforeAfter(&n.X, before, after) case *ast.IndexExpr: walkBeforeAfter(&n.X, before, after) walkBeforeAfter(&n.Index, before, after) case *ast.SliceExpr: walkBeforeAfter(&n.X, before, after) if n.Low != nil { walkBeforeAfter(&n.Low, before, after) } if n.High != nil { walkBeforeAfter(&n.High, before, after) } case *ast.TypeAssertExpr: walkBeforeAfter(&n.X, before, after) walkBeforeAfter(&n.Type, before, after) case *ast.CallExpr: walkBeforeAfter(&n.Fun, before, after) walkBeforeAfter(&n.Args, before, after) case *ast.StarExpr: walkBeforeAfter(&n.X, before, after) case *ast.UnaryExpr: walkBeforeAfter(&n.X, before, after) case *ast.BinaryExpr: walkBeforeAfter(&n.X, before, after) walkBeforeAfter(&n.Y, before, after) case *ast.KeyValueExpr: walkBeforeAfter(&n.Key, before, after) walkBeforeAfter(&n.Value, before, after) case *ast.ArrayType: walkBeforeAfter(&n.Len, before, after) walkBeforeAfter(&n.Elt, before, after) case *ast.StructType: walkBeforeAfter(&n.Fields, before, after) case *ast.FuncType: walkBeforeAfter(&n.Params, before, after) if n.Results != nil { walkBeforeAfter(&n.Results, before, after) } case *ast.InterfaceType: walkBeforeAfter(&n.Methods, before, after) case *ast.MapType: walkBeforeAfter(&n.Key, before, after) walkBeforeAfter(&n.Value, before, after) case *ast.ChanType: walkBeforeAfter(&n.Value, before, after) case *ast.BadStmt: case *ast.DeclStmt: walkBeforeAfter(&n.Decl, before, after) case *ast.EmptyStmt: case *ast.LabeledStmt: walkBeforeAfter(&n.Stmt, before, after) case *ast.ExprStmt: walkBeforeAfter(&n.X, before, after) case *ast.SendStmt: walkBeforeAfter(&n.Chan, before, after) walkBeforeAfter(&n.Value, before, after) case *ast.IncDecStmt: walkBeforeAfter(&n.X, before, after) case *ast.AssignStmt: walkBeforeAfter(&n.Lhs, before, after) walkBeforeAfter(&n.Rhs, before, after) case *ast.GoStmt: walkBeforeAfter(&n.Call, before, after) case *ast.DeferStmt: walkBeforeAfter(&n.Call, before, after) case *ast.ReturnStmt: walkBeforeAfter(&n.Results, before, after) case *ast.BranchStmt: case *ast.BlockStmt: walkBeforeAfter(&n.List, before, after) case *ast.IfStmt: walkBeforeAfter(&n.Init, before, after) walkBeforeAfter(&n.Cond, before, after) walkBeforeAfter(&n.Body, before, after) walkBeforeAfter(&n.Else, before, after) case *ast.CaseClause: walkBeforeAfter(&n.List, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.SwitchStmt: walkBeforeAfter(&n.Init, before, after) walkBeforeAfter(&n.Tag, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.TypeSwitchStmt: walkBeforeAfter(&n.Init, before, after) walkBeforeAfter(&n.Assign, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.CommClause: walkBeforeAfter(&n.Comm, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.SelectStmt: walkBeforeAfter(&n.Body, before, after) case *ast.ForStmt: walkBeforeAfter(&n.Init, before, after) walkBeforeAfter(&n.Cond, before, after) walkBeforeAfter(&n.Post, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.RangeStmt: walkBeforeAfter(&n.Key, before, after) walkBeforeAfter(&n.Value, before, after) walkBeforeAfter(&n.X, before, after) walkBeforeAfter(&n.Body, before, after) case *ast.ImportSpec: case *ast.ValueSpec: walkBeforeAfter(&n.Type, before, after) walkBeforeAfter(&n.Values, before, after) walkBeforeAfter(&n.Names, before, after) case *ast.TypeSpec: walkBeforeAfter(&n.Type, before, after) case *ast.BadDecl: case *ast.GenDecl: walkBeforeAfter(&n.Specs, before, after) case *ast.FuncDecl: if n.Recv != nil { walkBeforeAfter(&n.Recv, before, after) } walkBeforeAfter(&n.Type, before, after) if n.Body != nil { walkBeforeAfter(&n.Body, before, after) } case *ast.File: walkBeforeAfter(&n.Decls, before, after) case *ast.Package: walkBeforeAfter(&n.Files, before, after) case []*ast.File: for i := range n { walkBeforeAfter(&n[i], before, after) } case []ast.Decl: for i := range n { walkBeforeAfter(&n[i], before, after) } case []ast.Expr: for i := range n { walkBeforeAfter(&n[i], before, after) } case []*ast.Ident: for i := range n { walkBeforeAfter(&n[i], before, after) } case []ast.Stmt: for i := range n { walkBeforeAfter(&n[i], before, after) } case []ast.Spec: for i := range n { walkBeforeAfter(&n[i], before, after) } } after(x) } // imports reports whether f imports path. func imports(f *ast.File, path string) bool { return importSpec(f, path) != nil } // importSpec returns the import spec if f imports path, // or nil otherwise. func importSpec(f *ast.File, path string) *ast.ImportSpec { for _, s := range f.Imports { if importPath(s) == path { return s } } return nil } // importPath returns the unquoted import path of s, // or "" if the path is not properly quoted. func importPath(s *ast.ImportSpec) string { t, err := strconv.Unquote(s.Path.Value) if err == nil { return t } return "" } // declImports reports whether gen contains an import of path. func declImports(gen *ast.GenDecl, path string) bool { if gen.Tok != token.IMPORT { return false } for _, spec := range gen.Specs { impspec := spec.(*ast.ImportSpec) if importPath(impspec) == path { return true } } return false } // isPkgDot reports whether t is the expression "pkg.name" // where pkg is an imported identifier. func isPkgDot(t ast.Expr, pkg, name string) bool { sel, ok := t.(*ast.SelectorExpr) return ok && isTopName(sel.X, pkg) && sel.Sel.String() == name } // isPtrPkgDot reports whether f is the expression "*pkg.name" // where pkg is an imported identifier. func isPtrPkgDot(t ast.Expr, pkg, name string) bool { ptr, ok := t.(*ast.StarExpr) return ok && isPkgDot(ptr.X, pkg, name) } // isTopName reports whether n is a top-level unresolved identifier with the given name. func isTopName(n ast.Expr, name string) bool { id, ok := n.(*ast.Ident) return ok && id.Name == name && id.Obj == nil } // isName reports whether n is an identifier with the given name. func isName(n ast.Expr, name string) bool { id, ok := n.(*ast.Ident) return ok && id.String() == name } // isCall reports whether t is a call to pkg.name. func isCall(t ast.Expr, pkg, name string) bool { call, ok := t.(*ast.CallExpr) return ok && isPkgDot(call.Fun, pkg, name) } // If n is an *ast.Ident, isIdent returns it; otherwise isIdent returns nil. func isIdent(n interface{}) *ast.Ident { id, _ := n.(*ast.Ident) return id } // refersTo reports whether n is a reference to the same object as x. func refersTo(n ast.Node, x *ast.Ident) bool { id, ok := n.(*ast.Ident) // The test of id.Name == x.Name handles top-level unresolved // identifiers, which all have Obj == nil. return ok && id.Obj == x.Obj && id.Name == x.Name } // isBlank reports whether n is the blank identifier. func isBlank(n ast.Expr) bool { return isName(n, "_") } // isEmptyString reports whether n is an empty string literal. func isEmptyString(n ast.Expr) bool { lit, ok := n.(*ast.BasicLit) return ok && lit.Kind == token.STRING && len(lit.Value) == 2 } func warn(pos token.Pos, msg string, args ...interface{}) { if pos.IsValid() { msg = "%s: " + msg arg1 := []interface{}{fset.Position(pos).String()} args = append(arg1, args...) } fmt.Fprintf(os.Stderr, msg+"\n", args...) } // countUses returns the number of uses of the identifier x in scope. func countUses(x *ast.Ident, scope []ast.Stmt) int { count := 0 ff := func(n interface{}) { if n, ok := n.(ast.Node); ok && refersTo(n, x) { count++ } } for _, n := range scope { walk(n, ff) } return count } // rewriteUses replaces all uses of the identifier x and !x in scope // with f(x.Pos()) and fnot(x.Pos()). func rewriteUses(x *ast.Ident, f, fnot func(token.Pos) ast.Expr, scope []ast.Stmt) { var lastF ast.Expr ff := func(n interface{}) { ptr, ok := n.(*ast.Expr) if !ok { return } nn := *ptr // The child node was just walked and possibly replaced. // If it was replaced and this is a negation, replace with fnot(p). not, ok := nn.(*ast.UnaryExpr) if ok && not.Op == token.NOT && not.X == lastF { *ptr = fnot(nn.Pos()) return } if refersTo(nn, x) { lastF = f(nn.Pos()) *ptr = lastF } } for _, n := range scope { walk(n, ff) } } // assignsTo reports whether any of the code in scope assigns to or takes the address of x. func assignsTo(x *ast.Ident, scope []ast.Stmt) bool { assigned := false ff := func(n interface{}) { if assigned { return } switch n := n.(type) { case *ast.UnaryExpr: // use of &x if n.Op == token.AND && refersTo(n.X, x) { assigned = true return } case *ast.AssignStmt: for _, l := range n.Lhs { if refersTo(l, x) { assigned = true return } } } } for _, n := range scope { if assigned { break } walk(n, ff) } return assigned } // newPkgDot returns an ast.Expr referring to "pkg.name" at position pos. func newPkgDot(pos token.Pos, pkg, name string) ast.Expr { return &ast.SelectorExpr{ X: &ast.Ident{ NamePos: pos, Name: pkg, }, Sel: &ast.Ident{ NamePos: pos, Name: name, }, } } // renameTop renames all references to the top-level name old. // It returns true if it makes any changes. func renameTop(f *ast.File, old, new string) bool { var fixed bool // Rename any conflicting imports // (assuming package name is last element of path). for _, s := range f.Imports { if s.Name != nil { if s.Name.Name == old { s.Name.Name = new fixed = true } } else { _, thisName := path.Split(importPath(s)) if thisName == old { s.Name = ast.NewIdent(new) fixed = true } } } // Rename any top-level declarations. for _, d := range f.Decls { switch d := d.(type) { case *ast.FuncDecl: if d.Recv == nil && d.Name.Name == old { d.Name.Name = new d.Name.Obj.Name = new fixed = true } case *ast.GenDecl: for _, s := range d.Specs { switch s := s.(type) { case *ast.TypeSpec: if s.Name.Name == old { s.Name.Name = new s.Name.Obj.Name = new fixed = true } case *ast.ValueSpec: for _, n := range s.Names { if n.Name == old { n.Name = new n.Obj.Name = new fixed = true } } } } } } // Rename top-level old to new, both unresolved names // (probably defined in another file) and names that resolve // to a declaration we renamed. walk(f, func(n interface{}) { id, ok := n.(*ast.Ident) if ok && isTopName(id, old) { id.Name = new fixed = true } if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new { id.Name = id.Obj.Name fixed = true } }) return fixed } // matchLen returns the length of the longest prefix shared by x and y. func matchLen(x, y string) int { i := 0 for i < len(x) && i < len(y) && x[i] == y[i] { i++ } return i } // addImport adds the import path to the file f, if absent. func addImport(f *ast.File, ipath string) (added bool) { if imports(f, ipath) { return false } // Determine name of import. // Assume added imports follow convention of using last element. _, name := path.Split(ipath) // Rename any conflicting top-level references from name to name_. renameTop(f, name, name+"_") newImport := &ast.ImportSpec{ Path: &ast.BasicLit{ Kind: token.STRING, Value: strconv.Quote(ipath), }, } // Find an import decl to add to. var ( bestMatch = -1 lastImport = -1 impDecl *ast.GenDecl impIndex = -1 ) for i, decl := range f.Decls { gen, ok := decl.(*ast.GenDecl) if ok && gen.Tok == token.IMPORT { lastImport = i // Do not add to import "C", to avoid disrupting the // association with its doc comment, breaking cgo. if declImports(gen, "C") { continue } // Compute longest shared prefix with imports in this block. for j, spec := range gen.Specs { impspec := spec.(*ast.ImportSpec) n := matchLen(importPath(impspec), ipath) if n > bestMatch { bestMatch = n impDecl = gen impIndex = j } } } } // If no import decl found, add one after the last import. if impDecl == nil { impDecl = &ast.GenDecl{ Tok: token.IMPORT, } f.Decls = append(f.Decls, nil) copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) f.Decls[lastImport+1] = impDecl } // Ensure the import decl has parentheses, if needed. if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() { impDecl.Lparen = impDecl.Pos() } insertAt := impIndex + 1 if insertAt == 0 { insertAt = len(impDecl.Specs) } impDecl.Specs = append(impDecl.Specs, nil) copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) impDecl.Specs[insertAt] = newImport if insertAt > 0 { // Assign same position as the previous import, // so that the sorter sees it as being in the same block. prev := impDecl.Specs[insertAt-1] newImport.Path.ValuePos = prev.Pos() newImport.EndPos = prev.Pos() } f.Imports = append(f.Imports, newImport) return true } // deleteImport deletes the import path from the file f, if present. func deleteImport(f *ast.File, path string) (deleted bool) { oldImport := importSpec(f, path) // Find the import node that imports path, if any. for i, decl := range f.Decls { gen, ok := decl.(*ast.GenDecl) if !ok || gen.Tok != token.IMPORT { continue } for j, spec := range gen.Specs { impspec := spec.(*ast.ImportSpec) if oldImport != impspec { continue } // We found an import spec that imports path. // Delete it. deleted = true copy(gen.Specs[j:], gen.Specs[j+1:]) gen.Specs = gen.Specs[:len(gen.Specs)-1] // If this was the last import spec in this decl, // delete the decl, too. if len(gen.Specs) == 0 { copy(f.Decls[i:], f.Decls[i+1:]) f.Decls = f.Decls[:len(f.Decls)-1] } else if len(gen.Specs) == 1 { gen.Lparen = token.NoPos // drop parens } if j > 0 { // We deleted an entry but now there will be // a blank line-sized hole where the import was. // Close the hole by making the previous // import appear to "end" where this one did. gen.Specs[j-1].(*ast.ImportSpec).EndPos = impspec.End() } break } } // Delete it from f.Imports. for i, imp := range f.Imports { if imp == oldImport { copy(f.Imports[i:], f.Imports[i+1:]) f.Imports = f.Imports[:len(f.Imports)-1] break } } return } // rewriteImport rewrites any import of path oldPath to path newPath. func rewriteImport(f *ast.File, oldPath, newPath string) (rewrote bool) { for _, imp := range f.Imports { if importPath(imp) == oldPath { rewrote = true // record old End, because the default is to compute // it using the length of imp.Path.Value. imp.EndPos = imp.End() imp.Path.Value = strconv.Quote(newPath) } } return } func usesImport(f *ast.File, path string) (used bool) { spec := importSpec(f, path) if spec == nil { return } name := spec.Name.String() switch name { case "<nil>": // If the package name is not explicitly specified, // make an educated guess. This is not guaranteed to be correct. lastSlash := strings.LastIndex(path, "/") if lastSlash == -1 { name = path } else { name = path[lastSlash+1:] } case "_", ".": // Not sure if this import is used - err on the side of caution. return true } walk(f, func(n interface{}) { sel, ok := n.(*ast.SelectorExpr) if ok && isTopName(sel.X, name) { used = true } }) return } func expr(s string) ast.Expr { x, err := parser.ParseExpr(s) if err != nil { panic("parsing " + s + ": " + err.Error()) } // Remove position information to avoid spurious newlines. killPos(reflect.ValueOf(x)) return x } var posType = reflect.TypeOf(token.Pos(0)) func killPos(v reflect.Value) { switch v.Kind() { case reflect.Ptr, reflect.Interface: if !v.IsNil() { killPos(v.Elem()) } case reflect.Slice: n := v.Len() for i := 0; i < n; i++ { killPos(v.Index(i)) } case reflect.Struct: n := v.NumField() for i := 0; i < n; i++ { f := v.Field(i) if f.Type() == posType { f.SetInt(0) continue } killPos(f) } } } // A Rename describes a single renaming. type rename struct { OldImport string // only apply rename if this import is present NewImport string // add this import during rewrite Old string // old name: p.T or *p.T New string // new name: p.T or *p.T } func renameFix(tab []rename) func(*ast.File) bool { return func(f *ast.File) bool { return renameFixTab(f, tab) } } func parseName(s string) (ptr bool, pkg, nam string) { i := strings.Index(s, ".") if i < 0 { panic("parseName: invalid name " + s) } if strings.HasPrefix(s, "*") { ptr = true s = s[1:] i-- } pkg = s[:i] nam = s[i+1:] return } func renameFixTab(f *ast.File, tab []rename) bool { fixed := false added := map[string]bool{} check := map[string]bool{} for _, t := range tab { if !imports(f, t.OldImport) { continue } optr, opkg, onam := parseName(t.Old) walk(f, func(n interface{}) { np, ok := n.(*ast.Expr) if !ok { return } x := *np if optr { p, ok := x.(*ast.StarExpr) if !ok { return } x = p.X } if !isPkgDot(x, opkg, onam) { return } if t.NewImport != "" && !added[t.NewImport] { addImport(f, t.NewImport) added[t.NewImport] = true } *np = expr(t.New) check[t.OldImport] = true fixed = true }) } for ipath := range check { if !usesImport(f, ipath) { deleteImport(f, ipath) } } return fixed }