Author: Valentyn Yukhymenko Date: 2025-12-16T10:32:23-05:00 New Revision: 720b003af03727edab66ac518fb728dd91861ebf
URL: https://github.com/llvm/llvm-project/commit/720b003af03727edab66ac518fb728dd91861ebf DIFF: https://github.com/llvm/llvm-project/commit/720b003af03727edab66ac518fb728dd91861ebf.diff LOG: [clang-tidy] `bugprone-unchecked-optional-access`: handle inheritance from `BloombergLP::bslstl::Optional_Base` to prevent false-positives for allocator-aware BDE types (#168863) ### 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, false> -> std::optional<T>` 2. C++17 **Allocator-Aware**, and pre-C++17 `bdlb::NullableValue<T> -> bsl::optional<T> -> BloombergLP::bslstl::Optional_Base<T, true>` [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::has_value()` 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** Added: clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h Modified: clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 7e8016536afa2..b13d37198c7f6 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -427,8 +427,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..a12572351a41c 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,31 @@ #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`. -namespace bsl { - -// 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 - -template <typename T> -using remove_reference_t = typename remove_reference<T>::type; - -template <typename T> -constexpr T &&forward(remove_reference_t<T> &t) noexcept; - -template <typename T> -constexpr T &&forward(remove_reference_t<T> &&t) noexcept; - -template <typename T> -constexpr remove_reference_t<T> &&move(T &&x); - -struct nullopt_t { - constexpr explicit nullopt_t() {} -}; - -constexpr nullopt_t nullopt; +#include "../../std/types/optional.h" -template <typename T> -class optional { +namespace bsl { + class string {}; +} + +/// Mock of `BloombergLP::bslstl::Optional_Base` +namespace BloombergLP::bslstl { + +template <class T> +constexpr bool isAllocatorAware() { + return false; +} + +template <> +constexpr bool isAllocatorAware<bsl::string>() { + return true; +} + +// Note: real `Optional_Base` uses `BloombergLP::bslma::UsesBslmaAllocator` +// to check if type is allocator-aware. +// This is simplified mock to illustrate similar behaviour. +template <class T, bool AA = isAllocatorAware<T>()> +class Optional_Base { 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 +52,37 @@ 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); +}; + +template <class T> +class Optional_Base<T, false> : public std::optional<T> { +}; + +} // namespace BloombergLP::bslstl + - template <typename U> optional &operator=(const U &u); +/// Mock of `bsl::optional`. +namespace bsl { + +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..d331585a4c21c --- /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_ 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..984156c028c00 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 @@ -65,15 +65,44 @@ void bsl_optional_unchecked_value_access(const bsl::optional<int> &opt) { opt.value(); x = *opt; + + bsl::optional<int> opt1; + opt1.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access] +} + +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; + + bsl::optional<bsl::string> opt1; + opt1.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access] } void bsl_optional_checked_access(const bsl::optional<int> &opt) { if (opt.has_value()) { opt.value(); } + if (opt) { opt.value(); } + + if (opt) { + bsl::optional<int> opt1(opt); + opt1.value(); + } } void bsl_optional_value_after_swap(bsl::optional<int> &opt1, bsl::optional<int> &opt2) { @@ -109,6 +138,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..04a2a557debb2 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -66,8 +66,7 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { if (RD.getName() == "optional") { if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext())) return N->isStdNamespace() || - isFullyQualifiedNamespaceEqualTo(*N, "absl") || - isFullyQualifiedNamespaceEqualTo(*N, "bsl"); + isFullyQualifiedNamespaceEqualTo(*N, "absl"); return false; } @@ -78,6 +77,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 && _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
