Skip to content

Commit fbcb449

Browse files
authored
Merge pull request #16817 from hvitved/csharp/multi-body-dataflow-dispatch
C#: Restrict multi-body dataflow dispatch based on file-system distance
2 parents 6dd52e4 + 0459422 commit fbcb449

File tree

13 files changed

+370
-96
lines changed

13 files changed

+370
-96
lines changed

csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll

Lines changed: 175 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.code.csharp.dispatch.Dispatch
88
private import semmle.code.csharp.dispatch.RuntimeCallable
99
private import semmle.code.csharp.frameworks.system.Collections
1010
private import semmle.code.csharp.frameworks.system.collections.Generic
11+
private import semmle.code.csharp.internal.Location
1112

1213
/**
1314
* Gets a source declaration of callable `c` that has a body and is
@@ -24,6 +25,21 @@ newtype TReturnKind =
2425
TOutReturnKind(int i) { i = any(Parameter p | p.isOut()).getPosition() } or
2526
TRefReturnKind(int i) { i = any(Parameter p | p.isRef()).getPosition() }
2627

28+
private predicate hasMultipleSourceLocations(Callable c) { strictcount(getASourceLocation(c)) > 1 }
29+
30+
private module NearestBodyLocationInput implements NearestLocationInputSig {
31+
class C = ControlFlowElement;
32+
33+
predicate relevantLocations(ControlFlowElement body, Location l1, Location l2) {
34+
exists(Callable c |
35+
hasMultipleSourceLocations(c) and
36+
l1 = getASourceLocation(c) and
37+
body = c.getBody() and
38+
l2 = body.getLocation()
39+
)
40+
}
41+
}
42+
2743
cached
2844
private module Cached {
2945
/**
@@ -33,7 +49,18 @@ private module Cached {
3349
*/
3450
cached
3551
newtype TDataFlowCallable =
36-
TCallable(Callable c) { c.isUnboundDeclaration() } or
52+
TCallable(Callable c, Location l) {
53+
c.isUnboundDeclaration() and
54+
l = [c.getLocation(), getASourceLocation(c)] and
55+
(
56+
not hasMultipleSourceLocations(c)
57+
or
58+
// when `c` has multiple source locations, only use those with a body;
59+
// for example, `partial` methods may have multiple source locations but
60+
// we are only interested in the one with a body
61+
NearestLocation<NearestBodyLocationInput>::nearestLocation(_, l, _)
62+
)
63+
} or
3764
TSummarizedCallable(FlowSummary::SummarizedCallable sc) or
3865
TFieldOrPropertyCallable(FieldOrProperty f) or
3966
TCapturedVariableCallable(LocalScopeVariable v) { v.isCaptured() }
@@ -82,17 +109,23 @@ private module DispatchImpl {
82109
*/
83110
predicate mayBenefitFromCallContext(DataFlowCall call) { mayBenefitFromCallContext(call, _) }
84111

112+
bindingset[dc, result]
113+
pragma[inline_late]
114+
private Callable viableImplInCallContext0(DispatchCall dc, NonDelegateDataFlowCall ctx) {
115+
result = dc.getADynamicTargetInCallContext(ctx.getDispatchCall()).getUnboundDeclaration()
116+
}
117+
85118
/**
86119
* Gets a viable dispatch target of `call` in the context `ctx`. This is
87120
* restricted to those `call`s for which a context might make a difference.
88121
*/
89122
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
90-
exists(DispatchCall dc | dc = call.(NonDelegateDataFlowCall).getDispatchCall() |
91-
result.getUnderlyingCallable() =
92-
getCallableForDataFlow(dc.getADynamicTargetInCallContext(ctx.(NonDelegateDataFlowCall)
93-
.getDispatchCall()).getUnboundDeclaration())
123+
exists(DispatchCall dc, Callable c | dc = call.(NonDelegateDataFlowCall).getDispatchCall() |
124+
result = call.getARuntimeTarget() and
125+
getCallableForDataFlow(c) = result.asCallable(_) and
126+
c = viableImplInCallContext0(dc, ctx)
94127
or
95-
exists(Callable c, DataFlowCallable encl |
128+
exists(DataFlowCallable encl |
96129
result.asSummarizedCallable() = c and
97130
mayBenefitFromCallContext(call, encl) and
98131
encl = ctx.getARuntimeTarget() and
@@ -159,7 +192,69 @@ class RefReturnKind extends OutRefReturnKind, TRefReturnKind {
159192
/** A callable used for data flow. */
160193
class DataFlowCallable extends TDataFlowCallable {
161194
/** Gets the underlying source code callable, if any. */
162-
Callable asCallable() { this = TCallable(result) }
195+
Callable asCallable(Location l) { this = TCallable(result, l) }
196+
197+
/** Holds if the underlying callable is multi-bodied. */
198+
pragma[nomagic]
199+
predicate isMultiBodied() {
200+
exists(Location l1, Location l2, DataFlowCallable other |
201+
this.asCallable(l1) = other.asCallable(l2) and
202+
l1 != l2
203+
)
204+
}
205+
206+
pragma[nomagic]
207+
private ControlFlow::Nodes::ElementNode getAMultiBodyEntryNode(ControlFlow::BasicBlock bb, int i) {
208+
this.isMultiBodied() and
209+
exists(ControlFlowElement body, Location l |
210+
body = this.asCallable(l).getBody() and
211+
NearestLocation<NearestBodyLocationInput>::nearestLocation(body, l, _) and
212+
result = body.getAControlFlowEntryNode()
213+
) and
214+
bb.getNode(i) = result
215+
}
216+
217+
pragma[nomagic]
218+
private ControlFlow::Nodes::ElementNode getAMultiBodyControlFlowNodePred() {
219+
result = this.getAMultiBodyEntryNode(_, _).getAPredecessor()
220+
or
221+
result = this.getAMultiBodyControlFlowNodePred().getAPredecessor()
222+
}
223+
224+
pragma[nomagic]
225+
private ControlFlow::Nodes::ElementNode getAMultiBodyControlFlowNodeSuccSameBasicBlock() {
226+
exists(ControlFlow::BasicBlock bb, int i, int j |
227+
exists(this.getAMultiBodyEntryNode(bb, i)) and
228+
result = bb.getNode(j) and
229+
j > i
230+
)
231+
}
232+
233+
pragma[nomagic]
234+
private ControlFlow::BasicBlock getAMultiBodyBasicBlockSucc() {
235+
result = this.getAMultiBodyEntryNode(_, _).getBasicBlock().getASuccessor()
236+
or
237+
result = this.getAMultiBodyBasicBlockSucc().getASuccessor()
238+
}
239+
240+
pragma[inline]
241+
private ControlFlow::Nodes::ElementNode getAMultiBodyControlFlowNode() {
242+
result =
243+
[
244+
this.getAMultiBodyEntryNode(_, _), this.getAMultiBodyControlFlowNodePred(),
245+
this.getAMultiBodyControlFlowNodeSuccSameBasicBlock(),
246+
this.getAMultiBodyBasicBlockSucc().getANode()
247+
]
248+
}
249+
250+
/** Gets a control flow node belonging to this callable. */
251+
pragma[inline]
252+
ControlFlow::Node getAControlFlowNode() {
253+
result = this.getAMultiBodyControlFlowNode()
254+
or
255+
not this.isMultiBodied() and
256+
result.getEnclosingCallable() = this.asCallable(_)
257+
}
163258

164259
/** Gets the underlying summarized callable, if any. */
165260
FlowSummary::SummarizedCallable asSummarizedCallable() { this = TSummarizedCallable(result) }
@@ -171,7 +266,7 @@ class DataFlowCallable extends TDataFlowCallable {
171266

172267
/** Gets the underlying callable. */
173268
Callable getUnderlyingCallable() {
174-
result = this.asCallable() or result = this.asSummarizedCallable()
269+
result = this.asCallable(_) or result = this.asSummarizedCallable()
175270
}
176271

177272
/** Gets a textual representation of this dataflow callable. */
@@ -185,7 +280,9 @@ class DataFlowCallable extends TDataFlowCallable {
185280

186281
/** Get the location of this dataflow callable. */
187282
Location getLocation() {
188-
result = this.getUnderlyingCallable().getLocation()
283+
this = TCallable(_, result)
284+
or
285+
result = this.asSummarizedCallable().getLocation()
189286
or
190287
result = this.asFieldOrProperty().getLocation()
191288
or
@@ -236,6 +333,26 @@ abstract class DataFlowCall extends TDataFlowCall {
236333
}
237334
}
238335

336+
private predicate relevantFolder(Folder f) {
337+
exists(NonDelegateDataFlowCall call, Location l | f = l.getFile().getParentContainer() |
338+
l = call.getLocation() and
339+
call.getARuntimeTargetCandidate(_, _).isMultiBodied()
340+
or
341+
call.getARuntimeTargetCandidate(l, _).isMultiBodied()
342+
)
343+
}
344+
345+
private predicate adjacentFolders(Folder f1, Folder f2) {
346+
f1 = f2.getParentContainer()
347+
or
348+
f2 = f1.getParentContainer()
349+
}
350+
351+
bindingset[f1, f2]
352+
pragma[inline_late]
353+
private predicate folderDist(Folder f1, Folder f2, int i) =
354+
shortestDistances(relevantFolder/1, adjacentFolders/2)(f1, f2, i)
355+
239356
/** A non-delegate C# call relevant for data flow. */
240357
class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
241358
private ControlFlow::Nodes::ElementNode cfn;
@@ -246,25 +363,63 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
246363
/** Gets the underlying call. */
247364
DispatchCall getDispatchCall() { result = dc }
248365

249-
override DataFlowCallable getARuntimeTarget() {
250-
result.asCallable() = getCallableForDataFlow(dc.getADynamicTarget())
251-
or
366+
pragma[nomagic]
367+
private predicate hasSourceTarget() { dc.getADynamicTarget().fromSource() }
368+
369+
pragma[nomagic]
370+
private FlowSummary::SummarizedCallable getASummarizedCallableTarget() {
252371
// Only use summarized callables with generated summaries in case
253372
// we are not able to dispatch to a source declaration.
254-
exists(FlowSummary::SummarizedCallable sc, boolean static |
255-
result.asSummarizedCallable() = sc and
256-
sc = this.getATarget(static) and
373+
exists(boolean static |
374+
result = this.getATarget(static) and
257375
not (
258-
sc.applyGeneratedModel() and
259-
dc.getADynamicTarget().getUnboundDeclaration().getFile().fromSource()
376+
result.applyGeneratedModel() and
377+
this.hasSourceTarget()
260378
)
261379
|
262380
static = false
263381
or
264-
static = true and not sc instanceof RuntimeCallable
382+
static = true and not result instanceof RuntimeCallable
383+
)
384+
}
385+
386+
pragma[nomagic]
387+
DataFlowCallable getARuntimeTargetCandidate(Location l, Callable c) {
388+
c = result.asCallable(l) and
389+
c = getCallableForDataFlow(dc.getADynamicTarget())
390+
}
391+
392+
pragma[nomagic]
393+
private DataFlowCallable getAMultiBodiedRuntimeTargetCandidate(Callable c, int distance) {
394+
result.isMultiBodied() and
395+
exists(Location l | result = this.getARuntimeTargetCandidate(l, c) |
396+
inSameFile(l, this.getLocation()) and
397+
distance = -1
398+
or
399+
folderDist(l.getFile().getParentContainer(),
400+
this.getLocation().getFile().getParentContainer(), distance)
265401
)
266402
}
267403

404+
pragma[nomagic]
405+
override DataFlowCallable getARuntimeTarget() {
406+
// For calls to multi-bodied methods, we restrict the viable targets to those
407+
// that are closest to the call site, measured by file-system distance.
408+
exists(Callable c |
409+
result =
410+
min(DataFlowCallable cand, int distance |
411+
cand = this.getAMultiBodiedRuntimeTargetCandidate(c, distance)
412+
|
413+
cand order by distance
414+
)
415+
)
416+
or
417+
result = this.getARuntimeTargetCandidate(_, _) and
418+
not result.isMultiBodied()
419+
or
420+
result.asSummarizedCallable() = this.getASummarizedCallableTarget()
421+
}
422+
268423
/** Gets a static or dynamic target of this call. */
269424
Callable getATarget(boolean static) {
270425
result = dc.getADynamicTarget().getUnboundDeclaration() and static = false
@@ -276,9 +431,7 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
276431

277432
override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }
278433

279-
override DataFlowCallable getEnclosingCallable() {
280-
result.asCallable() = cfn.getEnclosingCallable()
281-
}
434+
override DataFlowCallable getEnclosingCallable() { result.getAControlFlowNode() = cfn }
282435

283436
override string toString() { result = cfn.toString() }
284437

@@ -306,9 +459,7 @@ class ExplicitDelegateLikeDataFlowCall extends DelegateDataFlowCall, TExplicitDe
306459

307460
override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }
308461

309-
override DataFlowCallable getEnclosingCallable() {
310-
result.asCallable() = cfn.getEnclosingCallable()
311-
}
462+
override DataFlowCallable getEnclosingCallable() { result.getAControlFlowNode() = cfn }
312463

313464
override string toString() { result = cfn.toString() }
314465

0 commit comments

Comments
 (0)