- Notifications
You must be signed in to change notification settings - Fork14.5k
[clang-tidy] Add support for bsl::optional#101450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
llvmbot commentedAug 1, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@llvm/pr-subscribers-clang-tidy @llvm/pr-subscribers-clang Author: Chris Cotter (ccotter) ChangesFull diff:https://github.com/llvm/llvm-project/pull/101450.diff 5 Files Affected:
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rstindex 642ad39cc0c1c..cc999b142561f 100644--- a/clang-tools-extra/docs/ReleaseNotes.rst+++ b/clang-tools-extra/docs/ReleaseNotes.rst@@ -104,6 +104,11 @@ New check aliases Changes in existing checks ^^^^^^^^^^^^^^^^^^^^^^^^^^+- Improved :doc:`bugprone-unchecked-optional-access+ <clang-tidy/checks/bugprone/unchecked-optional-access>` to support+ `bsl::optional` and `bdlb::NullableValue` from+ <https://github.com/bloomberg/bde>_.+ Removed checks ^^^^^^^^^^^^^^diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.hnew file mode 100644index 0000000000000..53efebba1bb9f--- /dev/null+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.h@@ -0,0 +1,38 @@+#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_NULLABLEVALUE_H_+#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_NULLABLEVALUE_H_++#include "bsl_optional.h"++/// Mock of `bdbl::NullableValue`.+namespace BloombergLP::bdlb {++template <typename T>+class NullableValue : public bsl::optional<T> {+public:+ constexpr NullableValue() noexcept;++ constexpr NullableValue(bsl::nullopt_t) noexcept;++ NullableValue(const NullableValue &) = default;++ NullableValue(NullableValue &&) = default;++ const T &value() const &;+ T &value() &;++ // 'operator bool' is inherited from bsl::optional++ constexpr bool isNull() const noexcept;++ template <typename U>+ constexpr T valueOr(U &&v) const &;++ // 'reset' is inherited from bsl::optional++ template <typename U> NullableValue &operator=(const U &u);+};+++} // namespace BloombergLP::bdlb++#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_NULLABLEVALUE_H_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.hnew file mode 100644index 0000000000000..7e1a129e04a55--- /dev/null+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bsl_optional.h@@ -0,0 +1,75 @@+#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;++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 bsl++#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_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.cppindex 13a3ff52f3ebc..3167b85f0e024 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@@ -2,6 +2,8 @@ #include "absl/types/optional.h" #include "folly/types/Optional.h"+#include "bde/types/bsl_optional.h"+#include "bde/types/bdlb_nullablevalue.h" void unchecked_value_access(const absl::optional<int> &opt) { opt.value();@@ -50,6 +52,95 @@ void folly_checked_access(const folly::Optional<int> &opt) { } }+void bsl_optional_unchecked_value_access(const bsl::optional<int> &opt) {+ opt.value();+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]++ int x = *opt;+ // CHECK-MESSAGES: :[[@LINE-1]]:12: 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();+ }+ if (opt) {+ opt.value();+ }+}++void bsl_optional_value_after_swap(bsl::optional<int> &opt1, bsl::optional<int> &opt2) {+ if (opt1) {+ opt1.swap(opt2);+ opt1.value();+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: unchecked access to optional value+ }+}++void nullable_value_unchecked_value_access(const BloombergLP::bdlb::NullableValue<int> &opt) {+ opt.value();+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]++ int x = *opt;+ // CHECK-MESSAGES: :[[@LINE-1]]:12: 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) {+ return;+ }++ opt.value();+ x = *opt;+}++void nullable_value_optional_checked_access(const BloombergLP::bdlb::NullableValue<int> &opt) {+ if (opt.has_value()) {+ opt.value();+ }+ if (opt) {+ opt.value();+ }+ if (!opt.isNull()) {+ opt.value();+ }+}++void nullable_value_emplaced(BloombergLP::bdlb::NullableValue<int> &opt) {+ opt.value();+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]++ opt.emplace(1);+ opt.value();++ opt.reset();+ opt.value();+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]+}++void nullable_value_after_swap(BloombergLP::bdlb::NullableValue<int> &opt1, BloombergLP::bdlb::NullableValue<int> &opt2) {+ if (opt1) {+ opt1.swap(opt2);+ opt1.value();+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: unchecked access to optional value+ }+}+ template <typename T> void function_template_without_user(const absl::optional<T> &opt) { opt.value(); // no-warningdiff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cppindex 0707aa662e4cc..9d88754639fa3 100644--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp@@ -38,10 +38,22 @@ namespace clang { namespace dataflow {-static bool isTopLevelNamespaceWithName(const NamespaceDecl &NS,- llvm::StringRef Name) {- return NS.getDeclName().isIdentifier() && NS.getName() == Name &&- NS.getParent() != nullptr && NS.getParent()->isTranslationUnit();+template <class... NameTypes>+static bool hasNestedNamespace(const NamespaceDecl &NS, llvm::StringRef Name,+ NameTypes... Names) {+ if (!(NS.getDeclName().isIdentifier() && NS.getName() == Name &&+ NS.getParent() != nullptr))+ return false;++ if constexpr (sizeof...(NameTypes) > 0) {+ if (NS.getParent()->isTranslationUnit())+ return false;+ if (const auto *NextNS = dyn_cast_or_null<NamespaceDecl>(NS.getParent()))+ return hasNestedNamespace(*NextNS, Names...);+ return false;+ } else {+ return NS.getParent()->isTranslationUnit();+ } } static bool hasOptionalClassName(const CXXRecordDecl &RD) {@@ -50,15 +62,21 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { if (RD.getName() == "optional") { if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()))- return N->isStdNamespace() || isTopLevelNamespaceWithName(*N, "absl");+ return N->isStdNamespace() || hasNestedNamespace(*N, "absl") ||+ hasNestedNamespace(*N, "bsl"); return false; } if (RD.getName() == "Optional") { // Check whether namespace is "::base" or "::folly". const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());- return N != nullptr && (isTopLevelNamespaceWithName(*N, "base") ||- isTopLevelNamespaceWithName(*N, "folly"));+ return N != nullptr &&+ (hasNestedNamespace(*N, "base") || hasNestedNamespace(*N, "folly"));+ }++ if (RD.getName() == "NullableValue") {+ const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());+ return N != nullptr && hasNestedNamespace(*N, "bdlb", "BloombergLP"); } return false;@@ -195,22 +213,25 @@ auto isOptionalOperatorCallWithName( } auto isMakeOptionalCall() {- return callExpr(callee(functionDecl(hasAnyName(- "std::make_optional", "base::make_optional",- "absl::make_optional", "folly::make_optional"))),- hasOptionalType());+ return callExpr(+ callee(functionDecl(hasAnyName(+ "std::make_optional", "base::make_optional", "absl::make_optional",+ "folly::make_optional", "bsl::make_optional"))),+ hasOptionalType()); } auto nulloptTypeDecl() { return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",- "base::nullopt_t", "folly::None"));+ "base::nullopt_t", "folly::None",+ "bsl::nullopt_t")); } auto hasNulloptType() { return hasType(nulloptTypeDecl()); } auto inPlaceClass() { return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t",- "base::in_place_t", "folly::in_place_t"));+ "base::in_place_t", "folly::in_place_t",+ "bsl::in_place_t")); } auto isOptionalNulloptConstructor() {@@ -415,6 +436,15 @@ void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr, } }+void transferOptionalIsNullCall(const CXXMemberCallExpr *CallExpr,+ const MatchFinder::MatchResult &,+ LatticeTransferState &State) {+ if (auto *HasValueVal = getHasValue(+ State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {+ State.Env.setValue(*CallExpr, State.Env.makeNot(*HasValueVal));+ }+}+ /// `ModelPred` builds a logical formula relating the predicate in /// `ValueOrPredExpr` to the optional's `has_value` property. void transferValueOrImpl(@@ -784,6 +814,12 @@ auto buildTransferMatchSwitch() { isOptionalMemberCallWithNameMatcher(hasName("operator bool")), transferOptionalHasValueCall)+ // NullableValue::isNull+ // Only NullableValue has isNull+ .CaseOfCFGStmt<CXXMemberCallExpr>(+ isOptionalMemberCallWithNameMatcher(hasAnyName("isNull")),+ transferOptionalIsNullCall)+ // optional::emplace .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher(hasName("emplace")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm fine with change, but at the same time this check should be made configurable.
Also please update check documentation before committing.
Agreed it should be configurable. I updated the docs, would you mind merging if you are OK as I don't have perms? |
clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
@ymand could you please re-review? |
dayshah left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
huge for bloomberg 🔥
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
LGTM. Rebase to fix release notes conflict and I'll merge 👍
...clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.h OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
…hecked-optional-access/bde/types/bdlb_nullablevalue.hCo-authored-by: Nicolas van Kempen <nvankemp@gmail.com>
11c423f
intollvm:mainUh oh!
There was an error while loading.Please reload this page.
No description provided.