From 2435081c0455f2476fb97a49a8fae4902089a82b Mon Sep 17 00:00:00 2001 From: AkshayK Date: Tue, 15 Sep 2026 17:49:02 -0400 Subject: [PATCH] C++: model standard and BDE optional contents --- .../lib/change-notes/2026-09-15-optional.md | 4 + cpp/ql/lib/ext/optional.model.yml | 33 ++++++++ cpp/ql/lib/semmle/code/cpp/models/Models.qll | 2 + .../cpp/models/implementations/Optional.qll | 83 +++++++++++++++++++ .../optional-std-backed/flow.expected | 0 .../dataflow/optional-std-backed/flow.ql | 16 ++++ .../dataflow/optional-std-backed/test.cpp | 16 ++++ .../dataflow/optional/conversion.cpp | 31 +++++++ .../dataflow/optional/emplacement.cpp | 36 ++++++++ .../dataflow/optional/flow.expected | 0 .../library-tests/dataflow/optional/flow.ql | 16 ++++ .../dataflow/optional/optional.h | 46 ++++++++++ .../library-tests/dataflow/optional/test.cpp | 56 +++++++++++++ 13 files changed, 339 insertions(+) create mode 100644 cpp/ql/lib/change-notes/2026-09-15-optional.md create mode 100644 cpp/ql/lib/ext/optional.model.yml create mode 100644 cpp/ql/lib/semmle/code/cpp/models/implementations/Optional.qll create mode 100644 cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.expected create mode 100644 cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.ql create mode 100644 cpp/ql/test/library-tests/dataflow/optional-std-backed/test.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/optional/conversion.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/optional/emplacement.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/optional/flow.expected create mode 100644 cpp/ql/test/library-tests/dataflow/optional/flow.ql create mode 100644 cpp/ql/test/library-tests/dataflow/optional/optional.h create mode 100644 cpp/ql/test/library-tests/dataflow/optional/test.cpp diff --git a/cpp/ql/lib/change-notes/2026-09-15-optional.md b/cpp/ql/lib/change-notes/2026-09-15-optional.md new file mode 100644 index 000000000000..9cb47ca6f9fc --- /dev/null +++ b/cpp/ql/lib/change-notes/2026-09-15-optional.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added contained-value models for standard and BDE optional construction, assignment, access, and single-argument scalar emplacement. diff --git a/cpp/ql/lib/ext/optional.model.yml b/cpp/ql/lib/ext/optional.model.yml new file mode 100644 index 000000000000..31491ac49d40 --- /dev/null +++ b/cpp/ql/lib/ext/optional.model.yml @@ -0,0 +1,33 @@ +# Contained-value access, including BDE inherited accessor declarations. +extensions: + - addsTo: + pack: codeql/cpp-all + extensible: summaryModel + data: + - ["std", "optional", False, "value", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["std", "optional", False, "value", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["std", "optional", False, "value", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["std", "optional", False, "operator*", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["std", "optional", False, "operator*", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["std", "optional", False, "operator*", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["std", "optional", False, "operator->", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["std", "optional", False, "operator->", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["std", "optional", False, "operator->", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["bsl", "optional", False, "value", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["bsl", "optional", False, "value", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["bsl", "optional", False, "value", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["bsl", "optional", False, "operator*", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["bsl", "optional", False, "operator*", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["bsl", "optional", False, "operator*", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["bsl", "optional", False, "operator->", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["bsl", "optional", False, "operator->", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["bsl", "optional", False, "operator->", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "value", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "value", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "value", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "operator*", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "operator*", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "operator*", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "operator->", "", "", "Argument[-1].Element[@]", "ReturnValue[*@]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "operator->", "", "", "ReturnValue[*]", "Argument[-1].Element[]", "value", "manual"] + - ["BloombergLP::bslstl", "Optional_Base", False, "operator->", "", "", "ReturnValue[**]", "Argument[-1].Element[*]", "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..788aa05dec09 100644 --- a/cpp/ql/lib/semmle/code/cpp/models/Models.qll +++ b/cpp/ql/lib/semmle/code/cpp/models/Models.qll @@ -58,3 +58,5 @@ private import implementations.CAtlTemporaryFile private import implementations.CRegKey private import implementations.WinHttp private import implementations.Http + +private import implementations.Optional diff --git a/cpp/ql/lib/semmle/code/cpp/models/implementations/Optional.qll b/cpp/ql/lib/semmle/code/cpp/models/implementations/Optional.qll new file mode 100644 index 000000000000..b5ad30c4fe87 --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/models/implementations/Optional.qll @@ -0,0 +1,83 @@ +/** Models the contained value of standard and BDE optional objects. */ + +private import cpp +private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl::Public + +private predicate isOptional(Class c) { + c.hasQualifiedName(["std", "bsl"], "optional") or + c.hasQualifiedName("BloombergLP::bslstl", "Optional_Base") +} + +/** Restrict value operations to types that need no user-defined conversion. */ +private class OptionalValueOperation extends SummarizedCallable { + Type sourceType; + Type targetType; + boolean copy; + + OptionalValueOperation() { + isOptional(this.getDeclaringType()) and + targetType = this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() and + exists(Type argType | + argType = + this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType().getUnspecifiedType() and + ( + (this instanceof Constructor or this.hasName("operator=")) and + // BDE constrains constructors with trailing defaulted tag parameters. + forall(Parameter p | p = this.getAParameter() and p.getIndex() > 0 | + p.getUnspecifiedType() + .(Class) + .hasQualifiedName("BloombergLP::bslstl", "Optional_OptNoSuchType") + ) and + ( + isOptional(argType.(Class)) and + copy = true and + sourceType = argType.(Class).getTemplateArgument(0).(Type).getUnspecifiedType() + or + copy = false and + sourceType = argType and + (sourceType = targetType or sourceType instanceof ArithmeticType) + ) + or + this.hasName("emplace") and + this.getNumberOfParameters() = 1 and + (targetType instanceof ArithmeticType or targetType instanceof PointerType) and + copy = false and + sourceType = argType + ) + ) and + ( + sourceType = targetType + or + sourceType instanceof ArithmeticType and targetType instanceof ArithmeticType + ) + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, Provenance provenance, boolean isExact, + string model + ) { + ( + exists(string stars | stars = ["", "*", "**", "***", "****"] | + ( + if copy = true + then input = "Argument[*0].Element[" + stars + "]" + else input = "Argument[*" + stars + "0]" + ) and + ( + output = "Argument[-1].Element[" + stars + "]" + or + this.hasName("emplace") and output = "ReturnValue[*" + stars + "]" + ) + ) and + (if sourceType = targetType then preservesValue = true else preservesValue = false) + 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/test/library-tests/dataflow/optional-std-backed/flow.expected b/cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.ql b/cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.ql new file mode 100644 index 000000000000..0dc321e24fa5 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.ql @@ -0,0 +1,16 @@ +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/optional-std-backed/test.cpp b/cpp/ql/test/library-tests/dataflow/optional-std-backed/test.cpp new file mode 100644 index 000000000000..205ef92c4086 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional-std-backed/test.cpp @@ -0,0 +1,16 @@ +#define TEST_STD_BACKED +#include "../optional/optional.h" +int source(); void sink(int); +void stdBacked() { + bsl::optional value(source()); + sink(*value); // $ ir + bsl::optional written(0); + written.value() = source(); + sink(*written); // $ ir + bsl::optional copy(static_cast&>(value)); + sink(copy.value()); // $ ir + bsl::optional placed; + placed.emplace(source()); + sink(*placed); // $ ir + sink(value.has_value()); +} diff --git a/cpp/ql/test/library-tests/dataflow/optional/conversion.cpp b/cpp/ql/test/library-tests/dataflow/optional/conversion.cpp new file mode 100644 index 000000000000..f7a5bf5675f6 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional/conversion.cpp @@ -0,0 +1,31 @@ +#include "optional.h" +int source(); void sink(int); +struct Ignore { + int value; + Ignore(int) : value(0) {} +}; +struct Use { int value; Use(int input) : value(input) {} }; +namespace std { +template template +optional::optional(U&& input) { + **this = T(static_cast(input)); +} +} +namespace bsl { +template template +optional::optional(U&& input, + BloombergLP::bslstl::Optional_OptNoSuchType, + BloombergLP::bslstl::Optional_OptNoSuchType) { + **this = T(static_cast(input)); +} +} +void conversions() { + std::optional used(source()); + sink(used->value); // $ ir + bsl::optional bused(source()); + sink(bused->value); // $ ir + std::optional a(source()); + sink(a->value); + bsl::optional b(source()); + sink(b->value); +} diff --git a/cpp/ql/test/library-tests/dataflow/optional/emplacement.cpp b/cpp/ql/test/library-tests/dataflow/optional/emplacement.cpp new file mode 100644 index 000000000000..05116d765bea --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional/emplacement.cpp @@ -0,0 +1,36 @@ +#include "optional.h" +int source(); void sink(int); +struct Constructed { + int value; + Constructed(int ignored, int used) : value(used) {} +}; +namespace std { +template template +T& optional::emplace(Args&&... args) { + T made(static_cast(args)...); + *this = made; + return **this; +} +} +namespace BloombergLP { namespace bslstl { +template template +T& Optional_Base::emplace(Args&&... args) { + T made(static_cast(args)...); + **this = made; + return **this; +} +} } +void constructors() { + std::optional a; + a.emplace(0, source()); + sink(a->value); // $ ir + std::optional b; + b.emplace(source(), 0); + sink(b->value); + bsl::optional c; + c.emplace(0, source()); + sink(c->value); // $ ir + bsl::optional d; + d.emplace(source(), 0); + sink(d->value); +} diff --git a/cpp/ql/test/library-tests/dataflow/optional/flow.expected b/cpp/ql/test/library-tests/dataflow/optional/flow.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cpp/ql/test/library-tests/dataflow/optional/flow.ql b/cpp/ql/test/library-tests/dataflow/optional/flow.ql new file mode 100644 index 000000000000..0dc321e24fa5 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional/flow.ql @@ -0,0 +1,16 @@ +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/optional/optional.h b/cpp/ql/test/library-tests/dataflow/optional/optional.h new file mode 100644 index 000000000000..41b9c9f69699 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional/optional.h @@ -0,0 +1,46 @@ +// Reduced C++11+ APIs; bodies are absent except in the emplacement regression. +namespace std { +template class optional { +public: + optional(); + optional(const optional&); + optional(optional&&); + template optional(U&&); + optional& operator=(const optional&); + optional& operator=(optional&&); + template optional& operator=(U&&); + T& value(); const T& value() const; + T& operator*(); const T& operator*() const; + T *operator->(); const T *operator->() const; + template T& emplace(Args&&...); + void reset(); bool has_value() const; +}; +} +namespace BloombergLP { namespace bslstl { +struct Optional_OptNoSuchType { Optional_OptNoSuchType(int); }; +template class Optional_Base { +public: + T& value(); const T& value() const; + T& operator*(); const T& operator*() const; + T *operator->(); const T *operator->() const; + template T& emplace(Args&&...); + void reset(); bool has_value() const; +}; +#ifdef TEST_STD_BACKED +template class Optional_Base : public std::optional {}; +#endif +} } +namespace bsl { +template class optional : public BloombergLP::bslstl::Optional_Base { +public: + optional(); + optional(const optional&); + optional(optional&&); + template optional(U&&, + BloombergLP::bslstl::Optional_OptNoSuchType = 0, + BloombergLP::bslstl::Optional_OptNoSuchType = 0); + optional& operator=(const optional&); + optional& operator=(optional&&); + template optional& operator=(U&&); +}; +} diff --git a/cpp/ql/test/library-tests/dataflow/optional/test.cpp b/cpp/ql/test/library-tests/dataflow/optional/test.cpp new file mode 100644 index 000000000000..3c49e313e78a --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/optional/test.cpp @@ -0,0 +1,56 @@ +#include "optional.h" +int source(); +void sink(int); +struct Payload { int member; }; + +template class O> void operations() { + O value(source()); + sink(value.value()); // $ ir + sink(*value); // $ ir + O written(0); + *written = source(); + sink(written.value()); // $ ir + const O& ref = value; + sink(*ref); // $ ir + sink(ref.value()); // $ ir + O copied(static_cast&>(value)); + sink(*copied); // $ ir + O moved(static_cast&&>(copied)); + sink(*moved); // $ ir + O assigned; + assigned = moved; + sink(*assigned); // $ ir + O moveAssigned; + moveAssigned = static_cast&&>(assigned); + sink(*moveAssigned); // $ ir + O scalar; + scalar = source(); + sink(*scalar); // $ ir + scalar.reset(); + sink(scalar.has_value()); + O clean; + clean = 0; + sink(*clean); + Payload payload = {source()}; + O object(payload); + sink(object->member); // $ ir + const O& constObject = object; + sink(constObject->member); // $ ir + O writtenObject(Payload{0}); + writtenObject->member = source(); + sink((*writtenObject).member); // $ ir + int integer = source(); + int *ptr = &integer; + O pointer(ptr); + sink(**pointer); // $ ir +} +void all() { operations(); operations(); } + +void scalarEmplacement() { + std::optional a; + sink(a.emplace(source())); // $ ir + sink(*a); // $ ir + bsl::optional b; + sink(b.emplace(source())); // $ ir + sink(*b); // $ ir +}