Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 759e3d5

Browse files
author
Max Schaefer
committed
Further refine potential call targets for interface calls.
The call target must belong to the method set of a type that implements the interface type of the method call receiver, if any. For example, assume `h` has type `hash.Hash`, then `h.Write(...)` should only be resolved to implementations of `Write` in types implementing `hash.Hash`, not arbitrary other `Writer`s.
1 parent 1c58028 commit 759e3d5

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,36 @@ private FuncDecl getConcreteTarget(DataFlow::CallNode call) {
6363
)
6464
}
6565

66+
/**
67+
* Holds if `call` is a method call whose receiver has an interface type.
68+
*/
69+
private predicate isInterfaceMethodCall(DataFlow::CallNode call) {
70+
isInterfaceCallReceiver(call, _, _, _)
71+
}
72+
73+
/**
74+
* Gets a method that might be called by `call`, where we restrict the result to
75+
* implement the interface type of the receiver of `call`.
76+
*/
77+
private MethodDecl getRestrictedInterfaceTarget(DataFlow::CallNode call) {
78+
exists(InterfaceType tp, Type recvtp, string m |
79+
isInterfaceCallReceiver(call, _, tp, m) and
80+
result = recvtp.getMethod(m).(DeclaredFunction).getFuncDecl() and
81+
recvtp.implements(tp)
82+
)
83+
}
84+
6685
/**
6786
* Gets a function that might be called by `call`.
6887
*/
6988
DataFlowCallable viableCallable(CallExpr ma) {
7089
exists(DataFlow::CallNode call | call.asExpr() = ma |
7190
if isConcreteInterfaceCall(call, _, _)
7291
then result = getConcreteTarget(call)
73-
else result = call.getACallee()
92+
else
93+
if isInterfaceMethodCall(call)
94+
then result = getRestrictedInterfaceTarget(call)
95+
else result = call.getACallee()
7496
)
7597
}
7698

ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ spuriousCallee
33
| main.go:44:3:44:7 | call to m | main.go:17:1:17:17 | function declaration |
44
| main.go:44:3:44:7 | call to m | main.go:21:1:21:20 | function declaration |
55
| main.go:56:2:56:6 | call to m | main.go:21:1:21:20 | function declaration |
6+
| test.go:42:2:42:13 | call to Write | test.go:36:1:39:1 | function declaration |
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"hash"
6+
"io"
7+
)
8+
9+
type Resetter struct{}
10+
11+
func (_ Resetter) Reset() {} // name: Resetter.Reset
12+
13+
type MockHash struct {
14+
Resetter
15+
}
16+
17+
func (_ MockHash) Write(p []byte) (n int, err error) { // name: MockHash.Write
18+
fmt.Println("MockHash.Write")
19+
return 0, nil
20+
}
21+
22+
func (_ MockHash) Sum(b []byte) []byte {
23+
return nil
24+
}
25+
26+
func (_ MockHash) Size() int {
27+
return 0
28+
}
29+
30+
func (_ MockHash) BlockSize() int {
31+
return 0
32+
}
33+
34+
type MockWriter struct{}
35+
36+
func (_ MockWriter) Write(p []byte) (n int, err error) { // name: MockWriter.Write
37+
fmt.Println("MockWriter.Write")
38+
return 0, nil
39+
}
40+
41+
func test5(h hash.Hash, w io.Writer) { // name: test5
42+
h.Write(nil) // callee: MockHash.Write
43+
w.Write(nil) // callee: MockWriter.Write callee: MockHash.Write
44+
h.Reset() // callee: Resetter.Reset
45+
}
46+
47+
func test6(h MockHash, w MockWriter) {
48+
test5(h, w) // callee: test5
49+
}

0 commit comments

Comments
 (0)