Skip to content

Commit f920aef

Browse files
authored
Merge pull request #22532 from MathiasVP/flow-through-forwards-using-callbacks-3
C++: Implement MaD support for flow through perfect-forwarding functions
2 parents 182bea4 + 18affcd commit f920aef

15 files changed

Lines changed: 973 additions & 219 deletions

File tree

cpp/ql/lib/ext/empty.model.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ extensions:
2121
pack: codeql/cpp-all
2222
extensible: summaryModel
2323
data: []
24+
- addsTo:
25+
pack: codeql/cpp-all
26+
extensible: forwardsModel
27+
data: []

cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll

Lines changed: 187 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
1616
* - BarrierGuards:
1717
* `namespace; type; subtypes; name; signature; ext; input; acceptingValue; kind; provenance`
18+
* - Forwards:
19+
* `namespace; type; subtypes; name; signature; ext; start; constructor; output; provenance`
1820
*
1921
* The interpretation of a row is similar to API-graphs with a left-to-right
2022
* reading.
@@ -108,13 +110,23 @@
108110
* - "manual": The model has been written by hand.
109111
* This information is used in a heuristic for dataflow analysis to determine, if a
110112
* model or source code should be used for determining flow.
113+
*
114+
* The "Forwards" relation allows modeling of function that perform C++11-style "perfect
115+
* forwarding" where a function receives a number of arguments and forwards those arguments
116+
* to a constructor of another type. For example, the row:
117+
* `"std"; "vector<T>"; "True"; "emplace"; ""; ""; "1"; T; Argument[-1].Element; manual`
118+
* says that `std::vector<T>::emplace(arg0, arg1, ..., argn)` forwards arguments
119+
* `arg1, ..., argn` to a constructor for `T`, and the result of `T(arg1, ..., argn)`
120+
* flows to `Argument[-1].Element` (see information about the semantics of the `output`
121+
* column further above).
111122
*/
112123

113124
import cpp
114125
private import new.DataFlow
115126
private import semmle.code.cpp.controlflow.IRGuards
116127
private import semmle.code.cpp.ir.dataflow.internal.DataFlowNodes as Nodes
117128
private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate as Private
129+
private import semmle.code.cpp.ir.dataflow.internal.SsaImpl as SsaImpl
118130
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
119131
private import internal.FlowSummaryImpl
120132
private import internal.FlowSummaryImpl::Public
@@ -160,6 +172,20 @@ predicate summaryModel(
160172
)
161173
}
162174

175+
/**
176+
* Holds if a forward model exists for the given parameters.
177+
*/
178+
predicate forwardsModel(
179+
string namespace, string type, boolean subtypes, string name, string signature, string ext,
180+
string start, string constructor, string output, string provenance, string model
181+
) {
182+
exists(QlBuiltins::ExtensionId madId |
183+
Extensions::forwardsModel(namespace, type, subtypes, name, signature, ext, start, constructor,
184+
output, provenance, madId) and
185+
model = "MaD:" + madId.toString()
186+
)
187+
}
188+
163189
/** Provides a query predicate to check the data for validation errors. */
164190
module ModelValidation {
165191
private string getInvalidModelInput() {
@@ -186,6 +212,8 @@ module ModelValidation {
186212
sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source"
187213
or
188214
summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary"
215+
or
216+
forwardsModel(_, _, _, _, _, _, _, _, output, _, _) and pred = "forwards"
189217
|
190218
invalidSpecComponent(output, part) and
191219
not part = "" and
@@ -259,7 +287,8 @@ private predicate elementSpec(
259287
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) or
260288
barrierModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) or
261289
barrierGuardModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) or
262-
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _)
290+
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) or
291+
forwardsModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _)
263292
}
264293

