Skip to content

Commit b11e151

Browse files
committed
Data flow: Sync files and add stubs
1 parent a373a52 commit b11e151

File tree

8 files changed

+1212
-68
lines changed

8 files changed

+1212
-68
lines changed

cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll

Lines changed: 292 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,243 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
2626
tupleLimit = 1000
2727
}
2828

29+
/**
30+
* Provides a simple data-flow analysis for resolving lambda calls. The analysis
31+
* currently excludes read-steps, store-steps, and flow-through.
32+
*
33+
* The analysis uses non-linear recursion: When computing a flow path in or out
34+
* of a call, we use the results of the analysis recursively to resolve lamba
35+
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
36+
*/
37+
private module LambdaFlow {
38+
private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) {
39+
p.isParameterOf(viableCallable(call), i)
40+
}
41+
42+
private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) {
43+
p.isParameterOf(viableCallableLambda(call, _), i)
44+
}
45+
46+
private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
47+
exists(int i |
48+
viableParamNonLambda(call, i, p) and
49+
arg.argumentOf(call, i)
50+
)
51+
}
52+
53+
private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
54+
exists(int i |
55+
viableParamLambda(call, i, p) and
56+
arg.argumentOf(call, i)
57+
)
58+
}
59+
60+
private newtype TReturnPositionSimple =
61+
TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) {
62+
exists(ReturnNode ret |
63+
c = getNodeEnclosingCallable(ret) and
64+
kind = ret.getKind()
65+
)
66+
}
67+
68+
pragma[noinline]
69+
private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) {
70+
result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind)
71+
}
72+
73+
pragma[nomagic]
74+
private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) {
75+
result = TReturnPositionSimple0(viableCallable(call), kind)
76+
}
77+
78+
pragma[nomagic]
79+
private TReturnPositionSimple viableReturnPosLambda(
80+
DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind
81+
) {
82+
result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind)
83+
}
84+
85+
private predicate viableReturnPosOutNonLambda(
86+
DataFlowCall call, TReturnPositionSimple pos, OutNode out
87+
) {
88+
exists(ReturnKind kind |
89+
pos = viableReturnPosNonLambda(call, kind) and
90+
out = getAnOutNode(call, kind)
91+
)
92+
}
93+
94+
private predicate viableReturnPosOutLambda(
95+
DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out
96+
) {
97+
exists(ReturnKind kind |
98+
pos = viableReturnPosLambda(call, lastCall, kind) and
99+
out = getAnOutNode(call, kind)
100+
)
101+
}
102+
103+
/**
104+
* Holds if data can flow (inter-procedurally) from `node` (of type `t`) to
105+
* the lambda call `lambdaCall`.
106+
*
107+
* The parameter `toReturn` indicates whether the path from `node` to
108+
* `lambdaCall` goes through a return, and `toJump` whether the path goes
109+
* through a jump step.
110+
*
111+
* The call context `lastCall` records the last call on the path from `node`
112+
* to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing
113+
* callable of `lambdaCall`.
114+
*/
115+
pragma[nomagic]
116+
predicate revLambdaFlow(
117+
DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
118+
boolean toJump, DataFlowCallOption lastCall
119+
) {
120+
revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and
121+
if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode
122+
then compatibleTypes(t, getNodeType(node))
123+
else any()
124+
}
125+
126+
pragma[nomagic]
127+
predicate revLambdaFlow0(
128+
DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
129+
boolean toJump, DataFlowCallOption lastCall
130+
) {
131+
lambdaCall(lambdaCall, kind, node) and
132+
t = getNodeType(node) and
133+
toReturn = false and
134+
toJump = false and
135+
lastCall = TDataFlowCallNone()
136+
or
137+
// local flow
138+
exists(Node mid, DataFlowType t0 |
139+
revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall)
140+
|
141+
simpleLocalFlowStep(node, mid) and
142+
t = t0
143+
or
144+
exists(boolean preservesValue |
145+
additionalLambdaFlowStep(node, mid, preservesValue) and
146+
getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid)
147+
|
148+
preservesValue = false and
149+
t = getNodeType(node)
150+
or
151+
preservesValue = true and
152+
t = t0
153+
)
154+
)
155+
or
156+
// jump step
157+
exists(Node mid, DataFlowType t0 |
158+
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and
159+
toReturn = false and
160+
toJump = true and
161+
lastCall = TDataFlowCallNone()
162+
|
163+
jumpStep(node, mid) and
164+
t = t0
165+
or
166+
exists(boolean preservesValue |
167+
additionalLambdaFlowStep(node, mid, preservesValue) and
168+
getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid)
169+
|
170+
preservesValue = false and
171+
t = getNodeType(node)
172+
or
173+
preservesValue = true and
174+
t = t0
175+
)
176+
)
177+
or
178+
// flow into a callable
179+
exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call |
180+
revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and
181+
(
182+
if lastCall0 = TDataFlowCallNone() and toJump = false
183+
then lastCall = TDataFlowCallSome(call)
184+
else lastCall = lastCall0
185+
) and
186+
toReturn = false
187+
|
188+
viableParamArgNonLambda(call, p, node)
189+
or
190+
viableParamArgLambda(call, p, node) // non-linear recursion
191+
)
192+
or
193+
// flow out of a callable
194+
exists(TReturnPositionSimple pos |
195+
revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and
196+
getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and
197+
toReturn = true
198+
)
199+
}
200+
201+
pragma[nomagic]
202+
predicate revLambdaFlowOutLambdaCall(
203+
DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump,
204+
DataFlowCall call, DataFlowCallOption lastCall
205+
) {
206+
revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
207+
exists(ReturnKindExt rk |
208+
out = rk.getAnOutNode(call) and
209+
lambdaCall(call, _, _)
210+
)
211+
}
212+
213+
pragma[nomagic]
214+
predicate revLambdaFlowOut(
215+
DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t,
216+
boolean toJump, DataFlowCallOption lastCall
217+
) {
218+
exists(DataFlowCall call, OutNode out |
219+
revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
220+
viableReturnPosOutNonLambda(call, pos, out)
221+
or
222+
// non-linear recursion
223+
revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and
224+
viableReturnPosOutLambda(call, _, pos, out)
225+
)
226+
}
227+
228+
pragma[nomagic]
229+
predicate revLambdaFlowIn(
230+
DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump,
231+
DataFlowCallOption lastCall
232+
) {
233+
revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall)
234+
}
235+
}
236+
237+
private DataFlowCallable viableCallableExt(DataFlowCall call) {
238+
result = viableCallable(call)
239+
or
240+
result = viableCallableLambda(call, _)
241+
}
242+
29243
cached
30244
private module Cached {
245+
/**
246+
* Gets a viable target for the lambda call `call`.
247+
*
248+
* `lastCall` records the call required to reach `call` in order for the result
249+
* to be a viable target, if any.
250+
*/
251+
cached
252+
DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) {
253+
exists(Node creation, LambdaCallKind kind |
254+
LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and
255+
lambdaCreation(creation, kind, result)
256+
)
257+
}
258+
31259
/**
32260
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
33261
* The instance parameter is considered to have index `-1`.
34262
*/
35263
pragma[nomagic]
36264
private predicate viableParam(DataFlowCall call, int i, ParameterNode p) {
37-
p.isParameterOf(viableCallable(call), i)
265+
p.isParameterOf(viableCallableExt(call), i)
38266
}
39267

