Skip to content

Commit aa52b10

Browse files
committed
Data flow: Add hook for preventing lambda dispatch in source call contexts
1 parent 2f859de commit aa52b10

File tree

6 files changed

+73
-3
lines changed

6 files changed

+73
-3
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ module CsharpDataFlow implements InputSig<Location> {
2929
predicate neverSkipInPathGraph(Node n) {
3030
exists(n.(AssignableDefinitionNode).getDefinition().getTargetAccess())
3131
}
32+
33+
DataFlowType getSourceContextParameterNodeType() { result.isSourceContextParameterType() }
3234
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,8 @@ private module Cached {
11791179
cached
11801180
newtype TDataFlowType =
11811181
TGvnDataFlowType(Gvn::GvnType t) or
1182-
TDelegateDataFlowType(Callable lambda) { lambdaCreationExpr(_, lambda) }
1182+
TDelegateDataFlowType(Callable lambda) { lambdaCreationExpr(_, lambda) } or
1183+
TSourceContextParameterType()
11831184
}
11841185

11851186
import Cached
@@ -2394,6 +2395,8 @@ class DataFlowType extends TDataFlowType {
23942395

23952396
Callable asDelegate() { this = TDelegateDataFlowType(result) }
23962397

2398+
predicate isSourceContextParameterType() { this = TSourceContextParameterType() }
2399+
23972400
/**
23982401
* Gets an expression that creates a delegate of this type.
23992402
*
@@ -2412,6 +2415,9 @@ class DataFlowType extends TDataFlowType {
24122415
result = this.asGvnType().toString()
24132416
or
24142417
result = this.asDelegate().toString()
2418+
or
2419+
this.isSourceContextParameterType() and
2420+
result = "<source context parameter type>"
24152421
}
24162422
}
24172423

@@ -2469,6 +2475,11 @@ private predicate compatibleTypesDelegateLeft(DataFlowType dt1, DataFlowType dt2
24692475
)
24702476
}
24712477

2478+
pragma[nomagic]
2479+
private predicate compatibleTypesSourceContextParameterTypeLeft(DataFlowType dt1, DataFlowType dt2) {
2480+
dt1.isSourceContextParameterType() and not exists(dt2.asDelegate())
2481+
}
2482+
24722483
/**
24732484
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
24742485
* a node of type `t1` to a node of type `t2`.
@@ -2499,6 +2510,10 @@ predicate compatibleTypes(DataFlowType dt1, DataFlowType dt2) {
24992510
compatibleTypesDelegateLeft(dt2, dt1)
25002511
or
25012512
dt1.asDelegate() = dt2.asDelegate()
2513+
or
2514+
compatibleTypesSourceContextParameterTypeLeft(dt1, dt2)
2515+
or
2516+
compatibleTypesSourceContextParameterTypeLeft(dt2, dt1)
25022517
}
25032518

25042519
pragma[nomagic]
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
unexpectedModel
2-
| Unexpected contentbased-summary found: Models;HigherOrderParameters;false;Apply;(System.Func<System.Object,System.Object>,System.Object);;Argument[1];ReturnValue;value;dfc-generated |
32
expectedModel

shared/dataflow/codeql/dataflow/DataFlow.qll

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,35 @@ signature module InputSig<LocationSig Location> {
6363

6464
DataFlowType getNodeType(Node node);
6565

66+
/**
67+
* Gets a special type to use for parameter nodes belonging to callables with a
68+
* source node where a source call context `FlowFeature` is used, if any.
69+
*
70+
* This can be used to prevent lambdas from being resolved, when a concrete call
71+
* context is needed. Example:
72+
*
73+
* ```csharp
74+
* void Foo(Action<string> a)
75+
* {
76+
* var x = Source();
77+
* a(x); // (1)
78+
* a = s => Sink(s); // (2)
79+
* a(x); // (3)
80+
* }
81+
*
82+
* void Bar()
83+
* {
84+
* Foo(s => Sink(s)); // (4)
85+
* }
86+
* ```
87+
*
88+
* If a source call context flow feature is used, `a` can be assigned a special
89+
* type that is incompatible with the type of _any_ lambda expression, which will
90+
* prevent the call edge from (1) to (4). Note that the call edge from (3) to (2)
91+
* will still be valid.
92+
*/
93+
default DataFlowType getSourceContextParameterNodeType() { none() }
94+
6695
predicate nodeIsHidden(Node node);
6796

6897
class DataFlowExpr;

shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,16 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
11031103
private module FwdTypeFlowInput implements TypeFlowInput {
11041104
predicate enableTypeFlow = Param::enableTypeFlow/0;
11051105

1106+
pragma[nomagic]
1107+
predicate isParameterNodeInSourceCallContext(ParamNode p) {
1108+
hasSourceCallCtx() and
1109+
exists(Node source, DataFlowCallable c |
1110+
Config::isSource(pragma[only_bind_into](source), _) and
1111+
nodeEnclosingCallable(source, c) and
1112+
nodeEnclosingCallable(p, c)
1113+
)
1114+
}
1115+
11061116
predicate relevantCallEdgeIn = PrevStage::relevantCallEdgeIn/2;
11071117

11081118
predicate relevantCallEdgeOut = PrevStage::relevantCallEdgeOut/2;
@@ -1410,6 +1420,8 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
14101420
private module RevTypeFlowInput implements TypeFlowInput {
14111421
predicate enableTypeFlow = Param::enableTypeFlow/0;
14121422

1423+
predicate isParameterNodeInSourceCallContext(ParamNode p) { none() }
1424+
14131425
predicate relevantCallEdgeIn(Call call, Callable c) {
14141426
flowOutOfCallAp(call, c, _, _, _, _, _)
14151427
}

shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
18931893
signature module TypeFlowInput {
18941894
predicate enableTypeFlow();
18951895

1896+
/** Holds if `p` is a parameter of a callable with a source node that has a call context. */
1897+
predicate isParameterNodeInSourceCallContext(ParamNode p);
1898+
18961899
/** Holds if the edge is possibly needed in the direction `call` to `c`. */
18971900
predicate relevantCallEdgeIn(Call call, Callable c);
18981901

@@ -1953,6 +1956,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
19531956
/**
19541957
* Holds if a sequence of calls may propagate the value of `arg` to some
19551958
* argument-to-parameter call edge that strengthens the static type.
1959+
*
1960+
* This predicate is a reverse flow computation, starting at calls that
1961+
* strengthen the type and then following relevant call edges backwards.
19561962
*/
19571963
pragma[nomagic]
19581964
private predicate trackedArgTypeCand(ArgNode arg) {
@@ -1987,6 +1993,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
19871993
* Holds if `p` is part of a value-propagating call path where the
19881994
* end-points have stronger types than the intermediate parameter and
19891995
* argument nodes.
1996+
*
1997+
* This predicate is a forward flow computation, intersecting with the
1998+
* reverse flow computation done in `trackedArgTypeCand`.
19901999
*/
19912000
private predicate trackedParamType(ParamNode p) {
19922001
exists(Call call1, Callable c1, ArgNode argOut, Call call2, Callable c2, ArgNode argIn |
@@ -2013,6 +2022,8 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
20132022
typeStrongerThanFilter(at, pt)
20142023
)
20152024
or
2025+
Input::isParameterNodeInSourceCallContext(p)
2026+
or
20162027
exists(ArgNode arg |
20172028
trackedArgType(arg) and
20182029
relevantCallEdge(_, _, arg, p) and
@@ -2106,7 +2117,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
21062117
private predicate typeFlowParamType(ParamNode p, Type t, boolean cc) {
21072118
exists(Callable c |
21082119
Input::dataFlowNonCallEntry(c, cc) and
2109-
trackedParamWithType(p, t, c)
2120+
if cc = true and exists(getSourceContextParameterNodeType())
2121+
then t = getSourceContextParameterNodeType()
2122+
else trackedParamWithType(p, t, c)
21102123
)
21112124
or
21122125
exists(Type t1, Type t2 |

0 commit comments

Comments
 (0)