diff --git a/cmd/oracle/main.go b/cmd/oracle/main.go index 96efcb66..f950472a 100644 --- a/cmd/oracle/main.go +++ b/cmd/oracle/main.go @@ -49,14 +49,14 @@ The -format flag controls the output format: json structured data in JSON syntax. xml structured data in XML syntax. -The -pos flag is required in all modes except 'callgraph'. +The -pos flag is required in all modes. The mode argument determines the query to perform: callees show possible targets of selected function call callers show possible callers of selected function - callgraph show complete callgraph of program callstack show path from callgraph root to selected function + definition show declaration of selected identifier describe describe selected syntax: definition, methods, etc freevars show free variables of selection implements show 'implements' relation for selected type or method @@ -166,8 +166,16 @@ func main() { } // Ask the oracle. - res, err := oracle.Query(args, mode, *posFlag, ptalog, &build.Default, *reflectFlag) - if err != nil { + query := oracle.Query{ + Mode: mode, + Pos: *posFlag, + Build: &build.Default, + Scope: args, + PTALog: ptalog, + Reflection: *reflectFlag, + } + + if err := oracle.Run(&query); err != nil { fmt.Fprintf(os.Stderr, "oracle: %s.\n", err) os.Exit(1) } @@ -175,7 +183,7 @@ func main() { // Print the result. switch *formatFlag { case "json": - b, err := json.MarshalIndent(res.Serial(), "", "\t") + b, err := json.MarshalIndent(query.Serial(), "", "\t") if err != nil { fmt.Fprintf(os.Stderr, "oracle: JSON error: %s.\n", err) os.Exit(1) @@ -183,7 +191,7 @@ func main() { os.Stdout.Write(b) case "xml": - b, err := xml.MarshalIndent(res.Serial(), "", "\t") + b, err := xml.MarshalIndent(query.Serial(), "", "\t") if err != nil { fmt.Fprintf(os.Stderr, "oracle: XML error: %s.\n", err) os.Exit(1) @@ -191,6 +199,6 @@ func main() { os.Stdout.Write(b) case "plain": - res.WriteTo(os.Stdout) + query.WriteTo(os.Stdout) } } diff --git a/oracle/TODO b/oracle/TODO index b9d4271f..8fbf5e86 100644 --- a/oracle/TODO +++ b/oracle/TODO @@ -6,15 +6,9 @@ ORACLE TODO General ======= -Refactor control flow so that each mode has a "one-shot setup" function. - -Use a fault-tolerant parser that can recover from bad parses. - Save unsaved editor buffers into an archive and provide that to the tools, which should act as if they were saved. -Fix: make the guessImportPath hack work with external _test.go files too. - Include complete pos/end information Serial output. But beware that sometimes a single token (e.g. +) is more helpful than the pos/end of the containing expression (e.g. x \n + \n y). @@ -34,12 +28,6 @@ implements definition, referrers - Use the parser's resolver information to answer the query - for local names. Only run the type checker if that fails. - (NB: gri's new parser won't do any resolution.) - - referrers: Show the text of the matching line of code, like grep. - definition: Make it work with qualified identifiers (SelectorExpr) too. references: Make it work on things that are implicit idents, like @@ -50,8 +38,6 @@ what Report def/ref info if available. Editors could use it to highlight all idents of the same local var. - Fix: support it in (*Oracle).Query (long-running tools). - More tests. pointsto @@ -95,5 +81,3 @@ Emacs: go-root-and-paths depends on the current buffer, so be sure to call it from within the source file, not the *go-oracle* buffer: the user may have switched workspaces and the oracle should run in the new one. - -Support other editors: vim, Eclipse, Sublime, etc. diff --git a/oracle/callees.go b/oracle/callees.go index c8be7285..e4b9f836 100644 --- a/oracle/callees.go +++ b/oracle/callees.go @@ -10,6 +10,8 @@ import ( "go/token" "sort" + "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" @@ -17,10 +19,40 @@ import ( // Callees reports the possible callees of the function call site // identified by the specified source location. -func callees(o *Oracle, qpos *QueryPos) (queryResult, error) { - pkg := o.prog.Package(qpos.info.Pkg) +func callees(q *Query) error { + lconf := loader.Config{Build: q.Build} + + // Determine initial packages for PTA. + args, err := lconf.FromArgs(q.Scope, true) + if err != nil { + return err + } + if len(args) > 0 { + return fmt.Errorf("surplus arguments: %q", args) + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos + if err != nil { + return err + } + + prog := ssa.Create(lprog, 0) + + ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) + if err != nil { + return err + } + + pkg := prog.Package(qpos.info.Pkg) if pkg == nil { - return nil, fmt.Errorf("no SSA package") + return fmt.Errorf("no SSA package") } // Determine the enclosing call for the specified position. @@ -31,7 +63,7 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) { } } if e == nil { - return nil, fmt.Errorf("there is no function call here") + return fmt.Errorf("there is no function call here") } // TODO(adonovan): issue an error if the call is "too far // away" from the current selection, as this most likely is @@ -39,39 +71,41 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) { // Reject type conversions. if qpos.info.Types[e.Fun].IsType() { - return nil, fmt.Errorf("this is a type conversion, not a function call") + return fmt.Errorf("this is a type conversion, not a function call") } // Reject calls to built-ins. if id, ok := unparen(e.Fun).(*ast.Ident); ok { if b, ok := qpos.info.Uses[id].(*types.Builtin); ok { - return nil, fmt.Errorf("this is a call to the built-in '%s' operator", b.Name()) + return fmt.Errorf("this is a call to the built-in '%s' operator", b.Name()) } } - buildSSA(o) + // Defer SSA construction till after errors are reported. + prog.BuildAll() // Ascertain calling function and call site. callerFn := ssa.EnclosingFunction(pkg, qpos.path) if callerFn == nil { - return nil, fmt.Errorf("no SSA function built for this location (dead code?)") + return fmt.Errorf("no SSA function built for this location (dead code?)") } // Find the call site. site, err := findCallSite(callerFn, e.Lparen) if err != nil { - return nil, err + return err } - funcs, err := findCallees(o, site) + funcs, err := findCallees(ptaConfig, site) if err != nil { - return nil, err + return err } - return &calleesResult{ + q.result = &calleesResult{ site: site, funcs: funcs, - }, nil + } + return nil } func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, error) { @@ -85,7 +119,7 @@ func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, erro return nil, fmt.Errorf("this call site is unreachable in this analysis") } -func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) { +func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) { // Avoid running the pointer analysis for static calls. if callee := site.Common().StaticCallee(); callee != nil { switch callee.String() { @@ -99,8 +133,8 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) { } // Dynamic call: use pointer analysis. - o.ptaConfig.BuildCallGraph = true - cg := ptrAnalysis(o).CallGraph + conf.BuildCallGraph = true + cg := ptrAnalysis(conf).CallGraph cg.DeleteSyntheticNodes() // Find all call edges from the site. diff --git a/oracle/callers.go b/oracle/callers.go index dde803b9..e6836de3 100644 --- a/oracle/callers.go +++ b/oracle/callers.go @@ -9,6 +9,7 @@ import ( "go/token" "golang.org/x/tools/go/callgraph" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/oracle/serial" ) @@ -16,35 +17,67 @@ import ( // Callers reports the possible callers of the function // immediately enclosing the specified source location. // -func callers(o *Oracle, qpos *QueryPos) (queryResult, error) { - pkg := o.prog.Package(qpos.info.Pkg) - if pkg == nil { - return nil, fmt.Errorf("no SSA package") +func callers(conf *Query) error { + lconf := loader.Config{Build: conf.Build} + + // Determine initial packages for PTA. + args, err := lconf.FromArgs(conf.Scope, true) + if err != nil { + return err } - if !ssa.HasEnclosingFunction(pkg, qpos.path) { - return nil, fmt.Errorf("this position is not inside a function") + if len(args) > 0 { + return fmt.Errorf("surplus arguments: %q", args) } - buildSSA(o) + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + conf.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, conf.Pos, false) + if err != nil { + return err + } + + prog := ssa.Create(lprog, 0) + + ptaConfig, err := setupPTA(prog, lprog, conf.PTALog, conf.Reflection) + if err != nil { + return err + } + + pkg := prog.Package(qpos.info.Pkg) + if pkg == nil { + return fmt.Errorf("no SSA package") + } + if !ssa.HasEnclosingFunction(pkg, qpos.path) { + return fmt.Errorf("this position is not inside a function") + } + + // Defer SSA construction till after errors are reported. + prog.BuildAll() target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { - return nil, fmt.Errorf("no SSA function built for this location (dead code?)") + return fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis, recording each // call found to originate from target. - o.ptaConfig.BuildCallGraph = true - cg := ptrAnalysis(o).CallGraph + ptaConfig.BuildCallGraph = true + cg := ptrAnalysis(ptaConfig).CallGraph cg.DeleteSyntheticNodes() edges := cg.CreateNode(target).In // TODO(adonovan): sort + dedup calls to ensure test determinism. - return &callersResult{ + conf.result = &callersResult{ target: target, callgraph: cg, edges: edges, - }, nil + } + return nil } type callersResult struct { diff --git a/oracle/callgraph.go b/oracle/callgraph.go deleted file mode 100644 index 09f22052..00000000 --- a/oracle/callgraph.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2013 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 oracle - -import ( - "fmt" - "go/token" - "sort" - - "golang.org/x/tools/go/callgraph" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -// doCallgraph displays the entire callgraph of the current program, -// or if a query -pos was provided, the query package. -func doCallgraph(o *Oracle, qpos *QueryPos) (queryResult, error) { - buildSSA(o) - - // Run the pointer analysis and build the callgraph. - o.ptaConfig.BuildCallGraph = true - cg := ptrAnalysis(o).CallGraph - cg.DeleteSyntheticNodes() - - var qpkg *types.Package - var isQueryPkg func(fn *ssa.Function) bool - var keep, remove, roots []*callgraph.Node - if qpos == nil { - // No -pos provided: show complete callgraph. - roots = append(roots, cg.Root) - isQueryPkg = func(fn *ssa.Function) bool { return true } - } else { - // A query -pos was provided: restrict result to - // functions belonging to the query package. - qpkg = qpos.info.Pkg - isQueryPkg = func(fn *ssa.Function) bool { - return fn.Pkg != nil && fn.Pkg.Object == qpkg - } - } - - // First compute the nodes to keep and remove. - for fn, cgn := range cg.Nodes { - if isQueryPkg(fn) { - keep = append(keep, cgn) - } else { - remove = append(remove, cgn) - } - } - - // Compact the Node.ID sequence of the kept nodes, - // preserving the original order. - sort.Sort(nodesByID(keep)) - for i, cgn := range keep { - cgn.ID = i - } - - // Compute the set of roots: - // in-package nodes with out-of-package callers. - // For determinism, roots are ordered by original Node.ID. - for _, cgn := range keep { - for _, e := range cgn.In { - if !isQueryPkg(e.Caller.Func) { - roots = append(roots, cgn) - break - } - } - } - - // Finally, discard all out-of-package nodes. - for _, cgn := range remove { - cg.DeleteNode(cgn) - } - - return &callgraphResult{qpkg, cg.Nodes, roots}, nil -} - -type callgraphResult struct { - qpkg *types.Package - nodes map[*ssa.Function]*callgraph.Node - roots []*callgraph.Node -} - -func (r *callgraphResult) display(printf printfFunc) { - descr := "the entire program" - if r.qpkg != nil { - descr = fmt.Sprintf("package %s", r.qpkg.Path()) - } - - printf(nil, ` -Below is a call graph of %s. -The numbered nodes form a spanning tree. -Non-numbered nodes indicate back- or cross-edges to the node whose - number follows in parentheses. -`, descr) - - printed := make(map[*callgraph.Node]int) - var print func(caller *callgraph.Node, indent int) - print = func(caller *callgraph.Node, indent int) { - if num, ok := printed[caller]; !ok { - num = len(printed) - printed[caller] = num - - // Sort the children into name order for deterministic* output. - // (*mostly: anon funcs' names are not globally unique.) - var funcs funcsByName - for callee := range callgraph.CalleesOf(caller) { - funcs = append(funcs, callee.Func) - } - sort.Sort(funcs) - - printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func.RelString(r.qpkg)) - for _, callee := range funcs { - print(r.nodes[callee], indent+1) - } - } else { - printf(caller.Func, "\t%*s%s (%d)", 4*indent, "", caller.Func.RelString(r.qpkg), num) - } - } - for _, root := range r.roots { - print(root, 0) - } -} - -type nodesByID []*callgraph.Node - -func (s nodesByID) Len() int { return len(s) } -func (s nodesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s nodesByID) Less(i, j int) bool { return s[i].ID < s[j].ID } - -type funcsByName []*ssa.Function - -func (s funcsByName) Len() int { return len(s) } -func (s funcsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() } - -func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) { - cg := make([]serial.CallGraph, len(r.nodes)) - for _, n := range r.nodes { - j := &cg[n.ID] - fn := n.Func - j.Name = fn.String() - j.Pos = fset.Position(fn.Pos()).String() - for callee := range callgraph.CalleesOf(n) { - j.Children = append(j.Children, callee.ID) - } - sort.Ints(j.Children) - } - res.Callgraph = cg -} diff --git a/oracle/callstack.go b/oracle/callstack.go index 4b2e3801..5144a0cc 100644 --- a/oracle/callstack.go +++ b/oracle/callstack.go @@ -9,6 +9,7 @@ import ( "go/token" "golang.org/x/tools/go/callgraph" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/oracle/serial" ) @@ -23,26 +24,57 @@ import ( // TODO(adonovan): permit user to specify a starting point other than // the analysis root. // -func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) { - pkg := o.prog.Package(qpos.info.Pkg) +func callstack(conf *Query) error { + fset := token.NewFileSet() + lconf := loader.Config{Fset: fset, Build: conf.Build} + + // Determine initial packages for PTA. + args, err := lconf.FromArgs(conf.Scope, true) + if err != nil { + return err + } + if len(args) > 0 { + return fmt.Errorf("surplus arguments: %q", args) + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + + qpos, err := parseQueryPos(lprog, conf.Pos, false) + if err != nil { + return err + } + + prog := ssa.Create(lprog, 0) + + ptaConfig, err := setupPTA(prog, lprog, conf.PTALog, conf.Reflection) + if err != nil { + return err + } + + pkg := prog.Package(qpos.info.Pkg) if pkg == nil { - return nil, fmt.Errorf("no SSA package") + return fmt.Errorf("no SSA package") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { - return nil, fmt.Errorf("this position is not inside a function") + return fmt.Errorf("this position is not inside a function") } - buildSSA(o) + // Defer SSA construction till after errors are reported. + prog.BuildAll() target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { - return nil, fmt.Errorf("no SSA function built for this location (dead code?)") + return fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis and build the complete call graph. - o.ptaConfig.BuildCallGraph = true - cg := ptrAnalysis(o).CallGraph + ptaConfig.BuildCallGraph = true + cg := ptrAnalysis(ptaConfig).CallGraph cg.DeleteSyntheticNodes() // Search for an arbitrary path from a root to the target function. @@ -52,15 +84,17 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) { callpath = callpath[1:] // remove synthetic edge from } - return &callstackResult{ + conf.Fset = fset + conf.result = &callstackResult{ qpos: qpos, target: target, callpath: callpath, - }, nil + } + return nil } type callstackResult struct { - qpos *QueryPos + qpos *queryPos target *ssa.Function callpath []*callgraph.Edge } diff --git a/oracle/definition.go b/oracle/definition.go index 0f149b2c..b569ba32 100644 --- a/oracle/definition.go +++ b/oracle/definition.go @@ -9,6 +9,7 @@ import ( "go/ast" "go/token" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" ) @@ -18,28 +19,48 @@ import ( // TODO(adonovan): opt: for intra-file references, the parser's // resolution might be enough; we should start with that. // -func definition(o *Oracle, qpos *QueryPos) (queryResult, error) { +func definition(q *Query) error { + lconf := loader.Config{Build: q.Build} + allowErrors(&lconf) + + if err := importQueryPackage(q.Pos, &lconf); err != nil { + return err + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, false) + if err != nil { + return err + } + id, _ := qpos.path[0].(*ast.Ident) if id == nil { - return nil, fmt.Errorf("no identifier here") + return fmt.Errorf("no identifier here") } obj := qpos.info.ObjectOf(id) if obj == nil { // Happens for y in "switch y := x.(type)", but I think that's all. - return nil, fmt.Errorf("no object for identifier") + return fmt.Errorf("no object for identifier") } - return &definitionResult{qpos, obj}, nil + q.result = &definitionResult{qpos, obj} + return nil } type definitionResult struct { - qpos *QueryPos + qpos *queryPos obj types.Object // object it denotes } func (r *definitionResult) display(printf printfFunc) { - printf(r.obj, "defined here as %s", r.qpos.ObjectString(r.obj)) + printf(r.obj, "defined here as %s", r.qpos.objectString(r.obj)) } func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) { diff --git a/oracle/describe.go b/oracle/describe.go index 9c21017b..00c0525b 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -27,32 +27,52 @@ import ( // - the definition of its referent (for identifiers) [now redundant] // - its type and method set (for an expression or type expression) // -func describe(o *Oracle, qpos *QueryPos) (queryResult, error) { +func describe(q *Query) error { + lconf := loader.Config{Build: q.Build} + allowErrors(&lconf) + + if err := importQueryPackage(q.Pos, &lconf); err != nil { + return err + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos) + if err != nil { + return err + } + if false { // debugging - fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s", + fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s", astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path)) } path, action := findInterestingNode(qpos.info, qpos.path) switch action { case actionExpr: - return describeValue(o, qpos, path) + q.result, err = describeValue(qpos, path) case actionType: - return describeType(o, qpos, path) + q.result, err = describeType(qpos, path) case actionPackage: - return describePackage(o, qpos, path) + q.result, err = describePackage(qpos, path) case actionStmt: - return describeStmt(o, qpos, path) + q.result, err = describeStmt(qpos, path) case actionUnknown: - return &describeUnknownResult{path[0]}, nil + q.result = &describeUnknownResult{path[0]} default: panic(action) // unreachable } + return err } type describeUnknownResult struct { @@ -288,7 +308,7 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No return nil, actionUnknown // unreachable } -func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) { +func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) { var expr ast.Expr var obj types.Object switch n := path[0].(type) { @@ -318,7 +338,7 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe } type describeValueResult struct { - qpos *QueryPos + qpos *queryPos expr ast.Expr // query node typ types.Type // type of expression constVal exact.Value // value of expression, if constant @@ -345,10 +365,10 @@ func (r *describeValueResult) display(printf printfFunc) { if r.obj != nil { if r.obj.Pos() == r.expr.Pos() { // defining ident - printf(r.expr, "definition of %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix) + printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix) } else { // referring ident - printf(r.expr, "reference to %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix) + printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix) if def := r.obj.Pos(); def != token.NoPos { printf(def, "defined here") } @@ -360,7 +380,7 @@ func (r *describeValueResult) display(printf printfFunc) { printf(r.expr, "%s%s", desc, suffix) } else { // non-constant expression - printf(r.expr, "%s of type %s", desc, r.qpos.TypeString(r.typ)) + printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ)) } } } @@ -379,7 +399,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) Pos: fset.Position(r.expr.Pos()).String(), Detail: "value", Value: &serial.DescribeValue{ - Type: r.qpos.TypeString(r.typ), + Type: r.qpos.typeString(r.typ), Value: value, ObjPos: objpos, }, @@ -388,7 +408,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) // ---- TYPE ------------------------------------------------------------ -func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) { +func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) { var description string var t types.Type switch n := path[0].(type) { @@ -415,14 +435,12 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu return nil, fmt.Errorf("unexpected AST for type: %T", n) } - description = description + "type " + qpos.TypeString(t) + description = description + "type " + qpos.typeString(t) // Show sizes for structs and named types (it's fairly obvious for others). switch t.(type) { case *types.Named, *types.Struct: - // TODO(adonovan): use o.imp.Config().TypeChecker.Sizes when - // we add the Config() method (needs some thought). - szs := types.StdSizes{8, 8} + szs := types.StdSizes{8, 8} // assume amd64 description = fmt.Sprintf("%s (size %d, align %d)", description, szs.Sizeof(t), szs.Alignof(t)) } @@ -437,7 +455,7 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu } type describeTypeResult struct { - qpos *QueryPos + qpos *queryPos node ast.Node description string typ types.Type @@ -449,7 +467,7 @@ func (r *describeTypeResult) display(printf printfFunc) { // Show the underlying type for a reference to a named type. if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() { - printf(nt.Obj(), "defined as %s", r.qpos.TypeString(nt.Underlying())) + printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying())) } // Print the method set, if the type kind is capable of bearing methods. @@ -461,7 +479,7 @@ func (r *describeTypeResult) display(printf printfFunc) { // TODO(adonovan): print these relative // to the owning package, not the // query package. - printf(meth.Obj(), "\t%s", r.qpos.SelectionString(meth)) + printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth)) } } else { printf(r.node, "No methods.") @@ -480,7 +498,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { Pos: fset.Position(r.node.Pos()).String(), Detail: "type", Type: &serial.DescribeType{ - Type: r.qpos.TypeString(r.typ), + Type: r.qpos.typeString(r.typ), NamePos: namePos, NameDef: nameDef, Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset), @@ -490,7 +508,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { // ---- PACKAGE ------------------------------------------------------------ -func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePackageResult, error) { +func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) { var description string var pkg *types.Package switch n := path[0].(type) { @@ -542,7 +560,7 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka } } - return &describePackageResult{o.fset, path[0], description, pkg, members}, nil + return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil } type describePackageResult struct { @@ -661,7 +679,7 @@ func tokenOf(o types.Object) string { // ---- STATEMENT ------------------------------------------------------------ -func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResult, error) { +func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) { var description string switch n := path[0].(type) { case *ast.Ident: @@ -675,7 +693,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu // Nothing much to say about statements. description = astutil.NodeDescription(n) } - return &describeStmtResult{o.fset, path[0], description}, nil + return &describeStmtResult{qpos.fset, path[0], description}, nil } type describeStmtResult struct { diff --git a/oracle/freevars.go b/oracle/freevars.go index 84a06b00..580c97b2 100644 --- a/oracle/freevars.go +++ b/oracle/freevars.go @@ -11,6 +11,7 @@ import ( "go/token" "sort" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" ) @@ -28,7 +29,26 @@ import ( // these might be interesting. Perhaps group the results into three // bands. // -func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) { +func freevars(q *Query) error { + lconf := loader.Config{Build: q.Build} + allowErrors(&lconf) + + if err := importQueryPackage(q.Pos, &lconf); err != nil { + return err + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, false) + if err != nil { + return err + } + file := qpos.path[len(qpos.path)-1] // the enclosing file fileScope := qpos.info.Scopes[file] pkgScope := fileScope.Parent() @@ -118,7 +138,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) { } typ := qpos.info.TypeOf(n.(ast.Expr)) - ref := freevarsRef{kind, printNode(o.fset, n), typ, obj} + ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj} refsMap[ref.ref] = ref if prune { @@ -136,14 +156,15 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) { } sort.Sort(byRef(refs)) - return &freevarsResult{ + q.result = &freevarsResult{ qpos: qpos, refs: refs, - }, nil + } + return nil } type freevarsResult struct { - qpos *QueryPos + qpos *queryPos refs []freevarsRef } diff --git a/oracle/implements.go b/oracle/implements.go index 1ad80b41..9af22698 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -12,16 +12,37 @@ import ( "sort" "strings" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" ) // Implements displays the "implements" relation as it pertains to the -// selected type. If the selection is a method, 'implements' displays +// selected type within a single package. +// If the selection is a method, 'implements' displays // the corresponding methods of the types that would have been reported // by an implements query on the receiver type. // -func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { +func implements(q *Query) error { + lconf := loader.Config{Build: q.Build} + allowErrors(&lconf) + + if err := importQueryPackage(q.Pos, &lconf); err != nil { + return err + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, false) + if err != nil { + return err + } + // Find the selected type. path, action := findInterestingNode(qpos.info, qpos.path) @@ -35,7 +56,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { recv := obj.Type().(*types.Signature).Recv() if recv == nil { - return nil, fmt.Errorf("this function is not a method") + return fmt.Errorf("this function is not a method") } method = obj T = recv.Type() @@ -45,7 +66,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { T = qpos.info.TypeOf(path[0].(ast.Expr)) } if T == nil { - return nil, fmt.Errorf("no type or method here") + return fmt.Errorf("no type or method here") } // Find all named types, even local types (which can have @@ -55,7 +76,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { // i.e. don't reduceScope? // var allNamed []types.Type - for _, info := range o.typeInfo { + for _, info := range lprog.AllPackages { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { allNamed = append(allNamed, obj.Type()) @@ -135,11 +156,14 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { } } - return &implementsResult{qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod}, nil + q.result = &implementsResult{ + qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, + } + return nil } type implementsResult struct { - qpos *QueryPos + qpos *queryPos t types.Type // queried type (not necessarily named) pos interface{} // pos of t (*types.Name or *QueryPos) @@ -160,7 +184,7 @@ func (r *implementsResult) display(printf printfFunc) { meth := func(sel *types.Selection) { if sel != nil { printf(sel.Obj(), "\t%s method (%s).%s", - relation, r.qpos.TypeString(sel.Recv()), sel.Obj().Name()) + relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name()) } } @@ -173,7 +197,7 @@ func (r *implementsResult) display(printf printfFunc) { if r.method == nil { printf(r.pos, "interface type %s", r.t) } else { - printf(r.method, "abstract method %s", r.qpos.ObjectString(r.method)) + printf(r.method, "abstract method %s", r.qpos.objectString(r.method)) } // Show concrete types (or methods) first; use two passes. @@ -214,7 +238,7 @@ func (r *implementsResult) display(printf printfFunc) { printf(r.pos, "%s type %s", typeKind(r.t), r.t) } else { printf(r.method, "concrete method %s", - r.qpos.ObjectString(r.method)) + r.qpos.objectString(r.method)) } for i, super := range r.from { if r.method == nil { @@ -231,7 +255,7 @@ func (r *implementsResult) display(printf printfFunc) { } else { // TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f. printf(r.method, "concrete method %s", - r.qpos.ObjectString(r.method)) + r.qpos.objectString(r.method)) } for i, psuper := range r.fromPtr { @@ -260,7 +284,7 @@ func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) { } if r.method != nil { res.Implements.Method = &serial.DescribeMethod{ - Name: r.qpos.ObjectString(r.method), + Name: r.qpos.objectString(r.method), Pos: fset.Position(r.method.Pos()).String(), } } diff --git a/oracle/oracle.go b/oracle/oracle.go index bf65c14c..7dda91c5 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -19,42 +19,13 @@ package oracle // import "golang.org/x/tools/oracle" // - show all places where an object of type T is created // (&T{}, var t T, new(T), new(struct{array [3]T}), etc. -// ORACLE CONTROL FLOW -// -// The Oracle is somewhat convoluted due to the need to support two -// very different use-cases, "one-shot" and "long running", and to do -// so quickly. -// -// The cmd/oracle tool issues "one-shot" queries via the exported -// Query function, which creates an Oracle to answer a single query. -// newOracle consults the 'needs' flags of the query mode and the -// package containing the query to avoid doing more work than it needs -// (loading, parsing, type checking, SSA construction). -// -// The Pythia tool (github.com/fzipp/pythia) is an example of a "long -// running" tool. It calls New() and then loops, calling -// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP -// query. Since New cannot see which queries will follow, it must -// load, parse, type-check and SSA-build the entire transitive closure -// of the analysis scope, retaining full debug information and all -// typed ASTs. -// -// TODO(adonovan): experiment with inverting the control flow by -// making each mode consist of two functions: a "one-shot setup" -// function and the existing "impl" function. The one-shot setup -// function would do all of the work of Query and newOracle, -// specialized to each mode, calling library utilities for the common -// things. This would give it more control over "scope reduction". -// Long running tools would not call the one-shot setup function but -// would have their own setup function equivalent to the existing -// 'needsAll' flow path. - import ( "fmt" "go/ast" "go/build" "go/token" "io" + "path/filepath" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/loader" @@ -64,64 +35,6 @@ import ( "golang.org/x/tools/oracle/serial" ) -// An Oracle holds the program state required for one or more queries. -type Oracle struct { - fset *token.FileSet // file set [all queries] - prog *ssa.Program // the SSA program [needSSA] - ptaConfig pointer.Config // pointer analysis configuration [needPTA] - typeInfo map[*types.Package]*loader.PackageInfo // type info for all ASTs in the program [needRetainTypeInfo] -} - -// A set of bits indicating the analytical requirements of each mode. -// -// Typed ASTs for the whole program are always constructed -// transiently; they are retained only for the queried package unless -// needRetainTypeInfo is set. -const ( - needPos = 1 << iota // needs a position - needExactPos // needs an exact AST selection; implies needPos - needRetainTypeInfo // needs to retain type info for all ASTs in the program - needSSA // needs ssa.Packages for whole program - needSSADebug // needs debug info for ssa.Packages - needPTA = needSSA // needs pointer analysis - needAll = -1 // needs everything (e.g. a sequence of queries) -) - -type modeInfo struct { - name string - needs int - impl func(*Oracle, *QueryPos) (queryResult, error) -} - -var modes = []*modeInfo{ - // Pointer analyses, whole program: - {"callees", needPTA | needExactPos, callees}, - {"callers", needPTA | needPos, callers}, - {"callgraph", needPTA, doCallgraph}, - {"callstack", needPTA | needPos, callstack}, - {"peers", needPTA | needSSADebug | needPos, peers}, - {"pointsto", needPTA | needSSADebug | needExactPos, pointsto}, - {"whicherrs", needPTA | needSSADebug | needExactPos, whicherrs}, - - // Type-based, modular analyses: - {"definition", needPos, definition}, - {"describe", needExactPos, describe}, - {"freevars", needPos, freevars}, - - // Type-based, whole-program analyses: - {"implements", needRetainTypeInfo | needPos, implements}, - {"referrers", needRetainTypeInfo | needPos, referrers}, -} - -func findMode(mode string) *modeInfo { - for _, m := range modes { - if m.name == mode { - return m - } - } - return nil -} - type printfFunc func(pos interface{}, format string, args ...interface{}) // queryResult is the interface of each query-specific result type. @@ -133,9 +46,8 @@ type queryResult interface { // A QueryPos represents the position provided as input to a query: // a textual extent in the program's source code, the AST node it // corresponds to, and the package to which it belongs. -// Instances are created by ParseQueryPos. -// -type QueryPos struct { +// Instances are created by parseQueryPos. +type queryPos struct { fset *token.FileSet start, end token.Pos // source extent of query path []ast.Node // AST path from query node to root of ast.File @@ -144,162 +56,139 @@ type QueryPos struct { } // TypeString prints type T relative to the query position. -func (qpos *QueryPos) TypeString(T types.Type) string { +func (qpos *queryPos) typeString(T types.Type) string { return types.TypeString(qpos.info.Pkg, T) } // ObjectString prints object obj relative to the query position. -func (qpos *QueryPos) ObjectString(obj types.Object) string { +func (qpos *queryPos) objectString(obj types.Object) string { return types.ObjectString(qpos.info.Pkg, obj) } // SelectionString prints selection sel relative to the query position. -func (qpos *QueryPos) SelectionString(sel *types.Selection) string { +func (qpos *queryPos) selectionString(sel *types.Selection) string { return types.SelectionString(qpos.info.Pkg, sel) } -// A Result encapsulates the result of an oracle.Query. -type Result struct { - fset *token.FileSet - q queryResult // the query-specific result - mode string // query mode - warnings []pointer.Warning // pointer analysis warnings (TODO(adonovan): fix: never populated!) +// A Query specifies a single oracle query. +type Query struct { + Mode string // query mode ("callers", etc) + Pos string // query position + Build *build.Context // package loading configuration + + // pointer analysis options + Scope []string // main package in (*loader.Config).FromArgs syntax + PTALog io.Writer // (optional) pointer-analysis log file + Reflection bool // model reflection soundly (currently slow). + + // Populated during Run() + Fset *token.FileSet + result queryResult } // Serial returns an instance of serial.Result, which implements the // {xml,json}.Marshaler interfaces so that query results can be // serialized as JSON or XML. // -func (res *Result) Serial() *serial.Result { - resj := &serial.Result{Mode: res.mode} - res.q.toSerial(resj, res.fset) - for _, w := range res.warnings { - resj.Warnings = append(resj.Warnings, serial.PTAWarning{ - Pos: res.fset.Position(w.Pos).String(), - Message: w.Message, - }) - } +func (q *Query) Serial() *serial.Result { + resj := &serial.Result{Mode: q.Mode} + q.result.toSerial(resj, q.Fset) return resj } -// Query runs a single oracle query. -// -// args specify the main package in (*loader.Config).FromArgs syntax. -// mode is the query mode ("callers", etc). -// ptalog is the (optional) pointer-analysis log file. -// buildContext is the go/build configuration for locating packages. -// reflection determines whether to model reflection soundly (currently slow). -// -// Clients that intend to perform multiple queries against the same -// analysis scope should use this pattern instead: -// -// conf := loader.Config{Build: buildContext} -// ... populate config, e.g. conf.FromArgs(args) ... -// iprog, err := conf.Load() -// if err != nil { ... } -// o, err := oracle.New(iprog, nil, false) -// if err != nil { ... } -// for ... { -// qpos, err := oracle.ParseQueryPos(imp, pos, needExact) -// if err != nil { ... } -// -// res, err := o.Query(mode, qpos) -// if err != nil { ... } -// -// // use res -// } -// -// TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos -// depends on the query mode; how should we expose this? -// -func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) { - if mode == "what" { - // Bypass package loading, type checking, SSA construction. - return what(pos, buildContext) +// WriteTo writes the oracle query result res to out in a compiler diagnostic format. +func (q *Query) WriteTo(out io.Writer) { + printf := func(pos interface{}, format string, args ...interface{}) { + fprintf(out, q.Fset, pos, format, args...) } - - minfo := findMode(mode) - if minfo == nil { - return nil, fmt.Errorf("invalid mode type: %q", mode) - } - - conf := loader.Config{Build: buildContext} - - // TODO(adonovan): tolerate type errors if we don't need SSA form. - // First we'll need ot audit the non-SSA modes for robustness - // in the face of type errors. - // if minfo.needs&needSSA == 0 { - // conf.AllowErrors = true - // } - - // Determine initial packages. - args, err := conf.FromArgs(args, true) - if err != nil { - return nil, err - } - if len(args) > 0 { - return nil, fmt.Errorf("surplus arguments: %q", args) - } - - // For queries needing only a single typed package, - // reduce the analysis scope to that package. - if minfo.needs&(needSSA|needRetainTypeInfo) == 0 { - reduceScope(pos, &conf) - } - - // TODO(adonovan): report type errors to the user via Serial - // types, not stderr? - // conf.TypeChecker.Error = func(err error) { - // E := err.(types.Error) - // fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg) - // } - - // Load/parse/type-check the program. - iprog, err := conf.Load() - if err != nil { - return nil, err - } - - o, err := newOracle(iprog, ptalog, minfo.needs, reflection) - if err != nil { - return nil, err - } - - qpos, err := ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0) - if err != nil && minfo.needs&(needPos|needExactPos) != 0 { - return nil, err - } - - // SSA is built and we have the QueryPos. - // Release the other ASTs and type info to the GC. - iprog = nil - - return o.query(minfo, qpos) + q.result.display(printf) } -// reduceScope is called for one-shot queries that need only a single -// typed package. It attempts to guess the query package from pos and -// reduce the analysis scope (set of loaded packages) to just that one -// plus (the exported parts of) its dependencies. It leaves its -// arguments unchanged on failure. -// -// TODO(adonovan): this is a real mess... but it's fast. -// -func reduceScope(pos string, conf *loader.Config) { +// Run runs an oracle query and populates its Fset and Result. +func Run(conf *Query) error { + switch conf.Mode { + case "callees": + return callees(conf) + case "callers": + return callers(conf) + case "callstack": + return callstack(conf) + case "peers": + return peers(conf) + case "pointsto": + return pointsto(conf) + case "whicherrs": + return whicherrs(conf) + case "definition": + return definition(conf) + case "describe": + return describe(conf) + case "freevars": + return freevars(conf) + case "implements": + return implements(conf) + case "referrers": + return referrers(conf) + case "what": + return what(conf) + default: + return fmt.Errorf("invalid mode: %q", conf.Mode) + } +} + +// 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 +} + +// importQueryPackage finds the package P containing the +// query position and tells conf to import it. +func importQueryPackage(pos string, conf *loader.Config) error { fqpos, err := fastQueryPos(pos) if err != nil { - return // bad query + return err // bad query } + filename := fqpos.fset.File(fqpos.start).Name() - // TODO(adonovan): fix: this gives the wrong results for files - // in non-importable packages such as tests and ad-hoc packages - // specified as a list of files (incl. the oracle's tests). - _, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build) + // This will not work for ad-hoc packages + // such as $GOROOT/src/net/http/triv.go. + // TODO(adonovan): ensure we report a clear error. + _, importPath, err := guessImportPath(filename, conf.Build) if err != nil { - return // can't find GOPATH dir + return err // can't find GOPATH dir } if importPath == "" { - return + return fmt.Errorf("can't guess import path from %s", filename) } // Check that it's possible to load the queried package. @@ -309,180 +198,77 @@ func reduceScope(pos string, conf *loader.Config) { cfg2.CgoEnabled = false bp, err := cfg2.Import(importPath, "", 0) if err != nil { - return // no files for package + return err // no files for package } - // Check that the queried file appears in the package: - // it might be a '// +build ignore' from an ad-hoc main - // package, e.g. $GOROOT/src/net/http/triv.go. - if !pkgContainsFile(bp, fqpos.fset.File(fqpos.start).Name()) { - return // not found + switch pkgContainsFile(bp, filename) { + case 'T': + conf.ImportWithTests(importPath) + case 'X': + conf.ImportWithTests(importPath) + importPath += "_test" // for TypeCheckFuncBodies + case 'G': + conf.Import(importPath) + default: + return fmt.Errorf("package %q doesn't contain file %s", + importPath, filename) } conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } - // Ignore packages specified on command line. - conf.CreatePkgs = nil - conf.ImportPkgs = nil - - // Instead load just the one containing the query position - // (and possibly its corresponding tests/production code). - // TODO(adonovan): set 'augment' based on which file list - // contains - conf.ImportWithTests(importPath) + return nil } -func pkgContainsFile(bp *build.Package, filename string) bool { - for _, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} { +// pkgContainsFile reports whether file was among the packages Go +// files, Test files, eXternal test files, or not found. +func pkgContainsFile(bp *build.Package, filename string) byte { + for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} { for _, file := range files { - if sameFile(file, filename) { - return true + if sameFile(filepath.Join(bp.Dir, file), filename) { + return "GTX"[i] } } } - return false + return 0 // not found } -// New constructs a new Oracle that can be used for a sequence of queries. -// -// iprog specifies the program to analyze. -// ptalog is the (optional) pointer-analysis log file. -// reflection determines whether to model reflection soundly (currently slow). -// -func New(iprog *loader.Program, ptalog io.Writer, reflection bool) (*Oracle, error) { - return newOracle(iprog, ptalog, needAll, reflection) -} - -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 -} - -// Query runs the query of the specified mode and selection. -// -// TODO(adonovan): fix: this function does not currently support the -// "what" query, which needs to access the go/build.Context. -// -func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) { - minfo := findMode(mode) - if minfo == nil { - return nil, fmt.Errorf("invalid mode type: %q", mode) - } - return o.query(minfo, qpos) -} - -func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) { - // Clear out residue of previous query (for long-running clients). - o.ptaConfig.Queries = nil - o.ptaConfig.IndirectQueries = nil - - res := &Result{ - mode: minfo.name, - fset: o.fset, - } - var err error - res.q, err = minfo.impl(o, qpos) - if err != nil { - return nil, err - } - return res, nil -} - -// ParseQueryPos parses the source query position pos. +// 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(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) { +func parseQueryPos(lprog *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) + start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset) if err != nil { return nil, err } - info, path, exact := iprog.PathEnclosingInterval(start, end) + 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{iprog.Fset, start, end, path, exact, info}, nil -} - -// WriteTo writes the oracle query result res to out in a compiler diagnostic format. -func (res *Result) WriteTo(out io.Writer) { - printf := func(pos interface{}, format string, args ...interface{}) { - fprintf(out, res.fset, pos, format, args...) - } - res.q.display(printf) - - // Print warnings after the main output. - if res.warnings != nil { - fmt.Fprintln(out, "\nPointer analysis warnings:") - for _, w := range res.warnings { - printf(w.Pos, "warning: "+w.Message) - } - } + return &queryPos{lprog.Fset, start, end, path, exact, info}, nil } // ---------- Utilities ---------- -// buildSSA constructs the SSA representation of Go-source function bodies. -// Not needed in simpler modes, e.g. freevars. -// -func buildSSA(o *Oracle) { - o.prog.BuildAll() +// allowErrors causes type errors to be silently ignored. +// (Not suitable if SSA construction follows.) +func allowErrors(lconf *loader.Config) { + lconf.AllowErrors = true + lconf.TypeChecker.Error = func(err error) {} } // ptrAnalysis runs the pointer analysis and returns its result. -func ptrAnalysis(o *Oracle) *pointer.Result { - result, err := pointer.Analyze(&o.ptaConfig) +func ptrAnalysis(conf *pointer.Config) *pointer.Result { + result, err := pointer.Analyze(conf) if err != nil { panic(err) // pointer analysis internal error } @@ -528,7 +314,7 @@ func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, a }: start = pos.Pos() end = start - case *QueryPos: + case *queryPos: start = pos.start end = pos.end case nil: diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index fe43fc28..977d075b 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -44,7 +44,6 @@ import ( "strings" "testing" - "golang.org/x/tools/go/loader" "golang.org/x/tools/oracle" ) @@ -151,9 +150,9 @@ func parseQueries(t *testing.T, filename string) []*query { } // WriteResult writes res (-format=plain) to w, stripping file locations. -func WriteResult(w io.Writer, res *oracle.Result) { +func WriteResult(w io.Writer, q *oracle.Query) { capture := new(bytes.Buffer) // capture standard output - res.WriteTo(capture) + q.WriteTo(capture) for _, line := range strings.Split(capture.String(), "\n") { // Remove a "file:line: " prefix. if i := strings.Index(line, ": "); i >= 0 { @@ -170,28 +169,30 @@ func doQuery(out io.Writer, q *query, useJson bool) { var buildContext = build.Default buildContext.GOPATH = "testdata" - res, err := oracle.Query([]string{q.filename}, - q.verb, - q.queryPos, - nil, // ptalog, - &buildContext, - true) // reflection - if err != nil { + query := oracle.Query{ + Mode: q.verb, + Pos: q.queryPos, + Build: &buildContext, + Scope: []string{q.filename}, + Reflection: true, + } + if err := oracle.Run(&query); err != nil { fmt.Fprintf(out, "\nError: %s\n", err) return } if useJson { // JSON output - b, err := json.MarshalIndent(res.Serial(), "", "\t") + b, err := json.MarshalIndent(query.Serial(), "", "\t") if err != nil { fmt.Fprintf(out, "JSON error: %s\n", err.Error()) return } out.Write(b) + fmt.Fprintln(out) } else { // "plain" (compiler diagnostic format) output - WriteResult(out, res) + WriteResult(out, &query) } } @@ -202,32 +203,29 @@ func TestOracle(t *testing.T) { } for _, filename := range []string{ - "testdata/src/main/calls.go", - "testdata/src/main/callgraph.go", - "testdata/src/main/callgraph2.go", - "testdata/src/main/describe.go", - "testdata/src/main/freevars.go", - "testdata/src/main/implements.go", - "testdata/src/main/implements-methods.go", - "testdata/src/main/imports.go", - "testdata/src/main/peers.go", - "testdata/src/main/pointsto.go", - "testdata/src/main/reflection.go", - "testdata/src/main/what.go", - "testdata/src/main/whicherrs.go", + "testdata/src/calls/main.go", + "testdata/src/describe/main.go", + "testdata/src/freevars/main.go", + "testdata/src/implements/main.go", + "testdata/src/implements-methods/main.go", + "testdata/src/imports/main.go", + "testdata/src/peers/main.go", + "testdata/src/pointsto/main.go", + "testdata/src/reflection/main.go", + "testdata/src/what/main.go", + "testdata/src/whicherrs/main.go", // JSON: // TODO(adonovan): most of these are very similar; combine them. - "testdata/src/main/callgraph-json.go", - "testdata/src/main/calls-json.go", - "testdata/src/main/peers-json.go", - "testdata/src/main/describe-json.go", - "testdata/src/main/implements-json.go", - "testdata/src/main/implements-methods-json.go", - "testdata/src/main/pointsto-json.go", - "testdata/src/main/referrers-json.go", - "testdata/src/main/what-json.go", + "testdata/src/calls-json/main.go", + "testdata/src/peers-json/main.go", + "testdata/src/describe-json/main.go", + "testdata/src/implements-json/main.go", + "testdata/src/implements-methods-json/main.go", + "testdata/src/pointsto-json/main.go", + "testdata/src/referrers-json/main.go", + "testdata/src/what-json/main.go", } { - useJson := strings.HasSuffix(filename, "-json.go") + useJson := strings.Contains(filename, "-json/") queries := parseQueries(t, filename) golden := filename + "lden" got := filename + "t" @@ -269,54 +267,3 @@ func TestOracle(t *testing.T) { } } } - -func TestMultipleQueries(t *testing.T) { - // Loader - var buildContext = build.Default - buildContext.GOPATH = "testdata" - conf := loader.Config{Build: &buildContext} - filename := "testdata/src/main/multi.go" - conf.CreateFromFilenames("", filename) - iprog, err := conf.Load() - if err != nil { - t.Fatalf("Load failed: %s", err) - } - - // Oracle - o, err := oracle.New(iprog, nil, true) - if err != nil { - t.Fatalf("oracle.New failed: %s", err) - } - - // QueryPos - pos := filename + ":#54,#58" - qpos, err := oracle.ParseQueryPos(iprog, pos, true) - if err != nil { - t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err) - } - // SSA is built and we have the QueryPos. - // Release the other ASTs and type info to the GC. - iprog = nil - - // Run different query modes on same scope and selection. - out := new(bytes.Buffer) - for _, mode := range [...]string{"callers", "describe", "freevars"} { - res, err := o.Query(mode, qpos) - if err != nil { - t.Errorf("(*oracle.Oracle).Query(%q) failed: %s", pos, err) - } - WriteResult(out, res) - } - want := `multi.f is called from these 1 sites: - static function call from multi.main - -function call (or conversion) of type () - -Free identifiers: -var x int - -` - if got := out.String(); got != want { - t.Errorf("Query output differs; want <<%s>>, got <<%s>>\n", want, got) - } -} diff --git a/oracle/peers.go b/oracle/peers.go index bcff11c1..bcf56bcd 100644 --- a/oracle/peers.go +++ b/oracle/peers.go @@ -10,6 +10,7 @@ import ( "go/token" "sort" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" @@ -22,20 +23,51 @@ import ( // TODO(adonovan): support reflect.{Select,Recv,Send,Close}. // TODO(adonovan): permit the user to query based on a MakeChan (not send/recv), // or the implicit receive in "for v := range ch". -func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { - opPos := findOp(qpos) - if opPos == token.NoPos { - return nil, fmt.Errorf("there is no channel operation here") +func peers(q *Query) error { + lconf := loader.Config{Build: q.Build} + + // Determine initial packages for PTA. + args, err := lconf.FromArgs(q.Scope, true) + if err != nil { + return err + } + if len(args) > 0 { + return fmt.Errorf("surplus arguments: %q", args) } - buildSSA(o) + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, false) + if err != nil { + return err + } + + prog := ssa.Create(lprog, ssa.GlobalDebug) + + ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) + if err != nil { + return err + } + + opPos := findOp(qpos) + if opPos == token.NoPos { + return fmt.Errorf("there is no channel operation here") + } + + // Defer SSA construction till after errors are reported. + prog.BuildAll() var queryOp chanOp // the originating send or receive operation var ops []chanOp // all sends/receives of opposite direction // Look at all channel operations in the whole ssa.Program. // Build a list of those of same type as the query. - allFuncs := ssautil.AllFunctions(o.prog) + allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { @@ -49,7 +81,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { } } if queryOp.ch == nil { - return nil, fmt.Errorf("ssa.Instruction for send/receive not found") + return fmt.Errorf("ssa.Instruction for send/receive not found") } // Discard operations of wrong channel element type. @@ -58,11 +90,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { // ignore both directionality and type names. queryType := queryOp.ch.Type() queryElemType := queryType.Underlying().(*types.Chan).Elem() - o.ptaConfig.AddQuery(queryOp.ch) + ptaConfig.AddQuery(queryOp.ch) i := 0 for _, op := range ops { if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { - o.ptaConfig.AddQuery(op.ch) + ptaConfig.AddQuery(op.ch) ops[i] = op i++ } @@ -70,7 +102,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { ops = ops[:i] // Run the pointer analysis. - ptares := ptrAnalysis(o) + ptares := ptrAnalysis(ptaConfig) // Find the points-to set. queryChanPtr := ptares.Queries[queryOp.ch] @@ -100,14 +132,15 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { sort.Sort(byPos(receives)) sort.Sort(byPos(closes)) - return &peersResult{ + q.result = &peersResult{ queryPos: opPos, queryType: queryType, makes: makes, sends: sends, receives: receives, closes: closes, - }, nil + } + return nil } // findOp returns the position of the enclosing send/receive/close op. @@ -115,7 +148,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { // for close operations, it's the Lparen of the function call. // // TODO(adonovan): handle implicit receive operations from 'for...range chan' statements. -func findOp(qpos *QueryPos) token.Pos { +func findOp(qpos *queryPos) token.Pos { for _, n := range qpos.path { switch n := n.(type) { case *ast.UnaryExpr: diff --git a/oracle/pointsto.go b/oracle/pointsto.go index 10ad0695..949554aa 100644 --- a/oracle/pointsto.go +++ b/oracle/pointsto.go @@ -25,10 +25,40 @@ import ( // // All printed sets are sorted to ensure determinism. // -func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) { +func pointsto(q *Query) error { + lconf := loader.Config{Build: q.Build} + + // Determine initial packages for PTA. + args, err := lconf.FromArgs(q.Scope, true) + if err != nil { + return err + } + if len(args) > 0 { + return fmt.Errorf("surplus arguments: %q", args) + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos + if err != nil { + return err + } + + prog := ssa.Create(lprog, ssa.GlobalDebug) + + ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) + if err != nil { + return err + } + path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { - return nil, fmt.Errorf("pointer analysis wants an expression; got %s", + return fmt.Errorf("pointer analysis wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } @@ -37,7 +67,7 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) { switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names - return nil, fmt.Errorf("multiple value specification") + return fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n @@ -45,41 +75,44 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) { expr = n default: // TODO(adonovan): is this reachable? - return nil, fmt.Errorf("unexpected AST for expr: %T", n) + return fmt.Errorf("unexpected AST for expr: %T", n) } // Reject non-pointerlike types (includes all constants---except nil). // TODO(adonovan): reject nil too. typ := qpos.info.TypeOf(expr) if !pointer.CanPoint(typ) { - return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) + return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) } // Determine the ssa.Value for the expression. var value ssa.Value var isAddr bool - var err error if obj != nil { // def/ref of func/var object - value, isAddr, err = ssaValueForIdent(o.prog, qpos.info, obj, path) + value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { - value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path) + value, isAddr, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { - return nil, err // e.g. trivially dead code + return err // e.g. trivially dead code } + // Defer SSA construction till after errors are reported. + prog.BuildAll() + // Run the pointer analysis. - ptrs, err := runPTA(o, value, isAddr) + ptrs, err := runPTA(ptaConfig, value, isAddr) if err != nil { - return nil, err // e.g. analytically unreachable + return err // e.g. analytically unreachable } - return &pointstoResult{ + q.result = &pointstoResult{ qpos: qpos, typ: typ, ptrs: ptrs, - }, nil + } + return nil } // ssaValueForIdent returns the ssa.Value for the ast.Ident whose path @@ -129,17 +162,15 @@ func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.No } // runPTA runs the pointer analysis of the selected SSA value or address. -func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) { - buildSSA(o) - +func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) { T := v.Type() if isAddr { - o.ptaConfig.AddIndirectQuery(v) + conf.AddIndirectQuery(v) T = deref(T) } else { - o.ptaConfig.AddQuery(v) + conf.AddQuery(v) } - ptares := ptrAnalysis(o) + ptares := ptrAnalysis(conf) var ptr pointer.Pointer if isAddr { @@ -177,7 +208,7 @@ type pointerResult struct { } type pointstoResult struct { - qpos *QueryPos + qpos *queryPos typ types.Type // type of expression ptrs []pointerResult // pointer info (typ is concrete => len==1) } @@ -188,17 +219,17 @@ func (r *pointstoResult) display(printf printfFunc) { // reflect.Value expression. if len(r.ptrs) > 0 { - printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.TypeString(r.typ)) + printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ)) for _, ptr := range r.ptrs { var obj types.Object if nt, ok := deref(ptr.typ).(*types.Named); ok { obj = nt.Obj() } if len(ptr.labels) > 0 { - printf(obj, "\t%s, may point to:", r.qpos.TypeString(ptr.typ)) + printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ)) printLabels(printf, ptr.labels, "\t\t") } else { - printf(obj, "\t%s", r.qpos.TypeString(ptr.typ)) + printf(obj, "\t%s", r.qpos.typeString(ptr.typ)) } } } else { @@ -208,11 +239,11 @@ func (r *pointstoResult) display(printf printfFunc) { // Show labels for other expressions. if ptr := r.ptrs[0]; len(ptr.labels) > 0 { printf(r.qpos, "this %s may point to these objects:", - r.qpos.TypeString(r.typ)) + r.qpos.typeString(r.typ)) printLabels(printf, ptr.labels, "\t") } else { printf(r.qpos, "this %s may not point to anything.", - r.qpos.TypeString(r.typ)) + r.qpos.typeString(r.typ)) } } } @@ -232,7 +263,7 @@ func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) { }) } pts = append(pts, serial.PointsTo{ - Type: r.qpos.TypeString(ptr.typ), + Type: r.qpos.typeString(ptr.typ), NamePos: namePos, Labels: labels, }) diff --git a/oracle/pos.go b/oracle/pos.go index d5d558b3..3c706f35 100644 --- a/oracle/pos.go +++ b/oracle/pos.go @@ -117,13 +117,7 @@ func sameFile(x, y string) bool { // fastQueryPos parses the -pos flag and returns a QueryPos. // It parses only a single file, and does not run the type checker. -// -// Caveat: the token.{FileSet,Pos} info it contains is not comparable -// with that from the oracle's FileSet! (We don't accept oracle.fset -// as a parameter because we don't want the same filename to appear -// multiple times in one FileSet.) -// -func fastQueryPos(posFlag string) (*QueryPos, error) { +func fastQueryPos(posFlag string) (*queryPos, error) { filename, startOffset, endOffset, err := parsePosFlag(posFlag) if err != nil { return nil, err @@ -145,5 +139,5 @@ func fastQueryPos(posFlag string) (*QueryPos, error) { return nil, fmt.Errorf("no syntax here") } - return &QueryPos{fset, start, end, path, exact, nil}, nil + return &queryPos{fset, start, end, path, exact, nil}, nil } diff --git a/oracle/referrers.go b/oracle/referrers.go index d54ffd49..af334af1 100644 --- a/oracle/referrers.go +++ b/oracle/referrers.go @@ -10,10 +10,13 @@ import ( "go/ast" "go/token" "io/ioutil" + "os" "sort" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" + "golang.org/x/tools/refactor/importgraph" ) // TODO(adonovan): use golang.org/x/tools/refactor/importgraph to choose @@ -21,36 +24,97 @@ import ( // Referrers reports all identifiers that resolve to the same object // as the queried identifier, within any package in the analysis scope. -// -func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) { - id, _ := qpos.path[0].(*ast.Ident) - if id == nil { - return nil, fmt.Errorf("no identifier here") +func referrers(q *Query) error { + lconf := loader.Config{Build: q.Build} + allowErrors(&lconf) + + if err := importQueryPackage(q.Pos, &lconf); err != nil { + return err } - obj := qpos.info.ObjectOf(id) - if obj == nil { - // Happens for y in "switch y := x.(type)", but I think that's all. - return nil, fmt.Errorf("no object for identifier") + var id *ast.Ident + var obj types.Object + var lprog *loader.Program + var pass2 bool + for { + // Load/parse/type-check the program. + var err error + lprog, err = lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, false) + if err != nil { + return err + } + + id, _ = qpos.path[0].(*ast.Ident) + if id == nil { + return fmt.Errorf("no identifier here") + } + + obj = qpos.info.ObjectOf(id) + if obj == nil { + // Happens for y in "switch y := x.(type)", but I think that's all. + return fmt.Errorf("no object for identifier") + } + + // If the identifier is exported, we must load all packages that + // depend transitively upon the package that defines it. + // + // TODO(adonovan): opt: skip this step if obj.Pkg() is a test or + // main package. + if pass2 || !obj.Exported() { + break + } + + // Scan the workspace and build the import graph. + _, rev, errors := importgraph.Build(q.Build) + if len(errors) > 0 { + for path, err := range errors { + fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) + } + return fmt.Errorf("failed to scan import graph for workspace") + } + + // Re-load the larger program. + // Create a new file set so that ... + // External test packages are never imported, + // so they will never appear in the graph. + // (We must reset the Config here, not just reset the Fset field.) + lconf = loader.Config{ + Fset: token.NewFileSet(), + Build: q.Build, + } + allowErrors(&lconf) + for path := range rev.Search(obj.Pkg().Path()) { + lconf.Import(path) + } + pass2 = true } // Iterate over all go/types' Uses facts for the entire program. var refs []*ast.Ident - for _, info := range o.typeInfo { + for _, info := range lprog.AllPackages { for id2, obj2 := range info.Uses { if sameObj(obj, obj2) { refs = append(refs, id2) } } } + // TODO(adonovan): is this sort stable? Pos order depends on + // when packages are reached. Use filename order? sort.Sort(byNamePos(refs)) - return &referrersResult{ - qpos: qpos, + q.result = &referrersResult{ + fset: q.Fset, query: id, obj: obj, refs: refs, - }, nil + } + return nil } // same reports whether x and y are identical, or both are PkgNames @@ -77,7 +141,7 @@ func (p byNamePos) Less(i, j int) bool { return p[i].NamePos < p[j].NamePos } func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] } type referrersResult struct { - qpos *QueryPos + fset *token.FileSet query *ast.Ident // identifier of query obj types.Object // object it denotes refs []*ast.Ident // set of all other references to it @@ -97,7 +161,7 @@ func (r *referrersResult) display(printf printfFunc) { // First pass: start the file reads concurrently. for _, ref := range r.refs { - posn := r.qpos.fset.Position(ref.Pos()) + posn := r.fset.Position(ref.Pos()) fi := fileinfosByName[posn.Filename] if fi == nil { fi = &fileinfo{data: make(chan []byte)} diff --git a/oracle/serial/serial.go b/oracle/serial/serial.go index 86ccb02c..65f0822a 100644 --- a/oracle/serial/serial.go +++ b/oracle/serial/serial.go @@ -64,20 +64,6 @@ type Caller struct { Caller string `json:"caller"` // full name of calling function } -// A CallGraph is one element of the slice returned by a 'callgraph' query. -// The index of each item in the slice is used to identify it in the -// Callers adjacency list. -// -// Multiple nodes may have the same Name due to context-sensitive -// treatment of some functions. -// -// TODO(adonovan): perhaps include edge labels (i.e. callsites). -type CallGraph struct { - Name string `json:"name"` // full name of function - Pos string `json:"pos"` // location of function - Children []int `json:"children,omitempty"` // indices of child nodes in callgraph list -} - // A CallStack is the result of a 'callstack' query. // It indicates an arbitrary path from the root of the callgraph to // the query function. @@ -232,11 +218,6 @@ type Describe struct { Value *DescribeValue `json:"value,omitempty"` } -type PTAWarning struct { - Pos string `json:"pos"` // location associated with warning - Message string `json:"message"` // warning message -} - // A WhichErrs is the result of a 'whicherrs' query. // It contains the position of the queried error and the possible globals, // constants, and types it may point to. @@ -264,7 +245,6 @@ type Result struct { // the one specified by 'mode'. Callees *Callees `json:"callees,omitempty"` Callers []Caller `json:"callers,omitempty"` - Callgraph []CallGraph `json:"callgraph,omitempty"` Callstack *CallStack `json:"callstack,omitempty"` Definition *Definition `json:"definition,omitempty"` Describe *Describe `json:"describe,omitempty"` @@ -275,6 +255,4 @@ type Result struct { Referrers *Referrers `json:"referrers,omitempty"` What *What `json:"what,omitempty"` WhichErrs *WhichErrs `json:"whicherrs,omitempty"` - - Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis } diff --git a/oracle/testdata/src/main/calls-json.go b/oracle/testdata/src/calls-json/main.go similarity index 100% rename from oracle/testdata/src/main/calls-json.go rename to oracle/testdata/src/calls-json/main.go diff --git a/oracle/testdata/src/main/calls-json.golden b/oracle/testdata/src/calls-json/main.golden similarity index 57% rename from oracle/testdata/src/main/calls-json.golden rename to oracle/testdata/src/calls-json/main.golden index 435db7eb..f5eced6b 100644 --- a/oracle/testdata/src/main/calls-json.golden +++ b/oracle/testdata/src/calls-json/main.golden @@ -2,32 +2,33 @@ { "mode": "callees", "callees": { - "pos": "testdata/src/main/calls-json.go:8:3", + "pos": "testdata/src/calls-json/main.go:8:3", "desc": "dynamic function call", "callees": [ { "name": "main.main$1", - "pos": "testdata/src/main/calls-json.go:12:7" + "pos": "testdata/src/calls-json/main.go:12:7" } ] } -}-------- @callstack callstack-main.anon -------- +} +-------- @callstack callstack-main.anon -------- { "mode": "callstack", "callstack": { - "pos": "testdata/src/main/calls-json.go:12:7", + "pos": "testdata/src/calls-json/main.go:12:7", "target": "main.main$1", "callers": [ { - "pos": "testdata/src/main/calls-json.go:8:3", + "pos": "testdata/src/calls-json/main.go:8:3", "desc": "dynamic function call", "caller": "main.call" }, { - "pos": "testdata/src/main/calls-json.go:12:6", + "pos": "testdata/src/calls-json/main.go:12:6", "desc": "static function call", "caller": "main.main" } ] } -} \ No newline at end of file +} diff --git a/oracle/testdata/src/main/calls.go b/oracle/testdata/src/calls/main.go similarity index 100% rename from oracle/testdata/src/main/calls.go rename to oracle/testdata/src/calls/main.go diff --git a/oracle/testdata/src/main/calls.golden b/oracle/testdata/src/calls/main.golden similarity index 100% rename from oracle/testdata/src/main/calls.golden rename to oracle/testdata/src/calls/main.golden diff --git a/oracle/testdata/src/main/describe-json.go b/oracle/testdata/src/describe-json/main.go similarity index 96% rename from oracle/testdata/src/main/describe-json.go rename to oracle/testdata/src/describe-json/main.go index 1f22d019..359c7310 100644 --- a/oracle/testdata/src/main/describe-json.go +++ b/oracle/testdata/src/describe-json/main.go @@ -4,7 +4,7 @@ package describe // @describe pkgdecl "describe" // See go.tools/oracle/oracle_test.go for explanation. // See describe-json.golden for expected query results. -func main() { // +func main() { var s struct{ x [3]int } p := &s.x[0] // @describe desc-val-p "p" _ = p diff --git a/oracle/testdata/src/main/describe-json.golden b/oracle/testdata/src/describe-json/main.golden similarity index 52% rename from oracle/testdata/src/main/describe-json.golden rename to oracle/testdata/src/describe-json/main.golden index 8baf837e..9d03661a 100644 --- a/oracle/testdata/src/main/describe-json.golden +++ b/oracle/testdata/src/describe-json/main.golden @@ -2,106 +2,110 @@ { "mode": "describe", "describe": { - "desc": "definition of package \"describe\"", - "pos": "testdata/src/main/describe-json.go:1:9", + "desc": "definition of package \"describe-json\"", + "pos": "testdata/src/describe-json/main.go:1:9", "detail": "package", "package": { - "path": "describe", + "path": "describe-json", "members": [ { "name": "C", "type": "int", - "pos": "testdata/src/main/describe-json.go:25:6", + "pos": "testdata/src/describe-json/main.go:25:6", "kind": "type", "methods": [ { "name": "method (C) f()", - "pos": "testdata/src/main/describe-json.go:28:12" + "pos": "testdata/src/describe-json/main.go:28:12" } ] }, { "name": "D", "type": "struct{}", - "pos": "testdata/src/main/describe-json.go:26:6", + "pos": "testdata/src/describe-json/main.go:26:6", "kind": "type", "methods": [ { "name": "method (*D) f()", - "pos": "testdata/src/main/describe-json.go:29:13" + "pos": "testdata/src/describe-json/main.go:29:13" } ] }, { "name": "I", "type": "interface{f()}", - "pos": "testdata/src/main/describe-json.go:21:6", + "pos": "testdata/src/describe-json/main.go:21:6", "kind": "type", "methods": [ { "name": "method (I) f()", - "pos": "testdata/src/main/describe-json.go:22:2" + "pos": "testdata/src/describe-json/main.go:22:2" } ] }, { "name": "main", "type": "func()", - "pos": "testdata/src/main/describe-json.go:7:6", + "pos": "testdata/src/describe-json/main.go:7:6", "kind": "func" } ] } } -}-------- @describe desc-val-p -------- +} +-------- @describe desc-val-p -------- { "mode": "describe", "describe": { "desc": "identifier", - "pos": "testdata/src/main/describe-json.go:9:2", + "pos": "testdata/src/describe-json/main.go:9:2", "detail": "value", "value": { "type": "*int", - "objpos": "testdata/src/main/describe-json.go:9:2" + "objpos": "testdata/src/describe-json/main.go:9:2" } } -}-------- @describe desc-val-i -------- +} +-------- @describe desc-val-i -------- { "mode": "describe", "describe": { "desc": "identifier", - "pos": "testdata/src/main/describe-json.go:16:8", + "pos": "testdata/src/describe-json/main.go:16:8", "detail": "value", "value": { "type": "I", - "objpos": "testdata/src/main/describe-json.go:12:6" + "objpos": "testdata/src/describe-json/main.go:12:6" } } -}-------- @describe desc-stmt -------- +} +-------- @describe desc-stmt -------- { "mode": "describe", "describe": { "desc": "go statement", - "pos": "testdata/src/main/describe-json.go:18:2", + "pos": "testdata/src/describe-json/main.go:18:2", "detail": "unknown" } -}-------- @describe desc-type-C -------- +} +-------- @describe desc-type-C -------- { "mode": "describe", "describe": { "desc": "definition of type C (size 8, align 8)", - "pos": "testdata/src/main/describe-json.go:25:6", + "pos": "testdata/src/describe-json/main.go:25:6", "detail": "type", "type": { "type": "C", - "namepos": "testdata/src/main/describe-json.go:25:6", + "namepos": "testdata/src/describe-json/main.go:25:6", "namedef": "int", "methods": [ { "name": "method (C) f()", - "pos": "testdata/src/main/describe-json.go:28:12" + "pos": "testdata/src/describe-json/main.go:28:12" } ] } } -} \ No newline at end of file +} diff --git a/oracle/testdata/src/main/describe.go b/oracle/testdata/src/describe/main.go similarity index 100% rename from oracle/testdata/src/main/describe.go rename to oracle/testdata/src/describe/main.go diff --git a/oracle/testdata/src/main/describe.golden b/oracle/testdata/src/describe/main.golden similarity index 100% rename from oracle/testdata/src/main/describe.golden rename to oracle/testdata/src/describe/main.golden diff --git a/oracle/testdata/src/main/freevars.go b/oracle/testdata/src/freevars/main.go similarity index 100% rename from oracle/testdata/src/main/freevars.go rename to oracle/testdata/src/freevars/main.go diff --git a/oracle/testdata/src/main/freevars.golden b/oracle/testdata/src/freevars/main.golden similarity index 100% rename from oracle/testdata/src/main/freevars.golden rename to oracle/testdata/src/freevars/main.golden diff --git a/oracle/testdata/src/main/implements-json.go b/oracle/testdata/src/implements-json/main.go similarity index 100% rename from oracle/testdata/src/main/implements-json.go rename to oracle/testdata/src/implements-json/main.go diff --git a/oracle/testdata/src/implements-json/main.golden b/oracle/testdata/src/implements-json/main.golden new file mode 100644 index 00000000..7e37f9e0 --- /dev/null +++ b/oracle/testdata/src/implements-json/main.golden @@ -0,0 +1,159 @@ +-------- @implements E -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-json.E", + "pos": "testdata/src/implements-json/main.go:10:6", + "kind": "interface" + } + } +} +-------- @implements F -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + }, + "to": [ + { + "name": "*implements-json.C", + "pos": "testdata/src/implements-json/main.go:21:6", + "kind": "pointer" + }, + { + "name": "implements-json.D", + "pos": "testdata/src/implements-json/main.go:22:6", + "kind": "struct" + }, + { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + } + ] + } +} +-------- @implements FG -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + }, + "to": [ + { + "name": "*implements-json.D", + "pos": "testdata/src/implements-json/main.go:22:6", + "kind": "pointer" + } + ], + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ] + } +} +-------- @implements slice -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "[]int", + "pos": "-", + "kind": "slice" + } + } +} +-------- @implements C -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-json.C", + "pos": "testdata/src/implements-json/main.go:21:6", + "kind": "basic" + }, + "fromptr": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ] + } +} +-------- @implements starC -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "*implements-json.C", + "pos": "testdata/src/implements-json/main.go:21:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ] + } +} +-------- @implements D -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-json.D", + "pos": "testdata/src/implements-json/main.go:22:6", + "kind": "struct" + }, + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ], + "fromptr": [ + { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + } + ] + } +} +-------- @implements starD -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "*implements-json.D", + "pos": "testdata/src/implements-json/main.go:22:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + }, + { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + } + ] + } +} diff --git a/oracle/testdata/src/main/implements-methods-json.go b/oracle/testdata/src/implements-methods-json/main.go similarity index 100% rename from oracle/testdata/src/main/implements-methods-json.go rename to oracle/testdata/src/implements-methods-json/main.go diff --git a/oracle/testdata/src/implements-methods-json/main.golden b/oracle/testdata/src/implements-methods-json/main.golden new file mode 100644 index 00000000..fa117df4 --- /dev/null +++ b/oracle/testdata/src/implements-methods-json/main.golden @@ -0,0 +1,290 @@ +-------- @implements F.f -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + }, + "to": [ + { + "name": "*implements-methods-json.C", + "pos": "testdata/src/implements-methods-json/main.go:21:6", + "kind": "pointer" + }, + { + "name": "implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "struct" + }, + { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (F).f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + }, + "to_method": [ + { + "name": "method (*C) f()", + "pos": "testdata/src/implements-methods-json/main.go:24:13" + }, + { + "name": "method (D) f()", + "pos": "testdata/src/implements-methods-json/main.go:25:12" + }, + { + "name": "method (FG) f()", + "pos": "testdata/src/implements-methods-json/main.go:17:2" + } + ] + } +} +-------- @implements FG.f -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + }, + "to": [ + { + "name": "*implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "pointer" + } + ], + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "method": { + "name": "func (FG).f()", + "pos": "testdata/src/implements-methods-json/main.go:17:2" + }, + "to_method": [ + { + "name": "method (*D) f()", + "pos": "testdata/src/implements-methods-json/main.go:25:12" + } + ], + "from_method": [ + { + "name": "method (F) f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + } + ] + } +} +-------- @implements FG.g -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + }, + "to": [ + { + "name": "*implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "pointer" + } + ], + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "method": { + "name": "func (FG).g() []int", + "pos": "testdata/src/implements-methods-json/main.go:18:2" + }, + "to_method": [ + { + "name": "method (*D) g() []int", + "pos": "testdata/src/implements-methods-json/main.go:27:13" + } + ], + "from_method": [ + { + "name": "", + "pos": "" + } + ] + } +} +-------- @implements *C.f -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "*implements-methods-json.C", + "pos": "testdata/src/implements-methods-json/main.go:21:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "method": { + "name": "func (*C).f()", + "pos": "testdata/src/implements-methods-json/main.go:24:13" + }, + "from_method": [ + { + "name": "method (F) f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + } + ] + } +} +-------- @implements D.f -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "struct" + }, + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "fromptr": [ + { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (D).f()", + "pos": "testdata/src/implements-methods-json/main.go:25:12" + }, + "from_method": [ + { + "name": "method (F) f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + } + ], + "fromptr_method": [ + { + "name": "method (FG) f()", + "pos": "testdata/src/implements-methods-json/main.go:17:2" + } + ] + } +} +-------- @implements *D.g -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "*implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + }, + { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (*D).g() []int", + "pos": "testdata/src/implements-methods-json/main.go:27:13" + }, + "from_method": [ + { + "name": "", + "pos": "" + }, + { + "name": "method (FG) g() []int", + "pos": "testdata/src/implements-methods-json/main.go:18:2" + } + ] + } +} +-------- @implements Len -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-methods-json.sorter", + "pos": "testdata/src/implements-methods-json/main.go:29:6", + "kind": "slice" + }, + "from": [ + { + "name": "lib.Sorter", + "pos": "testdata/src/lib/lib.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (sorter).Len() int", + "pos": "testdata/src/implements-methods-json/main.go:31:15" + }, + "from_method": [ + { + "name": "method (lib.Sorter) Len() int", + "pos": "testdata/src/lib/lib.go:17:2" + } + ] + } +} +-------- @implements I.Method -------- +{ + "mode": "implements", + "implements": { + "type": { + "name": "implements-methods-json.I", + "pos": "testdata/src/implements-methods-json/main.go:35:6", + "kind": "interface" + }, + "to": [ + { + "name": "lib.Type", + "pos": "testdata/src/lib/lib.go:3:6", + "kind": "basic" + } + ], + "method": { + "name": "func (I).Method(*int) *int", + "pos": "testdata/src/implements-methods-json/main.go:36:2" + }, + "to_method": [ + { + "name": "method (lib.Type) Method(x *int) *int", + "pos": "testdata/src/lib/lib.go:5:13" + } + ] + } +} diff --git a/oracle/testdata/src/main/implements-methods.go b/oracle/testdata/src/implements-methods/main.go similarity index 100% rename from oracle/testdata/src/main/implements-methods.go rename to oracle/testdata/src/implements-methods/main.go diff --git a/oracle/testdata/src/main/implements-methods.golden b/oracle/testdata/src/implements-methods/main.golden similarity index 100% rename from oracle/testdata/src/main/implements-methods.golden rename to oracle/testdata/src/implements-methods/main.golden diff --git a/oracle/testdata/src/main/implements.go b/oracle/testdata/src/implements/main.go similarity index 100% rename from oracle/testdata/src/main/implements.go rename to oracle/testdata/src/implements/main.go diff --git a/oracle/testdata/src/implements/main.golden b/oracle/testdata/src/implements/main.golden new file mode 100644 index 00000000..99dfe6d6 --- /dev/null +++ b/oracle/testdata/src/implements/main.golden @@ -0,0 +1,44 @@ +-------- @implements E -------- +empty interface type implements.E + +-------- @implements F -------- +interface type implements.F + is implemented by pointer type *implements.C + is implemented by struct type implements.D + is implemented by interface type implements.FG + +-------- @implements FG -------- +interface type implements.FG + is implemented by pointer type *implements.D + implements implements.F + +-------- @implements slice -------- +slice type []int implements only interface{} + +-------- @implements C -------- +pointer type *implements.C + implements implements.F + +-------- @implements starC -------- +pointer type *implements.C + implements implements.F + +-------- @implements D -------- +struct type implements.D + implements implements.F +pointer type *implements.D + implements implements.FG + +-------- @implements starD -------- +pointer type *implements.D + implements implements.F + implements implements.FG + +-------- @implements sorter -------- +slice type implements.sorter + implements lib.Sorter + +-------- @implements I -------- +interface type implements.I + is implemented by basic type lib.Type + diff --git a/oracle/testdata/src/main/imports.go b/oracle/testdata/src/imports/main.go similarity index 98% rename from oracle/testdata/src/main/imports.go rename to oracle/testdata/src/imports/main.go index 2f5ffa43..0f616abe 100644 --- a/oracle/testdata/src/main/imports.go +++ b/oracle/testdata/src/imports/main.go @@ -1,4 +1,4 @@ -package imports +package main import ( "hash/fnv" // @describe ref-pkg-import2 "fnv" diff --git a/oracle/testdata/src/main/imports.golden b/oracle/testdata/src/imports/main.golden similarity index 99% rename from oracle/testdata/src/main/imports.golden rename to oracle/testdata/src/imports/main.golden index 1994ad62..91442106 100644 --- a/oracle/testdata/src/main/imports.golden +++ b/oracle/testdata/src/imports/main.golden @@ -41,7 +41,7 @@ defined here -------- @pointsto p -------- this *int may point to these objects: - imports.a + main.a -------- @describe ref-pkg -------- reference to package "lib" diff --git a/oracle/testdata/src/main/callgraph-json.go b/oracle/testdata/src/main/callgraph-json.go deleted file mode 100644 index 33708fd0..00000000 --- a/oracle/testdata/src/main/callgraph-json.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -// Tests of call-graph queries, -format=json. -// See go.tools/oracle/oracle_test.go for explanation. -// See callgraph-json.golden for expected query results. - -func A() {} - -func B() {} - -// call is not (yet) treated context-sensitively. -func call(f func()) { - f() -} - -// nop *is* treated context-sensitively. -func nop() {} - -func call2(f func()) { - f() - f() -} - -func main() { - call(A) - call(B) - - nop() - nop() - - call2(func() { - // called twice from main.call2, - // but call2 is not context sensitive (yet). - }) - - print("builtin") - _ = string("type conversion") - call(nil) - if false { - main() - } - var nilFunc func() - nilFunc() - var i interface { - f() - } - i.f() -} - -func deadcode() { - main() -} - -// @callgraph callgraph "^" diff --git a/oracle/testdata/src/main/callgraph-json.golden b/oracle/testdata/src/main/callgraph-json.golden deleted file mode 100644 index 7d998707..00000000 --- a/oracle/testdata/src/main/callgraph-json.golden +++ /dev/null @@ -1,51 +0,0 @@ --------- @callgraph callgraph -------- -{ - "mode": "callgraph", - "callgraph": [ - { - "name": "main.main", - "pos": "testdata/src/main/callgraph-json.go:24:6", - "children": [ - 0, - 1, - 2, - 3 - ] - }, - { - "name": "main.call", - "pos": "testdata/src/main/callgraph-json.go:12:6", - "children": [ - 5, - 6 - ] - }, - { - "name": "main.nop", - "pos": "testdata/src/main/callgraph-json.go:17:6" - }, - { - "name": "main.call2", - "pos": "testdata/src/main/callgraph-json.go:19:6", - "children": [ - 7 - ] - }, - { - "name": "main.init", - "pos": "-" - }, - { - "name": "main.A", - "pos": "testdata/src/main/callgraph-json.go:7:6" - }, - { - "name": "main.B", - "pos": "testdata/src/main/callgraph-json.go:9:6" - }, - { - "name": "main.main$1", - "pos": "testdata/src/main/callgraph-json.go:31:8" - } - ] -} \ No newline at end of file diff --git a/oracle/testdata/src/main/callgraph.go b/oracle/testdata/src/main/callgraph.go deleted file mode 100644 index ee190df8..00000000 --- a/oracle/testdata/src/main/callgraph.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -// Tests of call-graph queries. -// See go.tools/oracle/oracle_test.go for explanation. -// See callgraph.golden for expected query results. - -import "lib" - -func A() {} - -func B() {} - -// call is not (yet) treated context-sensitively. -func call(f func()) { - f() -} - -// nop *is* treated context-sensitively. -func nop() {} - -func call2(f func()) { - f() - f() -} - -func main() { - call(A) - call(B) - - nop() - nop() - - call2(func() { - // called twice from main.call2, - // but call2 is not context sensitive (yet). - }) - - print("builtin") - _ = string("type conversion") - call(nil) - if false { - main() - } - var nilFunc func() - nilFunc() - var i interface { - f() - } - i.f() - - lib.Func() -} - -func deadcode() { - main() -} - -// @callgraph callgraph-main "^" - -// @callgraph callgraph-complete "nopos" diff --git a/oracle/testdata/src/main/callgraph.golden b/oracle/testdata/src/main/callgraph.golden deleted file mode 100644 index 697c8575..00000000 --- a/oracle/testdata/src/main/callgraph.golden +++ /dev/null @@ -1,37 +0,0 @@ --------- @callgraph callgraph-main -------- - -Below is a call graph of package main. -The numbered nodes form a spanning tree. -Non-numbered nodes indicate back- or cross-edges to the node whose - number follows in parentheses. - -0 init -1 main -2 call -3 A -4 B -5 call2 -6 main$1 - main (1) -7 nop - --------- @callgraph callgraph-complete -------- - -Below is a call graph of the entire program. -The numbered nodes form a spanning tree. -Non-numbered nodes indicate back- or cross-edges to the node whose - number follows in parentheses. - -0 -1 main.init -2 lib.init -3 main.main -4 lib.Func -5 main.call -6 main.A -7 main.B -8 main.call2 -9 main.main$1 - main.main (3) -10 main.nop - diff --git a/oracle/testdata/src/main/callgraph2.go b/oracle/testdata/src/main/callgraph2.go deleted file mode 100644 index 5da4c880..00000000 --- a/oracle/testdata/src/main/callgraph2.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -// Tests of call-graph queries. -// See go.tools/oracle/oracle_test.go for explanation. -// See callgraph2.golden for expected query results. - -// (Regression test for pointer analysis: programs that use reflection -// create some cgnodes before the root of the callgraph.) -import _ "reflect" - -func f() {} -func main() { - f() -} - -// @callgraph callgraph "^" diff --git a/oracle/testdata/src/main/callgraph2.golden b/oracle/testdata/src/main/callgraph2.golden deleted file mode 100644 index 1208b565..00000000 --- a/oracle/testdata/src/main/callgraph2.golden +++ /dev/null @@ -1,11 +0,0 @@ --------- @callgraph callgraph -------- - -Below is a call graph of package main. -The numbered nodes form a spanning tree. -Non-numbered nodes indicate back- or cross-edges to the node whose - number follows in parentheses. - -0 init -1 main -2 f - diff --git a/oracle/testdata/src/main/implements-json.golden b/oracle/testdata/src/main/implements-json.golden deleted file mode 100644 index d43969b9..00000000 --- a/oracle/testdata/src/main/implements-json.golden +++ /dev/null @@ -1,152 +0,0 @@ --------- @implements E -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.E", - "pos": "testdata/src/main/implements-json.go:10:6", - "kind": "interface" - } - } -}-------- @implements F -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.F", - "pos": "testdata/src/main/implements-json.go:12:6", - "kind": "interface" - }, - "to": [ - { - "name": "*main.C", - "pos": "testdata/src/main/implements-json.go:21:6", - "kind": "pointer" - }, - { - "name": "main.D", - "pos": "testdata/src/main/implements-json.go:22:6", - "kind": "struct" - }, - { - "name": "main.FG", - "pos": "testdata/src/main/implements-json.go:16:6", - "kind": "interface" - } - ] - } -}-------- @implements FG -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.FG", - "pos": "testdata/src/main/implements-json.go:16:6", - "kind": "interface" - }, - "to": [ - { - "name": "*main.D", - "pos": "testdata/src/main/implements-json.go:22:6", - "kind": "pointer" - } - ], - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-json.go:12:6", - "kind": "interface" - } - ] - } -}-------- @implements slice -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "[]int", - "pos": "-", - "kind": "slice" - } - } -}-------- @implements C -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.C", - "pos": "testdata/src/main/implements-json.go:21:6", - "kind": "basic" - }, - "fromptr": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-json.go:12:6", - "kind": "interface" - } - ] - } -}-------- @implements starC -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "*main.C", - "pos": "testdata/src/main/implements-json.go:21:6", - "kind": "pointer" - }, - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-json.go:12:6", - "kind": "interface" - } - ] - } -}-------- @implements D -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.D", - "pos": "testdata/src/main/implements-json.go:22:6", - "kind": "struct" - }, - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-json.go:12:6", - "kind": "interface" - } - ], - "fromptr": [ - { - "name": "main.FG", - "pos": "testdata/src/main/implements-json.go:16:6", - "kind": "interface" - } - ] - } -}-------- @implements starD -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "*main.D", - "pos": "testdata/src/main/implements-json.go:22:6", - "kind": "pointer" - }, - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-json.go:12:6", - "kind": "interface" - }, - { - "name": "main.FG", - "pos": "testdata/src/main/implements-json.go:16:6", - "kind": "interface" - } - ] - } -} \ No newline at end of file diff --git a/oracle/testdata/src/main/implements-methods-json.golden b/oracle/testdata/src/main/implements-methods-json.golden deleted file mode 100644 index 8a17efae..00000000 --- a/oracle/testdata/src/main/implements-methods-json.golden +++ /dev/null @@ -1,283 +0,0 @@ --------- @implements F.f -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.F", - "pos": "testdata/src/main/implements-methods-json.go:12:6", - "kind": "interface" - }, - "to": [ - { - "name": "*main.C", - "pos": "testdata/src/main/implements-methods-json.go:21:6", - "kind": "pointer" - }, - { - "name": "main.D", - "pos": "testdata/src/main/implements-methods-json.go:22:6", - "kind": "struct" - }, - { - "name": "main.FG", - "pos": "testdata/src/main/implements-methods-json.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (F).f()", - "pos": "testdata/src/main/implements-methods-json.go:13:2" - }, - "to_method": [ - { - "name": "method (*C) f()", - "pos": "testdata/src/main/implements-methods-json.go:24:13" - }, - { - "name": "method (D) f()", - "pos": "testdata/src/main/implements-methods-json.go:25:12" - }, - { - "name": "method (FG) f()", - "pos": "testdata/src/main/implements-methods-json.go:17:2" - } - ] - } -}-------- @implements FG.f -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.FG", - "pos": "testdata/src/main/implements-methods-json.go:16:6", - "kind": "interface" - }, - "to": [ - { - "name": "*main.D", - "pos": "testdata/src/main/implements-methods-json.go:22:6", - "kind": "pointer" - } - ], - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-methods-json.go:12:6", - "kind": "interface" - } - ], - "method": { - "name": "func (FG).f()", - "pos": "testdata/src/main/implements-methods-json.go:17:2" - }, - "to_method": [ - { - "name": "method (*D) f()", - "pos": "testdata/src/main/implements-methods-json.go:25:12" - } - ], - "from_method": [ - { - "name": "method (F) f()", - "pos": "testdata/src/main/implements-methods-json.go:13:2" - } - ] - } -}-------- @implements FG.g -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.FG", - "pos": "testdata/src/main/implements-methods-json.go:16:6", - "kind": "interface" - }, - "to": [ - { - "name": "*main.D", - "pos": "testdata/src/main/implements-methods-json.go:22:6", - "kind": "pointer" - } - ], - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-methods-json.go:12:6", - "kind": "interface" - } - ], - "method": { - "name": "func (FG).g() []int", - "pos": "testdata/src/main/implements-methods-json.go:18:2" - }, - "to_method": [ - { - "name": "method (*D) g() []int", - "pos": "testdata/src/main/implements-methods-json.go:27:13" - } - ], - "from_method": [ - { - "name": "", - "pos": "" - } - ] - } -}-------- @implements *C.f -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "*main.C", - "pos": "testdata/src/main/implements-methods-json.go:21:6", - "kind": "pointer" - }, - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-methods-json.go:12:6", - "kind": "interface" - } - ], - "method": { - "name": "func (*C).f()", - "pos": "testdata/src/main/implements-methods-json.go:24:13" - }, - "from_method": [ - { - "name": "method (F) f()", - "pos": "testdata/src/main/implements-methods-json.go:13:2" - } - ] - } -}-------- @implements D.f -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.D", - "pos": "testdata/src/main/implements-methods-json.go:22:6", - "kind": "struct" - }, - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-methods-json.go:12:6", - "kind": "interface" - } - ], - "fromptr": [ - { - "name": "main.FG", - "pos": "testdata/src/main/implements-methods-json.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (D).f()", - "pos": "testdata/src/main/implements-methods-json.go:25:12" - }, - "from_method": [ - { - "name": "method (F) f()", - "pos": "testdata/src/main/implements-methods-json.go:13:2" - } - ], - "fromptr_method": [ - { - "name": "method (FG) f()", - "pos": "testdata/src/main/implements-methods-json.go:17:2" - } - ] - } -}-------- @implements *D.g -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "*main.D", - "pos": "testdata/src/main/implements-methods-json.go:22:6", - "kind": "pointer" - }, - "from": [ - { - "name": "main.F", - "pos": "testdata/src/main/implements-methods-json.go:12:6", - "kind": "interface" - }, - { - "name": "main.FG", - "pos": "testdata/src/main/implements-methods-json.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (*D).g() []int", - "pos": "testdata/src/main/implements-methods-json.go:27:13" - }, - "from_method": [ - { - "name": "", - "pos": "" - }, - { - "name": "method (FG) g() []int", - "pos": "testdata/src/main/implements-methods-json.go:18:2" - } - ] - } -}-------- @implements Len -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.sorter", - "pos": "testdata/src/main/implements-methods-json.go:29:6", - "kind": "slice" - }, - "from": [ - { - "name": "lib.Sorter", - "pos": "testdata/src/lib/lib.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (sorter).Len() int", - "pos": "testdata/src/main/implements-methods-json.go:31:15" - }, - "from_method": [ - { - "name": "method (lib.Sorter) Len() int", - "pos": "testdata/src/lib/lib.go:17:2" - } - ] - } -}-------- @implements I.Method -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "main.I", - "pos": "testdata/src/main/implements-methods-json.go:35:6", - "kind": "interface" - }, - "to": [ - { - "name": "lib.Type", - "pos": "testdata/src/lib/lib.go:3:6", - "kind": "basic" - } - ], - "method": { - "name": "func (I).Method(*int) *int", - "pos": "testdata/src/main/implements-methods-json.go:36:2" - }, - "to_method": [ - { - "name": "method (lib.Type) Method(x *int) *int", - "pos": "testdata/src/lib/lib.go:5:13" - } - ] - } -} \ No newline at end of file diff --git a/oracle/testdata/src/main/implements.golden b/oracle/testdata/src/main/implements.golden deleted file mode 100644 index 9d5998ac..00000000 --- a/oracle/testdata/src/main/implements.golden +++ /dev/null @@ -1,44 +0,0 @@ --------- @implements E -------- -empty interface type main.E - --------- @implements F -------- -interface type main.F - is implemented by pointer type *main.C - is implemented by struct type main.D - is implemented by interface type main.FG - --------- @implements FG -------- -interface type main.FG - is implemented by pointer type *main.D - implements main.F - --------- @implements slice -------- -slice type []int implements only interface{} - --------- @implements C -------- -pointer type *main.C - implements main.F - --------- @implements starC -------- -pointer type *main.C - implements main.F - --------- @implements D -------- -struct type main.D - implements main.F -pointer type *main.D - implements main.FG - --------- @implements starD -------- -pointer type *main.D - implements main.F - implements main.FG - --------- @implements sorter -------- -slice type main.sorter - implements lib.Sorter - --------- @implements I -------- -interface type main.I - is implemented by basic type lib.Type - diff --git a/oracle/testdata/src/main/multi.go b/oracle/testdata/src/main/multi.go index 54caf15d..8c650cd2 100644 --- a/oracle/testdata/src/main/multi.go +++ b/oracle/testdata/src/main/multi.go @@ -1,4 +1,4 @@ -package multi +package main func g(x int) { } diff --git a/oracle/testdata/src/main/peers-json.golden b/oracle/testdata/src/main/peers-json.golden deleted file mode 100644 index 80eb3c43..00000000 --- a/oracle/testdata/src/main/peers-json.golden +++ /dev/null @@ -1,15 +0,0 @@ --------- @peers peer-recv-chA -------- -{ - "mode": "peers", - "peers": { - "pos": "testdata/src/main/peers-json.go:11:7", - "type": "chan *int", - "allocs": [ - "testdata/src/main/peers-json.go:8:13" - ], - "receives": [ - "testdata/src/main/peers-json.go:9:2", - "testdata/src/main/peers-json.go:11:7" - ] - } -} \ No newline at end of file diff --git a/oracle/testdata/src/main/referrers-json.golden b/oracle/testdata/src/main/referrers-json.golden deleted file mode 100644 index ad7ec1d3..00000000 --- a/oracle/testdata/src/main/referrers-json.golden +++ /dev/null @@ -1,51 +0,0 @@ --------- @referrers ref-package -------- -{ - "mode": "referrers", - "referrers": { - "pos": "testdata/src/main/referrers-json.go:14:8", - "objpos": "testdata/src/main/referrers-json.go:7:8", - "desc": "package lib", - "refs": [ - "testdata/src/main/referrers-json.go:14:8", - "testdata/src/main/referrers-json.go:14:19" - ] - } -}-------- @referrers ref-method -------- -{ - "mode": "referrers", - "referrers": { - "pos": "testdata/src/main/referrers-json.go:15:8", - "objpos": "testdata/src/lib/lib.go:5:13", - "desc": "func (lib.Type).Method(x *int) *int", - "refs": [ - "testdata/src/main/referrers-json.go:15:8", - "testdata/src/main/referrers-json.go:16:8" - ] - } -}-------- @referrers ref-local -------- -{ - "mode": "referrers", - "referrers": { - "pos": "testdata/src/main/referrers-json.go:17:2", - "objpos": "testdata/src/main/referrers-json.go:14:6", - "desc": "var v lib.Type", - "refs": [ - "testdata/src/main/referrers-json.go:15:6", - "testdata/src/main/referrers-json.go:16:6", - "testdata/src/main/referrers-json.go:17:2", - "testdata/src/main/referrers-json.go:18:2" - ] - } -}-------- @referrers ref-field -------- -{ - "mode": "referrers", - "referrers": { - "pos": "testdata/src/main/referrers-json.go:20:10", - "objpos": "testdata/src/main/referrers-json.go:10:2", - "desc": "field f int", - "refs": [ - "testdata/src/main/referrers-json.go:20:10", - "testdata/src/main/referrers-json.go:23:5" - ] - } -} \ No newline at end of file diff --git a/oracle/testdata/src/main/peers-json.go b/oracle/testdata/src/peers-json/main.go similarity index 94% rename from oracle/testdata/src/main/peers-json.go rename to oracle/testdata/src/peers-json/main.go index 1f5beb20..1df550be 100644 --- a/oracle/testdata/src/main/peers-json.go +++ b/oracle/testdata/src/peers-json/main.go @@ -1,4 +1,4 @@ -package peers +package main // Tests of channel 'peers' query, -format=json. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/peers-json/main.golden b/oracle/testdata/src/peers-json/main.golden new file mode 100644 index 00000000..8c2d06c7 --- /dev/null +++ b/oracle/testdata/src/peers-json/main.golden @@ -0,0 +1,15 @@ +-------- @peers peer-recv-chA -------- +{ + "mode": "peers", + "peers": { + "pos": "testdata/src/peers-json/main.go:11:7", + "type": "chan *int", + "allocs": [ + "testdata/src/peers-json/main.go:8:13" + ], + "receives": [ + "testdata/src/peers-json/main.go:9:2", + "testdata/src/peers-json/main.go:11:7" + ] + } +} diff --git a/oracle/testdata/src/main/peers.go b/oracle/testdata/src/peers/main.go similarity index 98% rename from oracle/testdata/src/main/peers.go rename to oracle/testdata/src/peers/main.go index 8370f64b..a4cf91b8 100644 --- a/oracle/testdata/src/main/peers.go +++ b/oracle/testdata/src/peers/main.go @@ -1,4 +1,4 @@ -package peers +package main // Tests of channel 'peers' query. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/main/peers.golden b/oracle/testdata/src/peers/main.golden similarity index 99% rename from oracle/testdata/src/main/peers.golden rename to oracle/testdata/src/peers/main.golden index f97e6729..597a3c6c 100644 --- a/oracle/testdata/src/main/peers.golden +++ b/oracle/testdata/src/peers/main.golden @@ -26,7 +26,7 @@ This channel of type chan *int may be: -------- @pointsto pointsto-rA -------- this *int may point to these objects: - peers.a2 + main.a2 a1 -------- @peers peer-recv-chB -------- diff --git a/oracle/testdata/src/main/pointsto-json.go b/oracle/testdata/src/pointsto-json/main.go similarity index 96% rename from oracle/testdata/src/main/pointsto-json.go rename to oracle/testdata/src/pointsto-json/main.go index 79d7d3dc..38f1148f 100644 --- a/oracle/testdata/src/main/pointsto-json.go +++ b/oracle/testdata/src/pointsto-json/main.go @@ -1,4 +1,4 @@ -package pointsto +package main // Tests of 'pointsto' queries, -format=json. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/main/pointsto-json.golden b/oracle/testdata/src/pointsto-json/main.golden similarity index 53% rename from oracle/testdata/src/main/pointsto-json.golden rename to oracle/testdata/src/pointsto-json/main.golden index b3f85116..13ac1df9 100644 --- a/oracle/testdata/src/main/pointsto-json.golden +++ b/oracle/testdata/src/pointsto-json/main.golden @@ -6,29 +6,30 @@ "type": "*int", "labels": [ { - "pos": "testdata/src/main/pointsto-json.go:8:6", + "pos": "testdata/src/pointsto-json/main.go:8:6", "desc": "s.x[*]" } ] } ] -}-------- @pointsto val-i -------- +} +-------- @pointsto val-i -------- { "mode": "pointsto", "pointsto": [ { "type": "*D", - "namepos": "testdata/src/main/pointsto-json.go:24:6", + "namepos": "testdata/src/pointsto-json/main.go:24:6", "labels": [ { - "pos": "testdata/src/main/pointsto-json.go:14:10", + "pos": "testdata/src/pointsto-json/main.go:14:10", "desc": "new" } ] }, { "type": "C", - "namepos": "testdata/src/main/pointsto-json.go:23:6" + "namepos": "testdata/src/pointsto-json/main.go:23:6" } ] -} \ No newline at end of file +} diff --git a/oracle/testdata/src/main/pointsto.go b/oracle/testdata/src/pointsto/main.go similarity index 99% rename from oracle/testdata/src/main/pointsto.go rename to oracle/testdata/src/pointsto/main.go index 0657facb..9064e469 100644 --- a/oracle/testdata/src/main/pointsto.go +++ b/oracle/testdata/src/pointsto/main.go @@ -1,4 +1,4 @@ -package pointsto +package main // Tests of 'pointsto' query. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/main/pointsto.golden b/oracle/testdata/src/pointsto/main.golden similarity index 91% rename from oracle/testdata/src/main/pointsto.golden rename to oracle/testdata/src/pointsto/main.golden index 7b12b2af..fd68bda6 100644 --- a/oracle/testdata/src/main/pointsto.golden +++ b/oracle/testdata/src/pointsto/main.golden @@ -3,33 +3,33 @@ Error: pointer analysis wants an expression of reference type; got untyped float -------- @pointsto func-ref-main -------- this func() may point to these objects: - pointsto.main + main.main -------- @pointsto func-ref-*C.f -------- this func() may point to these objects: - (*pointsto.C).f + (*main.C).f -------- @pointsto func-ref-D.f -------- this func() may point to these objects: - (pointsto.D).f + (main.D).f -------- @pointsto func-ref-I.f -------- -Error: func (pointsto.I).f() is an interface method +Error: func (main.I).f() is an interface method -------- @pointsto func-ref-d.f -------- this func() may point to these objects: - (pointsto.D).f + (main.D).f -------- @pointsto func-ref-i.f -------- -Error: func (pointsto.I).f() is an interface method +Error: func (main.I).f() is an interface method -------- @pointsto ref-lexical-d.f -------- this func() may point to these objects: - (pointsto.D).f + (main.D).f -------- @pointsto ref-anon -------- this func() may point to these objects: - pointsto.main$1 + main.main$1 -------- @pointsto ref-global -------- this *string may point to these objects: diff --git a/oracle/testdata/src/main/referrers-json.go b/oracle/testdata/src/referrers-json/main.go similarity index 95% rename from oracle/testdata/src/main/referrers-json.go rename to oracle/testdata/src/referrers-json/main.go index 4799e53c..f551ee01 100644 --- a/oracle/testdata/src/main/referrers-json.go +++ b/oracle/testdata/src/referrers-json/main.go @@ -1,4 +1,4 @@ -package referrers +package main // Tests of 'referrers' query. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/referrers-json/main.golden b/oracle/testdata/src/referrers-json/main.golden new file mode 100644 index 00000000..9d65222a --- /dev/null +++ b/oracle/testdata/src/referrers-json/main.golden @@ -0,0 +1,55 @@ +-------- @referrers ref-package -------- +{ + "mode": "referrers", + "referrers": { + "pos": "testdata/src/referrers-json/main.go:14:8", + "objpos": "testdata/src/referrers-json/main.go:7:8", + "desc": "package lib", + "refs": [ + "testdata/src/referrers-json/main.go:14:8", + "testdata/src/referrers-json/main.go:14:19" + ] + } +} +-------- @referrers ref-method -------- +{ + "mode": "referrers", + "referrers": { + "pos": "testdata/src/referrers-json/main.go:15:8", + "objpos": "testdata/src/lib/lib.go:5:13", + "desc": "func (lib.Type).Method(x *int) *int", + "refs": [ + "testdata/src/referrers-json/main.go:15:8", + "testdata/src/referrers-json/main.go:16:8", + "testdata/src/imports/main.go:22:9" + ] + } +} +-------- @referrers ref-local -------- +{ + "mode": "referrers", + "referrers": { + "pos": "testdata/src/referrers-json/main.go:17:2", + "objpos": "testdata/src/referrers-json/main.go:14:6", + "desc": "var v lib.Type", + "refs": [ + "testdata/src/referrers-json/main.go:15:6", + "testdata/src/referrers-json/main.go:16:6", + "testdata/src/referrers-json/main.go:17:2", + "testdata/src/referrers-json/main.go:18:2" + ] + } +} +-------- @referrers ref-field -------- +{ + "mode": "referrers", + "referrers": { + "pos": "testdata/src/referrers-json/main.go:20:10", + "objpos": "testdata/src/referrers-json/main.go:10:2", + "desc": "field f int", + "refs": [ + "testdata/src/referrers-json/main.go:20:10", + "testdata/src/referrers-json/main.go:23:5" + ] + } +} diff --git a/oracle/testdata/src/main/reflection.go b/oracle/testdata/src/reflection/main.go similarity index 97% rename from oracle/testdata/src/main/reflection.go rename to oracle/testdata/src/reflection/main.go index b10df0b2..392643ba 100644 --- a/oracle/testdata/src/main/reflection.go +++ b/oracle/testdata/src/reflection/main.go @@ -1,4 +1,4 @@ -package reflection +package main // This is a test of 'pointsto', but we split it into a separate file // so that pointsto.go doesn't have to import "reflect" each time. diff --git a/oracle/testdata/src/main/reflection.golden b/oracle/testdata/src/reflection/main.golden similarity index 90% rename from oracle/testdata/src/main/reflection.golden rename to oracle/testdata/src/reflection/main.golden index 4782132b..6190c065 100644 --- a/oracle/testdata/src/main/reflection.golden +++ b/oracle/testdata/src/reflection/main.golden @@ -1,18 +1,18 @@ -------- @pointsto mrv -------- this reflect.Value may contain these dynamic types: *bool, may point to: - reflection.b + main.b *int, may point to: - reflection.a + main.a map[*int]*bool, may point to: makemap -------- @pointsto p1 -------- this interface{} may contain these dynamic types: *bool, may point to: - reflection.b + main.b *int, may point to: - reflection.a + main.a map[*int]*bool, may point to: makemap @@ -23,7 +23,7 @@ this []reflect.Value may point to these objects: -------- @pointsto p3 -------- this reflect.Value may contain these dynamic types: *int, may point to: - reflection.a + main.a -------- @pointsto p4 -------- this reflect.Type may contain these dynamic types: diff --git a/oracle/testdata/src/main/what-json.go b/oracle/testdata/src/what-json/main.go similarity index 93% rename from oracle/testdata/src/main/what-json.go rename to oracle/testdata/src/what-json/main.go index d07a6c90..8d578c73 100644 --- a/oracle/testdata/src/main/what-json.go +++ b/oracle/testdata/src/what-json/main.go @@ -1,4 +1,4 @@ -package what +package main // Tests of 'what' queries, -format=json. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/main/what-json.golden b/oracle/testdata/src/what-json/main.golden similarity index 94% rename from oracle/testdata/src/main/what-json.golden rename to oracle/testdata/src/what-json/main.golden index 13860dde..9a311901 100644 --- a/oracle/testdata/src/main/what-json.golden +++ b/oracle/testdata/src/what-json/main.golden @@ -37,7 +37,6 @@ "modes": [ "callees", "callers", - "callgraph", "callstack", "definition", "describe", @@ -47,6 +46,6 @@ "referrers" ], "srcdir": "testdata/src", - "importpath": "main" + "importpath": "what-json" } -} \ No newline at end of file +} diff --git a/oracle/testdata/src/main/what.go b/oracle/testdata/src/what/main.go similarity index 87% rename from oracle/testdata/src/main/what.go rename to oracle/testdata/src/what/main.go index 041e9215..38e9dc7b 100644 --- a/oracle/testdata/src/main/what.go +++ b/oracle/testdata/src/what/main.go @@ -1,4 +1,4 @@ -package what // @what pkgdecl "what" +package main // @what pkgdecl "main" // Tests of 'what' queries. // See go.tools/oracle/oracle_test.go for explanation. diff --git a/oracle/testdata/src/main/what.golden b/oracle/testdata/src/what/main.golden similarity index 54% rename from oracle/testdata/src/main/what.golden rename to oracle/testdata/src/what/main.golden index 3f832912..56b97dde 100644 --- a/oracle/testdata/src/main/what.golden +++ b/oracle/testdata/src/what/main.golden @@ -1,9 +1,9 @@ -------- @what pkgdecl -------- identifier source file -modes: [callgraph definition describe freevars implements pointsto referrers] +modes: [definition describe freevars implements pointsto referrers] srcdir: testdata/src -import path: main +import path: what -------- @what call -------- identifier @@ -12,9 +12,9 @@ expression statement block function declaration source file -modes: [callees callers callgraph callstack definition describe freevars implements pointsto referrers] +modes: [callees callers callstack definition describe freevars implements pointsto referrers] srcdir: testdata/src -import path: main +import path: what -------- @what var -------- variable declaration @@ -22,9 +22,9 @@ variable declaration statement block function declaration source file -modes: [callers callgraph callstack describe freevars pointsto] +modes: [callers callstack describe freevars pointsto] srcdir: testdata/src -import path: main +import path: what -------- @what recv -------- identifier @@ -33,7 +33,7 @@ expression statement block function declaration source file -modes: [callers callgraph callstack definition describe freevars implements peers pointsto referrers] +modes: [callers callstack definition describe freevars implements peers pointsto referrers] srcdir: testdata/src -import path: main +import path: what diff --git a/oracle/testdata/src/main/whicherrs.go b/oracle/testdata/src/whicherrs/main.go similarity index 100% rename from oracle/testdata/src/main/whicherrs.go rename to oracle/testdata/src/whicherrs/main.go diff --git a/oracle/testdata/src/main/whicherrs.golden b/oracle/testdata/src/whicherrs/main.golden similarity index 100% rename from oracle/testdata/src/main/whicherrs.golden rename to oracle/testdata/src/whicherrs/main.golden diff --git a/oracle/what.go b/oracle/what.go index c1053f48..ca1a7fff 100644 --- a/oracle/what.go +++ b/oracle/what.go @@ -24,21 +24,19 @@ import ( // tools, e.g. to populate a menu of options of slower queries about // the selected location. // -func what(posFlag string, buildContext *build.Context) (*Result, error) { - qpos, err := fastQueryPos(posFlag) +func what(q *Query) error { + qpos, err := fastQueryPos(q.Pos) if err != nil { - return nil, err + return err } + q.Fset = qpos.fset // (ignore errors) - srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext) + srcdir, importPath, _ := guessImportPath(q.Fset.File(qpos.start).Name(), q.Build) // Determine which query modes are applicable to the selection. - // TODO(adonovan): refactor: make each minfo have an 'enable' - // predicate over qpos. enable := map[string]bool{ - "callgraph": true, // whole program; always enabled - "describe": true, // any syntax; always enabled + "describe": true, // any syntax; always enabled } if qpos.end > qpos.start { @@ -100,11 +98,10 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) { // If we don't have an exact selection, disable modes that need one. if !qpos.exact { - for _, minfo := range modes { - if minfo.needs&needExactPos != 0 { - enable[minfo.name] = false - } - } + enable["callees"] = false + enable["pointsto"] = false + enable["whicherrs"] = false + enable["describe"] = false } var modes []string @@ -113,17 +110,13 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) { } sort.Strings(modes) - return &Result{ - mode: "what", - fset: qpos.fset, - q: &whatResult{ - path: qpos.path, - srcdir: srcdir, - importPath: importPath, - modes: modes, - }, - }, nil - + q.result = &whatResult{ + path: qpos.path, + srcdir: srcdir, + importPath: importPath, + modes: modes, + } + return nil } // guessImportPath finds the package containing filename, and returns diff --git a/oracle/whicherrs.go b/oracle/whicherrs.go index a73aa8d1..45fa1434 100644 --- a/oracle/whicherrs.go +++ b/oracle/whicherrs.go @@ -11,6 +11,7 @@ import ( "sort" "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" @@ -27,10 +28,40 @@ var builtinErrorType = types.Universe.Lookup("error").Type() // // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err // can be queried recursively somehow. -func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) { +func whicherrs(q *Query) error { + lconf := loader.Config{Build: q.Build} + + // Determine initial packages for PTA. + args, err := lconf.FromArgs(q.Scope, true) + if err != nil { + return err + } + if len(args) > 0 { + return fmt.Errorf("surplus arguments: %q", args) + } + + // Load/parse/type-check the program. + lprog, err := lconf.Load() + if err != nil { + return err + } + q.Fset = lprog.Fset + + qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos + if err != nil { + return err + } + + prog := ssa.Create(lprog, ssa.GlobalDebug) + + ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) + if err != nil { + return err + } + path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { - return nil, fmt.Errorf("whicherrs wants an expression; got %s", + return fmt.Errorf("whicherrs wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } var expr ast.Expr @@ -38,46 +69,50 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) { switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names - return nil, fmt.Errorf("multiple value specification") + return fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: - return nil, fmt.Errorf("unexpected AST for expr: %T", n) + return fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) if !types.Identical(typ, builtinErrorType) { - return nil, fmt.Errorf("selection is not an expression of type 'error'") + return fmt.Errorf("selection is not an expression of type 'error'") } // Determine the ssa.Value for the expression. var value ssa.Value - var err error if obj != nil { // def/ref of func/var object - value, _, err = ssaValueForIdent(o.prog, qpos.info, obj, path) + value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { - value, _, err = ssaValueForExpr(o.prog, qpos.info, path) + value, _, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { - return nil, err // e.g. trivially dead code + return err // e.g. trivially dead code } - buildSSA(o) - globals := findVisibleErrs(o.prog, qpos) - constants := findVisibleConsts(o.prog, qpos) + // Defer SSA construction till after errors are reported. + prog.BuildAll() + + globals := findVisibleErrs(prog, qpos) + constants := findVisibleConsts(prog, qpos) res := &whicherrsResult{ qpos: qpos, errpos: expr.Pos(), } + // TODO(adonovan): the following code is heavily duplicated + // w.r.t. "pointsto". Refactor? + // Find the instruction which initialized the // global error. If more than one instruction has stored to the global // remove the global from the set of values that we want to query. - allFuncs := ssautil.AllFunctions(o.prog) + allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { @@ -104,12 +139,12 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) { } } - o.ptaConfig.AddQuery(value) + ptaConfig.AddQuery(value) for _, v := range globals { - o.ptaConfig.AddQuery(v) + ptaConfig.AddQuery(v) } - ptares := ptrAnalysis(o) + ptares := ptrAnalysis(ptaConfig) valueptr := ptares.Queries[value] for g, v := range globals { ptr, ok := ptares.Queries[v] @@ -174,11 +209,13 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) { sort.Sort(membersByPosAndString(res.globals)) sort.Sort(membersByPosAndString(res.consts)) sort.Sort(sorterrorType(res.types)) - return res, nil + + q.result = res + return nil } // findVisibleErrs returns a mapping from each package-level variable of type "error" to nil. -func findVisibleErrs(prog *ssa.Program, qpos *QueryPos) map[*ssa.Global]ssa.Value { +func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value { globals := make(map[*ssa.Global]ssa.Value) for _, pkg := range prog.AllPackages() { for _, mem := range pkg.Members { @@ -201,7 +238,7 @@ func findVisibleErrs(prog *ssa.Program, qpos *QueryPos) map[*ssa.Global]ssa.Valu } // findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil. -func findVisibleConsts(prog *ssa.Program, qpos *QueryPos) map[ssa.Const]*ssa.NamedConst { +func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst { constants := make(map[ssa.Const]*ssa.NamedConst) for _, pkg := range prog.AllPackages() { for _, mem := range pkg.Members { @@ -247,7 +284,7 @@ type errorType struct { } type whicherrsResult struct { - qpos *QueryPos + qpos *queryPos errpos token.Pos globals []ssa.Member consts []ssa.Member @@ -270,7 +307,7 @@ func (r *whicherrsResult) display(printf printfFunc) { if len(r.types) > 0 { printf(r.qpos, "this error may contain these dynamic types:") for _, t := range r.types { - printf(t.obj.Pos(), "\t%s", r.qpos.TypeString(t.typ)) + printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ)) } } } @@ -286,7 +323,7 @@ func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) { } for _, t := range r.types { var et serial.WhichErrsType - et.Type = r.qpos.TypeString(t.typ) + et.Type = r.qpos.typeString(t.typ) et.Position = fset.Position(t.obj.Pos()).String() we.Types = append(we.Types, et) }