diff options
Diffstat (limited to 'clang/unittests/Analysis')
6 files changed, 2918 insertions, 6 deletions
diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp index 95f8ae2..ef22960 100644 --- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp +++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp @@ -2038,4 +2038,42 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByConditionOperator) { EXPECT_TRUE(isPointeeMutated(Results, AST.get())); } +TEST(ExprMutationAnalyzerTest, PointeeMutatedByReturn) { + { + const std::string Code = R"( + int * f() { + int *const x = nullptr; + return x; + })"; + auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"}); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_TRUE(isPointeeMutated(Results, AST.get())); + } + { + const std::string Code = R"( + int * f() { + int *const x = nullptr; + return x; + })"; + // in C++23, AST will have NoOp cast. + auto AST = + buildASTFromCodeWithArgs(Code, {"-Wno-everything", "-std=c++23"}); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_TRUE(isPointeeMutated(Results, AST.get())); + } + { + const std::string Code = R"( + int const* f() { + int *const x = nullptr; + return x; + })"; + auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"}); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isPointeeMutated(Results, AST.get())); + } +} + } // namespace clang diff --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt index 3508238..1d932ec 100644 --- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt @@ -25,6 +25,8 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests TransferTest.cpp TypeErasedDataflowAnalysisTest.cpp UncheckedOptionalAccessModelTest.cpp + UncheckedStatusOrAccessModelTest.cpp + UncheckedStatusOrAccessModelTestFixture.cpp ValueTest.cpp WatchedLiteralsSolverTest.cpp CLANG_LIBS diff --git a/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp b/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp index d3dee58..d875542 100644 --- a/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp +++ b/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp @@ -459,6 +459,10 @@ struct is_scalar template <> struct is_scalar<nullptr_t> : public true_type {}; +struct in_place_t {}; + +constexpr in_place_t in_place; + } // namespace std #endif // STD_TYPE_TRAITS_H @@ -511,9 +515,8 @@ using remove_reference_t = typename std::remove_reference<T>::type; template <typename T> using decay_t = typename std::decay<T>::type; -struct in_place_t {}; - -constexpr in_place_t in_place; +using std::in_place; +using std::in_place_t; } // namespace absl #endif // ABSL_TYPE_TRAITS_H @@ -589,9 +592,6 @@ static constexpr char StdOptionalHeader[] = R"( namespace std { -struct in_place_t {}; -constexpr in_place_t in_place; - struct nullopt_t { constexpr explicit nullopt_t() {} }; diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTest.cpp new file mode 100644 index 0000000..20630a4c --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTest.cpp @@ -0,0 +1,34 @@ +//===- UncheckedStatusOrAccessModelTest.cpp -------------------------------===// +// +// 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 <utility> + +#include "UncheckedStatusOrAccessModelTestFixture.h" +#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h" +#include "gtest/gtest.h" + +namespace clang::dataflow::statusor_model { +namespace { + +auto Executor = std::make_unique< + UncheckedStatusOrAccessModelTestExecutor<UncheckedStatusOrAccessModel>>(); + +INSTANTIATE_TEST_SUITE_P( + UncheckedStatusOrAccessModelTest, UncheckedStatusOrAccessModelTest, + testing::Values( + std::make_pair(Executor.get(), + UncheckedStatusOrAccessModelTestAliasKind::kUnaliased), + std::make_pair( + Executor.get(), + UncheckedStatusOrAccessModelTestAliasKind::kPartiallyAliased), + std::make_pair( + Executor.get(), + UncheckedStatusOrAccessModelTestAliasKind::kFullyAliased))); +} // namespace + +} // namespace clang::dataflow::statusor_model diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp new file mode 100644 index 0000000..cae9265 --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp @@ -0,0 +1,2679 @@ +//===- UncheckedStatusOrAccessModelTestFixture.cpp ------------------------===// +// +// 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 "UncheckedStatusOrAccessModelTestFixture.h" +#include "MockHeaders.h" +#include "llvm/Support/ErrorHandling.h" + +#include <string> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" + +namespace clang::dataflow::statusor_model { +namespace { + +TEST_P(UncheckedStatusOrAccessModelTest, NoStatusOrMention) { + ExpectDiagnosticsFor(R"cc( + void target() { "nop"; } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Lvalue_CallToValue) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, NonExplicitInitialization) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + STATUSOR_INT target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + return x.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Lvalue_CallToValue_NewLine) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + sor. // force newline + value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Rvalue_CallToValue) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + std::move(sor).value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Lvalue_CallToValueOrDie) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + sor.ValueOrDie(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Rvalue_CallToValueOrDie) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + std::move(sor).ValueOrDie(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Lvalue_CallToOperatorStar) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + *sor; // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Lvalue_CallToOperatorStarSeparateLine) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + * // [[unsafe]] + sor; + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Rvalue_CallToOperatorStar) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + *std::move(sor); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Lvalue_CallToOperatorArrow) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + void foo(); + }; + + void target(absl::StatusOr<Foo> sor) { + sor->foo(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, + UnwrapWithoutCheck_Rvalue_CallToOperatorArrow) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + void foo(); + }; + + void target(absl::StatusOr<Foo> sor) { + std::move(sor)->foo(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UnwrapRvalueWithCheck) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (sor.ok()) std::move(sor).value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ParensInDeclInitExpr) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor = (Make<STATUSOR_INT>()); + if (sor.ok()) sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ReferenceInDeclInitExpr) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + const STATUSOR_INT& GetStatusOrInt() const; + }; + + void target(Foo foo) { + auto sor = foo.GetStatusOrInt(); + if (sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT& GetStatusOrInt(); + }; + + void target(Foo foo) { + auto sor = foo.GetStatusOrInt(); + if (sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT&& GetStatusOrInt() &&; + }; + + void target(Foo foo) { + auto sor = std::move(foo).GetStatusOrInt(); + if (sor.ok()) sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + if (auto sor = Make<STATUSOR_INT>(); sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, JoinSafeSafe) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor, bool b) { + if (sor.ok()) { + if (b) + sor.value(); + else + sor.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, JoinUnsafeUnsafe) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor, bool b) { + if (b) + sor.value(); // [[unsafe]] + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, InversedIfThenElse) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (!sor.ok()) + sor.value(); // [[unsafe]] + else + sor.value(); + + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, DoubleInversedIfThenElse) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (!!sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, TripleInversedIfThenElse) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (!!!sor.ok()) + sor.value(); // [[unsafe]] + else + sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_LhsAndRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (x.ok() && y.ok()) { + x.value(); + + y.value(); + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_NotLhsAndRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!x.ok() && y.ok()) { + y.value(); + + x.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_LhsAndNotRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (x.ok() && !y.ok()) { + x.value(); + + y.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_NotLhsAndNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!x.ok() && !y.ok()) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_LhsAndRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(x.ok() && y.ok())) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); + + y.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_NotLhsAndRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(!x.ok() && y.ok())) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + y.value(); + + x.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_LhsAndNotRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(x.ok() && !y.ok())) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_NotLhsAndNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(!x.ok() && !y.ok())) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_LhsOrRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (x.ok() || y.ok()) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_NotLhsOrRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!x.ok() || y.ok()) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_LhsOrNotRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (x.ok() || !y.ok()) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + y.value(); + + x.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_NotLhsOrNotRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!x.ok() || !y.ok()) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); + + y.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_LhsOrRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(x.ok() || y.ok())) { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_NotLhsOrRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(!x.ok() || y.ok())) { + x.value(); + + y.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_LhsOrNotRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(x.ok() || !y.ok())) { + y.value(); + + x.value(); // [[unsafe]] + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, IfThenElse_Not_NotLhsOrNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!(!x.ok() || !y.ok())) { + x.value(); + + y.value(); + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, TerminatingIfThenBranch) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (!sor.ok()) return; + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (sor.ok()) return; + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!x.ok() || !y.ok()) return; + + x.value(); + + y.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, TerminatingIfElseBranch) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (sor.ok()) { + } else { + return; + } + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (!sor.ok()) { + } else { + return; + } + + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, TerminatingIfThenBranchInLoop) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (Make<bool>()) { + if (!sor.ok()) continue; + + sor.value(); + } + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (Make<bool>()) { + if (!sor.ok()) break; + + sor.value(); + } + + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, TernaryConditionalOperator) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + sor.ok() ? sor.value() : 21; + + sor.ok() ? 21 : sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + !sor.ok() ? 21 : sor.value(); + + !sor.ok() ? sor.value() : 21; // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + !((__builtin_expect(false || (!(sor1.ok() && sor2.ok())), false))) + ? (void)0 + : (void)1; + do { + sor1.value(); // [[unsafe]] + sor2.value(); // [[unsafe]] + } while (true); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (Make<bool>()) sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (!sor.ok()) sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (!!sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (!!!sor.ok()) sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_LhsAndRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() && y.ok()) { + x.value(); + + y.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_NotLhsAndRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() && y.ok()) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() && y.ok()) y.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_LhsAndNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() && !y.ok()) x.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() && !y.ok()) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_NotLhsAndNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() && !y.ok()) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() && !y.ok()) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_LhsAndRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() && y.ok())) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() && y.ok())) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_NotLhsAndRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() && y.ok())) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() && y.ok())) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_LhsAndNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() && !y.ok())) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() && !y.ok())) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_NotLhsAndNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() && !y.ok())) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() && !y.ok())) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_LhsOrRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() || y.ok()) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() || y.ok()) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_NotLhsOrRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() || y.ok()) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() || y.ok()) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_LhsOrNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() || !y.ok()) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (x.ok() || !y.ok()) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_NotLhsOrNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() || !y.ok()) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!x.ok() || !y.ok()) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_LhsOrRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() || y.ok())) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() || y.ok())) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_NotLhsOrRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() || y.ok())) x.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() || y.ok())) y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_LhsOrNotRhs) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() || !y.ok())) x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(x.ok() || !y.ok())) y.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_Not_NotLhsOrNotRhs) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (!(!x.ok() || !y.ok())) { + x.value(); + + y.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_AccessAfterStmt) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (sor.ok()) { + } + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (!sor.ok()) { + } + + sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_TerminatingBranch_Return) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (!sor.ok()) return; + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (sor.ok()) return; + + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, While_NestedIfWithBinaryCondition) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (Make<bool>()) { + if (x.ok() && y.ok()) { + x.value(); + + y.value(); + } + } + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + while (Make<bool>()) { + if (!(!x.ok() || !y.ok())) { + x.value(); + + y.value(); + } + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, BuiltinExpect) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT x, STATUSOR_INT y) { + if (!__builtin_expect(!x.ok() || __builtin_expect(!y.ok(), true), false)) { + x.value(); + + y.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, CopyAssignment) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + if (sor.ok()) { + sor = Make<STATUSOR_INT>(); + sor.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + if (!sor.ok()) return; + + sor = Make<STATUSOR_INT>(); + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + if (x.ok()) { + STATUSOR_INT y = x; + x = Make<STATUSOR_INT>(); + + y.value(); + + x.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + STATUSOR_INT y = x; + if (!y.ok()) return; + + x.value(); + + y = Make<STATUSOR_INT>(); + x.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT bar; + }; + + void target(Foo foo) { + foo.bar = Make<STATUSOR_INT>(); + if (foo.bar.ok()) { + foo.bar.value(); + + foo.bar = Make<STATUSOR_INT>(); + foo.bar.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ShortCircuitingBinaryOperators) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_BOOL sor) { + bool b = sor.ok() & sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_BOOL sor) { + bool b = sor.ok() && sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_BOOL sor) { + bool b = !sor.ok() && sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_BOOL sor) { + bool b = sor.ok() || sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_BOOL sor) { + bool b = !sor.ok() || sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + if (b || sor.ok()) { + do { + sor.value(); // [[unsafe]] + } while (true); + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + if (__builtin_expect(b || sor.ok(), false)) { + do { + sor.value(); // [[unsafe]] + } while (false); + } + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + while (sor1.ok() && sor2.ok()) sor1.value(); + while (sor1.ok() && sor2.ok()) sor2.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, References) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + STATUSOR_INT& y = x; + if (x.ok()) { + x.value(); + + y.value(); + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + STATUSOR_INT& y = x; + if (y.ok()) { + x.value(); + + y.value(); + } else { + x.value(); // [[unsafe]] + + y.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + STATUSOR_INT& y = x; + if (!y.ok()) return; + + x.value(); + + y = Make<STATUSOR_INT>(); + x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make<STATUSOR_INT>(); + const STATUSOR_INT& y = x; + if (!y.ok()) return; + + y.value(); + + x = Make<STATUSOR_INT>(); + y.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, NoReturnAttribute) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + __attribute__((noreturn)) void f(); + + void target(STATUSOR_INT sor) { + if (!sor.ok()) f(); + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void f(); + + void target(STATUSOR_INT sor) { + if (!sor.ok()) f(); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + __attribute__((noreturn)) ~Foo(); + void Bar(); + }; + + void target(STATUSOR_INT sor) { + if (!sor.ok()) Foo().Bar(); + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + ~Foo(); + void Bar(); + }; + + void target(STATUSOR_INT sor) { + if (!sor.ok()) Foo().Bar(); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void f(); + __attribute__((noreturn)) void g(); + + void target(STATUSOR_INT sor) { + sor.ok() ? f() : g(); + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + __attribute__((noreturn)) void f(); + void g(); + + void target(STATUSOR_INT sor) { + !sor.ok() ? f() : g(); + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void f(); + void g(); + + void target(STATUSOR_INT sor) { + sor.ok() ? f() : g(); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void terminate() __attribute__((noreturn)); + + void target(STATUSOR_INT sor) { + sor.value(); // [[unsafe]] + terminate(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void terminate() __attribute__((noreturn)); + + void target(STATUSOR_INT sor) { + if (sor.ok()) sor.value(); + terminate(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void terminate() __attribute__((noreturn)); + + struct Foo { + ~Foo() __attribute__((noreturn)); + }; + + void target() { + auto sor = Make<absl::StatusOr<Foo>>(); + !(false || !(sor.ok())) ? (void)0 : terminate(); + sor.value(); + terminate(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, DeclInLoop) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + while (auto ok = sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + using BoolAlias = bool; + + void target(STATUSOR_INT sor) { + while (BoolAlias ok = sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + while (Make<bool>()) { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + sor.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + using StatusOrInt = STATUSOR_INT; + + void target() { + while (Make<bool>()) { + StatusOrInt sor = Make<STATUSOR_INT>(); + sor.value(); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, NonEvaluatedExprInCondition) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + bool unknown(); + + void target(STATUSOR_INT sor) { + if (unknown() && sor.ok()) sor.value(); + if (sor.ok() && unknown()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + bool unknown(); + + void target(STATUSOR_INT sor) { + if (!(!unknown() || !sor.ok())) sor.value(); + if (!(!sor.ok() || !unknown())) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + bool unknown(); + + void target(STATUSOR_INT sor) { + if (unknown() || sor.ok()) sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + bool unknown(); + + void target(STATUSOR_INT sor) { + if (sor.ok() || unknown()) sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, CorrelatedBranches) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + if (b || sor.ok()) { + if (!b) sor.value(); + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + if (b && !sor.ok()) return; + if (b) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + if (sor.ok()) b = true; + if (b) sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + if (b) return; + if (sor.ok()) b = true; + if (b) sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ConditionWithInitStmt) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + if (STATUSOR_INT sor = Make<STATUSOR_INT>(); sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + if (STATUSOR_INT sor = Make<STATUSOR_INT>(); !sor.ok()) + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, DeadCode) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + bool b = false; + if (b) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + bool b; + b = false; + if (b) sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, TemporaryDestructors) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + sor.ok() ? sor.value() : Fatal().value(); + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + !sor.ok() ? Fatal().value() : sor.value(); + + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + b ? 0 : sor.ok() ? sor.value() : Fatal().value(); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + for (int i = 0; i < 10; i++) { + (b && sor.ok()) ? sor.value() : Fatal().value(); + + if (b) sor.value(); + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + for (int i = 0; i < 10; i++) { + (b || !sor.ok()) ? Fatal().value() : 0; + + if (!b) sor.value(); + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b, STATUSOR_INT sor) { + for (int i = 0; i < 10; i++) { + (false || !(b && sor.ok())) ? Fatal().value() : 0; + + do { + sor.value(); + } while (b); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, CheckMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK(sor.ok()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK(!sor.ok()); + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, QcheckMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK(sor.ok()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK(!sor.ok()); + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, CheckNeMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_NE(sor.status(), absl::OkStatus()); + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, QcheckNeMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_NE(sor.status(), absl::OkStatus()); + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, GlobalVars) { + // The following examples are not sound as there could be opaque calls between + // the ok() and the value() calls that change the StatusOr value. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + static STATUSOR_INT sor; + + void target() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + static STATUSOR_INT sor; + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + static STATUSOR_INT sor; + }; + + void target(Foo foo) { + if (foo.sor.ok()) + foo.sor.value(); + else + foo.sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + static STATUSOR_INT sor; + }; + + void target() { + if (Foo::sor.ok()) + Foo::sor.value(); + else + Foo::sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + static STATUSOR_INT sor; + + static void target() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + }; + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + static STATUSOR_INT sor; + + void target() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + }; + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct S { + static const int x = -1; + }; + + int target(S s) { return s.x; } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ReferenceReceivers) { + // The following examples are not sound as there could be opaque calls between + // the ok() and the value() calls that change the StatusOr value. However, + // this is the behavior that users expect so it is here to stay. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT& sor) { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT& sor; + }; + + void target(Foo foo) { + if (foo.sor.ok()) + foo.sor.value(); + else + foo.sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Bar { + STATUSOR_INT sor; + }; + + struct Foo { + Bar& bar; + }; + + void target(Foo foo) { + if (foo.bar.sor.ok()) + foo.bar.sor.value(); + else + foo.bar.sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT& sor; + }; + + void target(Foo& foo) { + if (foo.sor.ok()) + foo.sor.value(); + else + foo.sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, Lambdas) { + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + [](STATUSOR_INT sor) { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + }(Make<STATUSOR_INT>()); + } + )cc"); + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + [sor]() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + }(); + } + )cc"); + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + [&sor]() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + }(); + } + )cc"); + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + [sor2 = sor]() { + if (sor2.ok()) + sor2.value(); + else + sor2.value(); // [[unsafe]] + }(); + } + )cc"); + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + [&]() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + }(); + } + )cc"); + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + [=]() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + }(); + } + )cc"); + ExpectDiagnosticsForLambda(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor; + + void target() { + [this]() { + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + }(); + } + }; + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, GoodLambda) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + int target() { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + if (sor.ok()) return [&s = sor.value()] { return s; }(); + return 0; + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, Status) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void foo(); + + void target(STATUS s) { + if (s.ok()) foo(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void foo(); + + void target() { + STATUS s = Make<STATUSOR_INT>().status(); + if (s.ok()) foo(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ExpectThatMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + EXPECT_THAT(sor, testing::status::IsOk()); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + EXPECT_THAT(sor.status(), testing::status::IsOk()); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + EXPECT_THAT(sor, testing::status::IsOk()); + + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ExpectOkMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + EXPECT_OK(sor); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + EXPECT_OK(sor.status()); + + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + EXPECT_OK(sor); + + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, BreadthFirstBlockTraversalLoop) { + // Evaluating the CFG blocks of the code below in breadth-first order results + // in an infinite loop. Each iteration of the while loop below results in a + // new value being assigned to the storage location of sor1. However, + // following a bread-first order of evaluation, downstream blocks will join + // environments of different generations of predecessor blocks having distinct + // values assigned to the sotrage location of sor1, resulting in not assigning + // a value to the storage location of sor1 in successors. As iterations of the + // analysis go, the state of the environment flips between having a value + // assigned to the storage location of sor1 and not having a value assigned to + // it. Since the evaluation of the copy constructor expression in bar(sor1) + // depends on a value being assigned to sor1, the state of the environment + // also flips between having a storage location assigned to the bar(sor1) + // expression and not having a storage location assigned to it. This leads to + // an infinite loop as the environment can't stabilize. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void foo(int, int); + STATUSOR_INT bar(STATUSOR_INT); + void baz(int); + + void target() { + while (true) { + STATUSOR_INT sor1 = Make<STATUSOR_INT>(); + if (sor1.ok()) { + STATUSOR_INT sor2 = Make<STATUSOR_INT>(); + if (sor2.ok()) foo(sor1.value(), sor2.value()); + } + + STATUSOR_INT sor3 = bar(sor1); + for (int i = 0; i < 5; i++) sor3 = bar(sor1); + + baz(sor3.value()); // [[unsafe]] + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ReturnValue) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT sor = Make<STATUSOR_INT>(); + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, Goto) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + label: + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + goto label; + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + label: + if (!sor.ok()) goto label; + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (!sor.ok()) return; + goto label; + label: + sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, JoinDistinctValues) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b) { + STATUSOR_INT sor; + if (b) + sor = Make<STATUSOR_INT>(); + else + sor = Make<STATUSOR_INT>(); + + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b) { + STATUSOR_INT sor; + if (b) { + sor = Make<STATUSOR_INT>(); + if (!sor.ok()) return; + } else { + sor = Make<STATUSOR_INT>(); + if (!sor.ok()) return; + } + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(bool b) { + STATUSOR_INT sor; + if (b) { + sor = Make<STATUSOR_INT>(); + if (!sor.ok()) return; + } else { + sor = Make<STATUSOR_INT>(); + } + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, VarDeclInitExprFromPairAccess) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor = Make<std::pair<int, STATUSOR_INT>>().second; + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + const auto& sor = Make<std::pair<int, STATUSOR_INT>>().second; + if (sor.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, LValueToRValueCastOfChangingValue) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + bool foo(); + + void target(bool b1) { + STATUSOR_INT sor; + if (b1) + sor = Make<STATUSOR_INT>(); + else + sor = Make<STATUSOR_INT>(); + + do { + const auto& b2 = foo(); + if (b2) break; + + sor.value(); // [[unsafe]] + } while (true); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, ConstructorInitializer) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class target { + target() : foo_(Make<STATUSOR_INT>().value()) { // [[unsafe]] + } + int foo_; + }; + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, AssignStatusToBoolVar) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + bool ok = sor.ok(); + if (ok) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + bool not_ok = !sor.ok(); + if (not_ok) + sor.value(); // [[unsafe]] + else + sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, StructuredBindings) { + // Binding to a pair (which is actually a struct in the mock header). + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + const auto [sor, x] = Make<std::pair<STATUSOR_INT, int>>(); + if (sor.ok()) sor.value(); + } + )cc"); + + // Unsafe case. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + const auto [sor, x] = Make<std::pair<STATUSOR_INT, int>>(); + sor.value(); // [[unsafe]] + } + )cc"); + + // As a reference. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + const auto& [sor, x] = Make<std::pair<STATUSOR_INT, int>>(); + if (sor.ok()) sor.value(); + } + )cc"); + + // Binding to a ref in a struct. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct S { + STATUSOR_INT& sor; + int i; + }; + + void target() { + const auto& [sor, i] = Make<S>(); + if (sor.ok()) sor.value(); + } + )cc"); + + // In a loop. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto vals = Make<std::vector<std::pair<int, STATUSOR_INT>>>(); + for (const auto& [x, sor] : vals) + if (sor.ok()) sor.value(); + } + )cc"); + + // Similar to the above, but InitExpr already has the storage initialized, + // and bindings refer to them. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto vals = Make<std::vector<std::pair<int, STATUSOR_INT>>>(); + for (const auto& p : vals) { + const auto& [i, sor] = p; + if (sor.ok()) sor.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, AssignCompositeLogicExprToVar) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor, bool b) { + bool c = sor.ok() && b; + if (c) sor.value(); + } + )cc"); + + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor, bool b) { + bool c = !(!sor.ok() || !b); + if (c) sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, Subclass) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo : public STATUSOR_INT {}; + + void target(Foo opt) { + opt.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, SubclassStatus) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo : public STATUS {}; + + void target(Foo opt) { opt.ok(); } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, SubclassOk) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo : public STATUSOR_INT {}; + + void target(Foo opt) { + if (opt.ok()) opt.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, SubclassOperator) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo : public STATUSOR_INT {}; + + void target(Foo opt) { + *opt; // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UnwrapValueWithStatusCheck) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (sor.status().ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UnwrapValueWithStatusRefCheck) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + const STATUS& s = sor.status(); + if (s.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UnwrapValueWithStatusPtrCheck) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + const STATUS* s = &sor.status(); + if (s->ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UnwrapValueWithMovedStatus) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + if (std::move(sor.status()).ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, MembersUsedInsideStatus) { + ExpectDiagnosticsFor(R"cc( + namespace absl { + + class Status { + public: + bool ok() const; + + void target() const { ok(); } + }; + + } // namespace absl + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, StatusUpdate) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + STATUS s; + s.Update(sor.status()); + if (s.ok()) + sor.value(); + else + sor.value(); // [[unsafe]] + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + STATUS s; + s.Update(sor1.status()); + s.Update(sor2.status()); + if (s.ok()) { + sor1.value(); + sor2.value(); + } + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + STATUS s; + s.Update(sor1.status()); + CHECK(s.ok()); + s.Update(sor2.status()); + sor1.value(); + sor2.value(); // [[unsafe]] + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + STATUS s; + s.Update(sor1.status()); + CHECK(s.ok()); + sor1.value(); + sor2.value(); // [[unsafe]] + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + STATUS s; + STATUS sor1_status = sor1.status(); + s.Update(std::move(sor1_status)); + CHECK(s.ok()); + sor1.value(); + sor2.value(); // [[unsafe]] + } + )cc"); + + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor1, STATUSOR_INT sor2) { + STATUS s; + STATUS sor1_status = sor1.status(); + sor1_status.Update(sor2.status()); + s.Update(std::move(sor1_status)); + CHECK(s.ok()); + sor1.value(); + sor2.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + const STATUS& OptStatus(); + + void target(STATUSOR_INT sor) { + auto s = sor.status(); + s.Update(OptStatus()); + if (s.ok()) sor.value(); + } + )cc"); +} + +} // namespace + +std::string +GetAliasMacros(UncheckedStatusOrAccessModelTestAliasKind AliasKind) { + switch (AliasKind) { + case UncheckedStatusOrAccessModelTestAliasKind::kUnaliased: + return R"cc( +#define STATUSOR_INT ::absl::StatusOr<int> +#define STATUSOR_BOOL ::absl::StatusOr<bool> +#define STATUSOR_VOIDPTR ::absl::StatusOr<void*> +#define STATUS ::absl::Status + )cc"; + case UncheckedStatusOrAccessModelTestAliasKind::kPartiallyAliased: + return R"cc( + template <typename T> + using StatusOrAlias = ::absl::StatusOr<T>; +#define STATUSOR_INT StatusOrAlias<int> +#define STATUSOR_BOOL StatusOrAlias<bool> +#define STATUSOR_VOIDPTR StatusOrAlias<void*> +#define STATUS ::absl::Status + )cc"; + case UncheckedStatusOrAccessModelTestAliasKind::kFullyAliased: + return R"cc( + using StatusOrIntAlias = ::absl::StatusOr<int>; +#define STATUSOR_INT StatusOrIntAlias + using StatusOrBoolAlias = ::absl::StatusOr<bool>; +#define STATUSOR_BOOL StatusOrBoolAlias + using StatusOrVoidPtrAlias = ::absl::StatusOr<void*>; +#define STATUSOR_VOIDPTR StatusOrVoidPtrAlias + using StatusAlias = ::absl::Status; +#define STATUS StatusAlias + )cc"; + } + llvm_unreachable("Unknown alias kind."); +} + +std::vector<std::pair<std::string, std::string>> +GetHeaders(UncheckedStatusOrAccessModelTestAliasKind AliasKind) { + auto Headers = test::getMockHeaders(); + + Headers.emplace_back("unchecked_statusor_access_test_defs.h", + R"cc( +#include "cstddef.h" +#include "statusor_defs.h" +#include "std_optional.h" +#include "std_vector.h" +#include "std_pair.h" +#include "absl_log.h" +#include "testing_defs.h" + + template <typename T> + T Make(); + + class Fatal { + public: + ~Fatal() __attribute__((noreturn)); + int value(); + }; + )cc" + + GetAliasMacros(AliasKind)); + return Headers; +} +} // namespace clang::dataflow::statusor_model diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.h b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.h new file mode 100644 index 0000000..92dcd93 --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.h @@ -0,0 +1,159 @@ +//===- UncheckedStatusOrAccessModelTestFixture.h --------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_UNCHECKEDSTATUSORACCESSMODELTESTFIXTURE_H_ +#define LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_UNCHECKEDSTATUSORACCESSMODELTESTFIXTURE_H_ + +#include <algorithm> +#include <iterator> +#include <string> +#include <utility> +#include <vector> + +#include "TestingSupport.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" +#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang::dataflow::statusor_model { + +enum class UncheckedStatusOrAccessModelTestAliasKind { + kUnaliased = 0, // no alias + kPartiallyAliased = 1, // template<typename T> using Alias = absl::StatusOr; + kFullyAliased = 2, // using Alias = absl::StatusOr<int>; +}; + +// Base class for the test executors. This is needed to abstract away the +// template parameter from the UncheckedStatusOrAccessModelTestExecutor. This +// allows us to use UncheckedStatusOrAccessModelTestExecutorBase* in the +// UncheckedStatusOrAccessModelTest. +class UncheckedStatusOrAccessModelTestExecutorBase { +public: + virtual void + ExpectDiagnosticsFor(std::string SourceCode, + UncheckedStatusOrAccessModelTestAliasKind) const = 0; + virtual void ExpectDiagnosticsForLambda( + std::string SourceCode, + UncheckedStatusOrAccessModelTestAliasKind) const = 0; + virtual ~UncheckedStatusOrAccessModelTestExecutorBase() = default; +}; + +// Returns these macros according to the alias kind: +// - STATUS +// - STATUSOR_INT +// - STATUSOR_BOOL +// - STATUSOR_VOIDPTR +// Tests should use these macros instead of e.g. absl::StatusOr<int> to ensure +// the model is insensitive to whether the StatusOr<> is aliased or not. +std::string GetAliasMacros(UncheckedStatusOrAccessModelTestAliasKind AliasKind); + +std::vector<std::pair<std::string, std::string>> +GetHeaders(UncheckedStatusOrAccessModelTestAliasKind AliasKind); + +// This allows us to run the same test suite for multiple models. This allows +// vendors to model internal APIs in an extension of the base model, and make +// sure that these tests still pass. +template <typename Model> +class UncheckedStatusOrAccessModelTestExecutor + : public UncheckedStatusOrAccessModelTestExecutorBase { +public: + void ExpectDiagnosticsFor( + std::string SourceCode, + UncheckedStatusOrAccessModelTestAliasKind AliasKind) const override { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + ExpectDiagnosticsFor(SourceCode, hasName("target"), AliasKind); + } + + void ExpectDiagnosticsForLambda( + std::string SourceCode, + UncheckedStatusOrAccessModelTestAliasKind AliasKind) const override { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + ExpectDiagnosticsFor(SourceCode, + allOf(hasOverloadedOperatorName("()"), + hasDeclContext(cxxRecordDecl(isLambda()))), + AliasKind); + } + + template <typename FuncDeclMatcher> + void ExpectDiagnosticsFor( + std::string SourceCode, FuncDeclMatcher FuncMatcher, + UncheckedStatusOrAccessModelTestAliasKind AliasKind) const { + std::vector<std::pair<std::string, std::string>> Headers = + GetHeaders(AliasKind); + + UncheckedStatusOrAccessModelOptions Options{}; + std::vector<SourceLocation> Diagnostics; + llvm::Error Error = test::checkDataflow<Model>( + test::AnalysisInputs<Model>( + SourceCode, std::move(FuncMatcher), + [](ASTContext &Ctx, Environment &Env) { return Model(Ctx, Env); }) + .withPostVisitCFG( + [&Diagnostics, + Diagnoser = UncheckedStatusOrAccessDiagnoser(Options)]( + ASTContext &Ctx, const CFGElement &Elt, + const TransferStateForDiagnostics< + UncheckedStatusOrAccessModel::Lattice> &State) mutable { + auto EltDiagnostics = Diagnoser(Elt, Ctx, State); + llvm::move(EltDiagnostics, std::back_inserter(Diagnostics)); + }) + .withASTBuildArgs( + {"-fsyntax-only", "-std=c++17", "-Wno-undefined-inline"}) + .withASTBuildVirtualMappedFiles( + tooling::FileContentMappings(Headers.begin(), Headers.end())), + /*VerifyResults=*/[&Diagnostics, SourceCode]( + const llvm::DenseMap<unsigned, std::string> + &Annotations, + const test::AnalysisOutputs &AO) { + llvm::DenseSet<unsigned> AnnotationLines; + for (const auto &[Line, _] : Annotations) + AnnotationLines.insert(Line); + auto &SrcMgr = AO.ASTCtx.getSourceManager(); + llvm::DenseSet<unsigned> DiagnosticLines; + for (SourceLocation &Loc : Diagnostics) + DiagnosticLines.insert(SrcMgr.getPresumedLineNumber(Loc)); + + EXPECT_THAT(DiagnosticLines, testing::ContainerEq(AnnotationLines)) + << "\nFailing code:\n" + << SourceCode; + }); + if (Error) + FAIL() << llvm::toString(std::move(Error)); + } + + ~UncheckedStatusOrAccessModelTestExecutor() override = default; +}; + +class UncheckedStatusOrAccessModelTest + : public ::testing::TestWithParam< + std::pair<UncheckedStatusOrAccessModelTestExecutorBase *, + UncheckedStatusOrAccessModelTestAliasKind>> { +protected: + void ExpectDiagnosticsFor(std::string SourceCode) { + GetParam().first->ExpectDiagnosticsFor(SourceCode, GetParam().second); + } + + void ExpectDiagnosticsForLambda(std::string SourceCode) { + GetParam().first->ExpectDiagnosticsForLambda(SourceCode, GetParam().second); + } +}; + +} // namespace clang::dataflow::statusor_model + +#endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_UNCHECKEDSTATUSORACCESSMODELTESTFIXTURE_H_ |