// Callstack displays an arbitrary path from a root of the callgraph // to the function at the current position. // // The information may be misleading in a context-insensitive // analysis. e.g. the call path X->Y->Z might be infeasible if Y never // calls Z when it is called from X. TODO(adonovan): think about UI. // // 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) if pkg == nil { return nil, fmt.Errorf("no SSA package") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { return nil, fmt.Errorf("this position is not inside a function") } buildSSA(o) target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis and build the complete call graph. o.config.BuildCallGraph = true callgraph := ptrAnalysis(o).CallGraph // Search for an arbitrary path from a root to the target function. isEnd := func(n call.GraphNode) bool { return n.Func() == target } callpath := call.PathSearch(callgraph.Root(), isEnd) if callpath != nil { callpath = callpath[1:] // remove synthetic edge from <root> } return &callstackResult{ qpos: qpos, target: target, callpath: callpath, }, nil }
// Callers reports the possible callers of the function // immediately enclosing the specified source location. // // TODO(adonovan): if a caller is a wrapper, show the caller's caller. // 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") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { return nil, fmt.Errorf("this position is not inside a function") } buildSSA(o) target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis, recording each // call found to originate from target. o.config.BuildCallGraph = true callgraph := ptrAnalysis(o).CallGraph var edges []call.Edge call.GraphVisitEdges(callgraph, func(edge call.Edge) error { if edge.Callee.Func() == target { edges = append(edges, edge) } return nil }) // TODO(adonovan): sort + dedup calls to ensure test determinism. return &callersResult{ target: target, callgraph: callgraph, edges: edges, }, nil }
// TODO(adonovan): move this test into ssa. func TestEnclosingFunction(t *testing.T) { tests := []struct { input string // the input file substr string // first occurrence of this string denotes interval fn string // name of expected containing function }{ // We use distinctive numbers as syntactic landmarks. // Ordinary function: {`package main func f() { println(1003) }`, "100", "main.f"}, // Methods: {`package main type T int func (t T) f() { println(200) }`, "200", "(main.T).f"}, // Function literal: {`package main func f() { println(func() { print(300) }) }`, "300", "[email protected]"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "[email protected]"}, // Implicit init for package-level var initializer. {"package main; var a = 400", "400", "main.init"}, // No code for constants: {"package main; const a = 500", "500", "(none)"}, // Explicit init() {"package main; func init() { println(600) }", "600", "main.init$1"}, // Multiple explicit init functions: {`package main func init() { println("foo") } func init() { println(800) }`, "800", "main.init$2"}, // init() containing FuncLit. {`package main func init() { println(func(){print(900)}) }`, "900", "[email protected]"}, } for _, test := range tests { imp := importer.New(new(importer.Config)) // (NB: no go/build.Config) f, start, end := findInterval(t, imp.Fset, test.input, test.substr) if f == nil { continue } path, exact := importer.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) continue } pkg := prog.Package(mainInfo.Pkg) pkg.Build() name := "(none)" fn := ssa.EnclosingFunction(pkg, path) if fn != nil { name = fn.String() } if name != test.fn { t.Errorf("EnclosingFunction(%q in %q) got %s, want %s", test.substr, test.input, name, test.fn) continue } // While we're here: test HasEnclosingFunction. if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) { t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v", test.substr, test.input, has, fn != nil) continue } } }