Skip to content

Commit 0f24db8

Browse files
committed
C#: Improve performance of SsaImpl::CallGraph::SimpleDelegateAnalysis
1 parent 972cc47 commit 0f24db8

File tree

1 file changed

+121
-31
lines changed
  • csharp/ql/src/semmle/code/csharp/dataflow/internal

1 file changed

+121
-31
lines changed

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

Lines changed: 121 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -268,56 +268,146 @@ private module CallGraph {
268268
)
269269
}
270270

271+
/**
272+
* A simple flow step that does not take flow through fields or flow out
273+
* of callables into account.
274+
*/
275+
pragma[nomagic]
271276
private predicate delegateFlowStep(Expr pred, Expr succ) {
272277
Steps::stepClosed(pred, succ)
273278
or
274-
exists(Call call, Callable callable |
275-
callable.getUnboundDeclaration().canReturn(pred) and
276-
call = succ
277-
|
278-
callable = call.getTarget() or
279-
callable = call.getTarget().(Method).getAnOverrider+() or
280-
callable = call.getTarget().(Method).getAnUltimateImplementor() or
281-
callable = getARuntimeDelegateTarget(call, false)
282-
)
283-
or
284279
pred = succ.(DelegateCreation).getArgument()
285280
or
286-
exists(AssignableDefinition def, Assignable a |
287-
a instanceof Field or
288-
a instanceof Property
289-
|
290-
a = def.getTarget() and
291-
succ.(AssignableRead) = a.getAnAccess() and
292-
pred = def.getSource()
293-
)
294-
or
295281
exists(AddEventExpr ae | succ.(EventAccess).getTarget() = ae.getTarget() |
296282
pred = ae.getRValue()
297283
)
298284
}
299285

300-
private predicate reachesDelegateCall(Expr e) {
301-
delegateCall(_, e, _)
286+
private predicate delegateCreationReaches(Callable c, Expr e) {
287+
delegateCreation(e, c, _)
302288
or
303-
exists(Expr mid | reachesDelegateCall(mid) | delegateFlowStep(e, mid))
289+
exists(Expr mid |
290+
delegateCreationReaches(c, mid) and
291+
delegateFlowStep(mid, e)
292+
)
304293
}
305294

306-
pragma[nomagic]
307-
private predicate delegateFlowStepReaches(Expr pred, Expr succ) {
308-
delegateFlowStep(pred, succ) and
309-
reachesDelegateCall(succ)
295+
private predicate reachesDelegateCall(Expr e, Call c, boolean libraryDelegateCall) {
296+
delegateCall(c, e, libraryDelegateCall)
297+
or
298+
exists(Expr mid |
299+
reachesDelegateCall(mid, c, libraryDelegateCall) and
300+
delegateFlowStep(e, mid)
301+
)
310302
}
311303

312-
private Expr delegateCallSource(Callable c) {
313-
delegateCreation(result, c, _)
314-
or
315-
delegateFlowStepReaches(delegateCallSource(c), result)
304+
/**
305+
* A "busy" flow element, that is, a node in the data-flow graph that typically
306+
* has a large fan-in or a large fan-out (or both).
307+
*
308+
* For such busy elements, we do not track flow directly from all delegate
309+
* creations, but instead we first perform a flow analysis between busy elements,
310+
* and then only in the end join up with delegate creations and delegate calls.
311+
*/
312+
abstract private class BusyFlowElement extends Element {
313+
pragma[nomagic]
314+
abstract Expr getAnInput();
315+
316+
pragma[nomagic]
317+
abstract Expr getAnOutput();
318+
319+
/** Holds if this element can be reached from expression `e`. */
320+
pragma[nomagic]
321+
private predicate exprReaches(Expr e) {
322+
this.reachesCall(_) and
323+
e = this.getAnInput()
324+
or
325+
exists(Expr mid |
326+
this.exprReaches(mid) and
327+
delegateFlowStep(e, mid)
328+
)
329+
}
330+
331+
/**
332+
* Holds if this element can reach a delegate call `c` directly without
333+
* passing through another busy element.
334+
*/
335+
pragma[nomagic]
336+
predicate delegateCall(Call c, boolean libraryDelegateCall) {
337+
reachesDelegateCall(this.getAnOutput(), c, libraryDelegateCall)
338+
}
339+
340+
pragma[nomagic]
341+
private BusyFlowElement getASuccessor() { result.exprReaches(this.getAnOutput()) }
342+
343+
/**
344+
* Holds if this element reaches another busy element `other`,
345+
* which can reach a delegate call directly without passing
346+
* through another busy element.
347+
*/
348+
pragma[nomagic]
349+
private predicate reachesCall(BusyFlowElement other) {
350+
this = other and
351+
other.delegateCall(_, _)
352+
or
353+
this.getASuccessor().reachesCall(other)
354+
}
355+
356+
/** Holds if this element is reached by a delegate creation for `c`. */
357+
pragma[nomagic]
358+
predicate isReachedBy(Callable c) {
359+
exists(BusyFlowElement pred |
360+
pred.reachesCall(this) and
361+
delegateCreationReaches(c, pred.getAnInput())
362+
)
363+
}
364+
}
365+
366+
private class TFieldOrProperty = @field or @property;
367+
368+
private class FieldOrPropertyFlow extends BusyFlowElement, Assignable, TFieldOrProperty {
369+
final override Expr getAnInput() {
370+
exists(Assignable target |
371+
target = this.getUnboundDeclaration() and
372+
result = target.getAnAssignedValue()
373+
)
374+
}
375+
376+
final override AssignableRead getAnOutput() {
377+
exists(Assignable target |
378+
target = this.getUnboundDeclaration() and
379+
result = target.getAnAccess()
380+
)
381+
}
382+
}
383+
384+
private class CallOutputFlow extends BusyFlowElement, Callable {
385+
final override Expr getAnInput() { this.canReturn(result) }
386+
387+
final override Call getAnOutput() {
388+
exists(Callable target | this = target.getUnboundDeclaration() |
389+
target = result.getTarget() or
390+
target = result.getTarget().(Method).getAnOverrider+() or
391+
target = result.getTarget().(Method).getAnUltimateImplementor() or
392+
target = getARuntimeDelegateTarget(result, false)
393+
)
394+
}
316395
}
317396

318397
/** Gets a run-time target for the delegate call `c`. */
398+
pragma[nomagic]
319399
Callable getARuntimeDelegateTarget(Call c, boolean libraryDelegateCall) {
320-
delegateCall(c, delegateCallSource(result), libraryDelegateCall)
400+
// directly resolvable without going through a "busy" element
401+
exists(Expr e |
402+
delegateCreationReaches(result, e) and
403+
delegateCall(c, e, libraryDelegateCall)
404+
)
405+
or
406+
// resolvable by going through one or more "busy" elements
407+
exists(BusyFlowElement busy |
408+
busy.isReachedBy(result) and
409+
busy.delegateCall(c, libraryDelegateCall)
410+
)
321411
}
322412
}
323413

0 commit comments

Comments
 (0)