// ParseQueryPos parses the source query position pos and returns the // AST node of the loaded program lprog that it identifies. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) { filename, startOffset, endOffset, err := parsePos(pos) if err != nil { return nil, err } // Find the named file among those in the loaded program. var file *token.File lprog.Fset.Iterate(func(f *token.File) bool { if sameFile(filename, f.Name()) { file = f return false // done } return true // continue }) if file == nil { return nil, fmt.Errorf("file %s not found in loaded program", filename) } start, end, err := fileOffsetToPos(file, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := lprog.PathEnclosingInterval(start, end) if path == nil { return nil, fmt.Errorf("no syntax here") } if needExact && !exact { return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) } return &queryPos{lprog.Fset, start, end, path, exact, info}, nil }
func printProgram(prog *loader.Program) { // Created packages are the initial packages specified by a call // to CreateFromFilenames or CreateFromFiles. var names []string for _, info := range prog.Created { names = append(names, info.Pkg.Path()) } fmt.Printf("created: %s\n", names) // Imported packages are the initial packages specified by a // call to Import or ImportWithTests. names = nil for _, info := range prog.Imported { names = append(names, info.Pkg.Path()) } sort.Strings(names) fmt.Printf("imported: %s\n", names) // InitialPackages contains the union of created and imported. names = nil for _, info := range prog.InitialPackages() { names = append(names, info.Pkg.Path()) } sort.Strings(names) fmt.Printf("initial: %s\n", names) // AllPackages contains all initial packages and their dependencies. names = nil for pkg := range prog.AllPackages { names = append(names, pkg.Path()) } sort.Strings(names) fmt.Printf("all: %s\n", names) }
// Create a pointer.Config whose scope is the initial packages of lprog // and their dependencies. func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) { // TODO(adonovan): the body of this function is essentially // duplicated in all go/pointer clients. Refactor. // For each initial package (specified on the command line), // if it has a main function, analyze that, // otherwise analyze its tests, if any. var testPkgs, mains []*ssa.Package for _, info := range lprog.InitialPackages() { initialPkg := prog.Package(info.Pkg) // Add package to the pointer analysis scope. if initialPkg.Func("main") != nil { mains = append(mains, initialPkg) } else { testPkgs = append(testPkgs, initialPkg) } } if testPkgs != nil { if p := prog.CreateTestMainPackage(testPkgs...); p != nil { mains = append(mains, p) } } if mains == nil { return nil, fmt.Errorf("analysis scope has no main and no tests") } return &pointer.Config{ Log: ptaLog, Reflection: reflection, Mains: mains, }, nil }
func getDeclareStructOrInterface(prog *loader.Program, v *types.Var) string { // From x/tools/refactor/rename/check.go(checkStructField)#L288 // go/types offers no easy way to get from a field (or interface // method) to its declaring struct (or interface), so we must // ascend the AST. _, path, _ := prog.PathEnclosingInterval(v.Pos(), v.Pos()) // path matches this pattern: // [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File] // Ascend to FieldList. var i int for { if _, ok := path[i].(*ast.FieldList); ok { break } i++ } i++ _ = path[i].(*ast.StructType) i++ for { if _, ok := path[i].(*ast.ParenExpr); !ok { break } i++ } if spec, ok := path[i].(*ast.TypeSpec); ok { return spec.Name.String() } return "" }
// progType returns the go/types type for the given reflect.Type, // which must represent a named non-predeclared Go type. func progType(prog *loader.Program, t reflect.Type) (*types.TypeName, error) { if t.Kind() == reflect.Ptr { t = t.Elem() } typeName := t.Name() if typeName == "" { return nil, errgo.Newf("type %s is not named", t) } pkgPath := t.PkgPath() if pkgPath == "" { // TODO could return types.Basic type here if we needed to. return nil, errgo.Newf("type %s not declared in package", t) } pkgInfo := prog.Package(pkgPath) if pkgInfo == nil { return nil, errgo.Newf("cannot find %q in imported code", pkgPath) } pkg := pkgInfo.Pkg obj := pkg.Scope().Lookup(typeName) if obj == nil { return nil, errgo.Newf("type %s not found in %s", typeName, pkgPath) } objTypeName, ok := obj.(*types.TypeName) if !ok { return nil, errgo.Newf("%s is not a type", typeName) } return objTypeName, nil }
// FindMains returns the set of all packages loaded into the given // loader.Program which contain main functions func FindMains(p *loader.Program, s *ssa.Program) []*ssa.Package { ips := p.InitialPackages() mains := make([]*ssa.Package, 0, len(ips)) for _, info := range ips { ssaPkg := s.Package(info.Pkg) if ssaPkg.Func("main") != nil { mains = append(mains, ssaPkg) } } return mains }
func findDotImports(prog *loader.Program, pi *loader.PackageInfo) { for _, file := range pi.Files { for _, importSpec := range file.Imports { if importSpec.Name != nil && importSpec.Name.Name == "." { dotImportImportPath := strings.Trim(importSpec.Path.Value, `"`) dotImportPi := prog.Package(dotImportImportPath) dotImports = append(dotImports, dotImportPi) findDotImports(prog, dotImportPi) } } } }
func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) { o := &Oracle{fset: iprog.Fset} // Retain type info for all ASTs in the program. if needs&needRetainTypeInfo != 0 { o.typeInfo = iprog.AllPackages } // Create SSA package for the initial packages and their dependencies. if needs&needSSA != 0 { var mode ssa.BuilderMode if needs&needSSADebug != 0 { mode |= ssa.GlobalDebug } prog := ssa.Create(iprog, mode) // For each initial package (specified on the command line), // if it has a main function, analyze that, // otherwise analyze its tests, if any. var testPkgs, mains []*ssa.Package for _, info := range iprog.InitialPackages() { initialPkg := prog.Package(info.Pkg) // Add package to the pointer analysis scope. if initialPkg.Func("main") != nil { mains = append(mains, initialPkg) } else { testPkgs = append(testPkgs, initialPkg) } } if testPkgs != nil { if p := prog.CreateTestMainPackage(testPkgs...); p != nil { mains = append(mains, p) } } if mains == nil { return nil, fmt.Errorf("analysis scope has no main and no tests") } o.ptaConfig.Log = ptalog o.ptaConfig.Reflection = reflection o.ptaConfig.Mains = mains o.prog = prog } return o, nil }
// ParseQueryPos parses the source query position pos. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) { filename, startOffset, endOffset, err := parsePosFlag(posFlag) if err != nil { return nil, err } start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := iprog.PathEnclosingInterval(start, end) if path == nil { return nil, fmt.Errorf("no syntax here") } if needExact && !exact { return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) } return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil }
// DocForPos attempts to get the documentation for an item given a filename and byte offset. func DocForPos(ctxt *build.Context, lprog *loader.Program, filename string, offset int64) (*Doc, error) { tokFile := FileFromProgram(lprog, filename) if tokFile == nil { return nil, fmt.Errorf("gogetdoc: couldn't find %s in program", filename) } offPos := tokFile.Pos(int(offset)) pkgInfo, nodes, _ := lprog.PathEnclosingInterval(offPos, offPos) for _, node := range nodes { switch i := node.(type) { case *ast.ImportSpec: abs, err := filepath.Abs(filename) if err != nil { return nil, err } return PackageDoc(ctxt, lprog.Fset, filepath.Dir(abs), ImportPath(i)) case *ast.Ident: // if we can't find the object denoted by the identifier, keep searching) if obj := pkgInfo.ObjectOf(i); obj == nil { continue } return IdentDoc(ctxt, i, pkgInfo, lprog) case *ast.File: if i.Doc != nil { return &Doc{ Doc: i.Doc.Text(), }, nil } for _, f := range pkgInfo.Files { if f.Doc != nil { return &Doc{ Doc: f.Doc.Text(), }, nil } } } } return nil, errors.New("gogetdoc: no documentation found") }
// Create a pointer.Config whose scope is the initial packages of lprog // and their dependencies. func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) { // For each initial package (specified on the command line), // if it has a main function, analyze that, // otherwise analyze its tests, if any. var mains []*ssa.Package for _, info := range lprog.InitialPackages() { p := prog.Package(info.Pkg) // Add package to the pointer analysis scope. if p.Pkg.Name() == "main" && p.Func("main") != nil { mains = append(mains, p) } else if main := prog.CreateTestMainPackage(p); main != nil { mains = append(mains, main) } } if mains == nil { return nil, fmt.Errorf("analysis scope has no main and no tests") } return &pointer.Config{ Log: ptaLog, Reflection: reflection, Mains: mains, }, nil }
func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) { var fromObjects []types.Object for _, info := range iprog.AllPackages { // restrict to specified filename // NB: under certain proprietary build systems, a given // filename may appear in multiple packages. for _, f := range info.Files { thisFile := iprog.Fset.File(f.Pos()) if !sameFile(thisFile.Name(), spec.filename) { continue } // This package contains the query file. if spec.offset != 0 { // Search for a specific ident by file/offset. id := identAtOffset(iprog.Fset, f, spec.offset) if id == nil { // can't happen? return nil, fmt.Errorf("identifier not found") } obj := info.Uses[id] if obj == nil { obj = info.Defs[id] if obj == nil { // Ident without Object. // Package clause? pos := thisFile.Pos(spec.offset) _, path, _ := iprog.PathEnclosingInterval(pos, pos) if len(path) == 2 { // [Ident File] // TODO(adonovan): support this case. return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported", path[1].(*ast.File).Name.Name) } // Implicit y in "switch y := x.(type) {"? if obj := typeSwitchVar(&info.Info, path); obj != nil { return []types.Object{obj}, nil } // Probably a type error. return nil, fmt.Errorf("cannot find object for %q", id.Name) } } if obj.Pkg() == nil { return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj) } fromObjects = append(fromObjects, obj) } else { // do a package-wide query objects, err := findObjects(info, spec) if err != nil { return nil, err } // filter results: only objects defined in thisFile var filtered []types.Object for _, obj := range objects { if iprog.Fset.File(obj.Pos()) == thisFile { filtered = append(filtered, obj) } } if len(filtered) == 0 { return nil, fmt.Errorf("no object %q declared in file %s", spec.fromName, spec.filename) } else if len(filtered) > 1 { return nil, ambiguityError(iprog.Fset, filtered) } fromObjects = append(fromObjects, filtered[0]) } break } } if len(fromObjects) == 0 { // can't happen? return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename) } return fromObjects, nil }
// IdentDoc attempts to get the documentation for a *ast.Ident. func IdentDoc(ctxt *build.Context, id *ast.Ident, info *loader.PackageInfo, prog *loader.Program) (*Doc, error) { // get definition of identifier obj := info.ObjectOf(id) var pos string if p := obj.Pos(); p.IsValid() { pos = prog.Fset.Position(p).String() } pkgPath := "" if obj.Pkg() != nil { pkgPath = obj.Pkg().Path() } // handle packages imported under a different name if p, ok := obj.(*types.PkgName); ok { return PackageDoc(ctxt, prog.Fset, "", p.Imported().Path()) // SRCDIR TODO TODO } _, nodes, _ := prog.PathEnclosingInterval(obj.Pos(), obj.Pos()) if len(nodes) == 0 { // special case - builtins doc, decl := findInBuiltin(obj.Name(), obj, prog) if doc != "" { return &Doc{ Import: "builtin", Name: obj.Name(), Doc: doc, Decl: decl, Pos: pos, }, nil } return nil, fmt.Errorf("No documentation found for %s", obj.Name()) } var doc *Doc for _, node := range nodes { switch node.(type) { case *ast.FuncDecl, *ast.GenDecl, *ast.Field: default: continue } doc = &Doc{ Import: stripVendorFromImportPath(pkgPath), Name: obj.Name(), Decl: formatNode(node, obj, prog), Pos: pos, } break } if doc == nil { // This shouldn't happen return nil, fmt.Errorf("No documentation found for %s", obj.Name()) } for i, node := range nodes { //fmt.Printf("for %s: found %T\n%#v\n", id.Name, node, node) switch n := node.(type) { case *ast.FuncDecl: doc.Doc = n.Doc.Text() return doc, nil case *ast.GenDecl: constValue := "" if c, ok := obj.(*types.Const); ok { constValue = c.Val().ExactString() } if len(n.Specs) > 0 { switch n.Specs[0].(type) { case *ast.TypeSpec: spec := findTypeSpec(n, nodes[i-1].Pos()) if spec.Doc != nil { doc.Doc = spec.Doc.Text() } else if spec.Comment != nil { doc.Doc = spec.Comment.Text() } case *ast.ValueSpec: spec := findVarSpec(n, nodes[i-1].Pos()) if spec.Doc != nil { doc.Doc = spec.Doc.Text() } else if spec.Comment != nil { doc.Doc = spec.Comment.Text() } } } // if we didn't find doc for the spec, check the overall GenDecl if doc.Doc == "" && n.Doc != nil { doc.Doc = n.Doc.Text() } if constValue != "" { doc.Doc += fmt.Sprintf("\nConstant Value: %s", constValue) } return doc, nil case *ast.Field: // check the doc first, if not present, then look for a comment if n.Doc != nil { doc.Doc = n.Doc.Text() return doc, nil } else if n.Comment != nil { doc.Doc = n.Comment.Text() return doc, nil } } } return doc, nil }
// Returns true if unused func checkObj(expr *ast.Ident, object types.Object, prog *loader.Program, ssaprog *ssa.Program, fset *token.FileSet) (unused bool) { if _, ok := object.(*types.Var); !ok { if debug { fmt.Println("Skipping object", object) } return false } pkg, node, _ := prog.PathEnclosingInterval(expr.Pos(), expr.End()) spkg := ssaprog.Package(pkg.Pkg) f := ssa.EnclosingFunction(spkg, node) if f == nil { if debug { fmt.Printf("Unknown function %v %v %v %v\n", fset.Position(expr.Pos()), object, pkg, prog) } return false } value, _ := f.ValueForExpr(expr) // Unwrap unops and grab the value inside if v, ok := value.(*ssa.UnOp); ok { if debug { fmt.Println("Unwrapping unop") } value = v.X } if debug { fmt.Printf("%v %v: %v %#v\n", fset.Position(expr.Pos()), expr, object, value) } if _, ok := value.(*ssa.Global); ok { if debug { fmt.Printf(" is global\n") } return false } if value == nil { if debug { fmt.Println("Value is nil", object) } return false } refs := value.Referrers() if refs == nil { if debug { fmt.Println("Referrers is nil", object) } return false } if debug { fmt.Printf(" (refs) %v\n", refs) } hasRef := false for _, r := range *refs { _, ok := r.(*ssa.DebugRef) hasRef = hasRef || !ok if debug && !ok { fmt.Printf("%v %v: %v %v\n", fset.Position(expr.Pos()), expr, object, r) } } if !hasRef { unused = true } return unused }