1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package oracle
6
7import (
8	"fmt"
9	"go/token"
10
11	"github.com/visualfc/gotools/oracle/oracle/serial"
12	"golang.org/x/tools/go/callgraph"
13	"golang.org/x/tools/go/loader"
14	"golang.org/x/tools/go/ssa"
15	"golang.org/x/tools/go/ssa/ssautil"
16)
17
18// Callers reports the possible callers of the function
19// immediately enclosing the specified source location.
20//
21func callers(q *Query) error {
22	lconf := loader.Config{Build: q.Build}
23
24	if err := setPTAScope(&lconf, q.Scope); err != nil {
25		return err
26	}
27
28	// Load/parse/type-check the program.
29	lprog, err := lconf.Load()
30	if err != nil {
31		return err
32	}
33	q.Fset = lprog.Fset
34
35	qpos, err := parseQueryPos(lprog, q.Pos, false)
36	if err != nil {
37		return err
38	}
39
40	prog := ssautil.CreateProgram(lprog, 0)
41
42	ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
43	if err != nil {
44		return err
45	}
46
47	pkg := prog.Package(qpos.info.Pkg)
48	if pkg == nil {
49		return fmt.Errorf("no SSA package")
50	}
51	if !ssa.HasEnclosingFunction(pkg, qpos.path) {
52		return fmt.Errorf("this position is not inside a function")
53	}
54
55	// Defer SSA construction till after errors are reported.
56	prog.Build()
57
58	target := ssa.EnclosingFunction(pkg, qpos.path)
59	if target == nil {
60		return fmt.Errorf("no SSA function built for this location (dead code?)")
61	}
62
63	// TODO(adonovan): opt: if function is never address-taken, skip
64	// the pointer analysis.  Just look for direct calls.  This can
65	// be done in a single pass over the SSA.
66
67	// Run the pointer analysis, recording each
68	// call found to originate from target.
69	ptaConfig.BuildCallGraph = true
70	cg := ptrAnalysis(ptaConfig).CallGraph
71	cg.DeleteSyntheticNodes()
72	edges := cg.CreateNode(target).In
73	// TODO(adonovan): sort + dedup calls to ensure test determinism.
74
75	q.result = &callersResult{
76		target:    target,
77		callgraph: cg,
78		edges:     edges,
79	}
80	return nil
81}
82
83type callersResult struct {
84	target    *ssa.Function
85	callgraph *callgraph.Graph
86	edges     []*callgraph.Edge
87}
88
89func (r *callersResult) display(printf printfFunc) {
90	root := r.callgraph.Root
91	if r.edges == nil {
92		printf(r.target, "%s is not reachable in this program.", r.target)
93	} else {
94		printf(r.target, "%s is called from these %d sites:", r.target, len(r.edges))
95		for _, edge := range r.edges {
96			if edge.Caller == root {
97				printf(r.target, "the root of the call graph")
98			} else {
99				printf(edge, "\t%s from %s", edge.Description(), edge.Caller.Func)
100			}
101		}
102	}
103}
104
105func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
106	var callers []serial.Caller
107	for _, edge := range r.edges {
108		callers = append(callers, serial.Caller{
109			Caller: edge.Caller.Func.String(),
110			Pos:    fset.Position(edge.Pos()).String(),
111			Desc:   edge.Description(),
112		})
113	}
114	res.Callers = callers
115}
116