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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
