Skip to content

Commit 0459422

Browse files
committed
C#: Restrict multi-body dataflow dispatch based on file-system distance
1 parent 35bf990 commit 0459422

File tree

8 files changed

+328
-104
lines changed

8 files changed

+328
-104
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
@@ -256,6 +353,26 @@ abstract class DataFlowCall extends TDataFlowCall {
256353
}
257354
}
258355

356+
private predicate relevantFolder(Folder f) {
357+
exists(NonDelegateDataFlowCall call, Location l | f = l.getFile().getParentContainer() |
358+
l = call.getLocation() and
359+
call.getARuntimeTargetCandidate(_, _).isMultiBodied()
360+
or
361+
call.getARuntimeTargetCandidate(l, _).isMultiBodied()
362+
)
363+
}
364+
365+
private predicate adjacentFolders(Folder f1, Folder f2) {
366+
f1 = f2.getParentContainer()
367+
or
368+
f2 = f1.getParentContainer()
369+
}
370+
371+
bindingset[f1, f2]
372+
pragma[inline_late]
373+
private predicate folderDist(Folder f1, Folder f2, int i) =
374+
shortestDistances(relevantFolder/1, adjacentFolders/2)(f1, f2, i)
375+
259376
/** A non-delegate C# call relevant for data flow. */
260377
class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
261378
private ControlFlow::Nodes::ElementNode cfn;
@@ -266,25 +383,63 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
266383
/** Gets the underlying call. */
267384
DispatchCall getDispatchCall() { result = dc }
268385

269-
override DataFlowCallable getARuntimeTarget() {
270-
result.asCallable() = getCallableForDataFlow(dc.getADynamicTarget())
271-
or
386+
pragma[nomagic]
387+
private predicate hasSourceTarget() { dc.getADynamicTarget().fromSource() }
388+
389+
pragma[nomagic]
390+
private FlowSummary::SummarizedCallable getASummarizedCallableTarget() {
272391
// Only use summarized callables with generated summaries in case
273392
// we are not able to dispatch to a source declaration.
274-
exists(FlowSummary::SummarizedCallable sc, boolean static |
275-
result.asSummarizedCallable() = sc and
276-
sc = this.getATarget(static) and
393+
exists(boolean static |
394+
result = this.getATarget(static) and
277395
not (
278-
sc.applyGeneratedModel() and
279-
dc.getADynamicTarget().getUnboundDeclaration().getFile().fromSource()
396+
result.applyGeneratedModel() and
397+
this.hasSourceTarget()
280398
)
281399
|
282400
static = false
283401
or
284-
static = true and not sc instanceof RuntimeCallable
402+
static = true and not result instanceof RuntimeCallable
403+
)
404+
}
405+
406+
pragma[nomagic]
407+
DataFlowCallable getARuntimeTargetCandidate(Location l, Callable c) {
408+
c = result.asCallable(l) and
409+
c = getCallableForDataFlow(dc.getADynamicTarget())
410+
}
411+
412+
pragma[nomagic]
413+
private DataFlowCallable getAMultiBodiedRuntimeTargetCandidate(Callable c, int distance) {
414+
result.isMultiBodied() and
415+
exists(Location l | result = this.getARuntimeTargetCandidate(l, c) |
416+
inSameFile(l, this.getLocation()) and
417+
distance = -1
418+
or
419+
folderDist(l.getFile().getParentContainer(),
420+
this.getLocation().getFile().getParentContainer(), distance)
285421
)
286422
}
287423

424+
pragma[nomagic]
425+
override DataFlowCallable getARuntimeTarget() {
426+
// For calls to multi-bodied methods, we restrict the viable targets to those
427+
// that are closest to the call site, measured by file-system distance.
428+
exists(Callable c |
429+
result =
430+
min(DataFlowCallable cand, int distance |
431+
cand = this.getAMultiBodiedRuntimeTargetCandidate(c, distance)
432+
|
433+
cand order by distance
434+
)
435+
)
436+
or
437+
result = this.getARuntimeTargetCandidate(_, _) and
438+
not result.isMultiBodied()
439+
or
440+
result.asSummarizedCallable() = this.getASummarizedCallableTarget()
441+
}
442+
288443
/** Gets a static or dynamic target of this call. */
289444
Callable getATarget(boolean static) {
290445
result = dc.getADynamicTarget().getUnboundDeclaration() and static = false
@@ -296,9 +451,7 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
296451

297452
override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }
298453

299-
override DataFlowCallable getEnclosingCallable() {
300-
result.asCallable() = cfn.getEnclosingCallable()
301-
}
454+
override DataFlowCallable getEnclosingCallable() { result.getAControlFlowNode() = cfn }
302455

303456
override string toString() { result = cfn.toString() }
304457

@@ -326,9 +479,7 @@ class ExplicitDelegateLikeDataFlowCall extends DelegateDataFlowCall, TExplicitDe
326479

327480
override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }
328481

329-
override DataFlowCallable getEnclosingCallable() {
330-
result.asCallable() = cfn.getEnclosingCallable()
331-
}
482+
override DataFlowCallable getEnclosingCallable() { result.getAControlFlowNode() = cfn }
332483

333484
override string toString() { result = cfn.toString() }
334485

0 commit comments

Comments
 (0)