Skip to content

Commit db6d27b

Browse files
committed
C++: Count return dispatch based on 2nd level scopes.
1 parent 9e39be5 commit db6d27b

File tree

4 files changed

+115
-6
lines changed

4 files changed

+115
-6
lines changed

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ module CppDataFlow implements InputSig<Location> {
2222

2323
predicate getAdditionalFlowIntoCallNodeTerm = Private::getAdditionalFlowIntoCallNodeTerm/2;
2424

25+
predicate getSecondLevelScope = Private::getSecondLevelScope/1;
26+
2527
predicate validParameterAliasStep = Private::validParameterAliasStep/2;
2628

2729
predicate mayBenefitFromCallContext = Private::mayBenefitFromCallContext/1;

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll

+71
Original file line numberDiff line numberDiff line change
@@ -1263,3 +1263,74 @@ predicate validParameterAliasStep(Node node1, Node node2) {
12631263
)
12641264
)
12651265
}
1266+
1267+
private predicate isTopLevel(Cpp::Stmt s) { any(Function f).getBlock().getAStmt() = s }
1268+
1269+
private Cpp::Stmt getAChainedBranch(Cpp::IfStmt s) {
1270+
result = s.getThen()
1271+
or
1272+
exists(Cpp::Stmt elseBranch | s.getElse() = elseBranch |
1273+
result = getAChainedBranch(elseBranch)
1274+
or
1275+
result = elseBranch and not elseBranch instanceof Cpp::IfStmt
1276+
)
1277+
}
1278+
1279+
private Instruction getInstruction(Node n) {
1280+
result = n.asInstruction() or
1281+
result = n.asOperand().getUse() or
1282+
result = n.(SsaPhiNode).getPhiNode().getBasicBlock().getFirstInstruction() or
1283+
n.(IndirectInstruction).hasInstructionAndIndirectionIndex(result, _) or
1284+
result = getInstruction(n.(PostUpdateNode).getPreUpdateNode())
1285+
}
1286+
1287+
private newtype TDataFlowSecondLevelScope =
1288+
TTopLevelIfBranch(Cpp::Stmt s) {
1289+
exists(Cpp::IfStmt ifstmt | s = getAChainedBranch(ifstmt) and isTopLevel(ifstmt))
1290+
} or
1291+
TTopLevelSwitchCase(Cpp::SwitchCase s) {
1292+
exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase() and isTopLevel(switchstmt))
1293+
}
1294+
1295+
/**
1296+
* A second-level control-flow scope in a `switch` or a chained `if` statement.
1297+
*
1298+
* This is a `switch` case or a branch of a chained `if` statement, given that
1299+
* the `switch` or `if` statement is top level, that is, it is not nested inside
1300+
* other CFG constructs.
1301+
*/
1302+
class DataFlowSecondLevelScope extends TDataFlowSecondLevelScope {
1303+
/** Gets a textual representation of this element. */
1304+
string toString() {
1305+
exists(Cpp::Stmt s | this = TTopLevelIfBranch(s) | result = s.toString())
1306+
or
1307+
exists(Cpp::SwitchCase s | this = TTopLevelSwitchCase(s) | result = s.toString())
1308+
}
1309+
1310+
/** Gets the primary location of this element. */
1311+
Cpp::Location getLocation() {
1312+
exists(Cpp::Stmt s | this = TTopLevelIfBranch(s) | result = s.getLocation())
1313+
or
1314+
exists(Cpp::SwitchCase s | this = TTopLevelSwitchCase(s) | result = s.getLocation())
1315+
}
1316+
1317+
/**
1318+
* Gets a statement directly contained in this scope. For an `if` branch, this
1319+
* is the branch itself, and for a `switch case`, this is one the statements
1320+
* of that case branch.
1321+
*/
1322+
private Cpp::Stmt getAStmt() {
1323+
exists(Cpp::Stmt s | this = TTopLevelIfBranch(s) | result = s)
1324+
or
1325+
exists(Cpp::SwitchCase s | this = TTopLevelSwitchCase(s) | result = s.getAStmt())
1326+
}
1327+
1328+
/** Gets a data-flow node nested within this scope. */
1329+
Node getANode() {
1330+
getInstruction(result).getAst().(Cpp::ControlFlowNode).getEnclosingStmt().getParentStmt*() =
1331+
this.getAStmt()
1332+
}
1333+
}
1334+
1335+
/** Gets the second-level scope containing the node `n`, if any. */
1336+
DataFlowSecondLevelScope getSecondLevelScope(Node n) { result.getANode() = n }

