Skip to content

Commit 04cc738

Browse files
committed
Java: Introduce 'with/without content' summary components
1 parent 2972af2 commit 04cc738

File tree

3 files changed

+94
-59
lines changed

3 files changed

+94
-59
lines changed

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

java/ql/lib/semmle/code/java/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() }
@@ -234,7 +246,9 @@ module Private {
234246
TContentSummaryComponent(ContentSet c) or
235247
TParameterSummaryComponent(ArgumentPosition pos) or
236248
TArgumentSummaryComponent(ParameterPosition pos) or
237-
TReturnSummaryComponent(ReturnKind rk)
249+
TReturnSummaryComponent(ReturnKind rk) or
250+
TWithoutContentSummaryComponent(ContentSet c) or
251+
TWithContentSummaryComponent(ContentSet c)
238252

239253
private TParameterSummaryComponent thisParam() {
240254
result = TParameterSummaryComponent(instanceParameterPosition())
@@ -296,6 +310,23 @@ module Private {
296310
SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and
297311
preservesValue = preservesValue1.booleanAnd(preservesValue2)
298312
)
313+
or
314+
exists(ParameterPosition ppos, ContentSet cs |
315+
c.clearsContent(ppos, cs) and
316+
input = SummaryComponentStack::push(SummaryComponent::withoutContent(cs), output) and
317+
output = SummaryComponentStack::argument(ppos) and
318+
preservesValue = true
319+
)
320+
}
321+
322+
private class MkClearStack extends RequiredSummaryComponentStack {
323+
override predicate required(SummaryComponent head, SummaryComponentStack tail) {
324+
exists(SummarizedCallable sc, ParameterPosition ppos, ContentSet cs |
325+
sc.clearsContent(ppos, cs) and
326+
head = SummaryComponent::withoutContent(cs) and
327+
tail = SummaryComponentStack::argument(ppos)
328+
)
329+
}
299330
}
300331

301332
/**
@@ -378,10 +409,7 @@ module Private {
378409

379410
private newtype TSummaryNodeState =
380411
TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or
381-
TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } or
382-
TSummaryNodeClearsContentState(ParameterPosition pos, boolean post) {
383-
any(SummarizedCallable sc).clearsContent(pos, _) and post in [false, true]
384-
}
412+
TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) }
385413

386414
/**
387415
* A state used to break up (complex) flow summaries into atomic flow steps.
@@ -428,12 +456,6 @@ module Private {
428456
this = TSummaryNodeOutputState(s) and
429457
result = "to write: " + s
430458
)
431-
or
432-
exists(ParameterPosition pos, boolean post, string postStr |
433-
this = TSummaryNodeClearsContentState(pos, post) and
434-
(if post = true then postStr = " (post)" else postStr = "") and
435-
result = "clear: " + pos + postStr
436-
)
437459
}
438460
}
439461

@@ -457,11 +479,6 @@ module Private {
457479
not parameterReadState(c, state, _)
458480
or
459481
state.isOutputState(c, _)
460-
or
461-
exists(ParameterPosition pos |
462-
c.clearsContent(pos, _) and
463-
state = TSummaryNodeClearsContentState(pos, _)
464-
)
465482
}
466483

467484
pragma[noinline]
@@ -497,24 +514,22 @@ module Private {
497514
parameterReadState(c, _, pos)
498515
or
499516
isParameterPostUpdate(_, c, pos)
500-
or
501-
c.clearsContent(pos, _)
502517
}
503518

504519
private predicate callbackOutput(
505520
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
506521
) {
507522
any(SummaryNodeState state).isInputState(c, s) and
508523
s.head() = TReturnSummaryComponent(rk) and
509-
receiver = summaryNodeInputState(c, s.drop(1))
524+
receiver = summaryNodeInputState(c, s.tail())
510525
}
511526

512527
private predicate callbackInput(
513528
SummarizedCallable c, SummaryComponentStack s, Node receiver, ArgumentPosition pos
514529
) {
515530
any(SummaryNodeState state).isOutputState(c, s) and
516531
s.head() = TParameterSummaryComponent(pos) and
517-
receiver = summaryNodeInputState(c, s.drop(1))
532+
receiver = summaryNodeInputState(c, s.tail())
518533
}
519534

520535
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
@@ -540,15 +555,21 @@ module Private {
540555
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
541556
n = summaryNodeInputState(c, s) and
542557
(
558+
exists(ContentSet cont | result = getContentType(cont) |
559+
head = TContentSummaryComponent(cont) or
560+
head = TWithContentSummaryComponent(cont)
561+
)
562+
or
543563
exists(ContentSet cont |
544-
head = TContentSummaryComponent(cont) and result = getContentType(cont)
564+
head = TWithoutContentSummaryComponent(cont) and
565+
result = getNodeType(summaryNodeInputState(c, s.tail()))
545566
)
546567
or
547568
exists(ReturnKind rk |
548569
head = TReturnSummaryComponent(rk) and
549570
result =
550571
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
551-
s.drop(1))), rk)
572+
s.tail())), rk)
552573
)
553574
)
554575
or
@@ -567,16 +588,10 @@ module Private {
567588
exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) |
568589
result =
569590
getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
570-
s.drop(1))), pos)
591+
s.tail())), pos)
571592
)
572593
)
573594
)
574-
or
575-
exists(SummarizedCallable c, ParameterPosition pos, ParamNode p |
576-
n = summaryNode(c, TSummaryNodeClearsContentState(pos, false)) and
577-
p.isParameterOf(c, pos) and
578-
result = getNodeType(p)
579-
)
580595
}
581596

582597
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
@@ -602,9 +617,6 @@ module Private {
602617
exists(SummarizedCallable c, ParameterPosition pos |
603618
isParameterPostUpdate(post, c, pos) and
604619
pre.(ParamNode).isParameterOf(c, pos)
605-
or
606-
pre = summaryNode(c, TSummaryNodeClearsContentState(pos, false)) and
607-
post = summaryNode(c, TSummaryNodeClearsContentState(pos, true))
608620
)
609621
or
610622
exists(SummarizedCallable callable, SummaryComponentStack s |
@@ -628,8 +640,6 @@ module Private {
628640
*/
629641
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
630642
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(c, ppos) |
631-
c.clearsContent(ppos, _)
632-
or
633643
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
634644
summary(c, inputContents, outputContents, _) and
635645
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and
@@ -658,9 +668,10 @@ module Private {
658668
preservesValue = false and not summary(c, inputContents, outputContents, true)
659669
)
660670
or
661-
exists(SummarizedCallable c, ParameterPosition pos |
662-
pred.(ParamNode).isParameterOf(c, pos) and
663-
succ = summaryNode(c, TSummaryNodeClearsContentState(pos, _)) and
671+
exists(SummarizedCallable c, SummaryComponentStack s |
672+
pred = summaryNodeInputState(c, s.tail()) and
673+
succ = summaryNodeInputState(c, s) and
674+
s.head() = [SummaryComponent::withContent(_), SummaryComponent::withoutContent(_)] and
664675
preservesValue = true
665676
)
666677
}
@@ -671,7 +682,7 @@ module Private {
671682
*/
672683
predicate summaryReadStep(Node pred, ContentSet c, Node succ) {
673684
exists(SummarizedCallable sc, SummaryComponentStack s |
674-
pred = summaryNodeInputState(sc, s.drop(1)) and
685+
pred = summaryNodeInputState(sc, s.tail()) and
675686
succ = summaryNodeInputState(sc, s) and
676687
SummaryComponent::content(c) = s.head()
677688
)
@@ -684,7 +695,7 @@ module Private {
684695
predicate summaryStoreStep(Node pred, ContentSet c, Node succ) {
685696
exists(SummarizedCallable sc, SummaryComponentStack s |
686697
pred = summaryNodeOutputState(sc, s) and
687-
succ = summaryNodeOutputState(sc, s.drop(1)) and
698+
succ = summaryNodeOutputState(sc, s.tail()) and
688699
SummaryComponent::content(c) = s.head()
689700
)
690701
}
@@ -709,9 +720,22 @@ module Private {
709720
* node where field `b` is cleared).
710721
*/
711722
predicate summaryClearsContent(Node n, ContentSet c) {
712-
exists(SummarizedCallable sc, ParameterPosition pos |
713-
n = summaryNode(sc, TSummaryNodeClearsContentState(pos, true)) and
714-
sc.clearsContent(pos, c)
723+
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
724+
n = summaryNode(sc, state) and
725+
state.isInputState(sc, stack) and
726+
stack.head() = SummaryComponent::withoutContent(c)
727+
)
728+
}
729+
730+
/**
731+
* Holds if the value that is being tracked is expected to be stored inside
732+
* content `c` at `n`.
733+
*/
734+
predicate summaryExpectsContent(Node n, ContentSet c) {
735+
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
736+
n = summaryNode(sc, state) and
737+
state.isInputState(sc, stack) and
738+
stack.head() = SummaryComponent::withContent(c)
715739
)
716740
}
717741

@@ -723,27 +747,32 @@ module Private {
723747
sc = viableCallable(call)
724748
}
725749

726-
/**
727-
* Holds if values stored inside content `c` are cleared inside a
728-
* callable to which `arg` is an argument.
729-
*
730-
* In such cases, it is important to prevent use-use flow out of
731-
* `arg` (see comment for `summaryClearsContent`).
732-
*/
733750
pragma[nomagic]
734-
predicate summaryClearsContentArg(ArgNode arg, ContentSet c) {
735-
exists(DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos |
751+
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg) {
752+
exists(ParameterPosition ppos, SummarizedCallable sc |
736753
argumentPositionMatch(call, arg, ppos) and
737-
viableParam(call, sc, ppos, _) and
738-
sc.clearsContent(ppos, c)
754+
viableParam(call, sc, ppos, result)
739755
)
740756
}
741757

758+
/**
759+
* Holds if use-use flow starting from `arg` should be prohibited.
760+
*
761+
* This is the case when `arg` is the argument of a call that targets a
762+
* flow summary where the corresponding parameter either clears contents
763+
* or expects contents.
764+
*/
742765
pragma[nomagic]
743-
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg) {
744-
exists(ParameterPosition ppos, SummarizedCallable sc |
745-
argumentPositionMatch(call, arg, ppos) and
746-
viableParam(call, sc, ppos, result)
766+
predicate prohibitsUseUseFlow(ArgNode arg) {
767+
exists(ParamNode p, Node mid, ParameterPosition ppos, Node ret |
768+
p = summaryArgParam0(_, arg) and
769+
p.isParameterOf(_, ppos) and
770+
summaryLocalStep(p, mid, true) and
771+
summaryLocalStep(mid, ret, true) and
772+
isParameterPostUpdate(ret, _, ppos)
773+
|
774+
summaryClearsContent(mid, _) or
775+
summaryExpectsContent(mid, _)
747776
)
748777
}
749778

@@ -1141,6 +1170,10 @@ module Private {
11411170
Private::Steps::summaryClearsContent(a.asNode(), c) and
11421171
b = a and
11431172
value = "clear (" + c + ")"
1173+
or
1174+
Private::Steps::summaryExpectsContent(a.asNode(), c) and
1175+
b = a and
1176+
value = "expect (" + c + ")"
11441177
)
11451178
or
11461179
summaryPostUpdateNode(b.asNode(), a.asNode()) and

0 commit comments

Comments
 (0)