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

Commit d8374ad

Browse files
authored
Merge pull request #219 from max-schaefer/refine-virtual-dispatch
Refine potential targets for method call through interface
2 parents b2ea236 + 1f68a32 commit d8374ad

File tree

5 files changed

+98
-7
lines changed

5 files changed

+98
-7
lines changed

change-notes/2020-06-19-call-graph.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Resolution of method calls through interfaces has been improved, resulting in more precise call-graph information, which in turn may eliminate false positives from the security queries.

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@ private import go
22
private import DataFlowPrivate
33

44
/**
5-
* Holds if `call` is an interface call to method `m`, meaning that its receiver `recv` has an
6-
* interface type.
5+
* Holds if `call` is an interface call to method `m`, meaning that its receiver `recv` has
6+
* interface type `tp`.
77
*/
8-
private predicate isInterfaceCallReceiver(DataFlow::CallNode call, DataFlow::Node recv, string m) {
8+
private predicate isInterfaceCallReceiver(
9+
DataFlow::CallNode call, DataFlow::Node recv, InterfaceType tp, string m
10+
) {
911
call.getReceiver() = recv and
10-
recv.getType().getUnderlyingType() instanceof InterfaceType and
12+
recv.getType().getUnderlyingType() = tp and
1113
m = call.getCalleeName()
1214
}
1315

1416
/** Gets a data-flow node that may flow into the receiver value of `call`, which is an interface value. */
1517
private DataFlow::Node getInterfaceCallReceiverSource(DataFlow::CallNode call) {
16-
isInterfaceCallReceiver(call, result.getASuccessor*(), _)
18+
isInterfaceCallReceiver(call, result.getASuccessor*(), _, _)
1719
}
1820

1921
/** Gets the type of `nd`, which must be a valid type and not an interface type. */
@@ -44,7 +46,7 @@ private predicate isConcreteValue(DataFlow::Node nd) {
4446
* types of `recv` can be established by local reasoning.
4547
*/
4648
private predicate isConcreteInterfaceCall(DataFlow::Node call, DataFlow::Node recv, string m) {
47-
isInterfaceCallReceiver(call, recv, m) and isConcreteValue(recv)
49+
isInterfaceCallReceiver(call, recv, _, m) and isConcreteValue(recv)
4850
}
4951

5052
/**
@@ -61,14 +63,36 @@ private FuncDecl getConcreteTarget(DataFlow::CallNode call) {
6163
)
6264
}
6365

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+
6485
/**
6586
* Gets a function that might be called by `call`.
6687
*/
6788
DataFlowCallable viableCallable(CallExpr ma) {
6889
exists(DataFlow::CallNode call | call.asExpr() = ma |
6990
if isConcreteInterfaceCall(call, _, _)
7091
then result = getConcreteTarget(call)
71-
else result = call.getACallee()
92+
else
93+
if isInterfaceMethodCall(call)
94+
then result = getRestrictedInterfaceTarget(call)
95+
else result = call.getACallee()
7296
)
7397
}
7498

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ class Node extends TNode {
6464
endcolumn = 0
6565
}
6666

67+
/** Gets the file in which this node appears. */
68+
File getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
69+
70+
/** Gets the start line of the location of this node. */
71+
int getStartLine() { hasLocationInfo(_, result, _, _, _) }
72+
73+
/** Gets the start column of the location of this node. */
74+
int getStartColumn() { hasLocationInfo(_, _, result, _, _) }
75+
76+
/** Gets the end line of the location of this node. */
77+
int getEndLine() { hasLocationInfo(_, _, _, result, _) }
78+
79+
/** Gets the end column of the location of this node. */
80+
int getEndColumn() { hasLocationInfo(_, _, _, _, result) }
81+
6782
/**
6883
* Gets an upper bound on the type of this node.
6984
*/

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)