Example #1
0
// 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
}
Example #2
0
// 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
}
Example #3
0
// Callees reports the possible callees of the function call site
// identified by the specified source location.
//
// TODO(adonovan): if a callee is a wrapper, show the callee's callee.
//
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.IsType(e.Fun) {
		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.ObjectOf(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
}
Example #4
0
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
// expression whose path to the root of the AST is path.  It may
// return a nil Value without an error to indicate the pointer
// analysis is not appropriate.
//
func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (ssa.Value, error) {
	pkg := prog.Package(qinfo.Pkg)
	pkg.SetDebugMode(true)
	pkg.Build()

	fn := ssa.EnclosingFunction(pkg, path)
	if fn == nil {
		return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
	}

	if v := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
		return v, nil
	}

	return nil, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
}
Example #5
0
// 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
		}
	}
}
// Ensure that, in debug mode, we can determine the ssa.Value
// corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) {
	imp := importer.New(new(importer.Config)) // (uses GCImporter)
	f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments)
	if err != nil {
		t.Error(err)
		return
	}

	mainInfo := imp.CreatePackage("main", f)

	prog := ssa.NewProgram(imp.Fset, 0)
	if err := prog.CreatePackages(imp); err != nil {
		t.Error(err)
		return
	}
	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.DumpTo(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 := imp.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, _ := importer.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.IsIdentical(T, mainInfo.TypeOf(e)) {
				t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
			}
		}
	}
}
Example #7
0
// Callees reports the possible callees of the function call site
// identified by the specified source location.
//
// TODO(adonovan): if a callee is a wrapper, show the callee's callee.
//
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.IsType(e.Fun) {
		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.ObjectOf(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?)")
	}

	o.config.BuildCallGraph = true
	callgraph := ptrAnalysis(o).CallGraph

	// Find the call site and all edges from it.
	var site ssa.CallInstruction
	calleesMap := make(map[*ssa.Function]bool)
	for _, n := range callgraph.Nodes() {
		if n.Func() == callerFn {
			if site == nil {
				// First node for callerFn: identify the site.
				for _, s := range n.Sites() {
					if s.Pos() == e.Lparen {
						site = s
						break
					}
				}
				if site == nil {
					return nil, fmt.Errorf("this call site is unreachable in this analysis")
				}
			}

			for _, edge := range n.Edges() {
				if edge.Site == site {
					calleesMap[edge.Callee.Func()] = true
				}
			}
		}
	}
	if site == nil {
		return nil, fmt.Errorf("this function is unreachable in this analysis")
	}

	// Discard context, de-duplicate and sort.
	funcs := make([]*ssa.Function, 0, len(calleesMap))
	for f := range calleesMap {
		funcs = append(funcs, f)
	}
	sort.Sort(byFuncPos(funcs))

	return &calleesResult{
		site:  site,
		funcs: funcs,
	}, nil
}