40268
/**
@@ -52,7 +280,7 @@ private module Cached {
52280

53281
pragma[nomagic]
54282
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
55-
viableCallable(call) = result.getCallable() and
283+
viableCallableExt(call) = result.getCallable() and
56284
kind = result.getKind()
57285
}
58286

@@ -317,17 +545,46 @@ private module Cached {
317545

318546
cached
319547
private module DispatchWithCallContext {
548+
/**
549+
* Holds if the set of viable implementations that can be called by `call`
550+
* might be improved by knowing the call context.
551+
*/
552+
pragma[nomagic]
553+
private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) {
554+
mayBenefitFromCallContext(call, callable)
555+
or
556+
callable = call.getEnclosingCallable() and
557+
exists(viableCallableLambda(call, TDataFlowCallSome(_)))
558+
}
559+
560+
/**
561+
* Gets a viable dispatch target of `call` in the context `ctx`. This is
562+
* restricted to those `call`s for which a context might make a difference.
563+
*/
564+
pragma[nomagic]
565+
private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) {
566+
result = viableImplInCallContext(call, ctx)
567+
or
568+
result = viableCallableLambda(call, TDataFlowCallSome(ctx))
569+
or
570+
exists(DataFlowCallable enclosing |
571+
mayBenefitFromCallContextExt(call, enclosing) and
572+
enclosing = viableCallableExt(ctx) and
573+
result = viableCallableLambda(call, TDataFlowCallNone())
574+
)
575+
}
576+
320577
/**
321578
* Holds if the call context `ctx` reduces the set of viable run-time
322579
* dispatch targets of call `call` in `c`.
323580
*/
324581
cached
325582
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
326583
exists(int tgts, int ctxtgts |
327-
mayBenefitFromCallContext(call, c) and
328-
c = viableCallable(ctx) and
329-
ctxtgts = count(viableImplInCallContext(call, ctx)) and
330-
tgts = strictcount(viableCallable(call)) and
584+
mayBenefitFromCallContextExt(call, c) and
585+
c = viableCallableExt(ctx) and
586+
ctxtgts = count(viableImplInCallContextExt(call, ctx)) and
587+
tgts = strictcount(viableCallableExt(call)) and
331588
ctxtgts < tgts
332589
)
333590
}
@@ -339,7 +596,7 @@ private module Cached {
339596
*/
340597
cached
341598
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
342-
result = viableImplInCallContext(call, ctx) and
599+
result = viableImplInCallContextExt(call, ctx) and
343600
reducedViableImplInCallContext(call, _, ctx)
344601
}
345602

