Skip to content

Commit 48de4cb

Browse files
committed
C++: Add MaD support for models that specify argument forwarding.
1 parent 04c2ac4 commit 48de4cb

4 files changed

Lines changed: 258 additions & 15 deletions

File tree

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

Lines changed: 121 additions & 1 deletion
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.
@@ -115,6 +117,7 @@ private import new.DataFlow
115117
private import semmle.code.cpp.controlflow.IRGuards
116118
private import semmle.code.cpp.ir.dataflow.internal.DataFlowNodes as Nodes
117119
private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate as Private
120+
private import semmle.code.cpp.ir.dataflow.internal.SsaImpl as SsaImpl
118121
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
119122
private import internal.FlowSummaryImpl
120123
private import internal.FlowSummaryImpl::Public
@@ -160,6 +163,20 @@ predicate summaryModel(
160163
)
161164
}
162165

166+
/**
167+
* Holds if a forward model exists for the given parameters.
168+
*/
169+
predicate forwardsModel(
170+
string namespace, string type, boolean subtypes, string name, string signature, string ext,
171+
string start, string constructor, string output, string provenance, string model
172+
) {
173+
exists(QlBuiltins::ExtensionId madId |
174+
Extensions::forwardsModel(namespace, type, subtypes, name, signature, ext, start, constructor,
175+
output, provenance, madId) and
176+
model = "MaD:" + madId.toString()
177+
)
178+
}
179+
163180
/** Provides a query predicate to check the data for validation errors. */
164181
module ModelValidation {
165182
private string getInvalidModelInput() {
@@ -186,6 +203,8 @@ module ModelValidation {
186203
sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source"
187204
or
188205
summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary"
206+
or
207+
forwardsModel(_, _, _, _, _, _, _, _, output, _, _) and pred = "forwards"
189208
|
190209
invalidSpecComponent(output, part) and
191210
not part = "" and
@@ -259,7 +278,8 @@ private predicate elementSpec(
259278
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) or
260279
barrierModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) or
261280
barrierGuardModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) or
262-
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _)
281+
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) or
282+
forwardsModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _)
263283
}
264284