265294
/**
@@ -596,6 +625,14 @@ private string getAtIndex(string s, int i) {
596625
not (s = "" and i = 0)
597626
}
598627

628+
/** Gets the number of comma-separated arguments in `s`. */
629+
bindingset[s]
630+
private int getNumberOfArguments(string s) {
631+
s = "" and result = 0
632+
or
633+
s != "" and result = count(s.indexOf(",")) + 1
634+
}
635+
599636
/**
600637
* Normalizes `partiallyNormalizedSignature` by replacing the `remaining`
601638
* number of template arguments in `partiallyNormalizedSignature` with their
@@ -605,7 +642,7 @@ private string getSignatureWithoutClassTemplateNames(
605642
string partiallyNormalizedSignature, string typeArgs, string nameArgs, int remaining
606643
) {
607644
elementSpecWithArguments0(_, _, _, partiallyNormalizedSignature, typeArgs, nameArgs) and
608-
remaining = count(partiallyNormalizedSignature.indexOf(",")) + 1 and
645+
remaining = getNumberOfArguments(typeArgs) and
609646
result = partiallyNormalizedSignature
610647
or
611648
exists(string mid |
@@ -619,7 +656,7 @@ private string getSignatureWithoutClassTemplateNames(
619656
)
620657
or
621658
// Make sure `remaining` is properly bound
622-
remaining = [0 .. count(partiallyNormalizedSignature.indexOf(",")) + 1] and
659+
remaining = [0 .. getNumberOfArguments(typeArgs)] and
623660
not exists(getAtIndex(typeArgs, remaining)) and
624661
result = mid
625662
)
@@ -636,7 +673,7 @@ pragma[nomagic]
636673
private string getSignatureWithoutFunctionTemplateNames(
637674
string partiallyNormalizedSignature, string typeArgs, string nameArgs, int remaining
638675
) {
639-
remaining = count(partiallyNormalizedSignature.indexOf(",")) + 1 and
676+
remaining = getNumberOfArguments(nameArgs) and
640677
result =
641678
getSignatureWithoutClassTemplateNames(partiallyNormalizedSignature, typeArgs, nameArgs, 0)
642679
or
@@ -651,7 +688,7 @@ private string getSignatureWithoutFunctionTemplateNames(
651688
)
652689
or
653690
// Make sure `remaining` is properly bound
654-
remaining = [0 .. count(partiallyNormalizedSignature.indexOf(",")) + 1] and
691+
remaining = [0 .. getNumberOfArguments(nameArgs)] and
655692
not exists(getAtIndex(nameArgs, remaining)) and
656693
result = mid
657694
)
@@ -1046,6 +1083,148 @@ private module Cached {
10461083

10471084
import Cached
10481085

1086+
/** Gets the constructor type selected by `constructorType` in a forwarding model. */
1087+
private Type getForwardedConstructorType(
1088+
Function forwarder, string namespace, string type, boolean subtypes, string name,
1089+
string signature, string ext, string constructorType
1090+
) {
1091+
exists(int index |
1092+
forwardsModel(namespace, type, subtypes, name, signature, ext, _, constructorType, _, _, _) and
1093+
forwarder = interpretElement(namespace, type, subtypes, name, signature, ext)
1094+
|
1095+
exists(string typeArguments |
1096+
parseAngles(type, _, typeArguments, "") and
1097+
constructorType = getAtIndex(typeArguments, index) and
1098+
result = forwarder.getDeclaringType().getTemplateArgument(index)
1099+
)
1100+
or
1101+
exists(string nameArguments |
1102+
parseAngles(name, _, nameArguments, "") and
1103+
constructorType = getAtIndex(nameArguments, index) and
1104+
result = forwarder.getTemplateArgument(index)
1105+
)
1106+
)
1107+
}
1108+
1109+
/** Interprets a forwarding model, retaining its constructed type, output, and provenance. */
1110+
private predicate interpretForwardsModelType(
1111+
Function forwarder, Type constructedType, int start, string output, string provenance,
1112+
string model
1113+
) {
1114+
exists(
1115+
string namespace, string type, boolean subtypes, string name, string signature, string ext,
1116+
string startString, string constructorType
1117+
|
1118+
forwardsModel(namespace, type, subtypes, name, signature, ext, startString, constructorType,
1119+
output, provenance, model) and
1120+
forwarder = interpretElement(namespace, type, subtypes, name, signature, ext) and
1121+
start = startString.toInt()
1122+
|
1123+
// Either the row specifies forwarding to a type given by the type or
1124+
// function template, in which case we need to resolve that from the type
1125+
// or function name.
1126+
constructedType =
1127+
getForwardedConstructorType(forwarder, namespace, type, subtypes, name, signature, ext,
1128+
constructorType).getUnspecifiedType()
1129+
or
1130+
// Or the row specifies forwarding to a specific type.
1131+
not exists(
1132+
getForwardedConstructorType(forwarder, namespace, type, subtypes, name, signature, ext,
1133+
constructorType)
1134+
) and
1135+
classHasQualifiedName(constructedType, namespace, constructorType)
1136+
)
1137+
}
1138+
1139+
/**
1140+
* Holds if `forwarder` may forward its arguments starting at `start` to `constructor`. The
1141+
* actual constructor being forwarded to depends on the types of arguments from `start`
1142+
* at calls to `forwarder`.
1143+
*/
1144+
private predicate interpretForwardsModel(
1145+
Function forwarder, Constructor constructor, int start, string output, string provenance,
1146+
string model
1147+
) {
1148+
interpretForwardsModelType(forwarder, constructor.getDeclaringType(), start, output, provenance,
1149+
model)
1150+
}
1151+
1152+
/** Holds if `forwarder` forwards its arguments starting at `start` to `constructor`. */
1153+
predicate forwards(Function forwarder, Constructor constructor, int start) {
1154+
interpretForwardsModel(forwarder, constructor, start, _, _, _)
1155+
}
1156+
1157+
private int referenceIndirection(Type unspecified) {
1158+
if unspecified instanceof ReferenceType then result = 1 else result = 0
1159+
}
1160+
1161+
/** Gets `unspecified`, but with its outermost reference removed, if any. */
1162+
private Type stripReference(Type unspecified) {
1163+
result = unspecified.(ReferenceType).getBaseType().getUnspecifiedType()
1164+
or
1165+
not unspecified instanceof ReferenceType and
1166+
result = unspecified
1167+
}
1168+
1169+
/**
1170+
* In order to support flow summaries for functions that perform "perfect
1171+
* forwarding" we interpret a call such as:
1172+
* ```cpp
1173+
* struct Foo { Foo(int) };
1174+
* std::vector<Foo> v;
1175+
* v.emplace_back(42);
1176+
* ```
1177+
* as:
1178+
* ```cpp
1179+
* v.emplace_back(42, &Foo);
1180+
* ```
1181+
* and add two summaries:
1182+
* (1) One flow from `42` to the first argument of a call to `Foo`
1183+
* (2) One flow from the return value of `Foo` to the `this` argument of the call
1184+
* to `emplace_back` (with a sequence of output `Content`s).
1185+
*
1186+
* These two summaries are automatically generated when a forwarding model
1187+
* for `emplace_back` exists.
1188+
*/
1189+
private predicate interpretForwardingSummary(
1190+
Function forwarder, string input, string output, string provenance, string model
1191+
) {
1192+
exists(Constructor constructor, int start, string constructorOutput |
1193+
interpretForwardsModel(forwarder, constructor, start, constructorOutput, provenance, model)
1194+
|
1195+
// Generate the (1) summary
1196+
exists(int index, Parameter arg, Parameter p, int indirection |
1197+
arg = forwarder.getParameter(start + index) and
1198+
p = constructor.getParameter(index) and
1199+
indirection = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and
1200+
input =
1201+
"Argument[" + repeatStars(indirection + referenceIndirection(arg.getUnspecifiedType())) +
1202+
(start + index) + "]" and
1203+
output =
1204+
"Argument[forward].Parameter[" +
1205+
repeatStars(indirection + referenceIndirection(p.getUnspecifiedType())) + index + "]"
1206+
)
1207+
or
1208+
// Generate the (2) summary
1209+
input = "Argument[forward].Parameter[-1]" and
1210+
output = constructorOutput
1211+
)
1212+
or
1213+
// Scalar types have no constructor to synthesize. In this case, directly
1214+
// preserve the value of the single forwarded argument at the modeled output.
1215+
exists(Type constructedType, int start, Parameter p, int indirection |
1216+
interpretForwardsModelType(forwarder, constructedType, start, output, provenance, model) and
1217+
not constructedType instanceof Class and
1218+
forwarder.getNumberOfParameters() = start + 1 and
1219+
p = forwarder.getParameter(start) and
1220+
stripReference(p.getUnspecifiedType()) = constructedType and
1221+
indirection = [0 .. SsaImpl::getMaxIndirectionsForPRType(constructedType)] and
1222+
input =
1223+
"Argument[" + repeatStars(indirection + referenceIndirection(p.getUnspecifiedType())) + start +
1224+
"]"
1225+
)
1226+
}
1227+
10491228
/**
10501229
* Holds if `node` is specified as a source with the given kind in a MaD flow
10511230
* model.
@@ -1074,6 +1253,9 @@ private predicate interpretSummary(
10741253
model) and
10751254
f = interpretElement(namespace, type, subtypes, name, signature, ext)
10761255
)
1256+
or
1257+
interpretForwardingSummary(f, input, output, provenance, model) and
1258+
kind = "value"
10771259
}
10781260

10791261
// adapter class for converting Mad summaries to `SummarizedCallable`s

cpp/ql/lib/semmle/code/cpp/dataflow/internal/ExternalFlowExtensions.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ extensible predicate neutralModel(
5151
string namespace, string type, string name, string signature, string kind, string provenance
5252
);
5353

54+
/**
55+
* Holds if a constructor forwarding model exists for the given parameters.
56+
*/
57+
extensible predicate forwardsModel(
58+
string namespace, string type, boolean subtypes, string name, string signature, string ext,
59+
string start, string constructor, string output, string provenance, QlBuiltins::ExtensionId madId
60+
);
61+
5462
module Extensions implements SharedMaD::ExtensionsSig {
5563
import ExternalFlowExtensions
5664

cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowSummaryImpl.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ module Input implements InputSig<Location, DataFlowImplSpecific::CppDataFlow> {
111111
pos = -1 and result = TIndirectionPosition(pos, indirection + 1)
112112
)
113113
)
114+
or
115+
argString = "forward" and
116+
result = TForwardPosition()
114117
}
115118

