Skip to content

Commit 3e6a9fb

Browse files
committed
C++: Count return dispatch based on 2nd level scopes.
1 parent a6419dc commit 3e6a9fb

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
@@ -21,6 +21,8 @@ module CppDataFlow implements InputSig {
2121

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

24+
predicate getSecondLevelScope = Private::getSecondLevelScope/1;
25+
2426
predicate validParameterAliasStep = Private::validParameterAliasStep/2;
2527

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

shared/dataflow/codeql/dataflow/DataFlow.qll

+18
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,24 @@ signature module InputSig {
267267
*/
268268
default int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() }
269269

270+
/**
271+
* A second-level control-flow scope in a callable.
272+
*
273+
* This is used to provide a more fine-grained separation of a callable
274+
* context for the purpose of identifying uncertain control flow. For most
275+
* languages, this is not needed, as this separation is handled through
276+
* virtual dispatch, but for some cases (for example, C++) this can be used to
277+
* identify, for example, large top-level switch statements acting like
278+
* virtual dispatch.
279+
*/
280+
class DataFlowSecondLevelScope {
281+
/** Gets a textual representation of this element. */
282+
string toString();
283+
}
284+
285+
/** Gets the second-level scope containing the node `n`, if any. */
286+
default DataFlowSecondLevelScope getSecondLevelScope(Node n) { none() }
287+
270288
bindingset[call, p, arg]
271289
default predicate golangSpecificParamArgFilter(
272290
DataFlowCall call, ParameterNode p, ArgumentNode arg

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

+24-6
Original file line numberDiff line numberDiff line change
@@ -1076,28 +1076,46 @@ module MakeImpl<InputSig Lang> {
10761076
result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode())
10771077
}
10781078

1079+
private module SndLevelScopeOption = Option<DataFlowSecondLevelScope>;
1080+
1081+
private class SndLevelScopeOption = SndLevelScopeOption::Option;
1082+
1083+
pragma[nomagic]
1084+
private SndLevelScopeOption getScope(RetNodeEx ret) {
1085+
result = SndLevelScopeOption::some(getSecondLevelScope(ret.asNode()))
1086+
or
1087+
result instanceof SndLevelScopeOption::None and not exists(getSecondLevelScope(ret.asNode()))
1088+
}
1089+
10791090
pragma[nomagic]
1080-
private predicate returnCallEdge1(DataFlowCallable c, DataFlowCall call, NodeEx out) {
1091+
private predicate returnCallEdge1(
1092+
DataFlowCallable c, SndLevelScopeOption scope, DataFlowCall call, NodeEx out
1093+
) {
10811094
exists(RetNodeEx ret |
1082-
flowOutOfCallNodeCand1(call, ret, _, out) and c = ret.getEnclosingCallable()
1095+
flowOutOfCallNodeCand1(call, ret, _, out) and
1096+
c = ret.getEnclosingCallable() and
1097+
scope = getScope(ret)
10831098
)
10841099
}
10851100

10861101
private int simpleDispatchFanoutOnReturn(DataFlowCall call, NodeEx out) {
1087-
result = strictcount(DataFlowCallable c | returnCallEdge1(c, call, out))
1102+
result =
1103+
strictcount(DataFlowCallable c, SndLevelScopeOption scope |
1104+
returnCallEdge1(c, scope, call, out)
1105+
)
10881106
}
10891107

10901108
private int ctxDispatchFanoutOnReturn(NodeEx out, DataFlowCall ctx) {
10911109
exists(DataFlowCall call, DataFlowCallable c |
10921110
simpleDispatchFanoutOnReturn(call, out) > 1 and
10931111
not Stage1::revFlow(out, false) and
10941112
call.getEnclosingCallable() = c and
1095-
returnCallEdge1(c, ctx, _) and
1113+
returnCallEdge1(c, _, ctx, _) and
10961114
mayBenefitFromCallContextExt(call, _) and
10971115
result =
1098-
count(DataFlowCallable tgt |
1116+
count(DataFlowCallable tgt, SndLevelScopeOption scope |
10991117
tgt = viableImplInCallContextExt(call, ctx) and
1100-
returnCallEdge1(tgt, call, out)
1118+
returnCallEdge1(tgt, scope, call, out)
11011119
)
11021120
)
11031121
}

0 commit comments

Comments
 (0)