oracle: attempt to deduce callees statically before building SSA
When querying for callees against a static call, the entire SSA form for the program was built. Since we can tell if a callee is statically dispatched after typechecking, try to do that before building the SSA form. This cuts 3.5 seconds off queries against static calls. Change-Id: I22291381d3bec490e3b1d6f9c6b5a0092fd9f635 Reviewed-on: https://go-review.googlesource.com/10230 Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
3f8aef80c8
commit
500e956000
|
@ -39,18 +39,6 @@ func callees(q *Query) error {
|
||||||
return err
|
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the enclosing call for the specified position.
|
// Determine the enclosing call for the specified position.
|
||||||
var e *ast.CallExpr
|
var e *ast.CallExpr
|
||||||
for _, n := range qpos.path {
|
for _, n := range qpos.path {
|
||||||
|
@ -70,11 +58,60 @@ func callees(q *Query) error {
|
||||||
return fmt.Errorf("this is a type conversion, not a function call")
|
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.
|
// Reject calls to built-ins.
|
||||||
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
|
return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name())
|
||||||
if b, ok := qpos.info.Uses[id].(*types.Builtin); ok {
|
case *types.Func:
|
||||||
return fmt.Errorf("this is a call to the built-in '%s' operator", b.Name())
|
// 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 {
|
||||||
|
recvtype := sel.Recv()
|
||||||
|
if !types.IsInterface(recvtype) {
|
||||||
|
// static method call
|
||||||
|
q.result = &calleesTypesResult{
|
||||||
|
site: e,
|
||||||
|
callee: sel.Obj().(*types.Func),
|
||||||
|
}
|
||||||
|
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.
|
// Defer SSA construction till after errors are reported.
|
||||||
|
@ -87,7 +124,7 @@ func callees(q *Query) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the call site.
|
// Find the call site.
|
||||||
site, err := findCallSite(callerFn, e.Lparen)
|
site, err := findCallSite(callerFn, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -97,23 +134,21 @@ func callees(q *Query) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
q.result = &calleesResult{
|
q.result = &calleesSSAResult{
|
||||||
site: site,
|
site: site,
|
||||||
funcs: funcs,
|
funcs: funcs,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, error) {
|
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
|
||||||
for _, b := range fn.Blocks {
|
instr, _ := fn.ValueForExpr(call)
|
||||||
for _, instr := range b.Instrs {
|
callInstr, _ := instr.(ssa.CallInstruction)
|
||||||
if site, ok := instr.(ssa.CallInstruction); ok && instr.Pos() == lparen {
|
if instr == nil {
|
||||||
return site, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||||
}
|
}
|
||||||
|
return callInstr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||||
// Avoid running the pointer analysis for static calls.
|
// Avoid running the pointer analysis for static calls.
|
||||||
|
@ -154,12 +189,17 @@ func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Functio
|
||||||
return funcs, nil
|
return funcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type calleesResult struct {
|
type calleesSSAResult struct {
|
||||||
site ssa.CallInstruction
|
site ssa.CallInstruction
|
||||||
funcs []*ssa.Function
|
funcs []*ssa.Function
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *calleesResult) display(printf printfFunc) {
|
type calleesTypesResult struct {
|
||||||
|
site *ast.CallExpr
|
||||||
|
callee *types.Func
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *calleesSSAResult) display(printf printfFunc) {
|
||||||
if len(r.funcs) == 0 {
|
if len(r.funcs) == 0 {
|
||||||
// dynamic call on a provably nil func/interface
|
// dynamic call on a provably nil func/interface
|
||||||
printf(r.site, "%s on nil value", r.site.Common().Description())
|
printf(r.site, "%s on nil value", r.site.Common().Description())
|
||||||
|
@ -171,7 +211,7 @@ func (r *calleesResult) display(printf printfFunc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *calleesResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
func (r *calleesSSAResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
j := &serial.Callees{
|
j := &serial.Callees{
|
||||||
Pos: fset.Position(r.site.Pos()).String(),
|
Pos: fset.Position(r.site.Pos()).String(),
|
||||||
Desc: r.site.Common().Description(),
|
Desc: r.site.Common().Description(),
|
||||||
|
@ -185,6 +225,25 @@ func (r *calleesResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
res.Callees = j
|
res.Callees = j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *calleesTypesResult) display(printf printfFunc) {
|
||||||
|
printf(r.site, "this static function call dispatches to:")
|
||||||
|
printf(r.callee, "\t%s", r.callee.FullName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *calleesTypesResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
|
j := &serial.Callees{
|
||||||
|
Pos: fset.Position(r.site.Pos()).String(),
|
||||||
|
Desc: "static function call",
|
||||||
|
}
|
||||||
|
j.Callees = []*serial.CalleesItem{
|
||||||
|
&serial.CalleesItem{
|
||||||
|
Name: r.callee.FullName(),
|
||||||
|
Pos: fset.Position(r.callee.Pos()).String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res.Callees = j
|
||||||
|
}
|
||||||
|
|
||||||
// NB: byFuncPos is not deterministic across packages since it depends on load order.
|
// NB: byFuncPos is not deterministic across packages since it depends on load order.
|
||||||
// Use lessPos if the tests need it.
|
// Use lessPos if the tests need it.
|
||||||
type byFuncPos []*ssa.Function
|
type byFuncPos []*ssa.Function
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Tests of call-graph queries.
|
// Tests of call-graph queries.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
// See calls.golden for expected query results.
|
// See calls.golden for expected query results.
|
||||||
|
@ -13,6 +17,9 @@ func B(x *int) { // @pointsto pointsto-B-x "x"
|
||||||
// @callers callers-B "^"
|
// @callers callers-B "^"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
}
|
||||||
|
|
||||||
// apply is not (yet) treated context-sensitively.
|
// apply is not (yet) treated context-sensitively.
|
||||||
func apply(f func(x *int), x *int) {
|
func apply(f func(x *int), x *int) {
|
||||||
f(x) // @callees callees-apply "f"
|
f(x) // @callees callees-apply "f"
|
||||||
|
@ -70,6 +77,12 @@ func main() {
|
||||||
|
|
||||||
i = new(myint)
|
i = new(myint)
|
||||||
i.f() // @callees callees-not-a-wrapper "f"
|
i.f() // @callees callees-not-a-wrapper "f"
|
||||||
|
|
||||||
|
// statically dispatched calls. Handled specially by callees, so test that they work.
|
||||||
|
foo() // @callees callees-static-call "foo"
|
||||||
|
fmt.Println() // @callees callees-qualified-call "Println"
|
||||||
|
m := new(method)
|
||||||
|
m.f() // @callees callees-static-method-call "f"
|
||||||
}
|
}
|
||||||
|
|
||||||
type myint int
|
type myint int
|
||||||
|
@ -78,6 +91,11 @@ func (myint) f() {
|
||||||
// @callers callers-not-a-wrapper "^"
|
// @callers callers-not-a-wrapper "^"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type method int
|
||||||
|
|
||||||
|
func (method) f() {
|
||||||
|
}
|
||||||
|
|
||||||
var dynamic = func() {}
|
var dynamic = func() {}
|
||||||
|
|
||||||
func deadcode() {
|
func deadcode() {
|
||||||
|
|
|
@ -84,6 +84,18 @@ dynamic method call on nil value
|
||||||
this dynamic method call dispatches to:
|
this dynamic method call dispatches to:
|
||||||
(main.myint).f
|
(main.myint).f
|
||||||
|
|
||||||
|
-------- @callees callees-static-call --------
|
||||||
|
this static function call dispatches to:
|
||||||
|
main.foo
|
||||||
|
|
||||||
|
-------- @callees callees-qualified-call --------
|
||||||
|
this static function call dispatches to:
|
||||||
|
fmt.Println
|
||||||
|
|
||||||
|
-------- @callees callees-static-method-call --------
|
||||||
|
this static function call dispatches to:
|
||||||
|
(main.method).f
|
||||||
|
|
||||||
-------- @callers callers-not-a-wrapper --------
|
-------- @callers callers-not-a-wrapper --------
|
||||||
(main.myint).f is called from these 1 sites:
|
(main.myint).f is called from these 1 sites:
|
||||||
dynamic method call from main.main
|
dynamic method call from main.main
|
||||||
|
|
Loading…
Reference in New Issue