Skip to content

Commit 4ee3f2c

Browse files
authored
Merge pull request #2139 from hvitved/csharp/dataflow/callcontext-bool-pruning
C#: Data-flow pruning based on call contexts
2 parents c0fdcf3 + c57015a commit 4ee3f2c

File tree

4 files changed

+380
-1
lines changed

4 files changed

+380
-1
lines changed

csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ private import cil
33
private import dotnet
44
private import DataFlowPublic
55
private import DataFlowDispatch
6+
private import DataFlowImplCommon::Public
67
private import ControlFlowReachability
78
private import DelegateDataFlow
89
private import semmle.code.csharp.Caching
910
private import semmle.code.csharp.ExprOrStmtParent
11+
private import semmle.code.csharp.controlflow.Guards
1012
private import semmle.code.csharp.dataflow.LibraryTypeDataFlow
1113
private import semmle.code.csharp.dispatch.Dispatch
1214
private import semmle.code.csharp.frameworks.EntityFramework
@@ -439,6 +441,22 @@ private module Cached {
439441
c.(FieldLikeContent).getField() = node2.asExpr().(FieldLikeRead).getTarget()
440442
)
441443
}
444+
445+
/**
446+
* Holds if the node `n` is unreachable when the call context is `call`.
447+
*/
448+
cached
449+
predicate isUnreachableInCall(Node n, DataFlowCall call) {
450+
exists(
451+
SsaDefinitionNode paramNode, Ssa::ExplicitDefinition param, Guard guard,
452+
ControlFlow::SuccessorTypes::BooleanSuccessor bs
453+
|
454+
viableConstantBooleanParamArg(paramNode, bs.getValue().booleanNot(), call) and
455+
paramNode.getDefinition() = param and
456+
param.getARead() = guard and
457+
guard.controlsBlock(n.getControlFlowNode().getBasicBlock(), bs)
458+
)
459+
}
442460
}
443461

444462
import Cached
@@ -1358,4 +1376,31 @@ class DataFlowType = DotNet::Type;
13581376

13591377
class DataFlowLocation = Location;
13601378