265285
/**
@@ -1054,6 +1074,103 @@ private module Cached {
10541074

10551075
import Cached
10561076

1077+
/** Gets the constructor type selected by `constructorType` in a forwarding model. */
1078+
bindingset[forwarder, type, name, constructorType]
1079+
private Type getForwardedConstructorType(
1080+
Function forwarder, string type, string name, string constructorType
1081+
) {
1082+
exists(string typeArguments, int index |
1083+
parseAngles(type, _, typeArguments, "") and
1084+
constructorType = getAtIndex(typeArguments, index) and
1085+
result = forwarder.getDeclaringType().getTemplateArgument(index)
1086+
)
1087+
or
1088+
exists(string nameArguments, int index |
1089+
parseAngles(name, _, nameArguments, "") and
1090+
constructorType = getAtIndex(nameArguments, index) and
1091+
result = forwarder.getTemplateArgument(index)
1092+
)
1093+
}
1094+
1095+
/** Interprets a forwarding model, retaining its output and provenance. */
1096+
private predicate interpretForwardsModel(
1097+
Function forwarder, Constructor constructor, int start, string output, string provenance,
1098+
string model
1099+
) {
1100+
exists(
1101+
string namespace, string type, boolean subtypes, string name, string signature, string ext,
1102+
string startString, string constructorType
1103+
|
1104+
forwardsModel(namespace, type, subtypes, name, signature, ext, startString, constructorType,
1105+
output, provenance, model) and
1106+
forwarder = interpretElement(namespace, type, subtypes, name, signature, ext) and
1107+
start = startString.toInt()
1108+
|
1109+
// Either the row specifies forwarding to a type given by the type or
1110+
// function template, in which case we need to resolve that from the type
1111+
// or function name.
1112+
constructor.getDeclaringType() =
1113+
getForwardedConstructorType(forwarder, type, name, constructorType).getUnspecifiedType()
1114+
or
1115+
// Or the row specifies forwarding to a specific type.
1116+
classHasQualifiedName(constructor.getDeclaringType(), namespace, constructorType)
1117+
)
1118+
}
1119+
1120+
/** Holds if `forwarder` forwards its arguments starting at `start` to `constructor`. */
1121+
predicate forwards(Function forwarder, Constructor constructor, int start) {
1122+
interpretForwardsModel(forwarder, constructor, start, _, _, _)
1123+
}
1124+
1125+
private int referenceIndirection(Type unspecified) {
1126+
if unspecified instanceof ReferenceType then result = 1 else result = 0
1127+
}
1128+
1129+
/**
1130+
* In order to support flow summaries for functions that perform "perfect
1131+
* forwarding" we interpret a call such as:
1132+
* ```cpp
1133+
* struct Foo { Foo(int) };
1134+
* std::vector<Foo> v;
1135+
* v.emplace_back(42);
1136+
* ```
1137+
* as:
1138+
* ```cpp
1139+
* v.emplace_back(42, &Foo);
1140+
* ```
1141+
* and add two summaries:
1142+
* (1) One flow from `42` to the first argument of a call to `Foo`
1143+
* (2) One flow from the return value of `Foo` to the `this` argument of the call
1144+
* to `emplace_back` (with a sequence of output `Content`s).
1145+
*
1146+
* These two summaries are automatically generated when a forwarding model
1147+
* for `emplace_back` exists.
1148+
*/
1149+
private predicate interpretForwardingSummary(
1150+
Function forwarder, string input, string output, string provenance, string model
1151+
) {
1152+
exists(Constructor constructor, int start, string constructorOutput |
1153+
interpretForwardsModel(forwarder, constructor, start, constructorOutput, provenance, model)
1154+
|
1155+
// Generate the (1) summary
1156+
exists(int index, Parameter arg, Parameter p, int indirection |
1157+
arg = forwarder.getParameter(start + index) and
1158+
p = constructor.getParameter(index) and
1159+
indirection = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and
1160+
input =
1161+
"Argument[" + repeatStars(indirection + referenceIndirection(arg.getUnspecifiedType())) +
1162+
(start + index) + "]" and
1163+
output =
1164+
"Argument[forward].Parameter[" +
1165+
repeatStars(indirection + referenceIndirection(p.getUnspecifiedType())) + index + "]"
1166+
)
1167+
or
1168+
// Generate the (2) summary
1169+
input = "Argument[forward].Parameter[-1]" and
1170+
output = constructorOutput
1171+
)
1172+
}
1173+
10571174
/**
10581175
* Holds if `node` is specified as a source with the given kind in a MaD flow
10591176
* model.
@@ -1082,6 +1199,9 @@ private predicate interpretSummary(
10821199
model) and
10831200
f = interpretElement(namespace, type, subtypes, name, signature, ext)
10841201
)
1202+
or
1203+
interpretForwardingSummary(f, input, output, provenance, model) and
1204+
kind = "value"
10851205
}
10861206

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

‎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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ private module Cached {
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

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

Lines changed: 131 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,101 @@ private class SideEffectArgumentNode extends ArgumentNode, SideEffectOperandNode
593593
}
594594
}
595595

596+
/**
597+
* Gets `unspecifiedType`, but with the outermost `ReferenceType` removed, if any.
598+
*/
599+
private Type stripReferences(Type unspecifiedType) {
600+
result = unspecifiedType.(Cpp::ReferenceType).getBaseType().getUnspecifiedType()
601+
or
602+
not unspecifiedType instanceof Cpp::ReferenceType and
603+
result = unspecifiedType
604+
}
605+
606+
predicate forwardingCallTargetsConstructor(
607+
CallInstruction call, Cpp::Constructor constructor, int start
608+
) {
609+
exists(int numberOfForwardedArguments |
610+
External::forwards(call.getStaticCallTarget(), constructor, start) and
611+
call.getNumberOfPositionalArguments() = start + numberOfForwardedArguments and
612+
forall(int i | i = [0 .. constructor.getNumberOfParameters() - 1] |
613+
// If we are still processing the forwarded arguments then we need to
614+
// check that the argument types match the parameter types.
615+
// Functions that perform perfect forwarding are always written as:
616+
// ```
617+
// template<typename... Args> void emplace(Args&&... args) { ... }
618+
// ```
619+
// and so all the arguments will be reference typed (lvalue or rvalued).
620+
// However, the constructor may not specify all the arguments by
621+
// reference.
622+
i < numberOfForwardedArguments and
623+
stripReferences(call.getPositionalArgument(start + i).getResultType()) =
624+
stripReferences(constructor.getParameter(i).getUnspecifiedType())
625+
or
626+
// If the constructor has a default argument and we have processed all
627+
// the forwarded arguments then we don't need to check the types.
628+
i >= numberOfForwardedArguments and constructor.getParameter(i).hasInitializer()
629+
)
630+
)
631+
}
632+
633+
/** Holds if `call` is a call that forwards arguments to a constructor call. */
634+
predicate isForwarderConstructorArgumentNodeImpl(CallInstruction call) {
635+
forwardingCallTargetsConstructor(call, _, _)
636+
}
637+
638+
/**
639+
* In order to implement a MaD summary for a flow such as:
640+
* ```
641+
* struct Foo {
642+
* int x;
643+
* Foo(int x) { // (2)
644+
* this->x = x;
645+
* }
646+
* }
647+
*
648+
* std::vector<Foo> v;
649+
* int x = source();
650+
* v.emplace_back(x); // (1)
651+
* sink(v.back());
652+
* ```
653+
* we model it as if the code was:
654+
* ```
655+
* v.__emplace_back(x, &Foo)
656+
* ```
657+
* (nevermind that this is not real C++ since you cannot take the address of a
658+
* constructor.)
659+
* where `__emplace_back` invokes `Foo` with the `x` argument and returns the
660+
* result.
661+
*
662+
* This class serves as the argument node for `&Foo`.
663+
*/
664+
private class ForwarderConstructorArgumentNode extends ArgumentNode,
665+
TForwarderConstructorArgumentNode
666+
{
667+
private CallInstruction call;
668+
669+
ForwarderConstructorArgumentNode() { this = TForwarderConstructorArgumentNode(call) }
670+
671+
override predicate sourceArgumentOf(CallInstruction c, ArgumentPosition pos) {
672+
c = call and pos = TForwardPosition()
673+
}
674+
675+
/**
676+
* Gets a constructor which may be targeted by this forwarding call.
677+
*/
678+
Cpp::Constructor getAConstructor() { forwardingCallTargetsConstructor(call, result, _) }
679+
680+
override DataFlowCallable getEnclosingCallable() {
681+
result.asSourceCallable() = this.getFunction()
682+
}
683+
684+
override Declaration getFunction() { result = call.getEnclosingFunction() }
685+
686+
override Location getLocationImpl() { result = call.getLocation() }
687+
688+
override string toStringImpl() { result = "forwarder for " + call.toString() }
689+
}
690+
596691
/**
597692
* An argument node that is part of a summary. These only occur when the
598693
* summary contains a synthesized call.
@@ -672,6 +767,12 @@ abstract class Position extends TPosition {
672767
this.getArgumentIndex() = -1 and
673768
result = call.getQualifier()
674769
}
770+
771+
/**
772+
* Holds if this position is the synthetic argument for an address of a
773+
* constructor used for functions which perform "perfect forwarding".
774+
*/
775+
predicate isForward() { none() }
675776
}
676777