shared/dataflow/codeql/dataflow/DataFlow.qll

+18
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,24 @@ signature module InputSig<LocationSig Location> {
308308
*/
309309
default int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() }
310310

311+
/**
312+
* A second-level control-flow scope in a callable.
313+
*
314+
* This is used to provide a more fine-grained separation of a callable
315+
* context for the purpose of identifying uncertain control flow. For most
316+
* languages, this is not needed, as this separation is handled through
317+
* virtual dispatch, but for some cases (for example, C++) this can be used to
318+
* identify, for example, large top-level switch statements acting like
319+
* virtual dispatch.
320+
*/
321+
class DataFlowSecondLevelScope {
322+
/** Gets a textual representation of this element. */
323+
string toString();
324+
}
325+
326+
/** Gets the second-level scope containing the node `n`, if any. */
327+
default DataFlowSecondLevelScope getSecondLevelScope(Node n) { none() }
328+
311329
bindingset[call, p, arg]
312330
default predicate golangSpecificParamArgFilter(
313331
DataFlowCall call, ParameterNode p, ArgumentNode arg

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

+24-6
Original file line numberDiff line numberDiff line change
@@ -1113,28 +1113,46 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
11131113
result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode())
11141114
}
11151115

1116+
private module SndLevelScopeOption = Option<DataFlowSecondLevelScope>;
1117+
1118+
private class SndLevelScopeOption = SndLevelScopeOption::Option;
1119+
1120+
pragma[nomagic]
1121+
private SndLevelScopeOption getScope(RetNodeEx ret) {
1122+
result = SndLevelScopeOption::some(getSecondLevelScope(ret.asNode()))
1123+
or
1124+
result instanceof SndLevelScopeOption::None and not exists(getSecondLevelScope(ret.asNode()))
1125+
}
1126+
11161127
pragma[nomagic]
1117-
private predicate returnCallEdge1(DataFlowCallable c, DataFlowCall call, NodeEx out) {
1128+
private predicate returnCallEdge1(
1129+
DataFlowCallable c, SndLevelScopeOption scope, DataFlowCall call, NodeEx out
1130+
) {
11181131
exists(RetNodeEx ret |
1119-
flowOutOfCallNodeCand1(call, ret, _, out) and c = ret.getEnclosingCallable()
1132+
flowOutOfCallNodeCand1(call, ret, _, out) and
1133+
c = ret.getEnclosingCallable() and
1134+
scope = getScope(ret)
11201135
)
11211136
}
11221137

11231138
private int simpleDispatchFanoutOnReturn(DataFlowCall call, NodeEx out) {
1124-
result = strictcount(DataFlowCallable c | returnCallEdge1(c, call, out))
1139+
result =
1140+
strictcount(DataFlowCallable c, SndLevelScopeOption scope |
1141+
returnCallEdge1(c, scope, call, out)
1142+
)
11251143
}
11261144

11271145
private int ctxDispatchFanoutOnReturn(NodeEx out, DataFlowCall ctx) {
11281146
exists(DataFlowCall call, DataFlowCallable c |
11291147
simpleDispatchFanoutOnReturn(call, out) > 1 and
11301148
not Stage1::revFlow(out, false) and
11311149
call.getEnclosingCallable() = c and
1132-
returnCallEdge1(c, ctx, _) and
1150+
returnCallEdge1(c, _, ctx, _) and
11331151
mayBenefitFromCallContextExt(call, _) and
11341152
result =
1135-
count(DataFlowCallable tgt |
1153+
count(DataFlowCallable tgt, SndLevelScopeOption scope |
11361154
tgt = viableImplInCallContextExt(call, ctx) and
1137-
returnCallEdge1(tgt, call, out)
1155+
returnCallEdge1(tgt, scope, call, out)
11381156
)
11391157
)
11401158
}

0 commit comments

Comments
 (0)