diff options
Diffstat (limited to 'clang/unittests/Analysis/FlowSensitive')
4 files changed, 780 insertions, 1 deletions
diff --git a/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp b/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp index 8863011..60925543 100644 --- a/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp @@ -149,9 +149,18 @@ recordState(Elements=8, Branches=2, Joins=1) enterElement(return b ? p : q;) transfer() recordState(Elements=9, Branches=2, Joins=1) +enterElement([Parm: q] (Lifetime ends)) +transfer() +recordState(Elements=10, Branches=2, Joins=1) +enterElement([Parm: p] (Lifetime ends)) +transfer() +recordState(Elements=11, Branches=2, Joins=1) +enterElement([Parm: b] (Lifetime ends)) +transfer() +recordState(Elements=12, Branches=2, Joins=1) enterBlock(0, false) -recordState(Elements=9, Branches=2, Joins=1) +recordState(Elements=12, Branches=2, Joins=1) endAnalysis() )"); diff --git a/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp b/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp index 2e528ed..8ea2015 100644 --- a/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp +++ b/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp @@ -1358,6 +1358,8 @@ bool operator!=(const Status &lhs, const Status &rhs); Status OkStatus(); Status InvalidArgumentError(const char *); +} // namespace absl + #endif // STATUS_H )cc"; @@ -1370,6 +1372,8 @@ constexpr const char StatusOrDefsHeader[] = R"cc( #include "std_type_traits.h" #include "std_utility.h" +namespace absl { + template <typename T> struct StatusOr; namespace internal_statusor { @@ -2232,6 +2236,95 @@ using testing::AssertionResult; #endif // TESTING_DEFS_H )cc"; +constexpr const char StdUniquePtrHeader[] = R"cc( +namespace std { + + template <typename T> + struct default_delete {}; + + template <typename T, typename D = default_delete<T>> + class unique_ptr { + public: + using element_type = T; + using deleter_type = D; + + constexpr unique_ptr(); + constexpr unique_ptr(nullptr_t) noexcept; + unique_ptr(unique_ptr&&); + explicit unique_ptr(T*); + template <typename U, typename E> + unique_ptr(unique_ptr<U, E>&&); + + ~unique_ptr(); + + unique_ptr& operator=(unique_ptr&&); + template <typename U, typename E> + unique_ptr& operator=(unique_ptr<U, E>&&); + unique_ptr& operator=(nullptr_t); + + void reset(T* = nullptr) noexcept; + T* release(); + T* get() const; + + T& operator*() const; + T* operator->() const; + explicit operator bool() const noexcept; + }; + + template <typename T, typename D> + class unique_ptr<T[], D> { + public: + T* get() const; + T& operator[](size_t i); + const T& operator[](size_t i) const; + }; + + template <typename T, typename... Args> + unique_ptr<T> make_unique(Args&&...); + + template <class T, class D> + void swap(unique_ptr<T, D>& x, unique_ptr<T, D>& y) noexcept; + + template <class T1, class D1, class T2, class D2> + bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator!=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator<(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator<=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator>(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator>=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + + template <class T, class D> + bool operator==(const unique_ptr<T, D>& x, nullptr_t) noexcept; + template <class T, class D> + bool operator==(nullptr_t, const unique_ptr<T, D>& y) noexcept; + template <class T, class D> + bool operator!=(const unique_ptr<T, D>& x, nullptr_t) noexcept; + template <class T, class D> + bool operator!=(nullptr_t, const unique_ptr<T, D>& y) noexcept; + template <class T, class D> + bool operator<(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator<(nullptr_t, const unique_ptr<T, D>& y); + template <class T, class D> + bool operator<=(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator<=(nullptr_t, const unique_ptr<T, D>& y); + template <class T, class D> + bool operator>(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator>(nullptr_t, const unique_ptr<T, D>& y); + template <class T, class D> + bool operator>=(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator>=(nullptr_t, const unique_ptr<T, D>& y); +} +)cc"; + std::vector<std::pair<std::string, std::string>> getMockHeaders() { std::vector<std::pair<std::string, std::string>> Headers; Headers.emplace_back("cstddef.h", CStdDefHeader); @@ -2249,6 +2342,7 @@ std::vector<std::pair<std::string, std::string>> getMockHeaders() { Headers.emplace_back("statusor_defs.h", StatusOrDefsHeader); Headers.emplace_back("absl_log.h", AbslLogHeader); Headers.emplace_back("testing_defs.h", TestingDefsHeader); + Headers.emplace_back("std_unique_ptr.h", StdUniquePtrHeader); return Headers; } diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp index 66b3bba..a6308d1 100644 --- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp @@ -17,6 +17,7 @@ #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/Formula.h" #include "clang/Analysis/FlowSensitive/NoopAnalysis.h" #include "clang/Analysis/FlowSensitive/NoopLattice.h" #include "clang/Analysis/FlowSensitive/RecordOps.h" @@ -4382,6 +4383,40 @@ TEST(TransferTest, VarDeclInitAssignConditionalOperator) { }); } +TEST(TransferTest, VarDeclInitReferenceAssignConditionalOperator) { + std::string Code = R"( + struct A { + int i; + }; + + void target(A Foo, A Bar, bool Cond) { + A &Baz = Cond ? Foo : Bar; + // Make sure A::i is modeled. + Baz.i; + /*[[p]]*/ + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); + + auto *FooIVal = cast<IntegerValue>(getFieldValue( + &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"), "i", + ASTCtx, Env)); + auto *BarIVal = cast<IntegerValue>(getFieldValue( + &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"), "i", + ASTCtx, Env)); + auto *BazIVal = cast<IntegerValue>(getFieldValue( + &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Baz"), "i", + ASTCtx, Env)); + + EXPECT_NE(BazIVal, FooIVal); + EXPECT_NE(BazIVal, BarIVal); + }); +} + TEST(TransferTest, VarDeclInDoWhile) { std::string Code = R"( void target(int *Foo) { @@ -6150,6 +6185,45 @@ TEST(TransferTest, ConditionalOperatorValue) { }); } +TEST(TransferTest, ConditionalOperatorValuesTested) { + // We should be able to show that the result of the conditional operator, + // JoinResultMustBeB1, must be equal to B1, because the condition is checking + // `B1 == B2` and selecting B1 on the false branch, or B2 on the true branch. + // Similarly, for JoinResultMustBeB2 == B2. + // Note that the conditional operator involves a join of two *different* + // glvalues, before casting the lvalue to an rvalue, which may affect the + // implementation of the transfer function, and thus affect whether or not we + // can prove that IsB1 == B1. + std::string Code = R"( + void target(bool B1, bool B2) { + bool JoinResultMustBeB1 = (B1 == B2) ? B2 : B1; + bool JoinResultMustBeB2 = (B1 == B2) ? B1 : B2; + // [[p]] + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); + + auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1"); + auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2"); + auto &JoinResultMustBeB1 = + getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB1"); + auto &JoinResultMustBeB2 = + getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB2"); + + const Formula &MustBeB1_Eq_B1 = + Env.arena().makeEquals(JoinResultMustBeB1.formula(), B1.formula()); + EXPECT_TRUE(Env.proves(MustBeB1_Eq_B1)); + + const Formula &MustBeB2_Eq_B2 = + Env.arena().makeEquals(JoinResultMustBeB2.formula(), B2.formula()); + EXPECT_TRUE(Env.proves(MustBeB2_Eq_B2)); + }); +} + TEST(TransferTest, ConditionalOperatorLocation) { std::string Code = R"( void target(bool Cond, int I1, int I2) { @@ -6177,6 +6251,66 @@ TEST(TransferTest, ConditionalOperatorLocation) { }); } +TEST(TransferTest, ConditionalOperatorLocationUpdatedAfter) { + // We don't currently model a Conditional Operator with an LValue result + // as having aliases to the LHS and RHS (if it isn't just the same LValue + // on both sides). We also don't model that the update "may" happen + // (a weak update). So, we don't consider the LHS and RHS as being weakly + // updated at [[after_diff]]. + std::string Code = R"( + void target(bool Cond, bool B1, bool B2) { + (void)0; + // [[before_same]] + (Cond ? B1 : B1) = !B1; + // [[after_same]] + (Cond ? B1 : B2) = !B1; + // [[after_diff]] + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + Environment BeforeSameEnv = + getEnvironmentAtAnnotation(Results, "before_same").fork(); + Environment AfterSameEnv = + getEnvironmentAtAnnotation(Results, "after_same").fork(); + Environment AfterDiffEnv = + getEnvironmentAtAnnotation(Results, "after_diff").fork(); + + auto &BeforeSameB1 = + getValueForDecl<BoolValue>(ASTCtx, BeforeSameEnv, "B1"); + auto &AfterSameB1 = + getValueForDecl<BoolValue>(ASTCtx, AfterSameEnv, "B1"); + auto &AfterDiffB1 = + getValueForDecl<BoolValue>(ASTCtx, AfterDiffEnv, "B1"); + + EXPECT_NE(&BeforeSameB1, &AfterSameB1); + EXPECT_NE(&BeforeSameB1, &AfterDiffB1); + // FIXME: The formula for AfterSameB1 should be different from + // AfterDiffB1 to reflect that B1 may be updated. + EXPECT_EQ(&AfterSameB1, &AfterDiffB1); + + // The value of B1 is definitely different from before_same vs + // after_same. + const Formula &B1ChangedForSame = + AfterSameEnv.arena().makeNot(AfterSameEnv.arena().makeEquals( + AfterSameB1.formula(), BeforeSameB1.formula())); + EXPECT_TRUE(AfterSameEnv.allows(B1ChangedForSame)); + EXPECT_TRUE(AfterSameEnv.proves(B1ChangedForSame)); + + const Formula &B1ChangedForDiff = + AfterDiffEnv.arena().makeNot(AfterDiffEnv.arena().makeEquals( + AfterDiffB1.formula(), AfterSameB1.formula())); + // FIXME: It should be possible that B1 *may* be updated, so it may be + // that AfterSameB1 != AfterDiffB1 or AfterSameB1 == AfterDiffB1. + EXPECT_FALSE(AfterSameEnv.allows(B1ChangedForDiff)); + // proves() should be false, since B1 may or may not have changed + // depending on `Cond`. + EXPECT_FALSE(AfterSameEnv.proves(B1ChangedForDiff)); + }); +} + TEST(TransferTest, ConditionalOperatorOnConstantExpr) { // This is a regression test: We used to crash when a `ConstantExpr` was used // in the branches of a conditional operator. diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp index 425beb9..deaeea3 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp @@ -1725,6 +1725,90 @@ TEST_P(UncheckedStatusOrAccessModelTest, QcheckMacro) { )cc"); } +TEST_P(UncheckedStatusOrAccessModelTest, CheckOkMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_OK(sor.status()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_OK(sor); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUS s = Make<STATUS>(); + CHECK_OK(s); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, QcheckOkMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_OK(sor.status()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_OK(sor); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUS s = Make<STATUS>(); + QCHECK_OK(s); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, CheckEqMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_EQ(sor.status(), absl::OkStatus()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + CHECK_EQ(Make<STATUS>(), absl::OkStatus()); + CHECK_EQ(absl::OkStatus(), Make<STATUS>()); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, QcheckEqMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_EQ(sor.status(), absl::OkStatus()); + sor.value(); + } + )cc"); +} + TEST_P(UncheckedStatusOrAccessModelTest, CheckNeMacro) { ExpectDiagnosticsFor(R"cc( #include "unchecked_statusor_access_test_defs.h" @@ -2975,6 +3059,463 @@ TEST_P(UncheckedStatusOrAccessModelTest, Emplace) { )cc"); } +TEST_P(UncheckedStatusOrAccessModelTest, ValueConstruction) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_BOOL result = false; + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT result = 21; + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT result = Make<STATUSOR_INT>(); + result.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_BOOL result = false; + if (result.ok()) + result.value(); + else + result.value(); + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_BOOL result(false); + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT result(21); + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT result(Make<STATUSOR_INT>()); + result.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_BOOL result(false); + if (result.ok()) + result.value(); + else + result.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ValueAssignment) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_BOOL result; + result = false; + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT result; + result = 21; + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT result; + result = Make<STATUSOR_INT>(); + result.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_BOOL result; + result = false; + if (result.ok()) + result.value(); + else + result.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, NestedStatusOr) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + absl::StatusOr<STATUSOR_INT> result; + result = Make<STATUSOR_INT>(); + result.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + absl::StatusOr<STATUSOR_INT> result = Make<STATUSOR_INT>(); + result.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, PtrConstruct) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_VOIDPTR sor = nullptr; + *sor; + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_VOIDPTR sor(nullptr); + *sor; + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, InPlaceConstruct) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_VOIDPTR absl_sor(absl::in_place, {nullptr}); + *absl_sor; + STATUSOR_VOIDPTR std_sor(std::in_place, {nullptr}); + *std_sor; + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ConstructStatusOrFromReference) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + void target() { + const auto sor1 = Make<STATUSOR_INT&>(); + const auto sor2 = Make<STATUSOR_INT&>(); + if (!sor1.ok() && !sor2.ok()) return; + if (sor1.ok() && !sor2.ok()) { + } else if (!sor1.ok() && sor2.ok()) { + } else { + sor1.value(); + sor2.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ConstructStatusFromReference) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + const auto sor1 = Make<STATUSOR_INT&>(); + const auto sor2 = Make<STATUSOR_INT&>(); + const auto s1 = Make<STATUS&>(); + const auto s2 = Make<STATUS&>(); + + if (!s1.ok() && !s2.ok()) return; + if (s1.ok() && !s2.ok()) { + } else if (!s1.ok() && s2.ok()) { + } else { + if (s1 != sor1.status() || s2 != sor2.status()) return; + sor1.value(); + sor2.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, AccessorCall) { + // Accessor returns reference. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); + } + )cc"); + + // Uses an operator + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& operator()() const { return sor_; } + }; + + void target(Foo foo) { + if (foo().ok()) foo().value(); + } + )cc"); + + // Calls nonconst method in between. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + void invalidate() {} + + const STATUSOR_INT& sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) { + foo.invalidate(); + foo.sor().value(); // [[unsafe]] + } + } + )cc"); + + // Calls nonconst operator in between. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + void operator()() {} + + const STATUSOR_INT& sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) { + foo(); + foo.sor().value(); // [[unsafe]] + } + } + )cc"); + + // Accessor returns copy. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + STATUSOR_INT sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); + } + )cc"); + + // Non-const accessor. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& sor() { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); // [[unsafe]] + } + )cc"); + + // Non-const rvalue accessor. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + STATUSOR_INT&& sor() { return std::move(sor_); } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); // [[unsafe]] + } + )cc"); + + // const pointer accessor. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT* sor() const { return &sor_; } + }; + + void target(Foo foo) { + if (foo.sor()->ok()) foo.sor()->value(); + } + )cc"); + + // const pointer operator. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT* operator->() const { return &sor_; } + }; + + void target(Foo foo) { + if (foo->ok()) foo->value(); + } + )cc"); + + // We copy the result of the accessor. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& sor() const { return sor_; } + }; + void target() { + Foo foo; + if (!foo.sor().ok()) return; + const auto sor = foo.sor(); + sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, PointerLike) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo { + public: + std::pair<int, STATUSOR_VOIDPTR>& operator*() const; + std::pair<int, STATUSOR_VOIDPTR>* operator->() const; + bool operator!=(const Foo& other) const; + }; + + void target() { + Foo foo; + if (foo->second.ok() && *foo->second != nullptr) { + *foo->second; + (*foo).second.value(); + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo { + public: + std::pair<int, STATUSOR_INT>& operator*() const; + std::pair<int, STATUSOR_INT>* operator->() const; + }; + void target() { + Foo foo; + if (!foo->second.ok()) return; + foo->second.value(); + (*foo).second.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(std::pair<int, STATUSOR_VOIDPTR>* foo) { + if (foo->second.ok() && *foo->second != nullptr) { + *foo->second; + (*foo).second.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UniquePtr) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor_up = Make<std::unique_ptr<STATUSOR_INT>>(); + if (sor_up->ok()) sor_up->value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UniquePtrReset) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor_up = Make<std::unique_ptr<STATUSOR_INT>>(); + if (sor_up->ok()) { + sor_up.reset(Make<STATUSOR_INT*>()); + sor_up->value(); // [[unsafe]] + } + } + )cc"); +} + } // namespace std::string @@ -3024,6 +3565,7 @@ GetHeaders(UncheckedStatusOrAccessModelTestAliasKind AliasKind) { #include "std_pair.h" #include "absl_log.h" #include "testing_defs.h" +#include "std_unique_ptr.h" template <typename T> T Make(); |
