[clang] [clang-tools-extra] [clang-tidy] `bugprone-unchecked-optional-access`: handle inheritance from `BloombergLP::bslstl::Optional_Base` to prevent false-positives for allocator-aware BDE types (PR #168863)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 20 04:07:02 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: Valentyn Yukhymenko (BaLiKfromUA)
<details>
<summary>Changes</summary>
### Problem
`bugprone-unchecked-optional-access` produces a lot of false positives if type inside of `bsl::optional` or `bdlb::NullableValue` is **allocator-aware**.
This is a very common pattern, especially due to frequent use of `bsl::string`.
[Compiler explorer example to showcase false-positives with BDE library](https://compiler-explorer.com/z/P4zh7KbGx)
### Context
https://github.com/llvm/llvm-project/pull/101450 added support for analysing `bsl::optional` access patterns.
However, mock `bsl::optional` type has been very simplified for testing purposes which lead to missing false-positives related to _inheritance_ logic for this type.
[According to this article](https://bloomberg.github.io/bde/articles/bsl_optional.html#interoperability-between-bsl-optional-and-bdlb-nullablevalue), there are two ways of inheritance for `bsl::optional` and `bdlb::NullableValue`:
1. C++17 non-allocator-aware type
` bdlb::NullableValue<T> -> bsl::optional<T> -> std::optional<T>`
2. C++17 **Allocator-Aware**, and pre-C++17
`bdlb::NullableValue<T> -> bsl::optional<T>`
But this is not a full picture :(
In practice, there is an additional layer in the inheritance chain:
`BloombergLP::bslstl::Optional_Base`.
Thus, the actual inheritance structure is:
1. C++17 non-allocator-aware
`bdlb::NullableValue<T> -> bsl::optional<T> -> BloombergLP::bslstl::Optional_Base<T> -> std::optional<T>`
2. C++17 **Allocator-Aware**, and pre-C++17
`bdlb::NullableValue<T> -> bsl::optional<T> -> BloombergLP::bslstl::Optional_Base<T>`
[Source code to show this inheritance](https://github.com/bloomberg/bde/blob/f8b09a9298a5a76741c0820344c8850bf0b2e177/groups/bsl/bslstl/bslstl_optional.h#L1851)
### Root cause
IIUC, because of this inheritance logic, function calls to `bsl::optional::hasValue()` are processed like:
1. `std::optional::has_value()` for non-allocator-aware type.
2. `BloombergLP::bslstl::Optional_Base::has_value()` for allocator-aware type.
Obviously, similar conversion are true for other common methods like `.value()`
**This PR tries to solve this issue by improving mocks and adding `BloombergLP::bslstl::Optional_Base<T>` to list of supported optional types**
---
Full diff: https://github.com/llvm/llvm-project/pull/168863.diff
5 Files Affected:
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+3-2)
- (modified) clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h (+50-30)
- (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h (+57)
- (modified) clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp (+40)
- (modified) clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp (+6)
``````````diff
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index a6f80e3721db1..5ebb717bfdc53 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -384,8 +384,9 @@ Changes in existing checks
- Improved :doc:`bugprone-unchecked-optional-access
<clang-tidy/checks/bugprone/unchecked-optional-access>` check by supporting
``NullableValue::makeValue`` and ``NullableValue::makeValueInplace`` to
- prevent false-positives for ``BloombergLP::bdlb::NullableValue`` type, and by
- adding the `IgnoreValueCalls` option to suppress diagnostics for
+ prevent false-positives for ``BloombergLP::bdlb::NullableValue``. Fixed
+ false-positives for ``bsl::optional`` containing allocator-aware type.
+ Added the `IgnoreValueCalls` option to suppress diagnostics for
``optional::value()`` and the `IgnoreSmartPointerDereference` option to
ignore optionals reached via smart-pointer-like dereference, while still
diagnosing UB-prone dereferences via ``operator*`` and ``operator->``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h
index 7e1a129e04a55..7d7d14cde6429 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h
@@ -1,44 +1,40 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_OPTIONAL_H_
#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_OPTIONAL_H_
-/// Mock of `bsl::optional`.
+#include "../../std/types/optional.h"
+
namespace bsl {
+ class string {};
+}
-// clang-format off
-template <typename T> struct remove_reference { using type = T; };
-template <typename T> struct remove_reference<T&> { using type = T; };
-template <typename T> struct remove_reference<T&&> { using type = T; };
-// clang-format on
+/// Mock of `BloombergLP::bslstl::Optional_Base`
+namespace BloombergLP::bslstl {
-template <typename T>
-using remove_reference_t = typename remove_reference<T>::type;
+template <class T>
+constexpr bool isAllocatorAware() {
+ return false;
+}
-template <typename T>
-constexpr T &&forward(remove_reference_t<T> &t) noexcept;
+template <>
+constexpr bool isAllocatorAware<bsl::string>() {
+ return true;
+}
-template <typename T>
-constexpr T &&forward(remove_reference_t<T> &&t) noexcept;
+// Note: real `Optional_Base` uses `BloombergLP::bslma::UsesBslmaAllocator`
+// to check if type is alocator-aware.
+// This is simplified mock to illustrate similar behaviour.
+template <class T, bool AA = isAllocatorAware<T>()>
+class Optional_Base {
-template <typename T>
-constexpr remove_reference_t<T> &&move(T &&x);
-
-struct nullopt_t {
- constexpr explicit nullopt_t() {}
};
-constexpr nullopt_t nullopt;
+template <class T>
+class Optional_Base<T, false> : public std::optional<T> {
+};
-template <typename T>
-class optional {
+template <class T>
+class Optional_Base<T, true> {
public:
- constexpr optional() noexcept;
-
- constexpr optional(nullopt_t) noexcept;
-
- optional(const optional &) = default;
-
- optional(optional &&) = default;
-
const T &operator*() const &;
T &operator*() &;
const T &&operator*() const &&;
@@ -65,9 +61,33 @@ class optional {
void reset() noexcept;
- void swap(optional &rhs) noexcept;
+ void swap(Optional_Base &rhs) noexcept;
+
+ template <typename U> Optional_Base &operator=(const U &u);
+};
+
+} // namespace BloombergLP::bslstl
+
+
+/// Mock of `bsl::optional`.
+namespace bsl {
- template <typename U> optional &operator=(const U &u);
+struct nullopt_t {
+ constexpr explicit nullopt_t() {}
+};
+
+constexpr nullopt_t nullopt;
+
+template <typename T>
+class optional : public BloombergLP::bslstl::Optional_Base<T> {
+public:
+ constexpr optional() noexcept;
+
+ constexpr optional(nullopt_t) noexcept;
+
+ optional(const optional &) = default;
+
+ optional(optional &&) = default;
};
} // namespace bsl
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h
new file mode 100644
index 0000000000000..75202b6cf1250
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h
@@ -0,0 +1,57 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_STD_TYPES_OPTIONAL_H_
+#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_STD_TYPES_OPTIONAL_H_
+
+namespace std {
+
+struct nullopt_t {
+ constexpr explicit nullopt_t() {}
+};
+
+constexpr nullopt_t nullopt;
+
+template <typename T>
+class optional {
+public:
+ constexpr optional() noexcept;
+
+ constexpr optional(nullopt_t) noexcept;
+
+ optional(const optional &) = default;
+
+ optional(optional &&) = default;
+
+ const T &operator*() const &;
+ T &operator*() &;
+ const T &&operator*() const &&;
+ T &&operator*() &&;
+
+ const T *operator->() const;
+ T *operator->();
+
+ const T &value() const &;
+ T &value() &;
+ const T &&value() const &&;
+ T &&value() &&;
+
+ constexpr explicit operator bool() const noexcept;
+ constexpr bool has_value() const noexcept;
+
+ template <typename U>
+ constexpr T value_or(U &&v) const &;
+ template <typename U>
+ T value_or(U &&v) &&;
+
+ template <typename... Args>
+ T &emplace(Args &&...args);
+
+ void reset() noexcept;
+
+ void swap(optional &rhs) noexcept;
+
+ template <typename U> optional &operator=(const U &u);
+};
+
+} // namespace std
+
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_STD_TYPES_OPTIONAL_H_
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
index 8d70a9dc4861e..4b345cc2c8c5b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
@@ -67,6 +67,21 @@ void bsl_optional_unchecked_value_access(const bsl::optional<int> &opt) {
x = *opt;
}
+void bsl_optional_unchecked_value_access_for_allocator_aware_class(const bsl::optional<bsl::string> &opt) {
+ opt.value();
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
+
+ bsl::string x = *opt;
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
+
+ if (!opt) {
+ return;
+ }
+
+ opt.value();
+ x = *opt;
+}
+
void bsl_optional_checked_access(const bsl::optional<int> &opt) {
if (opt.has_value()) {
opt.value();
@@ -109,6 +124,31 @@ void nullable_value_unchecked_value_access(const BloombergLP::bdlb::NullableValu
x = *opt;
}
+void nullable_value_unchecked_value_access_for_allocator_aware_type(const BloombergLP::bdlb::NullableValue<bsl::string> &opt) {
+ opt.value();
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
+
+ bsl::string x = *opt;
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
+
+ if (opt.isNull()) {
+ opt.value();
+ }
+ // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
+
+ if (!opt) {
+ opt.value();
+ }
+ // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
+
+ if (!opt.has_value()) {
+ return;
+ }
+
+ opt.value();
+ x = *opt;
+}
+
void nullable_value_optional_checked_access(const BloombergLP::bdlb::NullableValue<int> &opt) {
if (opt.has_value()) {
opt.value();
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index d90f5d4eaf7bb..1a4409f277c18 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -78,6 +78,12 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
isFullyQualifiedNamespaceEqualTo(*N, "folly"));
}
+ if (RD.getName() == "Optional_Base") {
+ const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
+ return N != nullptr &&
+ isFullyQualifiedNamespaceEqualTo(*N, "bslstl", "BloombergLP");
+ }
+
if (RD.getName() == "NullableValue") {
const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
return N != nullptr &&
``````````
</details>
https://github.com/llvm/llvm-project/pull/168863
More information about the cfe-commits
mailing list