aboutsummaryrefslogtreecommitdiff
path: root/clang/unittests/Analysis/FlowSensitive
diff options
context:
space:
mode:
Diffstat (limited to 'clang/unittests/Analysis/FlowSensitive')
-rw-r--r--clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp11
-rw-r--r--clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp94
-rw-r--r--clang/unittests/Analysis/FlowSensitive/TransferTest.cpp134
-rw-r--r--clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp542
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();