Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ module CsharpDataFlow implements InputSig<Location> {
predicate neverSkipInPathGraph(Node n) {
exists(n.(AssignableDefinitionNode).getDefinition().getTargetAccess())
}

DataFlowType getSourceContextParameterNodeType() { result.isSourceContextParameterType() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,8 @@ private module Cached {
cached
newtype TDataFlowType =
TGvnDataFlowType(Gvn::GvnType t) or
TDelegateDataFlowType(Callable lambda) { lambdaCreationExpr(_, lambda) }
TDelegateDataFlowType(Callable lambda) { lambdaCreationExpr(_, lambda) } or
TSourceContextParameterType()
}

import Cached
Expand Down Expand Up @@ -2394,6 +2395,8 @@ class DataFlowType extends TDataFlowType {

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

predicate isSourceContextParameterType() { this = TSourceContextParameterType() }

/**
* Gets an expression that creates a delegate of this type.
*
Expand All @@ -2412,6 +2415,9 @@ class DataFlowType extends TDataFlowType {
result = this.asGvnType().toString()
or
result = this.asDelegate().toString()
or
this.isSourceContextParameterType() and
result = "<source context parameter type>"
}
}

Expand Down Expand Up @@ -2469,6 +2475,11 @@ private predicate compatibleTypesDelegateLeft(DataFlowType dt1, DataFlowType dt2
)
}

pragma[nomagic]
private predicate compatibleTypesSourceContextParameterTypeLeft(DataFlowType dt1, DataFlowType dt2) {
dt1.isSourceContextParameterType() and not exists(dt2.asDelegate())
}

/**
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
* a node of type `t1` to a node of type `t2`.
Expand Down Expand Up @@ -2499,6 +2510,10 @@ predicate compatibleTypes(DataFlowType dt1, DataFlowType dt2) {
compatibleTypesDelegateLeft(dt2, dt1)
or
dt1.asDelegate() = dt2.asDelegate()
or
compatibleTypesSourceContextParameterTypeLeft(dt1, dt2)
or
compatibleTypesSourceContextParameterTypeLeft(dt2, dt1)
}

pragma[nomagic]
Expand Down
6 changes: 6 additions & 0 deletions csharp/ql/test/utils/modelgenerator/dataflow/Summaries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,12 @@ public void Apply(Action<object> a, object o)
{
a(o);
}

private void CallApply()
{
// Test that this call to `Apply` does not interfere with the flow summaries generated for `Apply`
Apply(x => x, null);
}
}

public static class HigherOrderExtensionMethods
Expand Down
29 changes: 29 additions & 0 deletions shared/dataflow/codeql/dataflow/DataFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ signature module InputSig<LocationSig Location> {

DataFlowType getNodeType(Node node);

/**
* Gets a special type to use for parameter nodes belonging to callables with a
* source node where a source call context `FlowFeature` is used, if any.
*
* This can be used to prevent lambdas from being resolved, when a concrete call
* context is needed. Example:
*
* ```csharp
* void Foo(Action<string> a)
* {
* var x = Source();
* a(x); // (1)
* a = s => Sink(s); // (2)
* a(x); // (3)
* }
*
* void Bar()
* {
* Foo(s => Sink(s)); // (4)
* }
* ```
*
* If a source call context flow feature is used, `a` can be assigned a special
* type that is incompatible with the type of _any_ lambda expression, which will
* prevent the call edge from (1) to (4). Note that the call edge from (3) to (2)
* will still be valid.
*/
default DataFlowType getSourceContextParameterNodeType() { none() }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether this type ought to be parameterised on the parameter. Conceptually it's a subtype of whatever callback type the parameter has and there could be multiple mutually disjoint callback types. And if this type is introduced as a singleton then it could conceptually ruin that disjointness.


predicate nodeIsHidden(Node node);

class DataFlowExpr;
Expand Down
12 changes: 12 additions & 0 deletions shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,16 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
private module FwdTypeFlowInput implements TypeFlowInput {
predicate enableTypeFlow = Param::enableTypeFlow/0;

pragma[nomagic]
predicate isParameterNodeInSourceCallContext(ParamNode p) {
hasSourceCallCtx() and
exists(Node source, DataFlowCallable c |
Config::isSource(pragma[only_bind_into](source), _) and
nodeEnclosingCallable(source, c) and
nodeEnclosingCallable(p, c)
)
}

predicate relevantCallEdgeIn = PrevStage::relevantCallEdgeIn/2;

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

predicate isParameterNodeInSourceCallContext(ParamNode p) { none() }

predicate relevantCallEdgeIn(Call call, Callable c) {
flowOutOfCallAp(call, c, _, _, _, _, _)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1893,6 +1893,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
signature module TypeFlowInput {
predicate enableTypeFlow();

/** Holds if `p` is a parameter of a callable with a source node that has a call context. */
predicate isParameterNodeInSourceCallContext(ParamNode p);

/** Holds if the edge is possibly needed in the direction `call` to `c`. */
predicate relevantCallEdgeIn(Call call, Callable c);

Expand Down Expand Up @@ -1953,6 +1956,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
/**
* Holds if a sequence of calls may propagate the value of `arg` to some
* argument-to-parameter call edge that strengthens the static type.
*
* This predicate is a reverse flow computation, starting at calls that
* strengthen the type and then following relevant call edges backwards.
*/
pragma[nomagic]
private predicate trackedArgTypeCand(ArgNode arg) {
Expand Down Expand Up @@ -1987,6 +1993,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
* Holds if `p` is part of a value-propagating call path where the
* end-points have stronger types than the intermediate parameter and
* argument nodes.
*
* This predicate is a forward flow computation, intersecting with the
* reverse flow computation done in `trackedArgTypeCand`.
*/
private predicate trackedParamType(ParamNode p) {
exists(Call call1, Callable c1, ArgNode argOut, Call call2, Callable c2, ArgNode argIn |
Expand All @@ -2013,6 +2022,8 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
typeStrongerThanFilter(at, pt)
)
or
Input::isParameterNodeInSourceCallContext(p)
or
exists(ArgNode arg |
trackedArgType(arg) and
relevantCallEdge(_, _, arg, p) and
Expand Down Expand Up @@ -2106,7 +2117,9 @@ module MakeImplCommon<LocationSig Location, InputSig<Location> Lang> {
private predicate typeFlowParamType(ParamNode p, Type t, boolean cc) {
exists(Callable c |
Input::dataFlowNonCallEntry(c, cc) and
trackedParamWithType(p, t, c)
if cc = true and exists(getSourceContextParameterNodeType())
then t = getSourceContextParameterNodeType()
else trackedParamWithType(p, t, c)
)
or
exists(Type t1, Type t2 |
Expand Down
Loading