diff --git a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h new file mode 100644 index 0000000000000..14f28543fc04c --- /dev/null +++ b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h @@ -0,0 +1,114 @@ +//===- UncheckedStatusOrAccessModel.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 CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_UNCHECKEDSTATUSORACCESSMODEL_H +#define CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_UNCHECKEDSTATUSORACCESSMODEL_H + +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" +#include "clang/Analysis/FlowSensitive/NoopLattice.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" + +namespace clang::dataflow::statusor_model { + +// The helper functions exported here are for use of downstream vendor +// extensions of this model. + +// Match declaration of `absl::StatusOr` and bind `T` to "T". +clang::ast_matchers::DeclarationMatcher statusOrClass(); +// Match declaration of `absl::Status`. +clang::ast_matchers::DeclarationMatcher statusClass(); +// Match declaration of `absl::internal_statusor::OperatorBase`. +clang::ast_matchers::DeclarationMatcher statusOrOperatorBaseClass(); +clang::ast_matchers::TypeMatcher possiblyAliasedStatusType(); +clang::ast_matchers::TypeMatcher possiblyAliasedStatusOrType(); +clang::ast_matchers::TypeMatcher statusOrType(); + +// Get RecordStorageLocation for the `Status` contained in the `StatusOr` +RecordStorageLocation &locForStatus(RecordStorageLocation &StatusOrLoc); +// Get the StorageLocation for the OK boolean in the `Status` +StorageLocation &locForOk(RecordStorageLocation &StatusLoc); +// Get the OK boolean in the `Status`, and initialize it if necessary. +BoolValue &valForOk(RecordStorageLocation &StatusLoc, Environment &Env); +// Get synthetic fields for the types modelled by +// `UncheckedStatusOrAccessModel`. +llvm::StringMap getSyntheticFields(QualType Ty, QualType StatusType, + const CXXRecordDecl &RD); + +// Initialize the synthetic fields of the `StatusOr`. +// N.B. if it is already initialized, the value gets reset. +BoolValue &initializeStatusOr(RecordStorageLocation &StatusOrLoc, + Environment &Env); +// Initialize the synthetic fields of the `Status`. +// N.B. if it is already initialized, the value gets reset. +BoolValue &initializeStatus(RecordStorageLocation &StatusLoc, Environment &Env); + +bool isRecordTypeWithName(QualType Type, llvm::StringRef TypeName); +// Return true if `Type` is instantiation of `absl::StatusOr` +bool isStatusOrType(QualType Type); +// Return true if `Type` is `absl::Status` +bool isStatusType(QualType Type); + +// Get `QualType` for `absl::Status`, or default-constructed +// QualType if it does not exist. +QualType findStatusType(const ASTContext &Ctx); + +struct UncheckedStatusOrAccessModelOptions {}; + +// Dataflow analysis that discovers unsafe uses of StatusOr values. +class UncheckedStatusOrAccessModel + : public DataflowAnalysis { +public: + explicit UncheckedStatusOrAccessModel(ASTContext &Ctx, Environment &Env); + + static Lattice initialElement() { return {}; } + + void transfer(const CFGElement &Elt, Lattice &L, Environment &Env); + +private: + CFGMatchSwitch> TransferMatchSwitch; +}; + +using LatticeTransferState = + TransferState; + +// Extend the Builder with the transfer functions for +// `UncheckedStatusOrAccessModel`. This is useful to write downstream models +// that extend the model. +CFGMatchSwitch +buildTransferMatchSwitch(ASTContext &Ctx, + CFGMatchSwitchBuilder Builder); + +class UncheckedStatusOrAccessDiagnoser { +public: + explicit UncheckedStatusOrAccessDiagnoser( + UncheckedStatusOrAccessModelOptions Options = {}); + + llvm::SmallVector operator()( + const CFGElement &Elt, ASTContext &Ctx, + const TransferStateForDiagnostics + &State); + +private: + CFGMatchSwitch> + DiagnoseMatchSwitch; +}; + +} // namespace clang::dataflow::statusor_model + +#endif // CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_UNCHECKEDSTATUSORACCESSMODEL_H diff --git a/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt index 89bbe8791eb2c..d1236f5714881 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt +++ b/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt @@ -1,6 +1,7 @@ add_clang_library(clangAnalysisFlowSensitiveModels ChromiumCheckModel.cpp UncheckedOptionalAccessModel.cpp + UncheckedStatusOrAccessModel.cpp LINK_LIBS clangAnalysis diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp new file mode 100644 index 0000000000000..9ea962abc24ee --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp @@ -0,0 +1,284 @@ +//===- UncheckedStatusOrAccessModel.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 "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h" + +#include +#include + +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/TypeBase.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/StringMap.h" + +namespace clang::dataflow::statusor_model { +namespace { + +using ::clang::ast_matchers::MatchFinder; +using ::clang::ast_matchers::StatementMatcher; + +} // namespace + +static bool isStatusOrOperatorBaseType(QualType type) { + return isRecordTypeWithName(type, "absl::internal_statusor::OperatorBase"); +} + +static bool isSafeUnwrap(RecordStorageLocation *StatusOrLoc, + const Environment &Env) { + if (!StatusOrLoc) + return false; + auto &StatusLoc = locForStatus(*StatusOrLoc); + auto *OkVal = Env.get(locForOk(StatusLoc)); + return OkVal != nullptr && Env.proves(OkVal->formula()); +} + +static ClassTemplateSpecializationDecl * +getStatusOrBaseClass(const QualType &Ty) { + auto *RD = Ty->getAsCXXRecordDecl(); + if (RD == nullptr) + return nullptr; + if (isStatusOrType(Ty) || + // In case we are analyzing code under OperatorBase itself that uses + // operator* (e.g. to implement operator->). + isStatusOrOperatorBaseType(Ty)) + return cast(RD); + if (!RD->hasDefinition()) + return nullptr; + for (const auto &Base : RD->bases()) + if (auto *QT = getStatusOrBaseClass(Base.getType())) + return QT; + return nullptr; +} + +static QualType getStatusOrValueType(ClassTemplateSpecializationDecl *TRD) { + return TRD->getTemplateArgs().get(0).getAsType(); +} + +static auto isStatusOrMemberCallWithName(llvm::StringRef member_name) { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return cxxMemberCallExpr( + on(expr(unless(cxxThisExpr()))), + callee(cxxMethodDecl( + hasName(member_name), + ofClass(anyOf(statusOrClass(), statusOrOperatorBaseClass()))))); +} + +static auto isStatusOrOperatorCallWithName(llvm::StringRef operator_name) { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return cxxOperatorCallExpr( + hasOverloadedOperatorName(operator_name), + callee(cxxMethodDecl( + ofClass(anyOf(statusOrClass(), statusOrOperatorBaseClass()))))); +} + +static auto valueCall() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return anyOf(isStatusOrMemberCallWithName("value"), + isStatusOrMemberCallWithName("ValueOrDie")); +} + +static auto valueOperatorCall() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return expr(anyOf(isStatusOrOperatorCallWithName("*"), + isStatusOrOperatorCallWithName("->"))); +} + +static auto +buildDiagnoseMatchSwitch(const UncheckedStatusOrAccessModelOptions &Options) { + return CFGMatchSwitchBuilder>() + // StatusOr::value, StatusOr::ValueOrDie + .CaseOfCFGStmt( + valueCall(), + [](const CXXMemberCallExpr *E, + const ast_matchers::MatchFinder::MatchResult &, + const Environment &Env) { + if (!isSafeUnwrap(getImplicitObjectLocation(*E, Env), Env)) + return llvm::SmallVector({E->getExprLoc()}); + return llvm::SmallVector(); + }) + + // StatusOr::operator*, StatusOr::operator-> + .CaseOfCFGStmt( + valueOperatorCall(), + [](const CXXOperatorCallExpr *E, + const ast_matchers::MatchFinder::MatchResult &, + const Environment &Env) { + RecordStorageLocation *StatusOrLoc = + Env.get(*E->getArg(0)); + if (!isSafeUnwrap(StatusOrLoc, Env)) + return llvm::SmallVector({E->getOperatorLoc()}); + return llvm::SmallVector(); + }) + .Build(); +} + +UncheckedStatusOrAccessDiagnoser::UncheckedStatusOrAccessDiagnoser( + UncheckedStatusOrAccessModelOptions Options) + : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {} + +llvm::SmallVector UncheckedStatusOrAccessDiagnoser::operator()( + const CFGElement &Elt, ASTContext &Ctx, + const TransferStateForDiagnostics + &State) { + return DiagnoseMatchSwitch(Elt, Ctx, State.Env); +} + +BoolValue &initializeStatus(RecordStorageLocation &StatusLoc, + Environment &Env) { + auto &OkVal = Env.makeAtomicBoolValue(); + Env.setValue(locForOk(StatusLoc), OkVal); + return OkVal; +} + +BoolValue &initializeStatusOr(RecordStorageLocation &StatusOrLoc, + Environment &Env) { + return initializeStatus(locForStatus(StatusOrLoc), Env); +} + +clang::ast_matchers::DeclarationMatcher statusOrClass() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return classTemplateSpecializationDecl( + hasName("absl::StatusOr"), + hasTemplateArgument(0, refersToType(type().bind("T")))); +} + +clang::ast_matchers::DeclarationMatcher statusClass() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return cxxRecordDecl(hasName("absl::Status")); +} + +clang::ast_matchers::DeclarationMatcher statusOrOperatorBaseClass() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return classTemplateSpecializationDecl( + hasName("absl::internal_statusor::OperatorBase")); +} + +clang::ast_matchers::TypeMatcher possiblyAliasedStatusOrType() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return hasUnqualifiedDesugaredType( + recordType(hasDeclaration(statusOrClass()))); +} + +clang::ast_matchers::TypeMatcher possiblyAliasedStatusType() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return hasUnqualifiedDesugaredType(recordType(hasDeclaration(statusClass()))); +} + +clang::ast_matchers::TypeMatcher statusOrType() { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return hasCanonicalType(qualType(hasDeclaration(statusOrClass()))); +} + +bool isRecordTypeWithName(QualType Type, llvm::StringRef TypeName) { + return Type->isRecordType() && + Type->getAsCXXRecordDecl()->getQualifiedNameAsString() == TypeName; +} + +bool isStatusOrType(QualType Type) { + return isRecordTypeWithName(Type, "absl::StatusOr"); +} + +bool isStatusType(QualType Type) { + return isRecordTypeWithName(Type, "absl::Status"); +} + +llvm::StringMap getSyntheticFields(QualType Ty, QualType StatusType, + const CXXRecordDecl &RD) { + if (auto *TRD = getStatusOrBaseClass(Ty)) + return {{"status", StatusType}, {"value", getStatusOrValueType(TRD)}}; + if (isStatusType(Ty) || (RD.hasDefinition() && + RD.isDerivedFrom(StatusType->getAsCXXRecordDecl()))) + return {{"ok", RD.getASTContext().BoolTy}}; + return {}; +} + +RecordStorageLocation &locForStatus(RecordStorageLocation &StatusOrLoc) { + return cast(StatusOrLoc.getSyntheticField("status")); +} + +StorageLocation &locForOk(RecordStorageLocation &StatusLoc) { + return StatusLoc.getSyntheticField("ok"); +} + +BoolValue &valForOk(RecordStorageLocation &StatusLoc, Environment &Env) { + if (auto *Val = Env.get(locForOk(StatusLoc))) + return *Val; + return initializeStatus(StatusLoc, Env); +} + +static void transferStatusOrOkCall(const CXXMemberCallExpr *Expr, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + RecordStorageLocation *StatusOrLoc = + getImplicitObjectLocation(*Expr, State.Env); + if (StatusOrLoc == nullptr) + return; + + auto &OkVal = valForOk(locForStatus(*StatusOrLoc), State.Env); + State.Env.setValue(*Expr, OkVal); +} + +CFGMatchSwitch +buildTransferMatchSwitch(ASTContext &Ctx, + CFGMatchSwitchBuilder Builder) { + using namespace ::clang::ast_matchers; // NOLINT: Too many names + return std::move(Builder) + .CaseOfCFGStmt(isStatusOrMemberCallWithName("ok"), + transferStatusOrOkCall) + .Build(); +} + +QualType findStatusType(const ASTContext &Ctx) { + for (Type *Ty : Ctx.getTypes()) + if (isStatusType(QualType(Ty, 0))) + return QualType(Ty, 0); + + return QualType(); +} + +UncheckedStatusOrAccessModel::UncheckedStatusOrAccessModel(ASTContext &Ctx, + Environment &Env) + : DataflowAnalysis(Ctx), + TransferMatchSwitch(buildTransferMatchSwitch(Ctx, {})) { + QualType StatusType = findStatusType(Ctx); + Env.getDataflowAnalysisContext().setSyntheticFieldCallback( + [StatusType](QualType Ty) -> llvm::StringMap { + CXXRecordDecl *RD = Ty->getAsCXXRecordDecl(); + if (RD == nullptr) + return {}; + + if (auto Fields = getSyntheticFields(Ty, StatusType, *RD); + !Fields.empty()) + return Fields; + return {}; + }); +} + +void UncheckedStatusOrAccessModel::transfer(const CFGElement &Elt, Lattice &L, + Environment &Env) { + LatticeTransferState State(L, Env); + TransferMatchSwitch(Elt, getASTContext(), State); +} + +} // namespace clang::dataflow::statusor_model diff --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt index 35082387b46e9..1d932ec6e8a96 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/UncheckedStatusOrAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTest.cpp new file mode 100644 index 0000000000000..07d3f2412e842 --- /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 + +#include "UncheckedStatusOrAccessModelTestFixture.h" +#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h" +#include "gtest/gtest.h" + +namespace clang::dataflow::statusor_model { +namespace { + +INSTANTIATE_TEST_SUITE_P( + UncheckedStatusOrAccessModelTest, UncheckedStatusOrAccessModelTest, + testing::Values( + std::make_pair(new UncheckedStatusOrAccessModelTestExecutor< + UncheckedStatusOrAccessModel>(), + UncheckedStatusOrAccessModelTestAliasKind::kUnaliased), + std::make_pair( + new UncheckedStatusOrAccessModelTestExecutor< + UncheckedStatusOrAccessModel>(), + UncheckedStatusOrAccessModelTestAliasKind::kPartiallyAliased), + std::make_pair( + new UncheckedStatusOrAccessModelTestExecutor< + UncheckedStatusOrAccessModel>(), + 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 0000000000000..4827cc1d0a7e9 --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp @@ -0,0 +1,2518 @@ +//===- 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 +#include +#include + +#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(); + 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 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 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()); + 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(); 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()) { + 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()) { + 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()) 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()) { + 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()) { + 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(); + if (sor.ok()) { + sor = Make(); + sor.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT sor = Make(); + if (!sor.ok()) return; + + sor = Make(); + sor.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make(); + if (x.ok()) { + STATUSOR_INT y = x; + x = Make(); + + y.value(); + + x.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make(); + STATUSOR_INT y = x; + if (!y.ok()) return; + + x.value(); + + y = Make(); + 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(); + if (foo.bar.ok()) { + foo.bar.value(); + + foo.bar = Make(); + 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& 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& 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& y = x; + if (!y.ok()) return; + + x.value(); + + y = Make(); + x.value(); // [[unsafe]] + } + )cc"); + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUSOR_INT x = Make(); + const STATUSOR_INT& y = x; + if (!y.ok()) return; + + y.value(); + + x = Make(); + 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>(); + !(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()) { + STATUSOR_INT sor = Make(); + sor.value(); // [[unsafe]] + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + using StatusOrInt = STATUSOR_INT; + + void target() { + while (Make()) { + StatusOrInt sor = Make(); + 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(); sor.ok()) sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + if (STATUSOR_INT sor = Make(); !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()); + } + )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(); + 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().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(); + 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(); + 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(); + if (sor1.ok()) { + STATUSOR_INT sor2 = Make(); + 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(); + 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(); + else + sor = Make(); + + 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(); + if (!sor.ok()) return; + } else { + sor = Make(); + 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(); + if (!sor.ok()) return; + } else { + sor = Make(); + } + sor.value(); // [[unsafe]] + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, VarDeclInitExprFromPairAccess) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor = Make>().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>().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(); + else + sor = Make(); + + 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().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>(); + 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>(); + sor.value(); // [[unsafe]] + } + )cc"); + + // As a reference. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + const auto& [sor, x] = Make>(); + 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(); + if (sor.ok()) sor.value(); + } + )cc"); + + // In a loop. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto vals = Make>>(); + 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>>(); + 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"); +} + +} // namespace + +std::string +GetAliasMacros(UncheckedStatusOrAccessModelTestAliasKind AliasKind) { + switch (AliasKind) { + case UncheckedStatusOrAccessModelTestAliasKind::kUnaliased: + return R"cc( +#define STATUSOR_INT ::absl::StatusOr +#define STATUSOR_BOOL ::absl::StatusOr +#define STATUSOR_VOIDPTR ::absl::StatusOr +#define STATUS ::absl::Status + )cc"; + case UncheckedStatusOrAccessModelTestAliasKind::kPartiallyAliased: + return R"cc( + template + using StatusOrAlias = ::absl::StatusOr; +#define STATUSOR_INT StatusOrAlias +#define STATUSOR_BOOL StatusOrAlias +#define STATUSOR_VOIDPTR StatusOrAlias +#define STATUS ::absl::Status + )cc"; + case UncheckedStatusOrAccessModelTestAliasKind::kFullyAliased: + return R"cc( + using StatusOrIntAlias = ::absl::StatusOr; +#define STATUSOR_INT StatusOrIntAlias + using StatusOrBoolAlias = ::absl::StatusOr; +#define STATUSOR_BOOL StatusOrBoolAlias + using StatusOrVoidPtrAlias = ::absl::StatusOr; +#define STATUSOR_VOIDPTR StatusOrVoidPtrAlias + using StatusAlias = ::absl::Status; +#define STATUS StatusAlias + )cc"; + } + llvm_unreachable("Unknown alias kind."); +} + +std::vector> +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 + 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 0000000000000..ff1d323a0086f --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.h @@ -0,0 +1,157 @@ +//===- 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 +#include +#include +#include +#include + +#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 using Alias = absl::StatusOr; + kFullyAliased = 2, // using Alias = absl::StatusOr; +}; + +// 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 to ensure +// the model is insensitive to whether the StatusOr<> is aliased or not. +std::string GetAliasMacros(UncheckedStatusOrAccessModelTestAliasKind AliasKind); + +std::vector> +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 +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 + void ExpectDiagnosticsFor( + std::string SourceCode, FuncDeclMatcher FuncMatcher, + UncheckedStatusOrAccessModelTestAliasKind AliasKind) const { + std::vector> Headers = + GetHeaders(AliasKind); + + UncheckedStatusOrAccessModelOptions Options{}; + std::vector Diagnostics; + llvm::Error Error = test::checkDataflow( + test::AnalysisInputs( + 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 + &Annotations, + const test::AnalysisOutputs &AO) { + llvm::DenseSet AnnotationLines; + for (const auto &[Line, _] : Annotations) + AnnotationLines.insert(Line); + auto &SrcMgr = AO.ASTCtx.getSourceManager(); + llvm::DenseSet 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)); + } +}; + +class UncheckedStatusOrAccessModelTest + : public ::testing::TestWithParam< + std::pair> { +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_ diff --git a/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/Models/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/Models/BUILD.gn index 3fd3aab7970d5..01a05bcfda415 100644 --- a/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/Models/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/Models/BUILD.gn @@ -11,5 +11,6 @@ static_library("Models") { sources = [ "ChromiumCheckModel.cpp", "UncheckedOptionalAccessModel.cpp", + "UncheckedStatusOrAccessModel.cpp", ] } diff --git a/llvm/utils/gn/secondary/clang/unittests/Analysis/FlowSensitive/BUILD.gn b/llvm/utils/gn/secondary/clang/unittests/Analysis/FlowSensitive/BUILD.gn index c9f3a0745ed46..c74a44c7016c6 100644 --- a/llvm/utils/gn/secondary/clang/unittests/Analysis/FlowSensitive/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/unittests/Analysis/FlowSensitive/BUILD.gn @@ -44,6 +44,8 @@ unittest("ClangAnalysisFlowSensitiveTests") { "TransferTest.cpp", "TypeErasedDataflowAnalysisTest.cpp", "UncheckedOptionalAccessModelTest.cpp", + "UncheckedStatusOrAccessModelTest.cpp", + "UncheckedStatusOrAccessModelTestFixture.cpp", "ValueTest.cpp", "WatchedLiteralsSolverTest.cpp", ]