From a2ae81f31f988418da6aa19aba08e56b57c422ac Mon Sep 17 00:00:00 2001 From: AkshayK Date: Tue, 15 Sep 2026 18:45:55 -0400 Subject: [PATCH] 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. --- .../change-notes/2026-09-14-bdlb-values.md | 4 + cpp/ql/lib/ext/bdlb.values.model.yml | 9 + cpp/ql/lib/semmle/code/cpp/models/Models.qll | 1 + .../cpp/models/implementations/BdlbValues.qll | 264 +++++++++++++ .../implementations/internal/ValueWrapper.qll | 41 ++ .../dataflow/bdlb-values/emplacement.cpp | 206 ++++++++++ .../dataflow/bdlb-values/fallback.cpp | 85 ++++ .../dataflow/bdlb-values/flow.expected | 0 .../dataflow/bdlb-values/flow.ql | 19 + .../bdlb-values/pointer-emplacement.cpp | 42 ++ .../bdlb-values/returned-references.cpp | 370 ++++++++++++++++++ .../dataflow/bdlb-values/test.cpp | 86 ++++ .../dataflow/bdlb-values/values.expected | 0 .../dataflow/bdlb-values/values.ql | 29 ++ .../dataflow/bdlb-values/wrappers.h | 75 ++++ 15 files changed, 1231 insertions(+) create mode 100644 cpp/ql/lib/change-notes/2026-09-14-bdlb-values.md create mode 100644 cpp/ql/lib/ext/bdlb.values.model.yml create mode 100644 cpp/ql/lib/semmle/code/cpp/models/implementations/BdlbValues.qll create mode 100644 cpp/ql/lib/semmle/code/cpp/models/implementations/internal/ValueWrapper.qll create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/emplacement.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/fallback.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/flow.expected create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/flow.ql create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/pointer-emplacement.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/returned-references.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/test.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/values.expected create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/values.ql create mode 100644 cpp/ql/test/library-tests/dataflow/bdlb-values/wrappers.h diff --git a/cpp/ql/lib/change-notes/2026-09-14-bdlb-values.md b/cpp/ql/lib/change-notes/2026-09-14-bdlb-values.md new file mode 100644 index 000000000000..234d1ec9e277 --- /dev/null +++ b/cpp/ql/lib/change-notes/2026-09-14-bdlb-values.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* 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. diff --git a/cpp/ql/lib/ext/bdlb.values.model.yml b/cpp/ql/lib/ext/bdlb.values.model.yml new file mode 100644 index 000000000000..68aa147d0451 --- /dev/null +++ b/cpp/ql/lib/ext/bdlb.values.model.yml @@ -0,0 +1,9 @@ +# Contained values of BDE nullable values and variants. +# https://github.com/bloomberg/bde/tree/ec310b87e008199ecbdbc00a0b0264a53d806a0a/groups/bdl/bdlb +extensions: + - addsTo: + pack: codeql/cpp-all + extensible: summaryModel + data: + - ["BloombergLP::bdlb", "NullableValue", False, "value", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["BloombergLP::bdlb", "VariantImp", False, "the", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] diff --git a/cpp/ql/lib/semmle/code/cpp/models/Models.qll b/cpp/ql/lib/semmle/code/cpp/models/Models.qll index 54dc0fa0ff64..cce6f1bef207 100644 --- a/cpp/ql/lib/semmle/code/cpp/models/Models.qll +++ b/cpp/ql/lib/semmle/code/cpp/models/Models.qll @@ -1,4 +1,5 @@ private import implementations.Allocation +private import implementations.BdlbValues private import implementations.Deallocation private import implementations.Fopen private import implementations.Fread diff --git a/cpp/ql/lib/semmle/code/cpp/models/implementations/BdlbValues.qll b/cpp/ql/lib/semmle/code/cpp/models/implementations/BdlbValues.qll new file mode 100644 index 000000000000..027c0556686f --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/models/implementations/BdlbValues.qll @@ -0,0 +1,264 @@ +/** + * Models contained values, copying, assignment, and scalar emplacement in BDE wrappers. + * `NullableValue` inherits `operator*` and `operator->` from optional; those + * declarations belong to the separate optional models, not to this module. + */ + +private import cpp +private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl::Public +private import semmle.code.cpp.models.implementations.internal.ValueWrapper + +/** Assignments preserve aggregate fields when the input has the stored type. */ +private class WrapperAssignment extends SummarizedCallable { + Type inputType; + Type storedType; + boolean nullable; + + WrapperAssignment() { + this.getNumberOfParameters() = 1 and + inputType = + this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and + ( + this.hasName("makeValue") and + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and + nullable = true and + storedType = + this.getType().getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() + or + this.hasName("assign") and + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp") and + nullable = false and + storedType = inputType + ) and + noUserDefinedConversion(inputType, storedType) + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, Provenance provenance, boolean isExact, + string model + ) { + ( + exists(string stars | stars = getContentStars() | + input = "Argument[*" + stars + "0]" and + ( + output = "Argument[-1].Element[" + stars + "]" + or + nullable = true and output = "ReturnValue[*" + stars + "]" + ) and + preservesValue = preservesConvertedValue(inputType, storedType, stars) + ) + or + nullable = true and + exists(string stars | stars = getContentStars() | + input = "ReturnValue[*" + stars + "]" and + output = "Argument[-1].Element[" + stars + "]" + ) and + preservesValue = true + or + nullable = false and + input = "Argument[-1]" and + output = "ReturnValue[*]" and + preservesValue = true + ) and + provenance = "manual" and + isExact = true and + model = "" + } +} + +/** + * Models single-argument scalar emplacement only. Class-typed and multi-argument + * construction is left to body analysis; see `multiArgumentNullable` and + * `ignoredArgumentNullable` in `emplacement.cpp`. Body analysis can track the + * direct result, but does not automatically populate abstract wrapper contents. + * Broader emplacement and `bdlat_NullableValueFunctions::accessValue` and + * `manipulateValue` belong with the separate Bloomberg pack models. + */ +private class ScalarEmplacement extends SummarizedCallable { + Type inputType; + Type storedType; + + ScalarEmplacement() { + ( + this.hasName("makeValueInplace") and + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") + or + this.hasName("createInPlace") and + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp") + ) and + this.getNumberOfParameters() = 1 and + // Class inputs may invoke user-defined conversions even when the stored type is scalar. + inputType = + this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and + (isNumeric(inputType) or inputType instanceof PointerType) and + storedType = + this.getType().getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and + (isNumeric(storedType) or storedType instanceof PointerType) and + noUserDefinedConversion(inputType, storedType) + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, Provenance provenance, boolean isExact, + string model + ) { + ( + exists(string stars | stars = getContentStars() | + input = "Argument[*" + stars + "0]" and + output = ["Argument[-1].Element[" + stars + "]", "ReturnValue[*" + stars + "]"] and + preservesValue = preservesConvertedValue(inputType, storedType, stars) + ) + or + exists(string stars | stars = getContentStars() | + input = "ReturnValue[*" + stars + "]" and + output = "Argument[-1].Element[" + stars + "]" + ) and + preservesValue = true + ) and + provenance = "manual" and + isExact = true and + model = "" + } +} + +/** + * Models writes through references and pointers returned into a wrapper. + * Input `@` is expanded by ExternalFlow, but validating high return indirections + * in a database without those return types produces spurious diagnostics. Keep + * the depth expansion here, where summaries are selected by actual functions. + * Engagement is not tracked: reset and disengagement do not clear contents. + */ +private class WrapperAccessor extends SummarizedCallable { + WrapperAccessor() { + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and + ( + this.hasName(["value", "valueOrNull", "addressOr"]) + or + this.hasName("valueOr") and this.getParameter(0).getUnspecifiedType() instanceof PointerType + ) + or + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "VariantImp") and + this.hasName("the") + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, Provenance provenance, boolean isExact, + string model + ) { + exists(string stars | stars = getContentStars() | + input = "ReturnValue[*" + stars + "]" and + output = "Argument[-1].Element[" + stars + "]" + or + this.hasName(["valueOrNull", "addressOr", "valueOr"]) and + input = "Argument[-1].Element[" + stars + "]" and + output = "ReturnValue[*" + stars + "]" + or + this.hasName(["addressOr", "valueOr"]) and + input = "Argument[*" + stars + "0]" and + output = "ReturnValue[*" + stars + "]" + ) and + preservesValue = true and + provenance = "manual" and + isExact = true and + model = "" + or + this.hasName(["addressOr", "valueOr"]) and + input = "Argument[0]" and + output = "ReturnValue" and + preservesValue = true and + provenance = "manual" and + isExact = true and + model = "" + } +} + +/** Models the by-value overload of `valueOr`, including pointer payload contents. */ +private class NullableValueOr extends SummarizedCallable { + NullableValueOr() { + this.getDeclaringType().hasQualifiedName("BloombergLP::bdlb", "NullableValue") and + this.hasName("valueOr") and + this.getParameter(0).getUnspecifiedType() instanceof ReferenceType + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, Provenance provenance, boolean isExact, + string model + ) { + exists(string stars | stars = getContentStars() | + input = ["Argument[-1].Element[" + stars + "]", "Argument[*" + stars + "0]"] and + (if stars = "" then output = "ReturnValue" else output = "ReturnValue[" + stars + "]") + ) and + preservesValue = true and + provenance = "manual" and + isExact = true and + model = "" + } +} + +/** Holds if `c` is a BDE value wrapper with its own copy and move declarations. */ +private predicate isValueWrapper(Class c) { + c.hasQualifiedName("BloombergLP::bdlb", ["NullableValue", "VariantImp", "Variant"]) + or + c.hasQualifiedName("BloombergLP::bdlb", "Variant" + [2 .. 19].toString()) +} + +/** + * Models same-specialization copies and C++11 moves, including allocator-extended + * constructors. C++03 `bslmf::MovableRef` class-based move emulation is out of scope. + * Scalar/value converting constructors and assignments are not wrapper copies. + */ +private class WrapperCopy extends SummarizedCallable { + WrapperCopy() { + isValueWrapper(this.getDeclaringType()) and + this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() = + this.getDeclaringType() and + ( + this instanceof Constructor and + ( + this.getNumberOfParameters() = 1 + or + this.getNumberOfParameters() = 2 and + ( + this.getParameter(1) + .getUnspecifiedType() + .(PointerType) + .getBaseType() + .getUnspecifiedType() + .(Class) + .hasQualifiedName("BloombergLP::bslma", "Allocator") + or + this.getParameter(1) + .getUnspecifiedType() + .(ReferenceType) + .getBaseType() + .getUnspecifiedType() + .(Class) + .hasQualifiedName("bsl", "allocator") + ) + ) + or + this.hasName("operator=") and this.getNumberOfParameters() = 1 + ) + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, Provenance provenance, boolean isExact, + string model + ) { + ( + exists(string stars | stars = getContentStars() | + input = "Argument[*0].Element[" + stars + "]" and + ( + output = "Argument[-1].Element[" + stars + "]" + or + this.hasName("operator=") and output = "ReturnValue[*].Element[" + stars + "]" + ) + ) + or + this.hasName("operator=") and input = "Argument[-1]" and output = "ReturnValue[*]" + ) and + preservesValue = true and + provenance = "manual" and + isExact = true and + model = "" + } +} diff --git a/cpp/ql/lib/semmle/code/cpp/models/implementations/internal/ValueWrapper.qll b/cpp/ql/lib/semmle/code/cpp/models/implementations/internal/ValueWrapper.qll new file mode 100644 index 000000000000..7969effaa739 --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/models/implementations/internal/ValueWrapper.qll @@ -0,0 +1,41 @@ +/** Internal helpers for contained-value assignment and emplacement models. */ + +private import cpp + +/** Holds if `t` is an arithmetic or enumeration type. */ +predicate isNumeric(Type t) { t instanceof ArithmeticType or t instanceof Enum } + +/** Holds if both types are pointers in an instantiated, well-typed operation. */ +predicate isPointerConversion(Type source, Type target) { + source instanceof PointerType and target instanceof PointerType +} + +/** + * Holds if the types agree or a conversion between scalar types needs no user code. + * Callers modeling emplacement must additionally exclude class-typed construction. + */ +predicate noUserDefinedConversion(Type source, Type target) { + source = target + or + isNumeric(source) and isNumeric(target) + or + isPointerConversion(source, target) +} + +/** Gets a supported indirection suffix for the contents of a value wrapper. */ +string getContentStars() { result = ["", "*", "**", "***", "****"] } + +/** + * Gets whether a conversion preserves values at the given indirection. + * A derived-to-base pointer conversion may adjust the address, so only its + * pointee contents are value-preserving. Numeric conversions propagate taint. + */ +bindingset[stars] +boolean preservesConvertedValue(Type source, Type target, string stars) { + if + source = target + or + isPointerConversion(source, target) and stars != "" + then result = true + else result = false +} diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/emplacement.cpp b/cpp/ql/test/library-tests/dataflow/bdlb-values/emplacement.cpp new file mode 100644 index 000000000000..1f7b3b3f8abf --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/emplacement.cpp @@ -0,0 +1,206 @@ +#include "wrappers.h" +int source(); +void sink(int); + +// Storage-backed bodies: unlike a call to makeValue/assign, these do not create +// abstract Element contents themselves. Unsupported constructor tests inspect +// the direct return value; subsequent accessor flow needs an explicit model. +void *operator new(decltype(sizeof(0)), void *address) noexcept { return address; } +namespace BloombergLP { namespace bdlb { +template +template +TYPE& NullableValue::makeValueInplace(ARGS&&... args) { + d_value = TYPE(static_cast(args)...); + return d_value; +} +template +template +TYPE& VariantImp::createInPlace(ARGS&&... args) { + return *new (static_cast(d_storage)) TYPE(static_cast(args)...); +} +} } + +struct Constructed { + int value; + Constructed(int ignored, int used) : value(used) {} +}; + +void multiArgumentNullable() { + BloombergLP::bdlb::NullableValue n; + sink(n.makeValueInplace(0, source()).value); // $ ir +} +void ignoredArgumentNullable() { + BloombergLP::bdlb::NullableValue n; + sink(n.makeValueInplace(source(), 0).value); +} +void multiArgumentVariant() { + BloombergLP::bdlb::Variant v; + sink(v.createInPlace(0, source()).value); // $ ir +} +void ignoredArgumentVariant() { + BloombergLP::bdlb::Variant v; + sink(v.createInPlace(source(), 0).value); +} + +// Scalar destinations can still require a user-defined input conversion. +struct ConvertedNumber { + int payload; + operator int() const { return payload; } +}; +struct IgnoredNumber { + int payload; + operator int() const { return 0; } +}; +void nullableScalarConversion() { + ConvertedNumber input = {source()}; + sink(static_cast(input)); // $ ir + BloombergLP::bdlb::NullableValue n; + sink(n.makeValueInplace(input)); // $ ir +} +void variantScalarConversion() { + ConvertedNumber input = {source()}; + sink(static_cast(input)); // $ ir + BloombergLP::bdlb::Variant v; + sink(v.createInPlace(input)); // $ ir +} +void ignoredScalarConversions() { + IgnoredNumber input = {source()}; + BloombergLP::bdlb::NullableValue n; + sink(n.makeValueInplace(input)); + BloombergLP::bdlb::Variant v; + sink(v.createInPlace(input)); +} + +// Pointer conversions must retain their base-subobject semantics. +using namespace BloombergLP::bdlb; +struct PointerBase { int member; }; +struct PointerPrefix { long padding; }; +struct PointerDerived : PointerPrefix, PointerBase { int other; }; +void nullableConstPointer() { + PointerBase data = {source()}; + PointerBase *ptr = &data; + NullableValue v; + sink(v.makeValueInplace(ptr)->member); // $ ir + sink(v.value()->member); // $ ir +} +void variantConstPointer() { + PointerBase data = {source()}; + PointerBase *ptr = &data; + VariantImp> v; + sink(v.createInPlace(ptr)->member); // $ ir + sink(v.the()->member); // $ ir +} +void nullablePointerDerivedPointer() { + PointerDerived data; + data.member = source(); + PointerDerived *ptr = &data; + NullableValue v; + sink(v.makeValueInplace(ptr)->member); // $ ir + sink(v.value()->member); // $ ir +} +void variantPointerDerivedPointer() { + PointerDerived data; + data.member = source(); + PointerDerived *ptr = &data; + VariantImp> v; + sink(v.createInPlace(ptr)->member); // $ ir + sink(v.the()->member); // $ ir +} +void constPointerControls() { + PointerBase data = {source()}; + const PointerBase *ptr = &data; + NullableValue n; + sink(n.makeValueInplace(ptr)->member); // $ ir + VariantImp> v; + sink(v.createInPlace(ptr)->member); // $ ir +} +void derivedPointerControls() { + PointerDerived data; + data.member = source(); + PointerBase *ptr = &data; + NullableValue n; + sink(n.makeValueInplace(ptr)->member); // $ ir + VariantImp> v; + sink(v.createInPlace(ptr)->member); // $ ir +} + +// makeValue also constructs directly in storage (through optional::emplace in BDE). +namespace BloombergLP { namespace bdlb { +template template +TYPE& NullableValue::makeValue(U&& value) { + d_value = TYPE(static_cast(value)); + return d_value; +} +} } +void nullablePointerAssignmentConversion() { + PointerDerived data; + data.member = source(); + PointerDerived *ptr = &data; + NullableValue n; + sink(n.makeValue(ptr)->member); // $ ir + sink(n.value()->member); // $ ir +} +enum class Choice { Clean }; +void enumEmplacement() { + Choice input = static_cast(source()); + NullableValue n; + sink(static_cast(n.makeValueInplace(input))); // $ ir + sink(static_cast(n.value())); // $ ir + VariantImp> v; + sink(static_cast(v.createInPlace(input))); // $ ir + sink(static_cast(v.the())); // $ ir +} +void cleanEnumEmplacement() { + NullableValue n; + n.makeValueInplace(Choice::Clean); + sink(static_cast(n.value())); + VariantImp> v; + v.createInPlace(Choice::Clean); + sink(static_cast(v.the())); +} + +void cleanBaseMember() { + PointerDerived data; + data.member = 0; + data.other = source(); + PointerDerived *ptr = &data; + NullableValue n; + n.makeValueInplace(ptr); + sink(n.value()->member); + VariantImp> v; + v.createInPlace(ptr); + sink(v.the()->member); +} + +PointerDerived *pointerSource(); +void pointerSink(PointerDerived *); +void pointerSink(PointerBase *); +void pointerValueIdentity() { + PointerDerived *ptr = pointerSource(); + NullableValue same; + pointerSink(same.makeValueInplace(ptr)); // $ value + pointerSink(same.value()); // $ value + NullableValue adjusted; + pointerSink(adjusted.makeValueInplace(ptr)); + pointerSink(adjusted.value()); + NullableValue assigned; + pointerSink(assigned.makeValue(ptr)); + pointerSink(assigned.value()); + VariantImp> sameVariant; + pointerSink(sameVariant.createInPlace(ptr)); // $ value + pointerSink(sameVariant.the()); // $ value + VariantImp> adjustedVariant; + pointerSink(adjustedVariant.createInPlace(ptr)); + pointerSink(adjustedVariant.the()); +} + +enum Number { Zero }; +void enumToIntEmplacement() { + Number input = static_cast(source()); + NullableValue n; + n.makeValueInplace(input); + sink(n.value()); // $ ir + Variant v; + v.createInPlace(input); + sink(v.the()); // $ ir +} diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/fallback.cpp b/cpp/ql/test/library-tests/dataflow/bdlb-values/fallback.cpp new file mode 100644 index 000000000000..5578a4485648 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/fallback.cpp @@ -0,0 +1,85 @@ +#include "wrappers.h" +using namespace BloombergLP::bdlb; +int source(); void sink(int); + +void containedValueOr() { + NullableValue n; + n.makeValue(source()); + sink(n.valueOr(0)); // $ ir +} +void defaultValueOr() { + NullableValue n; + sink(n.valueOr(source())); // $ ir +} +void cleanValueOr() { + NullableValue n; + n.makeValue(0); + sink(n.valueOr(0)); +} +void containedAddressOr() { + NullableValue n; + n.makeValue(source()); + int fallback = 0; + sink(*n.addressOr(&fallback)); // $ ir + sink(*n.valueOr(&fallback)); // $ ir + sink(*n.valueOrNull()); // $ ir +} +void defaultAddressOr() { + NullableValue n; + int fallback = source(); + sink(*n.addressOr(&fallback)); // $ ir + sink(*n.valueOr(&fallback)); // $ ir +} +void emptyValueOrNull() { + NullableValue n; + const int *p = n.valueOrNull(); + if (p) sink(*p); +} +void cleanAddressOr() { + NullableValue n; + n.makeValue(0); + int fallback = 0; + sink(*n.addressOr(&fallback)); + sink(*n.valueOr(&fallback)); + sink(*n.valueOrNull()); +} +void containedPointerValueOr() { + int input = source(); + int *p = &input; + int clean = 0; + int *fallback = &clean; + NullableValue n; + n.makeValue(p); + sink(*n.valueOr(fallback)); // $ ir +} +void defaultPointerValueOr() { + int input = source(); + int *fallback = &input; + NullableValue n; + sink(*n.valueOr(fallback)); // $ ir +} +void pointerAccessorWrite() { + NullableValue n; + **n.valueOrNull() = source(); + sink(*n.value()); // $ ir +} +void addressOrWrite() { + NullableValue n; + int *fallback = 0; + **n.addressOr(&fallback) = source(); + sink(*n.value()); // $ ir +} +void pointerValueOrWrite() { + NullableValue n; + int *fallback = 0; + **n.valueOr(&fallback) = source(); + sink(*n.value()); // $ ir +} +// Returning by value must not create a writable alias to the contained scalar. +void valueOrCopyWrite() { + NullableValue n; + n.makeValue(0); + int copy = n.valueOr(0); + copy = source(); + sink(n.value()); +} diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/flow.expected b/cpp/ql/test/library-tests/dataflow/bdlb-values/flow.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/flow.ql b/cpp/ql/test/library-tests/dataflow/bdlb-values/flow.ql new file mode 100644 index 000000000000..6bdf085e721b --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/flow.ql @@ -0,0 +1,19 @@ +import cpp +import utils.test.dataflow.FlowTestCommon +import semmle.code.cpp.ir.dataflow.TaintTracking + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + node.asExpr().(FunctionCall).getTarget().hasName("source") + } + + predicate isSink(DataFlow::Node node) { + exists(FunctionCall call | + call.getTarget().hasName("sink") and node.asExpr() = call.getArgument(0) + ) + } +} + +module Flow = TaintTracking::Global; + +import MakeTest> diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/pointer-emplacement.cpp b/cpp/ql/test/library-tests/dataflow/bdlb-values/pointer-emplacement.cpp new file mode 100644 index 000000000000..e9ec339a721a --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/pointer-emplacement.cpp @@ -0,0 +1,42 @@ +#include "wrappers.h" +using namespace BloombergLP::bdlb; +int source(); void sink(int); +struct Record { int member; }; +void nullablePointerEmplacement() { + Record record = {source()}; + Record *p = &record; + NullableValue n; + sink(n.makeValueInplace(p)->member); // $ ir + sink(n.value()->member); // $ ir +} +void variantPointerEmplacement() { + Record record = {source()}; + Record *p = &record; + VariantImp> v; + sink(v.createInPlace(p)->member); // $ ir + sink(v.the()->member); // $ ir +} +void nullableControl() { + Record record = {source()}; + Record *p = &record; + NullableValue n; + sink(n.makeValue(p)->member); // $ ir + sink(n.value()->member); // $ ir +} +void variantControl() { + Record record = {source()}; + Record *p = &record; + VariantImp> v; + v.assign(p); + sink(v.the()->member); // $ ir +} + +void scalarConversionEmplacement() { + int input = source(); + NullableValue n; + sink(n.makeValueInplace(input)); // $ ir + sink(n.value()); // $ ir + VariantImp> v; + sink(v.createInPlace(input)); // $ ir + sink(v.the()); // $ ir +} diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/returned-references.cpp b/cpp/ql/test/library-tests/dataflow/bdlb-values/returned-references.cpp new file mode 100644 index 000000000000..265cf43eb220 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/returned-references.cpp @@ -0,0 +1,370 @@ +#include "wrappers.h" +using namespace BloombergLP::bdlb; +int source(); void sink(int); +void nullableCopyResult() { + NullableValue src, dst; src.makeValue(source()); + sink((dst = static_cast&>(src)).value()); // $ ir + sink(dst.value()); // $ ir +} +void nullableMoveResult() { + NullableValue src, dst; src.makeValue(source()); + sink((dst = static_cast&&>(src)).value()); // $ ir + sink(dst.value()); // $ ir +} +void variantCopyResult() { + VariantImp> src, dst; src.assign(source()); + sink((dst = static_cast>&>(src)).the()); // $ ir + sink(dst.the()); // $ ir +} +void variantMoveResult() { + VariantImp> src, dst; src.assign(source()); + sink((dst = static_cast>&&>(src)).the()); // $ ir + sink(dst.the()); // $ ir +} +void makeReference() { + NullableValue n; + n.makeValue(0) = source(); + sink(n.value()); // $ ir +} +void nullableEmplaceReference() { + NullableValue n; + n.makeValueInplace(0) = source(); + sink(n.value()); // $ ir +} +void variantEmplaceReference() { + VariantImp> v; + v.createInPlace(0) = source(); + sink(v.the()); // $ ir +} + +void nullableCopyResultWrite() { + NullableValue a, b; + b.makeValue(0); + (a = static_cast&>(b)).value() = source(); + sink(a.value()); // $ ir +} + +void nullableMoveResultWrite() { + NullableValue a, b; + b.makeValue(0); + (a = static_cast&&>(b)).value() = source(); + sink(a.value()); // $ ir +} + +void variantCopyResultWrite() { + VariantImp> a, b; + b.assign(0); + (a = static_cast>&>(b)).the() = source(); + sink(a.the()); // $ ir +} + +void variantMoveResultWrite() { + VariantImp> a, b; + b.assign(0); + (a = static_cast>&&>(b)).the() = source(); + sink(a.the()); // $ ir +} + +struct PointerDerived; +PointerDerived *pointerSource(); +void pointerSink(PointerDerived *); +void nullableCopyValue() { + NullableValue src, dst; + src.makeValue(pointerSource()); + dst = static_cast&>(src); + pointerSink(dst.value()); // $ value +} + +void nullableDoublePointerWrite() { + NullableValue n; + **n.value() = source(); + sink(**n.value()); // $ ir +} +void variantDoublePointerWrite() { + Variant v; + **v.the() = source(); + sink(**v.the()); // $ ir +} + +void nullableEmplaceDoublePointerWrite() { + NullableValue n; + int **p = 0; + **n.makeValueInplace(p) = source(); + sink(**n.value()); // $ ir +} +void nullableMakeDoublePointerWrite() { + NullableValue n; + int **p = 0; + **n.makeValue(p) = source(); + sink(**n.value()); // $ ir +} +void variantEmplaceDoublePointerWrite() { + Variant v; + int **p = 0; + **v.createInPlace(p) = source(); + sink(**v.the()); // $ ir +} +void publicVariantCopyMove(BloombergLP::bslma::Allocator *allocator) { + Variant src; + src.assign(source()); + Variant copy(src); + sink(copy.the()); // $ ir + Variant moved(static_cast&&>(copy)); + sink(moved.the()); // $ ir + Variant allocated(src, allocator); + sink(allocated.the()); // $ ir + Variant movedAllocated(static_cast&&>(allocated), allocator); + sink(movedAllocated.the()); // $ ir + Variant dst; + sink((dst = src).the()); // $ ir + sink(dst.the()); // $ ir + sink((dst = static_cast&&>(src)).the()); // $ ir + sink(dst.the()); // $ ir +} +void allocatorMoves(BloombergLP::bslma::Allocator *allocator, + const bsl::allocator& nullableAllocator) { + VariantImp> src; + src.assign(source()); + VariantImp> dst(static_cast>&&>(src), allocator); + sink(dst.the()); // $ ir + NullableValue n; + n.makeValue(source()); + NullableValue copy(n, nullableAllocator); + sink(copy.value()); // $ ir + NullableValue moved(static_cast&&>(copy), nullableAllocator); + sink(moved.value()); // $ ir +} + +void numberedVariant2(BloombergLP::bslma::Allocator *allocator) { + using V = Variant2; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant3(BloombergLP::bslma::Allocator *allocator) { + using V = Variant3; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant4(BloombergLP::bslma::Allocator *allocator) { + using V = Variant4; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant5(BloombergLP::bslma::Allocator *allocator) { + using V = Variant5; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant6(BloombergLP::bslma::Allocator *allocator) { + using V = Variant6; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant7(BloombergLP::bslma::Allocator *allocator) { + using V = Variant7; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant8(BloombergLP::bslma::Allocator *allocator) { + using V = Variant8; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant9(BloombergLP::bslma::Allocator *allocator) { + using V = Variant9; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant10(BloombergLP::bslma::Allocator *allocator) { + using V = Variant10; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant11(BloombergLP::bslma::Allocator *allocator) { + using V = Variant11; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant12(BloombergLP::bslma::Allocator *allocator) { + using V = Variant12; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant13(BloombergLP::bslma::Allocator *allocator) { + using V = Variant13; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant14(BloombergLP::bslma::Allocator *allocator) { + using V = Variant14; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant15(BloombergLP::bslma::Allocator *allocator) { + using V = Variant15; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant16(BloombergLP::bslma::Allocator *allocator) { + using V = Variant16; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant17(BloombergLP::bslma::Allocator *allocator) { + using V = Variant17; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant18(BloombergLP::bslma::Allocator *allocator) { + using V = Variant18; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} + +void numberedVariant19(BloombergLP::bslma::Allocator *allocator) { + using V = Variant19; + V src, assigned; + src.assign(pointerSource()); + V copy(src, allocator); + pointerSink(copy.the()); // $ value + V moved(static_cast(copy), allocator); + pointerSink(moved.the()); // $ value + assigned = src; + pointerSink(assigned.the()); // $ value + pointerSink((assigned = static_cast(moved)).the()); // $ value +} diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/test.cpp b/cpp/ql/test/library-tests/dataflow/bdlb-values/test.cpp new file mode 100644 index 000000000000..b43a61d597c3 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/test.cpp @@ -0,0 +1,86 @@ +#include "wrappers.h" +int source(); +void sink(int); +using namespace BloombergLP::bdlb; + +void nullableReadWrite() { + NullableValue n; + int input = source(); + sink(n.makeValue(input)); // $ ir + sink(n.value()); // $ ir + const NullableValue& cn = n; + sink(cn.value()); // $ ir + sink(n.isNull()); +} +void nullableReferenceWrite() { + NullableValue n; + n.makeValue(); + n.value() = source(); + sink(n.value()); // $ ir +} +void nullableCopyMove() { + NullableValue n; + n.makeValue(source()); + NullableValue copy(n); + NullableValue moved(static_cast&&>(copy)); + sink(moved.value()); // $ ir +} +void nullablePointer() { + int input = source(); + int *p = &input; + NullableValue n; + n.makeValue(p); + sink(*n.value()); // $ ir +} +struct Payload { int value; }; +void nullableObject() { + Payload input = {source()}; + NullableValue n; + n.makeValue(input); + sink(n.value().value); // $ ir +} +void variantReadWrite() { + Variant v; + int input = source(); + v.assign(input); + sink(v.the()); // $ ir + sink(v.isUnset()); +} +void variantReferenceWrite() { + Variant v; + v.assign(0); + v.the() = source(); + sink(v.the()); // $ ir +} +void variantPointer() { + int input = source(); + int *p = &input; + Variant v; + v.assign(p); + sink(*v.the()); // $ ir +} +void variantCopy() { + VariantImp> v; + v.assign(source()); + VariantImp> copy(v); + sink(copy.the()); // $ ir +} + +void scalarEmplacement() { + NullableValue n; + int input = source(); + sink(n.makeValueInplace(input)); // $ ir + sink(n.value()); // $ ir + Variant v; + sink(v.createInPlace(input)); // $ ir + sink(v.the()); // $ ir +} + +void variantReplacementAndResetStatus() { + Variant v; + v.assign(0); + v.assign(source()); + sink(v.the()); // $ ir + v.reset(); + sink(v.isUnset()); +} diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/values.expected b/cpp/ql/test/library-tests/dataflow/bdlb-values/values.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/values.ql b/cpp/ql/test/library-tests/dataflow/bdlb-values/values.ql new file mode 100644 index 000000000000..2786f6ab6817 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/values.ql @@ -0,0 +1,29 @@ +import cpp +import utils.test.dataflow.FlowTestCommon +import semmle.code.cpp.ir.dataflow.DataFlow + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + node.asExpr().(FunctionCall).getTarget().hasName("pointerSource") + } + + predicate isSink(DataFlow::Node node) { + exists(FunctionCall call | + call.getTarget().hasName("pointerSink") and node.asExpr() = call.getArgument(0) + ) + } +} + +module Flow = DataFlow::Global; + +module Results = IRFlowTest; + +module ValueTest implements TestSig { + string getARelevantTag() { result = "value" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "value" and Results::hasActualResult(location, element, "ir", value) + } +} + +import MakeTest diff --git a/cpp/ql/test/library-tests/dataflow/bdlb-values/wrappers.h b/cpp/ql/test/library-tests/dataflow/bdlb-values/wrappers.h new file mode 100644 index 000000000000..bf94dd56ce36 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/bdlb-values/wrappers.h @@ -0,0 +1,75 @@ +// Reduced public API with concrete storage for the body-analysis regressions. +// Storage-backed methods never populate the models through other modeled APIs. +namespace bsl { template class allocator {}; } +namespace BloombergLP { namespace bslma { class Allocator; } namespace bdlb { +template class NullableValue { + TYPE d_value; +public: + NullableValue(); + NullableValue(const NullableValue&); + NullableValue(NullableValue&&); + NullableValue(const NullableValue&, const bsl::allocator&); + NullableValue(NullableValue&&, const bsl::allocator&); + NullableValue& operator=(const NullableValue&); + NullableValue& operator=(NullableValue&&); + template TYPE& makeValue(U&& value); + TYPE& makeValue(); + TYPE& value(); + const TYPE& value() const; + template TYPE& makeValueInplace(ARGS&&... args); + bool isNull() const; + TYPE valueOr(const TYPE&) const; + const TYPE *valueOr(const TYPE *) const; // Deprecated pointer overload. + const TYPE *addressOr(const TYPE *) const; + const TYPE *valueOrNull() const; +}; +template class VariantImp { + alignas(16) unsigned char d_storage[256]; +public: + VariantImp(); + VariantImp(const VariantImp&, bslma::Allocator * = 0); + VariantImp(VariantImp&&); + VariantImp(VariantImp&&, bslma::Allocator *); + VariantImp& operator=(const VariantImp&); + VariantImp& operator=(VariantImp&&); + template VariantImp& assign(const TYPE& value); + template VariantImp& assign(TYPE&& value); + template TYPE& the(); + template const TYPE& the() const; + template TYPE& createInPlace(ARGS&&... args); + void reset(); + bool isUnset() const; +}; +template struct Types {}; +// Public variants declare their own special members; they are not inherited. +#define DECLARE_VARIANT(NAME) \ +template class NAME : public VariantImp> { \ +public: \ + NAME(); \ + NAME(const NAME&, bslma::Allocator * = 0); \ + NAME(NAME&&); \ + NAME(NAME&&, bslma::Allocator *); \ + NAME& operator=(const NAME&); \ + NAME& operator=(NAME&&); \ +}; +DECLARE_VARIANT(Variant) +DECLARE_VARIANT(Variant2) +DECLARE_VARIANT(Variant3) +DECLARE_VARIANT(Variant4) +DECLARE_VARIANT(Variant5) +DECLARE_VARIANT(Variant6) +DECLARE_VARIANT(Variant7) +DECLARE_VARIANT(Variant8) +DECLARE_VARIANT(Variant9) +DECLARE_VARIANT(Variant10) +DECLARE_VARIANT(Variant11) +DECLARE_VARIANT(Variant12) +DECLARE_VARIANT(Variant13) +DECLARE_VARIANT(Variant14) +DECLARE_VARIANT(Variant15) +DECLARE_VARIANT(Variant16) +DECLARE_VARIANT(Variant17) +DECLARE_VARIANT(Variant18) +DECLARE_VARIANT(Variant19) +#undef DECLARE_VARIANT +} }