Пример #1
0
// FindNonConstCalls returns the set of callsites of the given set of methods
// for which the "query" parameter is not a compile-time constant.
func FindNonConstCalls(cg *callgraph.Graph, qms []*QueryMethod) []ssa.CallInstruction {
	cg.DeleteSyntheticNodes()

	// package database/sql has a couple helper functions which are thin
	// wrappers around other sensitive functions. Instead of handling the
	// general case by tracing down callsites of wrapper functions
	// recursively, let's just whitelist the functions we're already
	// tracking, since it happens to be good enough for our use case.
	okFuncs := make(map[*ssa.Function]struct{}, len(qms))
	for _, m := range qms {
		okFuncs[m.SSA] = struct{}{}
	}

	bad := make([]ssa.CallInstruction, 0)
	for _, m := range qms {
		node := cg.CreateNode(m.SSA)
		for _, edge := range node.In {
			if _, ok := okFuncs[edge.Site.Parent()]; ok {
				continue
			}
			cc := edge.Site.Common()
			args := cc.Args
			// The first parameter is occasionally the receiver.
			if len(args) == m.ArgCount+1 {
				args = args[1:]
			} else if len(args) != m.ArgCount {
				panic("arg count mismatch")
			}
			v := args[m.Param]
			if _, ok := v.(*ssa.Const); !ok {
				bad = append(bad, edge.Site)
			}
		}
	}

	return bad
}
Пример #2
0
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
	conf := loader.Config{
		Build:         ctxt,
		SourceImports: true,
	}

	if len(args) == 0 {
		fmt.Fprintln(os.Stderr, Usage)
		return nil
	}

	// Use the initial packages from the command line.
	args, err := conf.FromArgs(args, tests)
	if err != nil {
		return err
	}

	// Load, parse and type-check the whole program.
	iprog, err := conf.Load()
	if err != nil {
		return err
	}

	// Create and build SSA-form program representation.
	prog := ssa.Create(iprog, 0)
	prog.BuildAll()

	// -- call graph construction ------------------------------------------

	var cg *callgraph.Graph

	switch algo {
	case "static":
		cg = static.CallGraph(prog)

	case "cha":
		cg = cha.CallGraph(prog)

	case "pta":
		main, err := mainPackage(prog, tests)
		if err != nil {
			return err
		}
		config := &pointer.Config{
			Mains:          []*ssa.Package{main},
			BuildCallGraph: true,
		}
		ptares, err := pointer.Analyze(config)
		if err != nil {
			return err // internal error in pointer analysis
		}
		cg = ptares.CallGraph

	case "rta":
		main, err := mainPackage(prog, tests)
		if err != nil {
			return err
		}
		roots := []*ssa.Function{
			main.Func("init"),
			main.Func("main"),
		}
		rtares := rta.Analyze(roots, true)
		cg = rtares.CallGraph

		// NB: RTA gives us Reachable and RuntimeTypes too.

	default:
		return fmt.Errorf("unknown algorithm: %s", algo)
	}

	cg.DeleteSyntheticNodes()

	// -- output------------------------------------------------------------

	var before, after string

	// Pre-canned formats.
	switch format {
	case "digraph":
		format = `{{printf "%q %q" .Caller .Callee}}`

	case "graphviz":
		before = "digraph callgraph {\n"
		after = "}\n"
		format = `  {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}"`
	}

	tmpl, err := template.New("-format").Parse(format)
	if err != nil {
		return fmt.Errorf("invalid -format template: %v", err)
	}

	// Allocate these once, outside the traversal.
	var buf bytes.Buffer
	data := Edge{fset: prog.Fset}

	fmt.Fprint(stdout, before)
	if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
		data.position.Offset = -1
		data.edge = edge
		data.Caller = edge.Caller.Func
		data.Callee = edge.Callee.Func

		buf.Reset()
		if err := tmpl.Execute(&buf, &data); err != nil {
			return err
		}
		stdout.Write(buf.Bytes())
		if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
			fmt.Fprintln(stdout)
		}
		return nil
	}); err != nil {
		return err
	}
	fmt.Fprint(stdout, after)
	return nil
}
Пример #3
0
// doCallgraph computes the CALLEES and CALLERS relations.
func (a *analysis) doCallgraph(cg *callgraph.Graph) {
	log.Print("Deleting synthetic nodes...")
	// TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically
	// inefficient and can be (unpredictably) slow.
	cg.DeleteSyntheticNodes()
	log.Print("Synthetic nodes deleted")

	// Populate nodes of package call graphs (PCGs).
	for _, n := range cg.Nodes {
		a.pcgAddNode(n.Func)
	}
	// Within each PCG, sort funcs by name.
	for _, pcg := range a.pcgs {
		pcg.sortNodes()
	}

	calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool)
	callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool)
	for _, n := range cg.Nodes {
		for _, e := range n.Out {
			if e.Site == nil {
				continue // a call from a synthetic node such as <root>
			}

			// Add (site pos, callee) to calledFuncs.
			// (Dynamic calls only.)
			callee := e.Callee.Func

			a.pcgAddEdge(n.Func, callee)

			if callee.Synthetic != "" {
				continue // call of a package initializer
			}

			if e.Site.Common().StaticCallee() == nil {
				// dynamic call
				// (CALLEES information for static calls
				// is computed using SSA information.)
				lparen := e.Site.Common().Pos()
				if lparen != token.NoPos {
					fns := calledFuncs[e.Site]
					if fns == nil {
						fns = make(map[*ssa.Function]bool)
						calledFuncs[e.Site] = fns
					}
					fns[callee] = true
				}
			}

			// Add (callee, site) to callingSites.
			fns := callingSites[callee]
			if fns == nil {
				fns = make(map[ssa.CallInstruction]bool)
				callingSites[callee] = fns
			}
			fns[e.Site] = true
		}
	}

	// CALLEES.
	log.Print("Callees...")
	for site, fns := range calledFuncs {
		var funcs funcsByPos
		for fn := range fns {
			funcs = append(funcs, fn)
		}
		sort.Sort(funcs)

		a.addCallees(site, funcs)
	}

	// CALLERS
	log.Print("Callers...")
	for callee, sites := range callingSites {
		pos := funcToken(callee)
		if pos == token.NoPos {
			log.Printf("CALLERS: skipping %s: no pos", callee)
			continue
		}

		var this *types.Package // for relativizing names
		if callee.Pkg != nil {
			this = callee.Pkg.Pkg
		}

		// Compute sites grouped by parent, with text and URLs.
		sitesByParent := make(map[*ssa.Function]sitesByPos)
		for site := range sites {
			fn := site.Parent()
			sitesByParent[fn] = append(sitesByParent[fn], site)
		}
		var funcs funcsByPos
		for fn := range sitesByParent {
			funcs = append(funcs, fn)
		}
		sort.Sort(funcs)

		v := callersJSON{
			Callee:  callee.String(),
			Callers: []callerJSON{}, // (JS wants non-nil)
		}
		for _, fn := range funcs {
			caller := callerJSON{
				Func:  prettyFunc(this, fn),
				Sites: []anchorJSON{}, // (JS wants non-nil)
			}
			sites := sitesByParent[fn]
			sort.Sort(sites)
			for _, site := range sites {
				pos := site.Common().Pos()
				if pos != token.NoPos {
					caller.Sites = append(caller.Sites, anchorJSON{
						Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line),
						Href: a.posURL(pos, len("(")),
					})
				}
			}
			v.Callers = append(v.Callers, caller)
		}

		fi, offset := a.fileAndOffset(pos)
		fi.addLink(aLink{
			start:   offset,
			end:     offset + len("func"),
			title:   fmt.Sprintf("%d callers", len(sites)),
			onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)),
		})
	}

	// PACKAGE CALLGRAPH
	log.Print("Package call graph...")
	for pkg, pcg := range a.pcgs {
		// Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array.
		index := make(map[string]int)

		// Treat exported functions (and exported methods of
		// exported named types) as roots even if they aren't
		// actually called from outside the package.
		for i, n := range pcg.nodes {
			if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() {
				continue
			}
			recv := n.fn.Signature.Recv()
			if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() {
				roots := &pcg.nodes[0].edges
				roots.SetBit(roots, i, 1)
			}
			index[n.fn.RelString(pkg.Pkg)] = i
		}

		json := a.pcgJSON(pcg)

		// TODO(adonovan): pkg.Path() is not unique!
		// It is possible to declare a non-test package called x_test.
		a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index)
	}
}
Пример #4
0
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
	conf := loader.Config{
		Build:         ctxt,
		SourceImports: true,
	}

	if len(args) == 0 {
		fmt.Fprintln(os.Stderr, Usage)
		return nil
	}

	// Use the initial packages from the command line.
	args, err := conf.FromArgs(args, tests)
	if err != nil {
		return err
	}

	// Load, parse and type-check the whole program.
	iprog, err := conf.Load()
	if err != nil {
		return err
	}

	// Create and build SSA-form program representation.
	prog := ssa.Create(iprog, 0)
	prog.BuildAll()

	// Determine the main package.
	// TODO(adonovan): allow independent control over tests, mains
	// and libraries.
	// TODO(adonovan): put this logic in a library; we keep reinventing it.
	var main *ssa.Package
	pkgs := prog.AllPackages()
	if tests {
		// If -test, use all packages' tests.
		if len(pkgs) > 0 {
			main = prog.CreateTestMainPackage(pkgs...)
		}
		if main == nil {
			return fmt.Errorf("no tests")
		}
	} else {
		// Otherwise, use main.main.
		for _, pkg := range pkgs {
			if pkg.Object.Name() == "main" {
				main = pkg
				if main.Func("main") == nil {
					return fmt.Errorf("no func main() in main package")
				}
				break
			}
		}
		if main == nil {
			return fmt.Errorf("no main package")
		}
	}

	// Invariant: main package has a main() function.

	// -- call graph construction ------------------------------------------

	var cg *callgraph.Graph

	switch algo {
	case "pta":
		config := &pointer.Config{
			Mains:          []*ssa.Package{main},
			BuildCallGraph: true,
		}
		ptares, err := pointer.Analyze(config)
		if err != nil {
			return err // internal error in pointer analysis
		}
		cg = ptares.CallGraph

	case "rta":
		roots := []*ssa.Function{
			main.Func("init"),
			main.Func("main"),
		}
		rtares := rta.Analyze(roots, true)
		cg = rtares.CallGraph

		// NB: RTA gives us Reachable and RuntimeTypes too.

	default:
		return fmt.Errorf("unknown algorithm: %s", algo)
	}

	cg.DeleteSyntheticNodes()

	// -- output------------------------------------------------------------

	var before, after string

	// Pre-canned formats.
	switch format {
	case "digraph":
		format = `{{printf "%q %q" .Caller .Callee}}`

	case "graphviz":
		before = "digraph callgraph {\n"
		after = "}\n"
		format = `  {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}"`
	}

	tmpl, err := template.New("-format").Parse(format)
	if err != nil {
		return fmt.Errorf("invalid -format template: %v", err)
	}

	// Allocate these once, outside the traversal.
	var buf bytes.Buffer
	data := Edge{fset: prog.Fset}

	fmt.Fprint(stdout, before)
	if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
		data.position.Offset = -1
		data.edge = edge
		data.Caller = edge.Caller.Func
		data.Callee = edge.Callee.Func

		buf.Reset()
		if err := tmpl.Execute(&buf, &data); err != nil {
			return err
		}
		stdout.Write(buf.Bytes())
		if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
			fmt.Fprintln(stdout)
		}
		return nil
	}); err != nil {
		return err
	}
	fmt.Fprint(stdout, after)
	return nil
}
Пример #5
0
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
	conf := loader.Config{Build: ctxt}

	if len(args) == 0 {
		fmt.Fprintln(os.Stderr, Usage)
		return nil
	}

	if *fileFlag == "" || *lineFlag == -1 {
		fmt.Fprintln(os.Stderr, "Need input file and line")
		return nil
	}

	// Use the initial packages from the command line.
	args, err := conf.FromArgs(args, tests)
	if err != nil {
		return err
	}

	// Load, parse and type-check the whole program.
	iprog, err := conf.Load()
	if err != nil {
		return err
	}

	// Create and build SSA-form program representation.
	prog := ssautil.CreateProgram(iprog, 0)
	prog.Build()

	// -- call graph construction ------------------------------------------

	var cg *callgraph.Graph

	switch algo {
	case "static":
		cg = static.CallGraph(prog)

	case "cha":
		cg = cha.CallGraph(prog)

	case "pta":
		// Set up points-to analysis log file.
		var ptalog io.Writer
		if *ptalogFlag != "" {
			if f, err := os.Create(*ptalogFlag); err != nil {
				log.Fatalf("Failed to create PTA log file: %s", err)
			} else {
				buf := bufio.NewWriter(f)
				ptalog = buf
				defer func() {
					if err := buf.Flush(); err != nil {
						log.Printf("flush: %s", err)
					}
					if err := f.Close(); err != nil {
						log.Printf("close: %s", err)
					}
				}()
			}
		}

		main, err := mainPackage(prog, tests)
		if err != nil {
			return err
		}
		config := &pointer.Config{
			Mains:          []*ssa.Package{main},
			BuildCallGraph: true,
			Log:            ptalog,
		}
		ptares, err := pointer.Analyze(config)
		if err != nil {
			return err // internal error in pointer analysis
		}
		cg = ptares.CallGraph

	case "rta":
		main, err := mainPackage(prog, tests)
		if err != nil {
			return err
		}
		roots := []*ssa.Function{
			main.Func("init"),
			main.Func("main"),
		}
		rtares := rta.Analyze(roots, true)
		cg = rtares.CallGraph

		// NB: RTA gives us Reachable and RuntimeTypes too.

	default:
		return fmt.Errorf("unknown algorithm: %s", algo)
	}

	cg.DeleteSyntheticNodes()

	// -- output------------------------------------------------------------

	file := *fileFlag
	line := *lineFlag
	depth := *depthFlag

	node := findFunction(cg, file, line)
	if node == nil {
		panic("function not found")
	}

	ins, before := ChainBefore(node)
	after, outs := ChainAfter(node)
	chain := append(before, after[1:]...)

	sort.Sort(SortNodes(ins))
	for _, n := range ins {
		_, ch := ChainBefore(n)
		fmt.Printf("(%d) %s\n", impact(ch[0]), chainToString(ch))
	}
	fmt.Println()
	fmt.Println(chainToString(chain))
	fmt.Println()

	sort.Sort(SortNodes(outs))
	for _, n := range outs {
		if impact(n) > minImpact {
			continue
		}
		printFanout(depth, "", n)
	}

	return nil
}