Skip to content

Commit ed31d67

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 ed31d67

12 files changed

Lines changed: 478 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: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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)
89+
}
90+
91+
override predicate propagatesFlow(
92+
string input, string output, boolean preservesValue, Provenance provenance, boolean isExact,
93+
string model
94+
) {
95+
(
96+
exists(string stars | stars = ["", "*", "**", "***", "****"] |
97+
input = "Argument[*" + stars + "0]" and
98+
output = ["Argument[-1].Element[" + stars + "]", "ReturnValue[*" + stars + "]"]
99+
) and
100+
(if inputType = storedType then preservesValue = true else preservesValue = false)
101+
or
102+
exists(string stars | stars = ["", "*"] |
103+
input = "ReturnValue[*" + stars + "]" and
104+
output = "Argument[-1].Element[" + stars + "]"
105+
) and
106+
preservesValue = true
107+
) and
108+
provenance = "manual" and
109+
isExact = true and
110+
model = ""
111+
}
112+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
}

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+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include "wrappers.h"
2+
using namespace BloombergLP::bdlb;
3+
int source(); void sink(int);
4+
void nullableCopyResult() {
5+
NullableValue<int> src, dst; src.makeValue(source());
6+
sink((dst = static_cast<const NullableValue<int>&>(src)).value()); // $ ir
7+
sink(dst.value()); // $ ir
8+
}
9+
void nullableMoveResult() {
10+
NullableValue<int> src, dst; src.makeValue(source());
11+
sink((dst = static_cast<NullableValue<int>&&>(src)).value()); // $ ir
12+
sink(dst.value()); // $ ir
13+
}
14+
void variantCopyResult() {
15+
VariantImp<Types<int>> src, dst; src.assign(source());
16+
sink((dst = static_cast<const VariantImp<Types<int>>&>(src)).the<int>()); // $ ir
17+
sink(dst.the<int>()); // $ ir
18+
}
19+
void variantMoveResult() {
20+
VariantImp<Types<int>> src, dst; src.assign(source());
21+
sink((dst = static_cast<VariantImp<Types<int>>&&>(src)).the<int>()); // $ ir
22+
sink(dst.the<int>()); // $ ir
23+
}
24+
void makeReference() {
25+
NullableValue<int> n;
26+
n.makeValue(0) = source();
27+
sink(n.value()); // $ ir
28+
}
29+
void nullableEmplaceReference() {
30+
NullableValue<int> n;
31+
n.makeValueInplace(0) = source();
32+
sink(n.value()); // $ ir
33+
}
34+
void variantEmplaceReference() {
35+
VariantImp<Types<int>> v;
36+
v.createInPlace<int>(0) = source();
37+
sink(v.the<int>()); // $ ir
38+
}
39+
40+
void nullableCopyResultWrite() {
41+
NullableValue<int> a, b;
42+
b.makeValue(0);
43+
(a = static_cast<const NullableValue<int>&>(b)).value() = source();
44+
sink(a.value()); // $ ir
45+
}
46+
47+
void nullableMoveResultWrite() {
48+
NullableValue<int> a, b;
49+
b.makeValue(0);
50+
(a = static_cast<NullableValue<int>&&>(b)).value() = source();
51+
sink(a.value()); // $ ir
52+
}
53+
54+
void variantCopyResultWrite() {
55+
VariantImp<Types<int>> a, b;
56+
b.assign(0);
57+
(a = static_cast<const VariantImp<Types<int>>&>(b)).the<int>() = source();
58+
sink(a.the<int>()); // $ ir
59+
}
60+
61+
void variantMoveResultWrite() {
62+
VariantImp<Types<int>> a, b;
63+
b.assign(0);
64+
(a = static_cast<VariantImp<Types<int>>&&>(b)).the<int>() = source();
65+
sink(a.the<int>()); // $ ir
66+
}

0 commit comments

Comments
 (0)