1361-
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub implementation
1379+
/** Holds if `e` is an expression that always has the same Boolean value `val`. */
1380+
private predicate constantBooleanExpr(Expr e, boolean val) {
1381+
e = any(AbstractValues::BooleanValue bv | val = bv.getValue()).getAnExpr()
1382+
or
1383+
exists(Ssa::ExplicitDefinition def, Expr src |
1384+
e = def.getARead() and
1385+
src = def.getADefinition().getSource() and
1386+
constantBooleanExpr(src, val)
1387+
)
1388+
}
1389+
1390+
/** An argument that always has the same Boolean value. */
1391+
private class ConstantBooleanArgumentNode extends ExprNode {
1392+
ConstantBooleanArgumentNode() { constantBooleanExpr(this.(ArgumentNode).asExpr(), _) }
1393+
1394+
/** Gets the Boolean value of this expression. */
1395+
boolean getBooleanValue() { constantBooleanExpr(this.getExpr(), result) }
1396+
}
1397+
1398+
pragma[noinline]
1399+
private predicate viableConstantBooleanParamArg(
1400+
SsaDefinitionNode paramNode, boolean b, DataFlowCall call
1401+
) {
1402+
exists(ConstantBooleanArgumentNode arg |
1403+
viableParamArg(call, paramNode, arg) and
1404+
b = arg.getBooleanValue()
1405+
)
1406+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
public class A
2+
{
3+
public static void Sink(object o)
4+
{
5+
}
6+
7+
public object FlowThrough(object o, bool cond)
8+
{
9+
if (cond)
10+
{
11+
return o;
12+
}
13+
else
14+
{
15+
return null;
16+
}
17+
}
18+
19+
public void CallSinkIfTrue(object o, bool cond)
20+
{
21+
if (cond)
22+
{
23+
Sink(o);
24+
}
25+
}
26+
27+
public void CallSinkIfFalse(object o, bool cond)
28+
{
29+
if (!cond)
30+
{
31+
Sink(o);
32+
}
33+
}
34+
35+
public void CallSinkFromLoop(object o, bool cond)
36+
{
37+
while (cond)
38+
{
39+
Sink(o);
40+
}
41+
}
42+
43+
public void LocalCallSensitivity(object o, bool c)
44+
{
45+
object o1 = o;
46+
object o2 = null;
47+
if (c)
48+
{
49+
object tmp = o1;
50+
o2 = 1 == 1 ? (tmp) : (tmp);
51+
}
52+
object o3 = o2;
53+
Sink(o3);
54+
}
55+
56+
public void LocalCallSensitivity2(object o, bool b, bool c)
57+
{
58+
object o1 = o;
59+
object o2 = null;
60+
if (b || c)
61+
{
62+
object tmp = o1;
63+
o2 = 1 == 1 ? (tmp) : (tmp);
64+
}
65+
object o3 = o2;
66+
Sink(o3);
67+
}
68+
69+
public void M1()
70+
{
71+
// should not exhibit flow
72+
CallSinkIfTrue(new object(), false);
73+
CallSinkIfFalse(new object(), true);
74+
CallSinkFromLoop(new object(), false);
75+
LocalCallSensitivity(new object(), false);
76+
Sink(FlowThrough(new object(), false));
77+
// should exhibit flow
78+
CallSinkIfTrue(new object(), true);
79+
CallSinkIfFalse(new object(), false);
80+
CallSinkFromLoop(new object(), true);
81+
LocalCallSensitivity(new object(), true);
82+
LocalCallSensitivity2(new object(), true, true);
83+
LocalCallSensitivity2(new object(), false, true);
84+
LocalCallSensitivity2(new object(), true, false);
85+
Sink(FlowThrough(new object(), true));
86+
// expected false positive
87+
LocalCallSensitivity2(new object(), false, false);
88+
}
89+
90+
public void M2()
91+
{
92+
bool t = true;
93+
bool f = false;
94+
// should not exhibit flow
95+
CallSinkIfTrue(new object(), f);
96+
CallSinkIfFalse(new object(), t);
97+
CallSinkFromLoop(new object(), f);
98+
LocalCallSensitivity(new object(), f);
99+
Sink(FlowThrough(new object(), f));
100+
// should exhibit flow
101+
CallSinkIfTrue(new object(), t);
102+
CallSinkIfFalse(new object(), f);
103+
CallSinkFromLoop(new object(), t);
104+
LocalCallSensitivity(new object(), t);
105+
Sink(FlowThrough(new object(), t));
106+
}
107+
108+
public void M3(InterfaceA b)
109+
{
110+
bool t = true;
111+
bool f = false;
112+
// should not exhibit flow
113+
b.CallSinkIfTrue(new object(), f);
114+
b.CallSinkIfFalse(new object(), t);
115+
b.LocalCallSensitivity(new object(), f);
116+
// should exhibit flow
117+
b.CallSinkIfTrue(new object(), t);
118+
b.CallSinkIfFalse(new object(), f);
119+
b.LocalCallSensitivity(new object(), t);
120+
}
121+
122+
class B : InterfaceA
123+
{
124+
public void CallSinkIfTrue(object o, bool cond)
125+
{
126+
if (cond)
127+
{
128+
Sink(o);
129+
}
130+
}
131+
132+
133+
public void CallSinkIfFalse(object o, bool cond)
134+
{
135+
if (!cond)
136+
{
137+
Sink(o);
138+
}
139+
}
140+
141+
142+
public void LocalCallSensitivity(object o, bool c)
143+
{
144+
object o1 = o;
145+
object o2 = null;
146+
if (c)
147+
{
148+
object tmp = o1;
149+
o2 = 1 == 1 ? (tmp) : (tmp);
150+
}
151+
object o3 = o2;
152+
Sink(o3);
153+
}
154+
}
155+
}
156+
157+
public class A2
158+
{
159+
160+
public static void Sink(object o)
161+
{
162+
}
163+
164+
public void M()
165+
{
166+
167+
}
168+
169+
public void Callsite(InterfaceB intF)
170+
{
171+
B b = new B();
172+
// in both possible implementations of foo, this callsite is relevant
173+
// in IntA, it improves virtual dispatch,
174+
// and in IntB, it improves the dataflow analysis.
175+
intF.Foo(b, new object(), false);
176+
}
177+
178+
private class B : A2
179+
{
180+
public void M()
181+
{
182+
183+
}
184+
}
185+
186+
private class IntA : InterfaceB
187+
{
188+
189+
public void Foo(A2 obj, object o, bool cond)
190+
{
191+
obj.M();
192+
Sink(o);
193+
}
194+
}
195+
196+
private class IntB : InterfaceB
197+
{
198+
199+
public void Foo(A2 obj, object o, bool cond)
200+
{
201+
if (cond)
202+
{
203+
Sink(o);
204+
}
205+
}
206+
}
207+
208+
}
209+
210+
public interface InterfaceA
211+
{
212+
void CallSinkIfTrue(object o, bool cond);
213+
void CallSinkIfFalse(object o, bool cond);
214+
void LocalCallSensitivity(object o, bool c);
215+
}
216+
217+
public interface InterfaceB
218+
{
219+
void Foo(A2 a, object o, bool cond);
220+
}

0 commit comments

Comments
 (0)