diff --git a/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp b/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp index 8971530bab9b2..1083d4373a6bd 100644 --- a/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp @@ -27,6 +27,7 @@ #include "StringFindStrContainsCheck.h" #include "TimeComparisonCheck.h" #include "TimeSubtractionCheck.h" +#include "UncheckedStatusOrAccessCheck.h" #include "UpgradeDurationConversionsCheck.h" namespace clang::tidy { @@ -67,6 +68,8 @@ class AbseilModule : public ClangTidyModule { CheckFactories.registerCheck("abseil-time-comparison"); CheckFactories.registerCheck( "abseil-time-subtraction"); + CheckFactories.registerCheck( + "abseil-unchecked-statusor-access"); CheckFactories.registerCheck( "abseil-upgrade-duration-conversions"); } diff --git a/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt b/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt index ca7cc6782f1e6..0c02ffc7306d1 100644 --- a/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt @@ -25,6 +25,7 @@ add_clang_library(clangTidyAbseilModule STATIC TimeComparisonCheck.cpp TimeSubtractionCheck.cpp UpgradeDurationConversionsCheck.cpp + UncheckedStatusOrAccessCheck.cpp LINK_LIBS clangTidy diff --git a/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.cpp b/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.cpp new file mode 100644 index 0000000000000..d88917f3dfdc5 --- /dev/null +++ b/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.cpp @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UncheckedStatusOrAccessCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Error.h" + +namespace clang::tidy::abseil { +using ast_matchers::MatchFinder; +using dataflow::statusor_model::UncheckedStatusOrAccessDiagnoser; +using dataflow::statusor_model::UncheckedStatusOrAccessModel; + +static constexpr llvm::StringLiteral FuncID("fun"); + +void UncheckedStatusOrAccessCheck::registerMatchers(MatchFinder *Finder) { + using namespace ast_matchers; + + auto HasStatusOrCallDescendant = + hasDescendant(callExpr(callee(cxxMethodDecl(ofClass(hasAnyName( + "absl::StatusOr", "absl::internal_statusor::OperatorBase")))))); + Finder->addMatcher(functionDecl(unless(isExpansionInSystemHeader()), + hasBody(HasStatusOrCallDescendant)) + .bind(FuncID), + this); + Finder->addMatcher( + cxxConstructorDecl(hasAnyConstructorInitializer( + withInitializer(HasStatusOrCallDescendant))) + .bind(FuncID), + this); +} + +void UncheckedStatusOrAccessCheck::check( + const MatchFinder::MatchResult &Result) { + if (Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred()) + return; + + const auto *FuncDecl = Result.Nodes.getNodeAs(FuncID); + if (FuncDecl->isTemplated()) + return; + + UncheckedStatusOrAccessDiagnoser Diagnoser; + if (llvm::Expected> Locs = + dataflow::diagnoseFunction(*FuncDecl, *Result.Context, + Diagnoser)) + for (const SourceLocation &Loc : *Locs) + diag(Loc, "unchecked access to 'absl::StatusOr' value"); + else + llvm::consumeError(Locs.takeError()); +} + +bool UncheckedStatusOrAccessCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus; +} + +} // namespace clang::tidy::abseil diff --git a/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h b/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h new file mode 100644 index 0000000000000..0fb838ad235cf --- /dev/null +++ b/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h @@ -0,0 +1,24 @@ +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_UNCHECKEDSTATUSORACCESSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_UNCHECKEDSTATUSORACCESSCHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang::tidy::abseil { + +// Warns when the code is unwrapping an absl::StatusOr object without +// assuring that it contains a value. +// +// For details on the dataflow analysis implemented in this check see: +// clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp +class UncheckedStatusOrAccessCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; +}; + +} // namespace clang::tidy::abseil + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_UNCHECKEDSTATUSORACCESSCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 8efde0ab121e6..0d4119745cb41 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -199,6 +199,11 @@ Improvements to clang-tidy New checks ^^^^^^^^^^ +- New :doc:`abseil-unchecked-statusor-access + ` check. + + Finds uses of ``absl::StatusOr`` without checking if a value is present. + - New :doc:`bugprone-derived-method-shadowing-base-method ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst b/clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst new file mode 100644 index 0000000000000..0f1a0e55af022 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst @@ -0,0 +1,384 @@ +.. title:: clang-tidy - abseil-unchecked-statusor-access + +abseil-unchecked-statusor-access +================================ + +This check identifies unsafe accesses to values contained in +``absl::StatusOr`` objects. Below we will refer to this type as +``StatusOr``. + +An access to the value of an ``StatusOr`` occurs when one of its +``value``, ``operator*``, or ``operator->`` member functions is invoked. +To align with common misconceptions, the check considers these member +functions as equivalent, even though there are subtle differences +related to exceptions vs. undefined behavior. + +An access to the value of a ``StatusOr`` is considered safe if and +only if code in the local scope (e.g. function body) ensures that the +status of the ``StatusOr`` is ok in all possible execution paths that +can reach the access. That should happen either through an explicit +check, using the ``StatusOr::ok`` member function, or by constructing +the ``StatusOr`` in a way that shows that its status is unambiguously +ok (e.g. by passing a value to its constructor). + +Below we list some examples of safe and unsafe ``StatusOr`` access +patterns. + +Note: If the check isn’t behaving as you would have expected on a code +snippet, please `report it `__. + +False negatives +--------------- + +This check generally does **not** generate false negatives. That means that if +an access is not marked as unsafe, it is provably safe. If it cannot prove an +access safe, it is assumed to be unsafe. In some cases, the static analysis +cannot prove an access safe even though it is, for a variety of reasons (e.g. +unmodelled invariants of functions called). In these cases, the analysis does +produce false positive reports. + +That being said, there are some heuristics used that in very rare cases might +be incorrect: + +- `a const method accessor (without arguments) that returns different + values when called multiple times <#functionstability>`__. + +If you think the check generated a false negative, please `report +it `__. + +Known limitations +----------------- + +This is a non-exhaustive list of constructs that are currently not +modelled in the check and will lead to false positives: + +- `Checking a StatusOr and then capturing it in a lambda <#lambdas>`__ +- `Indexing into a container with the same index <#containers>`__ +- `Project specific helper-functions <#uncommonapi>`__, +- `Functions with a stable return value <#functionstability>`__ +- **Any** `cross-function reasoning <#crossfunction>`__. This is by + design and will not change in the future. + +Checking if the status is ok, then accessing the value +------------------------------------------------------ + +The check recognizes all straightforward ways for checking the status +and accessing the value contained in a ``StatusOr`` object. For +example: + +.. code:: cpp + + void f(absl::StatusOr sor) { + if (sor.ok()) { + use(*sor); + } + } + +Checking if the status is ok, then accessing the value from a copy +------------------------------------------------------------------ + +The criteria that the check uses is semantic, not syntactic. It +recognizes when a copy of the ``StatusOr`` object being accessed is +known to have ok status. For example: + +.. code:: cpp + + void f(absl::StatusOr sor1) { + if (sor1.ok()) { + absl::optional sor2 = sor1; + use(*sor2); + } + } + +Ensuring that the status is ok using common macros +-------------------------------------------------- + +The check is aware of common macros like ``ABSL_CHECK`` and ``ASSERT_THAT``. +Those can be used to ensure that the status of a ``StatusOr`` object +is ok. For example: + +.. code:: cpp + + void f(absl::StatusOr sor) { + ABSL_DCHECK_OK(sor); + use(*sor); + } + +Ensuring that the status is ok, then accessing the value in a correlated branch +------------------------------------------------------------------------------- + +The check is aware of correlated branches in the code and can figure out +when a ``StatusOr`` object is ensured to have ok status on all +execution paths that lead to an access. For example: + +.. code:: cpp + + void f(absl::StatusOr sor) { + bool safe = false; + if (sor.ok() && SomeOtherCondition()) { + safe = true; + } + // ... more code... + if (safe) { + use(*sor); + } + } + +Accessing the value without checking the status +----------------------------------------------- + +The check flags accesses to the value that are not locally guarded by a +status check: + +.. code:: cpp + + void f1(absl::StatusOr sor) { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + + void f2(absl::StatusOr sor) { + use(sor->member); // unsafe: it is unclear whether the status of `sor` is ok. + } + + void f3(absl::StatusOr sor) { + use(sor.value()); // unsafe: it is unclear whether the status of `sor` is ok. + } + +Use ``ABSL_CHECK_OK`` to signal that you knowingly want to crash on +non-OK values. + +NOTE: Even though using ``.value()`` on a non-``ok()`` ``StatusOr`` is defined +to crash, it is often unintentional. That is why our checker flags those as +well. + +Accessing the value in the wrong branch +--------------------------------------- + +The check is aware of the state of a ``StatusOr`` object in different +branches of the code. For example: + +.. code:: cpp + + void f(absl::StatusOr sor) { + if (sor.ok()) { + } else { + use(*sor); // unsafe: it is clear that the status of `sor` is *not* ok. + } + } + +.. _functionstability: + +Assuming a function result to be stable +--------------------------------------- + +The check is aware that function results might not be stable. That is, +consecutive calls to the same function might return different values. +For example: + +.. code:: cpp + + void f(Foo foo) { + if (foo.sor().ok()) { + use(*foo.sor()); // unsafe: it is unclear whether the status of `foo.sor()` is ok. + } + } + +In such cases it is best to store the result of the function call in a +local variable and use it to access the value. For example: + +.. code:: cpp + + void f(Foo foo) { + if (const auto& foo_sor = foo.sor(); foo_sor.ok()) { + use(*foo_sor); + } + } + +The check **does** assume that ``const``-qualified accessor functions +return a stable value if no non-const function was called between the +two calls: + +.. code:: cpp + + class Foo { + const absl::StatusOr& get() const { + [...]; + } + } + void f(Foo foo) { + if (foo.get().ok()) { + use(*foo.get()); + } + } + +If there is a call to a non-``const``-qualified function, the check +assumes the return value of the accessor was mutated. + +.. code:: cpp + + class Foo { + const absl::StatusOr& get() const { + [...]; + } + void mutate(); + } + void f(Foo foo) { + if (foo.get().ok()) { + foo.mutate(); + use(*foo.get()); // unsafe: mutate might have changed the state of the object + } + } + +.. _uncommonapi: + +Relying on invariants of uncommon APIs +-------------------------------------- + +The check is unaware of invariants of uncommon APIs. For example: + +.. code:: cpp + + void f(Foo foo) { + if (foo.HasProperty("bar")) { + use(*foo.GetProperty("bar")); // unsafe: it is unclear whether the status of `foo.GetProperty("bar")` is ok. + } + } + +In such cases it is best to check explicitly that the status of the +``StatusOr`` object is ok. For example: + +.. code:: cpp + + void f(Foo foo) { + if (const auto& property = foo.GetProperty("bar"); property.ok()) { + use(*property); + } + } + +.. _crossfunction: + +Checking if the status is ok, then passing the ``StatusOr`` to another function +---------------------------------------------------------------------------------- + +The check relies on local reasoning. The check and value access must +both happen in the same function. An access is considered unsafe even if +the caller of the function performing the access ensures that the status +of the ``StatusOr`` is ok. For example: + +.. code:: cpp + + void g(absl::StatusOr sor) { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + + void f(absl::StatusOr sor) { + if (sor.ok()) { + g(sor); + } + } + +In such cases it is best to either pass the value directly when calling +a function or check that the status of the ``StatusOr`` is ok in the +local scope of the callee. For example: + +.. code:: cpp + + void g(int val) { + use(val); + } + + void f(absl::StatusOr sor) { + if (sor.ok()) { + g(*sor); + } + } + +Aliases created via ``using`` declarations +------------------------------------------ + +The check is aware of aliases of ``StatusOr`` types that are created +via ``using`` declarations. For example: + +.. code:: cpp + + using StatusOrInt = absl::StatusOr; + + void f(StatusOrInt sor) { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + +Containers +---------- + +The check is more strict than necessary when it comes to containers of +``StatusOr`` values. Simply checking that the status of an element of +a container is ok is not sufficient to deem accessing it safe. For +example: + +.. code:: cpp + + void f(std::vector> sors) { + if (sors[0].ok()) { + use(*sors[0]); // unsafe: it is unclear whether the status of `sors[0]` is ok. + } + } + +One needs to grab a reference to a particular object and use that +instead: + +.. code:: cpp + + void f(std::vector> sors) { + absl::StatusOr& sor0 = sors[0]; + if (sor0.ok()) { + use(*sor0); + } + } + +A future version could improve the understanding of more safe usage +patterns that involve containers. + +Lambdas +------- + +The check is capable of reporting unsafe ``StatusOr`` accesses in +lambdas, but isn’t smart enough to propagate information from the +surrounding context through the lambda. This means that the following +pattern will be reported as an unsafe access: + +.. code:: cpp + + void f(absl::StatusOr sor) { + if (sor.ok()) { + [&sor]() { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + } + } + +To avoid the issue, you should grab a reference to the contained object +and capture that instead + +.. code:: cpp + + void f(absl::StatusOr sor) { + if (sor.ok()) { + auto& s = *sor; + [&s]() { + use(s); + } + } + } + +Alternatively you could add a check inside the lambda where the value is +accessed: + +.. code:: cpp + + void f(absl::StatusOr sor) { + [&sor]() { + if (sor.ok()) { + use(*sor); + } + } + } diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h new file mode 100644 index 0000000000000..06ce61dbcc1e7 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h @@ -0,0 +1,46 @@ +#include + +namespace absl { + +template +struct conjunction : std::true_type {}; + +template +struct conjunction + : std::conditional, T>::type {}; + +template +struct conjunction : T {}; + +template +struct disjunction : std::false_type {}; + +template +struct disjunction + : std::conditional>::type {}; + +template +struct disjunction : T {}; + +template +struct negation : std::integral_constant {}; + +template +using enable_if_t = typename std::enable_if::type; + + +template +using conditional_t = typename std::conditional::type; + +template +using remove_cv_t = typename std::remove_cv::type; + +template +using remove_reference_t = typename std::remove_reference::type; + +template +using decay_t = typename std::decay::type; + +using std::in_place; +using std::in_place_t; +} // namespace absl diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h new file mode 100644 index 0000000000000..fd0910e81436a --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h @@ -0,0 +1,69 @@ +namespace absl { +struct SourceLocation { + static constexpr SourceLocation current(); + static constexpr SourceLocation + DoNotInvokeDirectlyNoSeriouslyDont(int line, const char *file_name); +}; +} // namespace absl +namespace absl { +enum class StatusCode : int { + kOk, + kCancelled, + kUnknown, + kInvalidArgument, + kDeadlineExceeded, + kNotFound, + kAlreadyExists, + kPermissionDenied, + kResourceExhausted, + kFailedPrecondition, + kAborted, + kOutOfRange, + kUnimplemented, + kInternal, + kUnavailable, + kDataLoss, + kUnauthenticated, +}; +} // namespace absl + +namespace absl { +enum class StatusToStringMode : int { + kWithNoExtraData = 0, + kWithPayload = 1 << 0, + kWithSourceLocation = 1 << 1, + kWithEverything = ~kWithNoExtraData, + kDefault = kWithPayload, +}; +class Status { +public: + Status(); + Status(const Status &base_status, absl::SourceLocation loc); + Status(Status &&base_status, absl::SourceLocation loc); + ~Status() {} + + Status(const Status &); + Status &operator=(const Status &x); + + Status(Status &&) noexcept; + Status &operator=(Status &&); + + friend bool operator==(const Status &, const Status &); + friend bool operator!=(const Status &, const Status &); + + bool ok() const { return true; } + void CheckSuccess() const; + void IgnoreError() const; + int error_code() const; + absl::Status ToCanonical() const; + void Update(const Status &new_status); + void Update(Status &&new_status); +}; + +bool operator==(const Status &lhs, const Status &rhs); +bool operator!=(const Status &lhs, const Status &rhs); + +Status OkStatus(); +Status InvalidArgumentError(const char *); + +} // namespace absl diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h new file mode 100644 index 0000000000000..0151dda0cb97d --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h @@ -0,0 +1,346 @@ +#include "status.h" +#include +#include + +namespace absl { + +template struct StatusOr; + +namespace internal_statusor { + +template +struct HasConversionOperatorToStatusOr : std::false_type {}; + +template +void test(char (*)[sizeof(std::declval().operator absl::StatusOr())]); + +template +struct HasConversionOperatorToStatusOr(0))> + : std::true_type {}; + +template +using IsConstructibleOrConvertibleFromStatusOr = + absl::disjunction &>, + std::is_constructible &>, + std::is_constructible &&>, + std::is_constructible &&>, + std::is_convertible &, T>, + std::is_convertible &, T>, + std::is_convertible &&, T>, + std::is_convertible &&, T>>; + +template +using IsConstructibleOrConvertibleOrAssignableFromStatusOr = + absl::disjunction, + std::is_assignable &>, + std::is_assignable &>, + std::is_assignable &&>, + std::is_assignable &&>>; + +template +struct IsDirectInitializationAmbiguous + : public absl::conditional_t< + std::is_same>, + U>::value, + std::false_type, + IsDirectInitializationAmbiguous< + T, absl::remove_cv_t>>> {}; + +template +struct IsDirectInitializationAmbiguous> + : public IsConstructibleOrConvertibleFromStatusOr {}; + +template +using IsDirectInitializationValid = absl::disjunction< + // Short circuits if T is basically U. + std::is_same>>, + absl::negation, + absl::remove_cv_t>>, + std::is_same>>, + std::is_same>>, + IsDirectInitializationAmbiguous>>>; + +template +struct IsForwardingAssignmentAmbiguous + : public absl::conditional_t< + std::is_same>, + U>::value, + std::false_type, + IsForwardingAssignmentAmbiguous< + T, absl::remove_cv_t>>> {}; + +template +struct IsForwardingAssignmentAmbiguous> + : public IsConstructibleOrConvertibleOrAssignableFromStatusOr {}; + +template +using IsForwardingAssignmentValid = absl::disjunction< + // Short circuits if T is basically U. + std::is_same>>, + absl::negation, + absl::remove_cv_t>>, + std::is_same>>, + std::is_same>>, + IsForwardingAssignmentAmbiguous>>>; + +template +using IsForwardingAssignmentValid = absl::disjunction< + // Short circuits if T is basically U. + std::is_same>>, + absl::negation, + absl::remove_cv_t>>, + std::is_same>>, + std::is_same>>, + IsForwardingAssignmentAmbiguous>>>; + +template struct OperatorBase { + const T &value() const &; + T &value() &; + const T &&value() const &&; + T &&value() &&; + + const T &operator*() const &; + T &operator*() &; + const T &&operator*() const &&; + T &&operator*() &&; + + // To test that analyses are okay if there is a use of operator* + // within this base class. + const T *operator->() const { return __builtin_addressof(**this); } + T *operator->() { return __builtin_addressof(**this); } +}; + +} // namespace internal_statusor + +template +struct StatusOr : private internal_statusor::OperatorBase { + explicit StatusOr(); + + StatusOr(const StatusOr &) = default; + StatusOr &operator=(const StatusOr &) = default; + + StatusOr(StatusOr &&) = default; + StatusOr &operator=(StatusOr &&) = default; + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, + std::is_convertible, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr(const StatusOr &); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, + absl::negation>, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + explicit StatusOr(const StatusOr &); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, std::is_convertible, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr(StatusOr &&); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, + absl::negation>, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + explicit StatusOr(StatusOr &&); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, + std::is_assignable, + absl::negation< + internal_statusor:: + IsConstructibleOrConvertibleOrAssignableFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr &operator=(const StatusOr &); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, std::is_assignable, + absl::negation< + internal_statusor:: + IsConstructibleOrConvertibleOrAssignableFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr &operator=(StatusOr &&); + + template < + typename U = absl::Status, + absl::enable_if_t< + absl::conjunction< + std::is_convertible, + std::is_constructible, + absl::negation, absl::StatusOr>>, + absl::negation, T>>, + absl::negation, absl::in_place_t>>, + absl::negation>>::value, + int> = 0> + StatusOr(U &&); + + template < + typename U = absl::Status, + absl::enable_if_t< + absl::conjunction< + absl::negation>, + std::is_constructible, + absl::negation, absl::StatusOr>>, + absl::negation, T>>, + absl::negation, absl::in_place_t>>, + absl::negation>>::value, + int> = 0> + explicit StatusOr(U &&); + + template < + typename U = absl::Status, + absl::enable_if_t< + absl::conjunction< + std::is_convertible, + std::is_constructible, + absl::negation, absl::StatusOr>>, + absl::negation, T>>, + absl::negation, absl::in_place_t>>, + absl::negation>>::value, + int> = 0> + StatusOr &operator=(U &&); + + template < + typename U = T, + typename = typename std::enable_if, std::is_assignable, + absl::disjunction< + std::is_same>, T>, + absl::conjunction< + absl::negation>, + absl::negation< + internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>>, + internal_statusor::IsForwardingAssignmentValid>::value>:: + type> + StatusOr &operator=(U &&); + + template explicit StatusOr(absl::in_place_t, Args &&...); + + template + explicit StatusOr(absl::in_place_t, std::initializer_list, Args &&...); + + template < + typename U = T, + absl::enable_if_t< + absl::conjunction< + internal_statusor::IsDirectInitializationValid, + std::is_constructible, std::is_convertible, + absl::disjunction< + std::is_same>, + T>, + absl::conjunction< + absl::negation>, + absl::negation< + internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>>>::value, + int> = 0> + StatusOr(U &&); + + template < + typename U = T, + absl::enable_if_t< + absl::conjunction< + internal_statusor::IsDirectInitializationValid, + absl::disjunction< + std::is_same>, + T>, + absl::conjunction< + absl::negation>, + absl::negation< + internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>>, + std::is_constructible, + absl::negation>>::value, + int> = 0> + explicit StatusOr(U &&); + + bool ok() const; + + const Status &status() const & { return status_; } + Status status() &&; + + using StatusOr::OperatorBase::value; + + const T &ValueOrDie() const &; + T &ValueOrDie() &; + const T &&ValueOrDie() const &&; + T &&ValueOrDie() &&; + + using StatusOr::OperatorBase::operator*; + using StatusOr::OperatorBase::operator->; + + template T value_or(U &&default_value) const &; + template T value_or(U &&default_value) &&; + + template T &emplace(Args &&...args); + + template < + typename U, typename... Args, + absl::enable_if_t &, + Args &&...>::value, + int> = 0> + T &emplace(std::initializer_list ilist, Args &&...args); + +private: + absl::Status status_; +}; + +template +bool operator==(const StatusOr &lhs, const StatusOr &rhs); + +template +bool operator!=(const StatusOr &lhs, const StatusOr &rhs); + +} // namespace absl diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h new file mode 100644 index 0000000000000..633260f24f99b --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h @@ -0,0 +1,10 @@ +namespace std { + +typedef decltype(sizeof(char)) size_t; + +using nullptr_t = decltype(nullptr); + +} // namespace std + +typedef decltype(sizeof(char)) size_t; +typedef decltype(sizeof(char*)) ptrdiff_t; diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list new file mode 100644 index 0000000000000..886a54fe217f4 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list @@ -0,0 +1,11 @@ + +namespace std { + +template +class initializer_list { + public: + const T *a, *b; + initializer_list() noexcept; +}; + +} // namespace std \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits new file mode 100644 index 0000000000000..c97ae9c2d14bd --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits @@ -0,0 +1,427 @@ +#include "cstddef.h" + +namespace std { + +template +struct integral_constant { + static constexpr T value = V; +}; + +using true_type = integral_constant; +using false_type = integral_constant; + +template< class T > struct remove_reference {typedef T type;}; +template< class T > struct remove_reference {typedef T type;}; +template< class T > struct remove_reference {typedef T type;}; + +template + using remove_reference_t = typename remove_reference::type; + +template +struct remove_extent { + typedef T type; +}; + +template +struct remove_extent { + typedef T type; +}; + +template +struct remove_extent { + typedef T type; +}; + +template +struct is_array : false_type {}; + +template +struct is_array : true_type {}; + +template +struct is_array : true_type {}; + +template +struct is_function : false_type {}; + +template +struct is_function : true_type {}; + +namespace detail { + +template +struct type_identity { + using type = T; +}; // or use type_identity (since C++20) + +template +auto try_add_pointer(int) -> type_identity::type*>; +template +auto try_add_pointer(...) -> type_identity; + +} // namespace detail + +template +struct add_pointer : decltype(detail::try_add_pointer(0)) {}; + +template +struct conditional { + typedef T type; +}; + +template +struct conditional { + typedef F type; +}; + +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; + +template +using remove_cv_t = typename remove_cv::type; + +template +struct decay { + private: + typedef typename remove_reference::type U; + + public: + typedef typename conditional< + is_array::value, typename remove_extent::type*, + typename conditional::value, typename add_pointer::type, + typename remove_cv::type>::type>::type type; +}; + +template +struct enable_if {}; + +template +struct enable_if { + typedef T type; +}; + +template +using enable_if_t = typename enable_if::type; + +template +struct is_same : false_type {}; + +template +struct is_same : true_type {}; + +template +struct is_void : is_same::type> {}; + +namespace detail { + +template +auto try_add_lvalue_reference(int) -> type_identity; +template +auto try_add_lvalue_reference(...) -> type_identity; + +template +auto try_add_rvalue_reference(int) -> type_identity; +template +auto try_add_rvalue_reference(...) -> type_identity; + +} // namespace detail + +template +struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference(0)) { +}; + +template +struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference(0)) { +}; + +template +typename add_rvalue_reference::type declval() noexcept; + +namespace detail { + +template +auto test_returnable(int) + -> decltype(void(static_cast(nullptr)), true_type{}); +template +auto test_returnable(...) -> false_type; + +template +auto test_implicitly_convertible(int) + -> decltype(void(declval()(declval())), true_type{}); +template +auto test_implicitly_convertible(...) -> false_type; + +} // namespace detail + +template +struct is_convertible + : integral_constant(0))::value && + decltype(detail::test_implicitly_convertible( + 0))::value) || + (is_void::value && is_void::value)> {}; + +template +inline constexpr bool is_convertible_v = is_convertible::value; + +template +using void_t = void; + +template +struct is_constructible_ : false_type {}; + +template +struct is_constructible_()...))>, T, Args...> + : true_type {}; + +template +using is_constructible = is_constructible_, T, Args...>; + +template +inline constexpr bool is_constructible_v = is_constructible::value; + +template +struct __uncvref { + typedef typename remove_cv::type>::type type; +}; + +template +using __uncvref_t = typename __uncvref<_Tp>::type; + +template +using _BoolConstant = integral_constant; + +template +using _IsSame = _BoolConstant<__is_same(_Tp, _Up)>; + +template +using _IsNotSame = _BoolConstant; + +template +struct _MetaBase; +template <> +struct _MetaBase { + template + using _SelectImpl = _Tp; + template