116119
bindingset[token]

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ private module Cached {
144144
TNonUnionContent(CanonicalField f, int indirectionIndex) {
145145
// the indirection index for field content starts at 1 (because `TNonUnionContent` is thought of as
146146
// the address of the field, `FieldAddress` in the IR).
147-
indirectionIndex = [1 .. max(SsaImpl::getMaxIndirectionsForType(f.getAnUnspecifiedType()))] and
147+
indirectionIndex = [1 .. max(SsaImpl::getMaxIndirectionsForGLType(f.getAnUnspecifiedType()))] and
148148
// Reads and writes of union fields are tracked using `UnionContent`.
149149
not f.getDeclaringType() instanceof Union
150150
} or
@@ -156,7 +156,7 @@ private module Cached {
156156
// field can be read by any read of the union's fields. Again, the indirection index
157157
// is 1-based (because 0 is considered the address).
158158
indirectionIndex =
159-
[1 .. max(SsaImpl::getMaxIndirectionsForType(getAFieldWithSize(u, bytes)
159+
[1 .. max(SsaImpl::getMaxIndirectionsForGLType(getAFieldWithSize(u, bytes)
160160
.getAnUnspecifiedType())
161161
)]
162162
)
@@ -184,13 +184,16 @@ private module Cached {
184184
TNode0(Node0Impl node) { DataFlowImplCommon::forceCachingInSameStage() } or
185185
TGlobalLikeVariableNode(GlobalLikeVariable var, int indirectionIndex) {
186186
indirectionIndex =
187-
[getMinIndirectionsForType(var.getUnspecifiedType()) .. SsaImpl::getMaxIndirectionsForType(var.getUnspecifiedType())]
187+
[getMinIndirectionsForType(var.getUnspecifiedType()) .. SsaImpl::getMaxIndirectionsForGLType(var.getUnspecifiedType())]
188188
} or
189189
TPostUpdateNodeImpl(Operand operand, int indirectionIndex) {
190190
isPostUpdateNodeImpl(operand, indirectionIndex)
191191
} or
192192
TSsaSynthNode(SsaImpl::SynthNode n) or
193193
TSsaIteratorNode(IteratorFlow::IteratorFlowNode n) or
194+
TForwarderConstructorArgumentNode(CallInstruction call) {
195+
isForwarderConstructorArgumentNodeImpl(call)
196+
} or
194197
TRawIndirectOperand0(Node0Impl node, int indirectionIndex) {
195198
SsaImpl::hasRawIndirectOperand(node.asOperand(), indirectionIndex)
196199
} or
@@ -209,10 +212,7 @@ private module Cached {
209212
TBodyLessParameterNodeImpl(Parameter p, int indirectionIndex) {
210213
// Rule out parameters of catch blocks.
211214
not exists(p.getCatchBlock()) and
212-
// We subtract one because `getMaxIndirectionsForType` returns the maximum
213-
// indirection for a glvalue of a given type, and this doesn't apply to
214-
// parameters.
215-
indirectionIndex = [0 .. SsaImpl::getMaxIndirectionsForType(p.getUnspecifiedType()) - 1] and
215+
indirectionIndex = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and
216216
not any(InitializeParameterInstruction init).getParameter() = p
217217
} or
218218
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn)

0 commit comments

Comments
 (0)