Skip to content

Commit 07cf84e

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 07cf84e

12 files changed

Lines changed: 536 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 `bdlb::VariantImp` access, assignment, copying, and single-argument scalar emplacement.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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", "NullableValue", False, "value", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"]
10+
- ["BloombergLP::bdlb", "NullableValue", False, "value", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"]
11+
- ["BloombergLP::bdlb", "NullableValue", False, "NullableValue", "(const NullableValue &)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
12+
- ["BloombergLP::bdlb", "NullableValue", False, "operator=", "(const NullableValue &)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
13+
- ["BloombergLP::bdlb", "NullableValue", False, "operator=", "(const NullableValue &)", "", "Argument[*0].Element[@]", "ReturnValue[*].Element[@]", "value", "manual"]
14+
- ["BloombergLP::bdlb", "NullableValue", False, "operator=", "(const NullableValue &)", "", "Argument[-1]", "ReturnValue[*]", "value", "manual"]
15+
- ["BloombergLP::bdlb", "NullableValue", False, "NullableValue", "(NullableValue &&)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
16+
- ["BloombergLP::bdlb", "NullableValue", False, "operator=", "(NullableValue &&)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
17+
- ["BloombergLP::bdlb", "NullableValue", False, "operator=", "(NullableValue &&)", "", "Argument[*0].Element[@]", "ReturnValue[*].Element[@]", "value", "manual"]
18+
- ["BloombergLP::bdlb", "NullableValue", False, "operator=", "(NullableValue &&)", "", "Argument[-1]", "ReturnValue[*]", "value", "manual"]
19+
- ["BloombergLP::bdlb", "VariantImp", False, "the", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"]
20+
- ["BloombergLP::bdlb", "VariantImp", False, "the", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"]
21+
- ["BloombergLP::bdlb", "VariantImp", False, "the", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"]
22+
- ["BloombergLP::bdlb", "VariantImp", False, "VariantImp", "(const VariantImp &,bslma::Allocator *)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
23+
- ["BloombergLP::bdlb", "VariantImp", False, "operator=", "(const VariantImp &)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
24+
- ["BloombergLP::bdlb", "VariantImp", False, "operator=", "(const VariantImp &)", "", "Argument[*0].Element[@]", "ReturnValue[*].Element[@]", "value", "manual"]
25+
- ["BloombergLP::bdlb", "VariantImp", False, "operator=", "(const VariantImp &)", "", "Argument[-1]", "ReturnValue[*]", "value", "manual"]
26+
- ["BloombergLP::bdlb", "VariantImp", False, "VariantImp", "(VariantImp &&)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
27+
- ["BloombergLP::bdlb", "VariantImp", False, "operator=", "(VariantImp &&)", "", "Argument[*0].Element[@]", "Argument[-1].Element[@]", "value", "manual"]
28+
- ["BloombergLP::bdlb", "VariantImp", False, "operator=", "(VariantImp &&)", "", "Argument[*0].Element[@]", "ReturnValue[*].Element[@]", "value", "manual"]
29+
- ["BloombergLP::bdlb", "VariantImp", False, "operator=", "(VariantImp &&)", "", "Argument[-1]", "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: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/** Models type-aware assignment and scalar emplacement in BDE value wrappers. */
2+
3+
private import cpp
4+
private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl::Public
5+
6+
/** Assignments preserve aggregate fields when the input has the stored type. */
7+
private class WrapperAssignment extends SummarizedCallable {
8+
Type inputType;
9+
Type storedType;
10+
boolean nullable;
11+
12+
WrapperAssignment() {
13+
this.getNumberOfParameters() = 1 and
14+
inputType =
15+
this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and
16+
(
17+
this.hasName("makeValue") and
18+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and
19+
nullable = true and
20+
storedType =
21+
this.getType().getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType()
22+
or
23+
this.hasName("assign") and
24+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp") and
25+
nullable = false and
26+
storedType = inputType
27+
) and
28+
(
29+
inputType = storedType
30+
or
31+
inputType instanceof ArithmeticType and storedType instanceof ArithmeticType
32+
)
33+
}
34+
35+
override predicate propagatesFlow(
36+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
37+
string model
38+
) {
39+
(
40+
exists(string stars | stars = ["", "*", "**", "***", "****"] |
41+
input = "Argument[*" + stars + "0]" and
42+
(
43+
output = "Argument[-1].Element[" + stars + "]"
44+
or
45+
nullable = true and output = "ReturnValue[*" + stars + "]"
46+
)
47+
) and
48+
(if inputType = storedType then preservesValue = true else preservesValue = false)
49+
or
50+
nullable = true and
51+
exists(string stars | stars = ["", "*"] |
52+
input = "ReturnValue[*" + stars + "]" and
53+
output = "Argument[-1].Element[" + stars + "]"
54+
) and
55+
preservesValue = true
56+
or
57+
nullable = false and
58+
input = "Argument[-1]" and
59+
output = "ReturnValue[*]" and
60+
preservesValue = true
61+
) and
62+
provenance = "manual" and
63+
isExact = true and
64+
model = ""
65+
}
66+
}
67+
68+
/** User-defined constructors are deliberately left to body analysis. */
69+
private class ScalarEmplacement extends SummarizedCallable {
70+
Type inputType;
71+
Type storedType;
72+
73+
ScalarEmplacement() {
74+
(
75+
this.hasName("makeValueInplace") and
76+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue")
77+
or
78+
this.hasName("createInPlace") and
79+
this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp")
80+
) and
81+
this.getNumberOfParameters() = 1 and
82+
// Class inputs may invoke user-defined conversions even when the stored type is scalar.
83+
inputType =
84+
this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and
85+
(inputType instanceof ArithmeticType or inputType instanceof PointerType) and
86+
storedType =
87+
this.getType().getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and
88+
(storedType instanceof ArithmeticType or storedType instanceof PointerType) and
89+
// Differing pointer types may require a base-subobject adjustment. Keep the conversion body.
90+
(
91+
inputType = storedType
92+
or
93+
inputType instanceof ArithmeticType and storedType instanceof ArithmeticType
94+
)
95+
}
96+
97+
override predicate propagatesFlow(
98+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
99+
string model
100+
) {
101+
(
102+
exists(string stars | stars = ["", "*", "**", "***", "****"] |
103+
input = "Argument[*" + stars + "0]" and
104+
output = ["Argument[-1].Element[" + stars + "]", "ReturnValue[*" + stars + "]"]
105+
) and
106+
(if inputType = storedType then preservesValue = true else preservesValue = false)
107+
or
108+
exists(string stars | stars = ["", "*"] |
109+
input = "ReturnValue[*" + stars + "]" and
110+
output = "Argument[-1].Element[" + stars + "]"
111+
) and
112+
preservesValue = true
113+
) and
114+
provenance = "manual" and
115+
isExact = true and
116+
model = ""
117+
}
118+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include "wrappers.h"
2+
int source();
3+
void sink(int);
4+
5+
// Reduced forwarding bodies. User-defined construction must remain visible.
6+
namespace BloombergLP { namespace bdlb {
7+
template<class TYPE>
8+
template<class... ARGS>
9+
TYPE& NullableValue<TYPE>::makeValueInplace(ARGS&&... args) {
10+
TYPE made(static_cast<ARGS&&>(args)...);
11+
return makeValue(made);
12+
}
13+
template<class TYPES>
14+
template<class TYPE, class... ARGS>
15+
TYPE& VariantImp<TYPES>::createInPlace(ARGS&&... args) {
16+
TYPE made(static_cast<ARGS&&>(args)...);
17+
assign(made);
18+
return the<TYPE>();
19+
}
20+
} }
21+
22+
struct Constructed {
23+
int value;
24+
Constructed(int ignored, int used) : value(used) {}
25+
};
26+
27+
void multiArgumentNullable() {
28+
BloombergLP::bdlb::NullableValue<Constructed> n;
29+
n.makeValueInplace(0, source());
30+
sink(n.value().value); // $ ir
31+
}
32+
void ignoredArgumentNullable() {
33+
BloombergLP::bdlb::NullableValue<Constructed> n;
34+
n.makeValueInplace(source(), 0);
35+
sink(n.value().value);
36+
}
37+
void multiArgumentVariant() {
38+
BloombergLP::bdlb::Variant<Constructed> v;
39+
v.createInPlace<Constructed>(0, source());
40+
sink(v.the<Constructed>().value); // $ ir
41+
}
42+
void ignoredArgumentVariant() {
43+
BloombergLP::bdlb::Variant<Constructed> v;
44+
v.createInPlace<Constructed>(source(), 0);
45+
sink(v.the<Constructed>().value);
46+
}
47+
48+
// Scalar destinations can still require a user-defined input conversion.
49+
struct ConvertedNumber {
50+
int payload;
51+
operator int() const { return payload; }
52+
};
53+
struct IgnoredNumber {
54+
int payload;
55+
operator int() const { return 0; }
56+
};
57+
void nullableScalarConversion() {
58+
ConvertedNumber input = {source()};
59+
sink(static_cast<int>(input)); // $ ir
60+
BloombergLP::bdlb::NullableValue<int> n;
61+
n.makeValueInplace(input);
62+
sink(n.value()); // $ ir
63+
}
64+
void variantScalarConversion() {
65+
ConvertedNumber input = {source()};
66+
sink(static_cast<int>(input)); // $ ir
67+
BloombergLP::bdlb::Variant<int> v;
68+
v.createInPlace<int>(input);
69+
sink(v.the<int>()); // $ ir
70+
}
71+
void ignoredScalarConversions() {
72+
IgnoredNumber input = {source()};
73+
BloombergLP::bdlb::NullableValue<int> n;
74+
n.makeValueInplace(input);
75+
sink(n.value());
76+
BloombergLP::bdlb::Variant<int> v;
77+
v.createInPlace<int>(input);
78+
sink(v.the<int>());
79+
}
80+
81+
// Pointer conversions must retain their base-subobject semantics.
82+
using namespace BloombergLP::bdlb;
83+
struct PointerBase { int member; };
84+
struct PointerDerived : PointerBase { int other; };
85+
void nullableConstPointer() {
86+
PointerBase data = {source()};
87+
PointerBase *ptr = &data;
88+
NullableValue<const PointerBase*> v;
89+
sink(v.makeValueInplace(ptr)->member); // $ ir
90+
sink(v.value()->member); // $ ir
91+
}
92+
void variantConstPointer() {
93+
PointerBase data = {source()};
94+
PointerBase *ptr = &data;
95+
VariantImp<Types<const PointerBase*>> v;
96+
sink(v.createInPlace<const PointerBase*>(ptr)->member); // $ ir
97+
sink(v.the<const PointerBase*>()->member); // $ ir
98+
}
99+
void nullablePointerDerivedPointer() {
100+
PointerDerived data;
101+
data.member = source();
102+
PointerDerived *ptr = &data;
103+
NullableValue<PointerBase*> v;
104+
sink(v.makeValueInplace(ptr)->member); // $ ir
105+
sink(v.value()->member); // $ ir
106+
}
107+
void variantPointerDerivedPointer() {
108+
PointerDerived data;
109+
data.member = source();
110+
PointerDerived *ptr = &data;
111+
VariantImp<Types<PointerBase*>> v;
112+
sink(v.createInPlace<PointerBase*>(ptr)->member); // $ ir
113+
sink(v.the<PointerBase*>()->member); // $ ir
114+
}
115+
void constPointerControls() {
116+
PointerBase data = {source()};
117+
const PointerBase *ptr = &data;
118+
NullableValue<const PointerBase*> n;
119+
sink(n.makeValueInplace(ptr)->member); // $ ir
120+
VariantImp<Types<const PointerBase*>> v;
121+
sink(v.createInPlace<const PointerBase*>(ptr)->member); // $ ir
122+
}
123+
void derivedPointerControls() {
124+
PointerDerived data;
125+
data.member = source();
126+
PointerBase *ptr = &data;
127+
NullableValue<PointerBase*> n;
128+
sink(n.makeValueInplace(ptr)->member); // $ ir
129+
VariantImp<Types<PointerBase*>> v;
130+
sink(v.createInPlace<PointerBase*>(ptr)->member); // $ ir
131+
}

cpp/ql/test/library-tests/dataflow/bdlb-values/flow.expected

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import cpp
2+
import utils.test.dataflow.FlowTestCommon
3+
import semmle.code.cpp.ir.dataflow.TaintTracking
4+
5+
module Config implements DataFlow::ConfigSig {
6+
predicate isSource(DataFlow::Node node) {
7+
node.asExpr().(FunctionCall).getTarget().hasName("source")
8+
}
9+
10+
predicate isSink(DataFlow::Node node) {
11+
exists(FunctionCall call |
12+
call.getTarget().hasName("sink") and node.asExpr() = call.getArgument(0)
13+
)
14+
}
15+
}
16+
17+
module Flow = TaintTracking::Global<Config>;
18+
19+
import MakeTest<IRFlowTest<Flow>>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include "wrappers.h"
2+
using namespace BloombergLP::bdlb;
3+
int source(); void sink(int);
4+
struct Record { int member; };
5+
void nullablePointerEmplacement() {
6+
Record record = {source()};
7+
Record *p = &record;
8+
NullableValue<Record*> n;
9+
sink(n.makeValueInplace(p)->member); // $ ir
10+
sink(n.value()->member); // $ ir
11+
}
12+
void variantPointerEmplacement() {
13+
Record record = {source()};
14+
Record *p = &record;
15+
VariantImp<Types<Record*>> v;
16+
sink(v.createInPlace<Record*>(p)->member); // $ ir
17+
sink(v.the<Record*>()->member); // $ ir
18+
}
19+
void nullableControl() {
20+
Record record = {source()};
21+
Record *p = &record;
22+
NullableValue<Record*> n;
23+
sink(n.makeValue(p)->member); // $ ir
24+
sink(n.value()->member); // $ ir
25+
}
26+
void variantControl() {
27+
Record record = {source()};
28+
Record *p = &record;
29+
VariantImp<Types<Record*>> v;
30+
v.assign(p);
31+
sink(v.the<Record*>()->member); // $ ir
32+
}
33+
34+
void scalarConversionEmplacement() {
35+
int input = source();
36+
NullableValue<double> n;
37+
sink(n.makeValueInplace(input)); // $ ir
38+
sink(n.value()); // $ ir
39+
VariantImp<Types<double>> v;
40+
sink(v.createInPlace<double>(input)); // $ ir
41+
sink(v.the<double>()); // $ ir
42+
}

0 commit comments

Comments
 (0)