677778
class DirectPosition extends Position, TDirectPosition {
@@ -721,6 +822,16 @@ class FlowSummaryPosition extends Position, TFlowSummaryPosition {
721822
final override int getIndirectionIndex() { result = rk.getIndirectionIndex() }
722823
}
723824

825+
class ForwardPosition extends Position, TForwardPosition {
826+
final override predicate isForward() { any() }
827+
828+
override int getArgumentIndex() { none() }
829+
830+
final override int getIndirectionIndex() { result = 0 }
831+
832+
override string toString() { result = "forward" }
833+
}
834+
724835
newtype TPosition =
725836
TDirectPosition(int argumentIndex) {
726837
exists(any(CallInstruction c).getArgument(argumentIndex))
@@ -740,6 +851,7 @@ newtype TPosition =
740851
indirectionIndex = [1 .. Ssa::getMaxIndirectionsForPRType(p.getUnspecifiedType())]
741852
)
742853
} or
854+
TForwardPosition() or
743855
TFlowSummaryPosition(ReturnKind rk) { FlowSummaryImpl::Private::relevantFlowSummaryPosition(rk) }
744856

745857
private newtype TReturnKind =
@@ -1258,6 +1370,19 @@ private predicate summarizedCallableIsManual(SummarizedCallable sc) {
12581370
sc.asSummarizedCallable().hasManualModel()
12591371
}
12601372

