Skip to content

Commit a2ae81f

Browse files
committed
C++: model BDE nullable and variant contents
Track contained values through NullableValue and VariantImp accessors, copy/move operations, assignment, and scalar emplacement. Preserve assignment-result aliasing, returned-reference writes, and exact-type pointer payloads. Leave user-defined conversions and emplacement constructors available for body analysis. Add regressions for scalar, pointer and aggregate payloads; copy/move result reads and writes; arithmetic conversions; and constructors and conversions that use or ignore their inputs. Wrapper flow tests and external model validation pass.
1 parent b9cb90c commit a2ae81f

15 files changed

Lines changed: 1231 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added contained-value flow models for BDE `bdlb::NullableValue` and the `bdlb::Variant` family, including copy and move operations, allocator-extended copies and moves, and single-argument arithmetic, enum, and pointer emplacement. Nullable access includes `value`, `valueOr`, `addressOr`, and `valueOrNull`; variant access includes `the`. Writes through returned references and pointers are tracked at nested indirection depths.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Contained values of BDE nullable values and variants.
2+
# https://github.com/bloomberg/bde/tree/ec310b87e008199ecbdbc00a0b0264a53d806a0a/groups/bdl/bdlb
3+
extensions:
4+
- addsTo:
5+
pack: codeql/cpp-all
6+
extensible: summaryModel
7+
data:
8+
- ["BloombergLP::bdlb", "NullableValue", False, "value", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"]
9+
- ["BloombergLP::bdlb", "VariantImp", False, "the", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"]

cpp/ql/lib/semmle/code/cpp/models/Models.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
private import implementations.Allocation
2+
private import implementations.BdlbValues
23
private import implementations.Deallocation
34
private import implementations.Fopen
45
private import implementations.Fread
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/**
2+
* Models contained values, copying, assignment, and scalar emplacement in BDE wrappers.
3+
* `NullableValue` inherits `operator*` and `operator->` from optional; those
4+
* declarations belong to the separate optional models, not to this module.
5+
*/
6+
7+
private import cpp
8+
private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl::Public
9+
private import semmle.code.cpp.models.implementations.internal.ValueWrapper
10+
11+
/** Assignments preserve aggregate fields when the input has the stored type. */
12+
private class WrapperAssignment extends SummarizedCallable {
13+
Type inputType;
14+
Type storedType;
15+
boolean nullable;
16+
17+
WrapperAssignment() {
18+
this.getNumberOfParameters() = 1 and
19+
inputType =
20+
this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and
21+
(
22+
this.hasName("makeValue") and
23+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and
24+
nullable = true and
25+
storedType =
26+
this.getType().getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType()
27+
or
28+
this.hasName("assign") and
29+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp") and
30+
nullable = false and
31+
storedType = inputType
32+
) and
33+
noUserDefinedConversion(inputType, storedType)
34+
}
35+
36+
override predicate propagatesFlow(
37+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
38+
string model
39+
) {
40+
(
41+
exists(string stars | stars = getContentStars() |
42+
input = "Argument[*" + stars + "0]" and
43+
(
44+
output = "Argument[-1].Element[" + stars + "]"
45+
or
46+
nullable = true and output = "ReturnValue[*" + stars + "]"
47+
) and
48+
preservesValue = preservesConvertedValue(inputType, storedType, stars)
49+
)
50+
or
51+
nullable = true and
52+
exists(string stars | stars = getContentStars() |
53+
input = "ReturnValue[*" + stars + "]" and
54+
output = "Argument[-1].Element[" + stars + "]"
55+
) and
56+
preservesValue = true
57+
or
58+
nullable = false and
59+
input = "Argument[-1]" and
60+
output = "ReturnValue[*]" and
61+
preservesValue = true
62+
) and
63+
provenance = "manual" and
64+
isExact = true and
65+
model = ""
66+
}
67+
}
68+
69+
/**
70+
* Models single-argument scalar emplacement only. Class-typed and multi-argument
71+
* construction is left to body analysis; see `multiArgumentNullable` and
72+
* `ignoredArgumentNullable` in `emplacement.cpp`. Body analysis can track the
73+
* direct result, but does not automatically populate abstract wrapper contents.
74+
* Broader emplacement and `bdlat_NullableValueFunctions::accessValue` and
75+
* `manipulateValue` belong with the separate Bloomberg pack models.
76+
*/
77+
private class ScalarEmplacement extends SummarizedCallable {
78+
Type inputType;
79+
Type storedType;
80+
81+
ScalarEmplacement() {
82+
(
83+
this.hasName("makeValueInplace") and
84+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue")
85+
or
86+
this.hasName("createInPlace") and
87+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp")
88+
) and
89+
this.getNumberOfParameters() = 1 and
90+
// Class inputs may invoke user-defined conversions even when the stored type is scalar.
91+
inputType =
92+
this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and
93+
(isNumeric(inputType) or inputType instanceof PointerType) and
94+
storedType =
95+
this.getType().getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and
96+
(isNumeric(storedType) or storedType instanceof PointerType) and
97+
noUserDefinedConversion(inputType, storedType)
98+
}
99+
100+
override predicate propagatesFlow(
101+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
102+
string model
103+
) {
104+
(
105+
exists(string stars | stars = getContentStars() |
106+
input = "Argument[*" + stars + "0]" and
107+
output = ["Argument[-1].Element[" + stars + "]", "ReturnValue[*" + stars + "]"] and
108+
preservesValue = preservesConvertedValue(inputType, storedType, stars)
109+
)
110+
or
111+
exists(string stars | stars = getContentStars() |
112+
input = "ReturnValue[*" + stars + "]" and
113+
output = "Argument[-1].Element[" + stars + "]"
114+
) and
115+
preservesValue = true
116+
) and
117+
provenance = "manual" and
118+
isExact = true and
119+
model = ""
120+
}
121+
}
122+
123+
/**
124+
* Models writes through references and pointers returned into a wrapper.
125+
* Input `@` is expanded by ExternalFlow, but validating high return indirections
126+
* in a database without those return types produces spurious diagnostics. Keep
127+
* the depth expansion here, where summaries are selected by actual functions.
128+
* Engagement is not tracked: reset and disengagement do not clear contents.
129+
*/
130+
private class WrapperAccessor extends SummarizedCallable {
131+
WrapperAccessor() {
132+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and
133+
(
134+
this.hasName(["value", "valueOrNull", "addressOr"])
135+
or
136+
this.hasName("valueOr") and this.getParameter(0).getUnspecifiedType() instanceof PointerType
137+
)
138+
or
139+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp") and
140+
this.hasName("the")
141+
}
142+
143+
override predicate propagatesFlow(
144+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
145+
string model
146+
) {
147+
exists(string stars | stars = getContentStars() |
148+
input = "ReturnValue[*" + stars + "]" and
149+
output = "Argument[-1].Element[" + stars + "]"
150+
or
151+
this.hasName(["valueOrNull", "addressOr", "valueOr"]) and
152+
input = "Argument[-1].Element[" + stars + "]" and
153+
output = "ReturnValue[*" + stars + "]"
154+
or
155+
this.hasName(["addressOr", "valueOr"]) and
156+
input = "Argument[*" + stars + "0]" and
157+
output = "ReturnValue[*" + stars + "]"
158+
) and
159+
preservesValue = true and
160+
provenance = "manual" and
161+
isExact = true and
162+
model = ""
163+
or
164+
this.hasName(["addressOr", "valueOr"]) and
165+
input = "Argument[0]" and
166+
output = "ReturnValue" and
167+
preservesValue = true and
168+
provenance = "manual" and
169+
isExact = true and
170+
model = ""
171+
}
172+
}
173+
174+
/** Models the by-value overload of `valueOr`, including pointer payload contents. */
175+
private class NullableValueOr extends SummarizedCallable {
176+
NullableValueOr() {
177+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and
178+
this.hasName("valueOr") and
179+
this.getParameter(0).getUnspecifiedType() instanceof ReferenceType
180+
}
181+
182+
override predicate propagatesFlow(
183+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
184+
string model
185+
) {
186+
exists(string stars | stars = getContentStars() |
187+
input = ["Argument[-1].Element[" + stars + "]", "Argument[*" + stars + "0]"] and
188+
(if stars = "" then output = "ReturnValue" else output = "ReturnValue[" + stars + "]")
189+
) and
190+
preservesValue = true and
191+
provenance = "manual" and
192+
isExact = true and
193+
model = ""
194+
}
195+
}
196+
197+
/** Holds if `c` is a BDE value wrapper with its own copy and move declarations. */
198+
private predicate isValueWrapper(Class c) {
199+
c.hasQualifiedName("BloombergLP::bdlb", ["NullableValue", "VariantImp", "Variant"])
200+
or
201+
c.hasQualifiedName("BloombergLP::bdlb", "Variant" + [2 .. 19].toString())
202+
}
203+
204+
/**
205+
* Models same-specialization copies and C++11 moves, including allocator-extended
206+
* constructors. C++03 `bslmf::MovableRef` class-based move emulation is out of scope.
207+
* Scalar/value converting constructors and assignments are not wrapper copies.
208+
*/
209+
private class WrapperCopy extends SummarizedCallable {
210+
WrapperCopy() {
211+
isValueWrapper(this.getDeclaringType()) and
212+
this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() =
213+
this.getDeclaringType() and
214+
(
215+
this instanceof Constructor and
216+
(
217+
this.getNumberOfParameters() = 1
218+
or
219+
this.getNumberOfParameters() = 2 and
220+
(
221+
this.getParameter(1)
222+
.getUnspecifiedType()
223+
.(PointerType)
224+
.getBaseType()
225+
.getUnspecifiedType()
226+
.(Class)
227+
.hasQualifiedName("BloombergLP::bslma", "Allocator")
228+
or
229+
this.getParameter(1)
230+
.getUnspecifiedType()
231+
.(ReferenceType)
232+
.getBaseType()
233+
.getUnspecifiedType()
234+
.(Class)
235+
.hasQualifiedName("bsl", "allocator")
236+
)
237+
)
238+
or
239+
this.hasName("operator=") and this.getNumberOfParameters() = 1
240+
)
241+
}
242+
243+
override predicate propagatesFlow(
244+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
245+
string model
246+
) {
247+
(
248+
exists(string stars | stars = getContentStars() |
249+
input = "Argument[*0].Element[" + stars + "]" and
250+
(
251+
output = "Argument[-1].Element[" + stars + "]"
252+
or
253+
this.hasName("operator=") and output = "ReturnValue[*].Element[" + stars + "]"
254+
)
255+
)
256+
or
257+
this.hasName("operator=") and input = "Argument[-1]" and output = "ReturnValue[*]"
258+
) and
259+
preservesValue = true and
260+
provenance = "manual" and
261+
isExact = true and
262+
model = ""
263+
}
264+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/** Internal helpers for contained-value assignment and emplacement models. */
2+
3+
private import cpp
4+
5+
/** Holds if `t` is an arithmetic or enumeration type. */
6+
predicate isNumeric(Type t) { t instanceof ArithmeticType or t instanceof Enum }
7+
8+
/** Holds if both types are pointers in an instantiated, well-typed operation. */
9+
predicate isPointerConversion(Type source, Type target) {
10+
source instanceof PointerType and target instanceof PointerType
11+
}
12+
13+
/**
14+
* Holds if the types agree or a conversion between scalar types needs no user code.
15+
* Callers modeling emplacement must additionally exclude class-typed construction.
16+
*/
17+
predicate noUserDefinedConversion(Type source, Type target) {
18+
source = target
19+
or
20+
isNumeric(source) and isNumeric(target)
21+
or
22+
isPointerConversion(source, target)
23+
}
24+
25+
/** Gets a supported indirection suffix for the contents of a value wrapper. */
26+
string getContentStars() { result = ["", "*", "**", "***", "****"] }
27+
28+
/**
29+
* Gets whether a conversion preserves values at the given indirection.
30+
* A derived-to-base pointer conversion may adjust the address, so only its
31+
* pointee contents are value-preserving. Numeric conversions propagate taint.
32+
*/
33+
bindingset[stars]
34+
boolean preservesConvertedValue(Type source, Type target, string stars) {
35+
if
36+
source = target
37+
or
38+
isPointerConversion(source, target) and stars != ""
39+
then result = true
40+
else result = false
41+
}

0 commit comments

Comments
 (0)