go/callgraph/cha: fix bug computing correspondence of abstract/concrete methods
In a dynamic interface method call x.f() where x has type I, it is not sound to assume that the receiver of the types.Signature for types.Func f has type I, as Func objects for abstract methods may be shared by multiple interfaces. Fixes golang/go#23925 Change-Id: I755e3010d1310480c46855e072946346626b3e59 Reviewed-on: https://go-review.googlesource.com/95697 Reviewed-by: Robert Griesemer <gri@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
5e776fee60
commit
2c62430689
|
@ -47,23 +47,36 @@ func CallGraph(prog *ssa.Program) *callgraph.Graph {
|
||||||
|
|
||||||
// methodsByName contains all methods,
|
// methodsByName contains all methods,
|
||||||
// grouped by name for efficient lookup.
|
// grouped by name for efficient lookup.
|
||||||
|
// (methodsById would be better but not every SSA method has a go/types ID.)
|
||||||
methodsByName := make(map[string][]*ssa.Function)
|
methodsByName := make(map[string][]*ssa.Function)
|
||||||
|
|
||||||
// methodsMemo records, for every abstract method call call I.f on
|
// An imethod represents an interface method I.m.
|
||||||
// interface type I, the set of concrete methods C.f of all
|
// (There's no go/types object for it;
|
||||||
|
// a *types.Func may be shared by many interfaces due to interface embedding.)
|
||||||
|
type imethod struct {
|
||||||
|
I *types.Interface
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
// methodsMemo records, for every abstract method call I.m on
|
||||||
|
// interface type I, the set of concrete methods C.m of all
|
||||||
// types C that satisfy interface I.
|
// types C that satisfy interface I.
|
||||||
methodsMemo := make(map[*types.Func][]*ssa.Function)
|
//
|
||||||
lookupMethods := func(m *types.Func) []*ssa.Function {
|
// Abstract methods may be shared by several interfaces,
|
||||||
methods, ok := methodsMemo[m]
|
// hence we must pass I explicitly, not guess from m.
|
||||||
|
//
|
||||||
|
// methodsMemo is just a cache, so it needn't be a typeutil.Map.
|
||||||
|
methodsMemo := make(map[imethod][]*ssa.Function)
|
||||||
|
lookupMethods := func(I *types.Interface, m *types.Func) []*ssa.Function {
|
||||||
|
id := m.Id()
|
||||||
|
methods, ok := methodsMemo[imethod{I, id}]
|
||||||
if !ok {
|
if !ok {
|
||||||
I := m.Type().(*types.Signature).Recv().Type().Underlying().(*types.Interface)
|
|
||||||
for _, f := range methodsByName[m.Name()] {
|
for _, f := range methodsByName[m.Name()] {
|
||||||
C := f.Signature.Recv().Type() // named or *named
|
C := f.Signature.Recv().Type() // named or *named
|
||||||
if types.Implements(C, I) {
|
if types.Implements(C, I) {
|
||||||
methods = append(methods, f)
|
methods = append(methods, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
methodsMemo[m] = methods
|
methodsMemo[imethod{I, id}] = methods
|
||||||
}
|
}
|
||||||
return methods
|
return methods
|
||||||
}
|
}
|
||||||
|
@ -109,7 +122,8 @@ func CallGraph(prog *ssa.Program) *callgraph.Graph {
|
||||||
if site, ok := instr.(ssa.CallInstruction); ok {
|
if site, ok := instr.(ssa.CallInstruction); ok {
|
||||||
call := site.Common()
|
call := site.Common()
|
||||||
if call.IsInvoke() {
|
if call.IsInvoke() {
|
||||||
addEdges(fnode, site, lookupMethods(call.Method))
|
tiface := call.Value.Type().Underlying().(*types.Interface)
|
||||||
|
addEdges(fnode, site, lookupMethods(tiface, call.Method))
|
||||||
} else if g := call.StaticCallee(); g != nil {
|
} else if g := call.StaticCallee(); g != nil {
|
||||||
addEdge(fnode, site, g)
|
addEdge(fnode, site, g)
|
||||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||||
|
|
|
@ -30,6 +30,7 @@ var inputs = []string{
|
||||||
"testdata/func.go",
|
"testdata/func.go",
|
||||||
"testdata/iface.go",
|
"testdata/iface.go",
|
||||||
"testdata/recv.go",
|
"testdata/recv.go",
|
||||||
|
"testdata/issue23925.go",
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectation(f *ast.File) (string, token.Pos) {
|
func expectation(f *ast.File) (string, token.Pos) {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Regression test for https://github.com/golang/go/issues/23925
|
||||||
|
|
||||||
|
type stringFlagImpl string
|
||||||
|
|
||||||
|
func (*stringFlagImpl) Set(s string) error { return nil }
|
||||||
|
|
||||||
|
type boolFlagImpl bool
|
||||||
|
|
||||||
|
func (*boolFlagImpl) Set(s string) error { return nil }
|
||||||
|
func (*boolFlagImpl) extra() {}
|
||||||
|
|
||||||
|
// A copy of flag.boolFlag interface, without a dependency.
|
||||||
|
// Must appear first, so that it becomes the owner of the Set methods.
|
||||||
|
type boolFlag interface {
|
||||||
|
flagValue
|
||||||
|
extra()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A copy of flag.Value, without adding a dependency.
|
||||||
|
type flagValue interface {
|
||||||
|
Set(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var x flagValue = new(stringFlagImpl)
|
||||||
|
x.Set("")
|
||||||
|
|
||||||
|
var y boolFlag = new(boolFlagImpl)
|
||||||
|
y.Set("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WANT:
|
||||||
|
// Dynamic calls
|
||||||
|
// main --> (*boolFlagImpl).Set
|
||||||
|
// main --> (*boolFlagImpl).Set
|
||||||
|
// main --> (*stringFlagImpl).Set
|
Loading…
Reference in New Issue