1373+
private DataFlowCallable getTarget(Declaration target) {
1374+
// Don't use the source callable if there is a manual model for the target.
1375+
not exists(SummarizedCallable sc |
1376+
sc.asSummarizedCallable() = target and
1377+
summarizedCallableIsManual(sc)
1378+
) and
1379+
result.asSourceCallable() = target
1380+
or
1381+
// When there is no function body, or when we have a manual model, dispatch to the summary.
1382+
(not target.hasDefinition() or summarizedCallableIsManual(result)) and
1383+
result.asSummarizedCallable() = target
1384+
}
1385+
12611386
/**
12621387
* A function call relevant for data flow. This includes calls from source
12631388
* code and calls inside library callables with a flow summary.
@@ -1293,20 +1418,7 @@ class DataFlowCall extends TDataFlowCall {
12931418
* whether is it manual or generated.
12941419
*/
12951420
final DataFlowCallable getStaticCallTarget() {
1296-
exists(Declaration target | target = this.getStaticCallSourceTarget() |
1297-
// Don't use the source callable if there is a manual model for the
1298-
// target
1299-
not exists(SummarizedCallable sc |
1300-
sc.asSummarizedCallable() = target and
1301-
summarizedCallableIsManual(sc)
1302-
) and
1303-
result.asSourceCallable() = target
1304-
or
1305-
// When there is no function body, or when we have a manual model then
1306-
// we dispatch to the summary.
1307-
(not target.hasDefinition() or summarizedCallableIsManual(result)) and
1308-
result.asSummarizedCallable() = target
1309-
)
1421+
result = getTarget(this.getStaticCallSourceTarget())
13101422
}
13111423

13121424
/**
@@ -1493,6 +1605,8 @@ predicate nodeIsHidden(Node n) {
14931605
n instanceof SsaSynthNode
14941606
or
14951607
n.(FlowSummaryNode).getSummaryNode().isHidden()
1608+
or
1609+
n instanceof ForwarderConstructorArgumentNode
14961610
}
14971611

14981612
predicate neverSkipInPathGraph(Node n) {
@@ -1574,6 +1688,9 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
15741688
kind.isFunctionPointer() and
15751689
creation.asInstruction().(FunctionAddressInstruction).getFunctionSymbol() = c.asSourceCallable()
15761690
or
1691+
kind.isFunctionPointer() and
1692+
c = getTarget(creation.(ForwarderConstructorArgumentNode).getAConstructor())
1693+
or
15771694
kind.isFunctor() and
15781695
exists(OperatorCall operator | operator = c.asSourceCallable() |
15791696
isFunctorCreationWithoutConstructor(creation, operator)

0 commit comments

Comments
 (0)