Skip to content

[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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 25, 2024
Merged
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
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ Changes in existing checks
usages of ``sizeof()``, ``alignof()``, and ``offsetof()`` when adding or
subtracting from a pointer.

- 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>_.

- Improved :doc:`cert-flp30-c <clang-tidy/checks/cert/flp30-c>` check to
fix false positive that floating point variable is only used in increment
expression.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ results. Therefore, it may be more resource intensive (RAM, CPU) than the
average clang-tidy check.

This check identifies unsafe accesses to values contained in
``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``, or
``folly::Optional<T>`` objects. Below we will refer to all these types
collectively as ``optional<T>``.
``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``,
``folly::Optional<T>``, ``bsl::optional``, or
``BloombergLP::bdlb::NullableValue`` objects. Below we will refer to all these
types collectively as ``optional<T>``.

An access to the value of an ``optional<T>`` occurs when one of its ``value``,
``operator*``, or ``operator->`` member functions is invoked. To align with
Expand Down
Original file line number Diff line number Diff line change
@@ -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 `bdlb::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_
Original file line number Diff line number Diff line change
@@ -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_
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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-warning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,25 @@
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();
// Note: the Names appear in reverse order. E.g., to check
// if NS is foo::bar::, call isFullyQualifiedNamespaceEqualTo(NS, "bar", "foo")
template <class... NameTypes>
static bool isFullyQualifiedNamespaceEqualTo(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 isFullyQualifiedNamespaceEqualTo(*NextNS, Names...);
return false;
} else {
return NS.getParent()->isTranslationUnit();
}
}

static bool hasOptionalClassName(const CXXRecordDecl &RD) {
Expand All @@ -50,15 +65,23 @@ 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() ||
isFullyQualifiedNamespaceEqualTo(*N, "absl") ||
isFullyQualifiedNamespaceEqualTo(*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 && (isFullyQualifiedNamespaceEqualTo(*N, "base") ||
isFullyQualifiedNamespaceEqualTo(*N, "folly"));
}

if (RD.getName() == "NullableValue") {
const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
return N != nullptr &&
isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP");
}

return false;
Expand Down Expand Up @@ -195,22 +218,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() {
Expand Down Expand Up @@ -415,6 +441,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(
Expand Down Expand Up @@ -784,6 +819,12 @@ auto buildTransferMatchSwitch() {
isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
transferOptionalHasValueCall)

// NullableValue::isNull
// Only NullableValue has isNull
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("isNull")),
transferOptionalIsNullCall)

// optional::emplace
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("emplace")),
Expand Down
Loading