@@ -351,10 +608,10 @@ private module Cached {
351608
cached
352609
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
353610
exists(int tgts, int ctxtgts |
354-
mayBenefitFromCallContext(call, _) and
355-
c = viableCallable(call) and
356-
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
357-
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
611+
mayBenefitFromCallContextExt(call, _) and
612+
c = viableCallableExt(call) and
613+
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and
614+
tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and
358615
ctxtgts < tgts
359616
)
360617
}
@@ -367,7 +624,7 @@ private module Cached {
367624
*/
368625
cached
369626
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
370-
result = viableImplInCallContext(call, ctx) and
627+
result = viableImplInCallContextExt(call, ctx) and
371628
reducedViableImplInReturn(result, call)
372629
}
373630
}
@@ -481,6 +738,11 @@ private module Cached {
481738
TBooleanNone() or
482739
TBooleanSome(boolean b) { b = true or b = false }
483740

741+
cached
742+
newtype TDataFlowCallOption =
743+
TDataFlowCallNone() or
744+
TDataFlowCallSome(DataFlowCall call)
745+
484746
cached
485747
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
486748

@@ -777,7 +1039,7 @@ ReturnPosition getReturnPosition(ReturnNodeExt ret) {
7771039

7781040
bindingset[cc, callable]
7791041
predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) {
780-
cc instanceof CallContextAny and callable = viableCallable(call)
1042+
cc instanceof CallContextAny and callable = viableCallableExt(call)
7811043
or
7821044
exists(DataFlowCallable c0, DataFlowCall call0 |
7831045
call0.getEnclosingCallable() = callable and
@@ -791,14 +1053,14 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
7911053
exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
7921054
if reducedViableImplInCallContext(call, _, ctx)
7931055
then result = prunedViableImplInCallContext(call, ctx)
794-
else result = viableCallable(call)
1056+
else result = viableCallableExt(call)
7951057
)
7961058
or
797-
result = viableCallable(call) and cc instanceof CallContextSomeCall
1059+
result = viableCallableExt(call) and cc instanceof CallContextSomeCall
7981060
or
799-
result = viableCallable(call) and cc instanceof CallContextAny
1061+
result = viableCallableExt(call) and cc instanceof CallContextAny
8001062
or
801-
result = viableCallable(call) and cc instanceof CallContextReturn
1063+
result = viableCallableExt(call) and cc instanceof CallContextReturn
8021064
}
8031065

8041066
predicate read = readStep/3;
@@ -812,6 +1074,19 @@ class BooleanOption extends TBooleanOption {
8121074
}
8131075
}
8141076

1077+
/** An optional `DataFlowCall`. */
1078+
class DataFlowCallOption extends TDataFlowCallOption {
1079+
string toString() {
1080+
this = TDataFlowCallNone() and
1081+
result = "(none)"
1082+
or
1083+
exists(DataFlowCall call |
1084+
this = TDataFlowCallSome(call) and
1085+
result = call.toString()
1086+
)
1087+
}
1088+
}
1089+
8151090
/** Content tagged with the type of a containing object. */
8161091
class TypedContent extends MkTypedContent {
8171092
private Content c;

0 commit comments

Comments
 (0)