Skip to content

Commit 333780e

Browse files
authored
Merge pull request #8898 from hvitved/dataflow/clear-expect-summary-components
Data flow: Introduce 'with/without content' summary components
2 parents 46f309c + 04cc738 commit 333780e

File tree

9 files changed

+283
-180
lines changed

9 files changed

+283
-180
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
421421
or
422422
exists(Ssa::Definition def |
423423
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
424-
not FlowSummaryImpl::Private::Steps::summaryClearsContentArg(nodeFrom, _) and
424+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom) and
425425
not LocalFlow::usesInstanceField(def)
426426
)
427427
or
@@ -1718,7 +1718,9 @@ predicate clearsContent(Node n, Content c) {
17181718
* Holds if the value that is being tracked is expected to be stored inside content `c`
17191719
* at node `n`.
17201720
*/
1721-
predicate expectsContent(Node n, ContentSet c) { none() }
1721+
predicate expectsContent(Node n, ContentSet c) {
1722+
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n, c)
1723+
}
17221724

17231725
/**
17241726
* Holds if the node `n` is unreachable when the call context is `call`.

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

Lines changed: 90 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ module Public {
2626
string toString() {
2727
exists(ContentSet c | this = TContentSummaryComponent(c) and result = c.toString())
2828
or
29+
exists(ContentSet c | this = TWithoutContentSummaryComponent(c) and result = "without " + c)
30+
or
31+
exists(ContentSet c | this = TWithContentSummaryComponent(c) and result = "with " + c)
32+
or
2933
exists(ArgumentPosition pos |
3034
this = TParameterSummaryComponent(pos) and result = "parameter " + pos
3135
)
@@ -43,6 +47,12 @@ module Public {
4347
/** Gets a summary component for content `c`. */
4448
SummaryComponent content(ContentSet c) { result = TContentSummaryComponent(c) }
4549

50+
/** Gets a summary component where data is not allowed to be stored in `c`. */
51+
SummaryComponent withoutContent(ContentSet c) { result = TWithoutContentSummaryComponent(c) }
52+
53+
/** Gets a summary component where data must be stored in `c`. */
54+
SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) }
55+
4656
/** Gets a summary component for a parameter at position `pos`. */
4757
SummaryComponent parameter(ArgumentPosition pos) { result = TParameterSummaryComponent(pos) }
4858

