Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cpp/ql/lib/change-notes/2026-09-15-optional.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added contained-value models for standard and BDE optional construction, assignment, access, and single-argument scalar emplacement.
33 changes: 33 additions & 0 deletions cpp/ql/lib/ext/optional.model.yml
Original file line number Diff line number Diff line change
@@ -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"]
Comment on lines +8 to +9
- ["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"]
2 changes: 2 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/models/Models.qll
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ private import implementations.CAtlTemporaryFile
private import implementations.CRegKey
private import implementations.WinHttp
private import implementations.Http

private import implementations.Optional
83 changes: 83 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/models/implementations/Optional.qll
Original file line number Diff line number Diff line change
@@ -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
)
Comment on lines +48 to +52
}

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 = ""
}
}
Empty file.
16 changes: 16 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional-std-backed/flow.ql
Original file line number Diff line number Diff line change
@@ -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<Config>;
import MakeTest<IRFlowTest<Flow>>
16 changes: 16 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional-std-backed/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#define TEST_STD_BACKED
#include "../optional/optional.h"
int source(); void sink(int);
void stdBacked() {
bsl::optional<int> value(source());
sink(*value); // $ ir
bsl::optional<int> written(0);
written.value() = source();
sink(*written); // $ ir
bsl::optional<int> copy(static_cast<const bsl::optional<int>&>(value));
sink(copy.value()); // $ ir
bsl::optional<int> placed;
placed.emplace(source());
sink(*placed); // $ ir
sink(value.has_value());
}
31 changes: 31 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional/conversion.cpp
Original file line number Diff line number Diff line change
@@ -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<class T> template<class U>
optional<T>::optional(U&& input) {
**this = T(static_cast<U&&>(input));
}
}
namespace bsl {
template<class T> template<class U>
optional<T>::optional(U&& input,
BloombergLP::bslstl::Optional_OptNoSuchType,
BloombergLP::bslstl::Optional_OptNoSuchType) {
**this = T(static_cast<U&&>(input));
}
}
void conversions() {
std::optional<Use> used(source());
sink(used->value); // $ ir
bsl::optional<Use> bused(source());
sink(bused->value); // $ ir
std::optional<Ignore> a(source());
sink(a->value);
bsl::optional<Ignore> b(source());
sink(b->value);
}
36 changes: 36 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional/emplacement.cpp
Original file line number Diff line number Diff line change
@@ -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<class T> template<class... Args>
T& optional<T>::emplace(Args&&... args) {
T made(static_cast<Args&&>(args)...);
*this = made;
return **this;
}
}
namespace BloombergLP { namespace bslstl {
template<class T, bool B> template<class... Args>
T& Optional_Base<T, B>::emplace(Args&&... args) {
T made(static_cast<Args&&>(args)...);
**this = made;
return **this;
}
} }
void constructors() {
std::optional<Constructed> a;
a.emplace(0, source());
sink(a->value); // $ ir
std::optional<Constructed> b;
b.emplace(source(), 0);
sink(b->value);
bsl::optional<Constructed> c;
c.emplace(0, source());
sink(c->value); // $ ir
bsl::optional<Constructed> d;
d.emplace(source(), 0);
sink(d->value);
}
Empty file.
16 changes: 16 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional/flow.ql
Original file line number Diff line number Diff line change
@@ -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<Config>;
import MakeTest<IRFlowTest<Flow>>
46 changes: 46 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional/optional.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Reduced C++11+ APIs; bodies are absent except in the emplacement regression.
namespace std {
template<class T> class optional {
public:
optional();
optional(const optional&);
optional(optional&&);
template<class U = T> optional(U&&);
optional& operator=(const optional&);
optional& operator=(optional&&);
template<class U = T> optional& operator=(U&&);
T& value(); const T& value() const;
T& operator*(); const T& operator*() const;
T *operator->(); const T *operator->() const;
template<class... Args> T& emplace(Args&&...);
void reset(); bool has_value() const;
};
}
namespace BloombergLP { namespace bslstl {
struct Optional_OptNoSuchType { Optional_OptNoSuchType(int); };
template<class T, bool ALLOC = false> class Optional_Base {
public:
T& value(); const T& value() const;
T& operator*(); const T& operator*() const;
T *operator->(); const T *operator->() const;
template<class... Args> T& emplace(Args&&...);
void reset(); bool has_value() const;
};
#ifdef TEST_STD_BACKED
template<class T> class Optional_Base<T, false> : public std::optional<T> {};
#endif
} }
namespace bsl {
template<class T> class optional : public BloombergLP::bslstl::Optional_Base<T> {
public:
optional();
optional(const optional&);
optional(optional&&);
template<class U = T> optional(U&&,
BloombergLP::bslstl::Optional_OptNoSuchType = 0,
BloombergLP::bslstl::Optional_OptNoSuchType = 0);
optional& operator=(const optional&);
optional& operator=(optional&&);
template<class U = T> optional& operator=(U&&);
};
}
56 changes: 56 additions & 0 deletions cpp/ql/test/library-tests/dataflow/optional/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "optional.h"
int source();
void sink(int);
struct Payload { int member; };

template<template<class> class O> void operations() {
O<int> value(source());
sink(value.value()); // $ ir
sink(*value); // $ ir
O<int> written(0);
*written = source();
sink(written.value()); // $ ir
const O<int>& ref = value;
sink(*ref); // $ ir
sink(ref.value()); // $ ir
O<int> copied(static_cast<const O<int>&>(value));
sink(*copied); // $ ir
O<int> moved(static_cast<O<int>&&>(copied));
sink(*moved); // $ ir
O<int> assigned;
assigned = moved;
sink(*assigned); // $ ir
O<int> moveAssigned;
moveAssigned = static_cast<O<int>&&>(assigned);
sink(*moveAssigned); // $ ir
O<int> scalar;
scalar = source();
sink(*scalar); // $ ir
scalar.reset();
sink(scalar.has_value());
O<int> clean;
clean = 0;
sink(*clean);
Payload payload = {source()};
O<Payload> object(payload);
sink(object->member); // $ ir
const O<Payload>& constObject = object;
sink(constObject->member); // $ ir
O<Payload> writtenObject(Payload{0});
writtenObject->member = source();
sink((*writtenObject).member); // $ ir
int integer = source();
int *ptr = &integer;
O<int*> pointer(ptr);
sink(**pointer); // $ ir
}
void all() { operations<std::optional>(); operations<bsl::optional>(); }

void scalarEmplacement() {
std::optional<int> a;
sink(a.emplace(source())); // $ ir
sink(*a); // $ ir
bsl::optional<int> b;
sink(b.emplace(source())); // $ ir
sink(*b); // $ ir
}