// 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.ptaConfig.BuildCallGraph = true cg := ptrAnalysis(o).CallGraph cg.DeleteSyntheticNodes() // Search for an arbitrary path from a root to the target function. isEnd := func(n *callgraph.Node) bool { return n.Func == target } callpath := callgraph.PathSearch(cg.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. // 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.ptaConfig.BuildCallGraph = true cg := ptrAnalysis(o).CallGraph cg.DeleteSyntheticNodes() edges := cg.CreateNode(target).In // TODO(adonovan): sort + dedup calls to ensure test determinism. return &callersResult{ target: target, callgraph: cg, edges: edges, }, nil }
// 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) if pkg == nil { return nil, fmt.Errorf("no SSA package") } // Determine the enclosing call for the specified position. var e *ast.CallExpr for _, n := range qpos.path { if e, _ = n.(*ast.CallExpr); e != nil { break } } if e == nil { return nil, 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 // not what the user intended. // Reject type conversions. if qpos.info.Types[e.Fun].IsType() { return nil, 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()) } } buildSSA(o) // 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?)") } // Find the call site. site, err := findCallSite(callerFn, e.Lparen) if err != nil { return nil, err } funcs, err := findCallees(o, site) if err != nil { return nil, err } return &calleesResult{ site: site, funcs: funcs, }, nil }
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident // expression whose path to the root of the AST is path. // func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) { pkg := prog.Package(qinfo.Pkg) pkg.SetDebugMode(true) pkg.Build() fn := ssa.EnclosingFunction(pkg, path) if fn == nil { return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)") } if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil { return v, addr, nil } return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn) }
func (l langType) FuncName(fnx *ssa.Function) string { pn := "" if fnx.Signature.Recv() != nil { pn = fnx.Signature.Recv().Type().String() // NOTE no use of underlying here } else { pn, _ = pogo.FuncPathName(fnx) //fmt.Sprintf("fn%d", fnx.Pos()) fn := ssa.EnclosingFunction(fnx.Package(), []ast.Node{fnx.Syntax()}) if fn == nil { fn = fnx } if fn.Pkg != nil { if fn.Pkg.Object != nil { pn = fn.Pkg.Object.Path() // was .Name() } } else { if fn.Object() != nil { if fn.Object().Pkg() != nil { pn = fn.Object().Pkg().Path() // was .Name() } } } } return l.LangName(pn, fnx.Name()) }
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", "main.f$1"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "main.f$1$1"}, // 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", "main.init#1$1"}, } for _, test := range tests { conf := loader.Config{Fset: token.NewFileSet()} f, start, end := findInterval(t, conf.Fset, test.input, test.substr) if f == nil { continue } path, exact := astutil.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) continue } prog := ssautil.CreateProgram(iprog, 0) pkg := prog.Package(iprog.Created[0].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 } } }
// Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { if runtime.GOOS == "android" { t.Skipf("no testdata dir on %s", runtime.GOOS) } conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("testdata/valueforexpr.go", nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } mainInfo := iprog.Created[0] prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() if false { // debugging for _, mem := range mainPkg.Members { if fn, ok := mem.(*ssa.Function); ok { fn.WriteTo(os.Stderr) } } } // Find the actual AST node for each canonical position. parenExprByPos := make(map[token.Pos]*ast.ParenExpr) ast.Inspect(f, func(n ast.Node) bool { if n != nil { if e, ok := n.(*ast.ParenExpr); ok { parenExprByPos[e.Pos()] = e } } return true }) // Find all annotations of form /*@kind*/. for _, c := range f.Comments { text := strings.TrimSpace(c.Text()) if text == "" || text[0] != '@' { continue } text = text[1:] pos := c.End() + 1 position := prog.Fset.Position(pos) var e ast.Expr if target := parenExprByPos[pos]; target == nil { t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text) continue } else { e = target.X } path, _ := astutil.PathEnclosingInterval(f, pos, pos) if path == nil { t.Errorf("%s: can't find AST path from root to comment: %s", position, text) continue } fn := ssa.EnclosingFunction(mainPkg, path) if fn == nil { t.Errorf("%s: can't find enclosing function", position) continue } v, gotAddr := fn.ValueForExpr(e) // (may be nil) got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.") if want := text; got != want { t.Errorf("%s: got value %q, want %q", position, got, want) } if v != nil { T := v.Type() if gotAddr { T = T.Underlying().(*types.Pointer).Elem() // deref } if !types.Identical(T, mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) } } } }
// 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(q *Query) error { fset := token.NewFileSet() lconf := loader.Config{Fset: fset, Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // Load/parse/type-check the program. lprog, err := loadWithSoftErrors(&lconf) if err != nil { return err } qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { return err } prog := ssautil.CreateProgram(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 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.Build() target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return fmt.Errorf("no SSA function built for this location (dead code?)") } var callpath []*callgraph.Edge isEnd := func(n *callgraph.Node) bool { return n.Func == target } // First, build a callgraph containing only static call edges, // and search for an arbitrary path from a root to the target function. // This is quick, and the user wants a static path if one exists. cg := static.CallGraph(prog) cg.DeleteSyntheticNodes() for _, ep := range entryPoints(ptaConfig.Mains) { callpath = callgraph.PathSearch(cg.CreateNode(ep), isEnd) if callpath != nil { break } } // No fully static path found. // Run the pointer analysis and build a complete call graph. if callpath == nil { ptaConfig.BuildCallGraph = true cg := ptrAnalysis(ptaConfig).CallGraph cg.DeleteSyntheticNodes() callpath = callgraph.PathSearch(cg.Root, isEnd) if callpath != nil { callpath = callpath[1:] // remove synthetic edge from <root> } } q.Output(fset, &callstackResult{ qpos: qpos, target: target, callpath: callpath, }) return nil }
// 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(q *Query) error { fset := token.NewFileSet() lconf := loader.Config{Fset: fset, Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { return err } prog := ssautil.CreateProgram(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 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 fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis and build the complete call graph. ptaConfig.BuildCallGraph = true cg := ptrAnalysis(ptaConfig).CallGraph cg.DeleteSyntheticNodes() // Search for an arbitrary path from a root to the target function. isEnd := func(n *callgraph.Node) bool { return n.Func == target } callpath := callgraph.PathSearch(cg.Root, isEnd) if callpath != nil { callpath = callpath[1:] // remove synthetic edge from <root> } q.Fset = fset q.result = &callstackResult{ qpos: qpos, target: target, callpath: callpath, } return nil }
// Callees reports the possible callees of the function call site // identified by the specified source location. func callees(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); 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) // needs exact pos if err != nil { return err } // Determine the enclosing call for the specified position. var e *ast.CallExpr for _, n := range qpos.path { if e, _ = n.(*ast.CallExpr); e != nil { break } } if e == nil { 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 // not what the user intended. // Reject type conversions. if qpos.info.Types[e.Fun].IsType() { return fmt.Errorf("this is a type conversion, not a function call") } // Deal with obviously static calls before constructing SSA form. // Some static calls may yet require SSA construction, // e.g. f := func(){}; f(). switch funexpr := unparen(e.Fun).(type) { case *ast.Ident: switch obj := qpos.info.Uses[funexpr].(type) { case *types.Builtin: // Reject calls to built-ins. return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name()) case *types.Func: // This is a static function call q.result = &calleesTypesResult{ site: e, callee: obj, } return nil } case *ast.SelectorExpr: sel := qpos.info.Selections[funexpr] if sel == nil { // qualified identifier. // May refer to top level function variable // or to top level function. callee := qpos.info.Uses[funexpr.Sel] if obj, ok := callee.(*types.Func); ok { q.result = &calleesTypesResult{ site: e, callee: obj, } return nil } } else if sel.Kind() == types.MethodVal { // Inspect the receiver type of the selected method. // If it is concrete, the call is statically dispatched. // (Due to implicit field selections, it is not enough to look // at sel.Recv(), the type of the actual receiver expression.) method := sel.Obj().(*types.Func) recvtype := method.Type().(*types.Signature).Recv().Type() if !types.IsInterface(recvtype) { // static method call q.result = &calleesTypesResult{ site: e, callee: method, } return nil } } } prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } pkg := prog.Package(qpos.info.Pkg) if pkg == nil { return fmt.Errorf("no SSA package") } // Defer SSA construction till after errors are reported. prog.Build() // Ascertain calling function and call site. callerFn := ssa.EnclosingFunction(pkg, qpos.path) if callerFn == nil { return fmt.Errorf("no SSA function built for this location (dead code?)") } // Find the call site. site, err := findCallSite(callerFn, e) if err != nil { return err } funcs, err := findCallees(ptaConfig, site) if err != nil { return err } q.result = &calleesSSAResult{ site: site, funcs: funcs, } return nil }
// Callers reports the possible callers of the function // immediately enclosing the specified source location. // func callers(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); 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 } prog := ssautil.CreateProgram(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 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.Build() target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return fmt.Errorf("no SSA function built for this location (dead code?)") } // If the function is never address-taken, all calls are direct // and can be found quickly by inspecting the whole SSA program. cg := directCallsTo(target, entryPoints(ptaConfig.Mains)) if cg == nil { // Run the pointer analysis, recording each // call found to originate from target. // (Pointer analysis may return fewer results than // directCallsTo because it ignores dead code.) ptaConfig.BuildCallGraph = true cg = ptrAnalysis(ptaConfig).CallGraph } cg.DeleteSyntheticNodes() edges := cg.CreateNode(target).In // TODO(adonovan): sort + dedup calls to ensure test determinism. q.result = &callersResult{ target: target, callgraph: cg, edges: edges, } return nil }
// Returns true if unused func checkObj(expr *ast.Ident, object types.Object, prog *loader.Program, ssaprog *ssa.Program, fset *token.FileSet) (unused bool) { if _, ok := object.(*types.Var); !ok { if debug { fmt.Println("Skipping object", object) } return false } pkg, node, _ := prog.PathEnclosingInterval(expr.Pos(), expr.End()) spkg := ssaprog.Package(pkg.Pkg) f := ssa.EnclosingFunction(spkg, node) if f == nil { if debug { fmt.Printf("Unknown function %v %v %v %v\n", fset.Position(expr.Pos()), object, pkg, prog) } return false } value, _ := f.ValueForExpr(expr) // Unwrap unops and grab the value inside if v, ok := value.(*ssa.UnOp); ok { if debug { fmt.Println("Unwrapping unop") } value = v.X } if debug { fmt.Printf("%v %v: %v %#v\n", fset.Position(expr.Pos()), expr, object, value) } if _, ok := value.(*ssa.Global); ok { if debug { fmt.Printf(" is global\n") } return false } if value == nil { if debug { fmt.Println("Value is nil", object) } return false } refs := value.Referrers() if refs == nil { if debug { fmt.Println("Referrers is nil", object) } return false } if debug { fmt.Printf(" (refs) %v\n", refs) } hasRef := false for _, r := range *refs { _, ok := r.(*ssa.DebugRef) hasRef = hasRef || !ok if debug && !ok { fmt.Printf("%v %v: %v %v\n", fset.Position(expr.Pos()), expr, object, r) } } if !hasRef { unused = true } return unused }
// Callers reports the possible callers of the function // immediately enclosing the specified source location. // func callers(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); 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 } prog := ssautil.CreateProgram(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 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.Build() target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return fmt.Errorf("no SSA function built for this location (dead code?)") } // TODO(adonovan): opt: if function is never address-taken, skip // the pointer analysis. Just look for direct calls. This can // be done in a single pass over the SSA. // Run the pointer analysis, recording each // call found to originate from target. ptaConfig.BuildCallGraph = true cg := ptrAnalysis(ptaConfig).CallGraph cg.DeleteSyntheticNodes() edges := cg.CreateNode(target).In // TODO(adonovan): sort + dedup calls to ensure test determinism. q.result = &callersResult{ target: target, callgraph: cg, edges: edges, } return nil }