@@ -216,6 +226,8 @@ module Public {
216226
/**
217227
* Holds if values stored inside `content` are cleared on objects passed as
218228
* arguments at position `pos` to this callable.
229+
*
230+
* TODO: Remove once all languages support `WithoutContent` tokens.
219231
*/
220232
pragma[nomagic]
221233
predicate clearsContent(ParameterPosition pos, ContentSet content) { none() }
@@ -239,7 +251,9 @@ module Private {
239251
TContentSummaryComponent(ContentSet c) or
240252
TParameterSummaryComponent(ArgumentPosition pos) or
241253
TArgumentSummaryComponent(ParameterPosition pos) or
242-
TReturnSummaryComponent(ReturnKind rk)
254+
TReturnSummaryComponent(ReturnKind rk) or
255+
TWithoutContentSummaryComponent(ContentSet c) or
256+
TWithContentSummaryComponent(ContentSet c)
243257

244258
private TParameterSummaryComponent thisParam() {
245259
result = TParameterSummaryComponent(instanceParameterPosition())
@@ -301,6 +315,23 @@ module Private {
301315
SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and
302316
preservesValue = preservesValue1.booleanAnd(preservesValue2)
303317
)
318+
or
319+
exists(ParameterPosition ppos, ContentSet cs |
320+
c.clearsContent(ppos, cs) and
321+
input = SummaryComponentStack::push(SummaryComponent::withoutContent(cs), output) and
322+
output = SummaryComponentStack::argument(ppos) and
323+
preservesValue = true
324+
)
325+
}
326+
327+
private class MkClearStack extends RequiredSummaryComponentStack {
328+
override predicate required(SummaryComponent head, SummaryComponentStack tail) {
329+
exists(SummarizedCallable sc, ParameterPosition ppos, ContentSet cs |
330+
sc.clearsContent(ppos, cs) and
331+
head = SummaryComponent::withoutContent(cs) and
332+
tail = SummaryComponentStack::argument(ppos)
333+
)
334+
}
304335
}
305336

306337
/**
@@ -383,10 +414,7 @@ module Private {
383414

384415
private newtype TSummaryNodeState =
385416
TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or
386-
TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } or
387-
TSummaryNodeClearsContentState(ParameterPosition pos, boolean post) {
388-
any(SummarizedCallable sc).clearsContent(pos, _) and post in [false, true]
389-
}
417+
TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) }
390418

391419
/**
392420
* A state used to break up (complex) flow summaries into atomic flow steps.
@@ -433,12 +461,6 @@ module Private {
433461
this = TSummaryNodeOutputState(s) and
434462
result = "to write: " + s
435463
)
436-
or
437-
exists(ParameterPosition pos, boolean post, string postStr |
438-
this = TSummaryNodeClearsContentState(pos, post) and
439-
(if post = true then postStr = " (post)" else postStr = "") and
440-
result = "clear: " + pos + postStr
441-
)
442464
}
443465
}
444466

@@ -462,11 +484,6 @@ module Private {
462484
not parameterReadState(c, state, _)
463485
or
464486
state.isOutputState(c, _)
465-
or
466-
exists(ParameterPosition pos |
467-
c.clearsContent(pos, _) and
468-
state = TSummaryNodeClearsContentState(pos, _)
469-
)
470487
}
471488

472489
pragma[noinline]
@@ -502,24 +519,22 @@ module Private {
502519
parameterReadState(c, _, pos)
503520
or
504521
isParameterPostUpdate(_, c, pos)
505-
or
506-
c.clearsContent(pos, _)
507522
}
508523

509524
private predicate callbackOutput(
510525
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
511526
) {
512527
any(SummaryNodeState state).isInputState(c, s) and
513528
s.head() = TReturnSummaryComponent(rk) and
514-
receiver = summaryNodeInputState(c, s.drop(1))
529+
receiver = summaryNodeInputState(c, s.tail())
515530
}
516531

517532
private predicate callbackInput(
518533
SummarizedCallable c, SummaryComponentStack s, Node receiver, ArgumentPosition pos
519534
) {
520535
any(SummaryNodeState state).isOutputState(c, s) and
521536
s.head() = TParameterSummaryComponent(pos) and
522-
receiver = summaryNodeInputState(c, s.drop(1))
537+
receiver = summaryNodeInputState(c, s.tail())
523538
}
524539

525540
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
@@ -545,15 +560,21 @@ module Private {
545560
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
546561
n = summaryNodeInputState(c, s) and
547562
(
563+
exists(ContentSet cont | result = getContentType(cont) |
564+
head = TContentSummaryComponent(cont) or
565+
head = TWithContentSummaryComponent(cont)
566+
)
567+
or
548568
exists(ContentSet cont |
549-
head = TContentSummaryComponent(cont) and result = getContentType(cont)
569+
head = TWithoutContentSummaryComponent(cont) and
570+
result = getNodeType(summaryNodeInputState(c, s.tail()))
550571
)
551572
or
552573
exists(ReturnKind rk |
553574
head = TReturnSummaryComponent(rk) and
554575
result =
555576
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
556-
s.drop(1))), rk)
577+
s.tail())), rk)
557578
)
558579
)
559580
or
@@ -572,16 +593,10 @@ module Private {
572593
exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) |
573594
result =
574595
getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
575-
s.drop(1))), pos)
596+
s.tail())), pos)
576597
)
577598
)
578599
)
579-
or
580-
exists(SummarizedCallable c, ParameterPosition pos, ParamNode p |
581-
n = summaryNode(c, TSummaryNodeClearsContentState(pos, false)) and
582-
p.isParameterOf(c, pos) and
583-
result = getNodeType(p)
584-
)
585600
}
586601

587602
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
@@ -607,9 +622,6 @@ module Private {
607622
exists(SummarizedCallable c, ParameterPosition pos |
608623
isParameterPostUpdate(post, c, pos) and
609624
pre.(ParamNode).isParameterOf(c, pos)
610-
or
611-
pre = summaryNode(c, TSummaryNodeClearsContentState(pos, false)) and
612-
post = summaryNode(c, TSummaryNodeClearsContentState(pos, true))
613625
)
614626
or
615627
exists(SummarizedCallable callable, SummaryComponentStack s |
@@ -633,8 +645,6 @@ module Private {
633645
*/
634646
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
635647
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(c, ppos) |
636-
c.clearsContent(ppos, _)
637-
or
638648
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
639649
summary(c, inputContents, outputContents, _) and
640650
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and
@@ -663,9 +673,10 @@ module Private {
663673
preservesValue = false and not summary(c, inputContents, outputContents, true)
664674
)
665675
or
666-
exists(SummarizedCallable c, ParameterPosition pos |
667-
pred.(ParamNode).isParameterOf(c, pos) and
668-
succ = summaryNode(c, TSummaryNodeClearsContentState(pos, _)) and
676+
exists(SummarizedCallable c, SummaryComponentStack s |
677+
pred = summaryNodeInputState(c, s.tail()) and
678+
succ = summaryNodeInputState(c, s) and
679+
s.head() = [SummaryComponent::withContent(_), SummaryComponent::withoutContent(_)] and
669680
preservesValue = true
670681
)
671682
}
@@ -676,7 +687,7 @@ module Private {
676687
*/
677688
predicate summaryReadStep(Node pred, ContentSet c, Node succ) {
678689
exists(SummarizedCallable sc, SummaryComponentStack s |
679-
pred = summaryNodeInputState(sc, s.drop(1)) and
690+
pred = summaryNodeInputState(sc, s.tail()) and
680691
succ = summaryNodeInputState(sc, s) and
681692
SummaryComponent::content(c) = s.head()
682693
)
@@ -689,7 +700,7 @@ module Private {
689700
predicate summaryStoreStep(Node pred, ContentSet c, Node succ) {
690701
exists(SummarizedCallable sc, SummaryComponentStack s |
691702
pred = summaryNodeOutputState(sc, s) and
692-
succ = summaryNodeOutputState(sc, s.drop(1)) and
703+
succ = summaryNodeOutputState(sc, s.tail()) and
693704
SummaryComponent::content(c) = s.head()
694705
)
695706
}
@@ -714,9 +725,22 @@ module Private {
714725
* node where field `b` is cleared).
715726
*/
716727
predicate summaryClearsContent(Node n, ContentSet c) {
717-
exists(SummarizedCallable sc, ParameterPosition pos |
718-
n = summaryNode(sc, TSummaryNodeClearsContentState(pos, true)) and
719-
sc.clearsContent(pos, c)
728+
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
729+
n = summaryNode(sc, state) and
730+
state.isInputState(sc, stack) and
731+
stack.head() = SummaryComponent::withoutContent(c)
732+
)
733+
}
734+
735+
/**
736+
* Holds if the value that is being tracked is expected to be stored inside
737+
* content `c` at `n`.
738+
*/
739+
predicate summaryExpectsContent(Node n, ContentSet c) {
740+
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
741+
n = summaryNode(sc, state) and
742+
state.isInputState(sc, stack) and
743+
stack.head() = SummaryComponent::withContent(c)
720744
)
721745
}
722746

@@ -728,27 +752,32 @@ module Private {
728752
sc = viableCallable(call)
729753
}
730754

731-
/**
732-
* Holds if values stored inside content `c` are cleared inside a
733-
* callable to which `arg` is an argument.
734-
*
735-
* In such cases, it is important to prevent use-use flow out of
736-
* `arg` (see comment for `summaryClearsContent`).
737-
*/
738755
pragma[nomagic]
739-
predicate summaryClearsContentArg(ArgNode arg, ContentSet c) {
740-
exists(DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos |
756+
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg) {
757+
exists(ParameterPosition ppos, SummarizedCallable sc |
741758
argumentPositionMatch(call, arg, ppos) and
742-
viableParam(call, sc, ppos, _) and
743-
sc.clearsContent(ppos, c)
759+
viableParam(call, sc, ppos, result)
744760
)
745761
}
746762

763+
/**
764+
* Holds if use-use flow starting from `arg` should be prohibited.
765+
*
766+
* This is the case when `arg` is the argument of a call that targets a
767+
* flow summary where the corresponding parameter either clears contents
768+
* or expects contents.
769+
*/
747770
pragma[nomagic]
748-
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg) {
749-
exists(ParameterPosition ppos, SummarizedCallable sc |
750-
argumentPositionMatch(call, arg, ppos) and
751-
viableParam(call, sc, ppos, result)
771+
predicate prohibitsUseUseFlow(ArgNode arg) {
772+
exists(ParamNode p, Node mid, ParameterPosition ppos, Node ret |
773+
p = summaryArgParam0(_, arg) and
774+
p.isParameterOf(_, ppos) and
775+
summaryLocalStep(p, mid, true) and
776+
summaryLocalStep(mid, ret, true) and
777+
isParameterPostUpdate(ret, _, ppos)
778+
|
779+
summaryClearsContent(mid, _) or
780+
summaryExpectsContent(mid, _)
752781
)
753782
}
754783

@@ -1152,6 +1181,10 @@ module Private {
11521181
Private::Steps::summaryClearsContent(a.asNode(), c) and
11531182
b = a and
11541183
value = "clear (" + c + ")"
1184+
or
1185+
Private::Steps::summaryExpectsContent(a.asNode(), c) and
1186+
b = a and
1187+
value = "expect (" + c + ")"
11551188
)
11561189
or
11571190
summaryPostUpdateNode(b.asNode(), a.asNode()) and

csharp/ql/lib/semmle/code/csharp/frameworks/system/Collections.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private class SystemCollectionsIEnumerableClearFlow extends SummarizedCallable {
5555
}
5656

5757
override predicate clearsContent(ParameterPosition pos, DataFlow::ContentSet content) {
58-
pos.isThisParameter() and
58+
(if this.(Modifiable).isStatic() then pos.getPosition() = 0 else pos.isThisParameter()) and
5959
content instanceof DataFlow::ElementContent
6060
}
6161
}

csharp/ql/test/library-tests/dataflow/external-models/steps.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ summaryThroughStep
88
| Steps.cs:22:13:22:16 | this access | Steps.cs:22:13:22:30 | call to method StepQualRes | false |
99
| Steps.cs:23:13:23:25 | this access | Steps.cs:23:13:23:25 | call to method StepQualRes | false |
1010
| Steps.cs:26:13:26:31 | this access | Steps.cs:26:25:26:30 | [post] access to local variable argOut | false |
11-
| Steps.cs:30:13:30:16 | this access | Steps.cs:30:13:30:16 | [post] this access | true |
12-
| Steps.cs:34:13:34:16 | this access | Steps.cs:34:13:34:16 | [post] this access | true |
1311
| Steps.cs:41:29:41:29 | 0 | Steps.cs:41:13:41:30 | call to method StepGeneric | true |
1412
| Steps.cs:42:30:42:34 | false | Steps.cs:42:13:42:35 | call to method StepGeneric2<Boolean> | true |
1513
| Steps.cs:44:36:44:43 | "string" | Steps.cs:44:13:44:44 | call to method StepOverride | true |

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ predicate clearsContent(Node n, Content c) {
164164
* Holds if the value that is being tracked is expected to be stored inside content `c`
165165
* at node `n`.
166166
*/
167-
predicate expectsContent(Node n, ContentSet c) { none() }
167+
predicate expectsContent(Node n, ContentSet c) {
168+
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n, c)
169+
}
168170

169171
/**
170172
* Gets a representative (boxed) type for `t` for the purpose of pruning

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
136136
not exists(FieldRead fr |
137137
hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr()
138138
) and
139-
not FlowSummaryImpl::Private::Steps::summaryClearsContentArg(node1, _)
139+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1)
140140
or
141141
ThisFlow::adjacentThisRefs(node1, node2)
142142
or

0 commit comments

Comments
 (0)