diff --git a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.h b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.h @@ -0,0 +1,56 @@ +#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_UNCHECKED_OPTIONAL_USE_MODEL_H +#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_UNCHECKED_OPTIONAL_USE_MODEL_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Stmt.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" +#include "clang/Analysis/FlowSensitive/SourceLocationsLattice.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include +#include + +namespace clang { +namespace dataflow { + +// Dataflow-analysis based checker that discovers unsafe uses of optional values +// and adds the respective source locations to the lattice. +class UncheckedOptionalUseModel + : public DataflowAnalysis { +public: + explicit UncheckedOptionalUseModel(clang::ASTContext &AstContext); + + static SourceLocationsLattice initialElement() { + return SourceLocationsLattice(); + } + + void transfer(const clang::Stmt *Stmt, + clang::dataflow::SourceLocationsLattice &State, + clang::dataflow::Environment &Environment); + + bool compareEquivalent(clang::QualType Type, + const clang::dataflow::Value &Val1, + const clang::dataflow::Environment &Env1, + const clang::dataflow::Value &Val2, + const clang::dataflow::Environment &Env2) final; + + bool merge(QualType Type, const Value &Val1, const Environment &Env1, + const Value &Val2, const Environment &Env2, Value &MergedVal, + Environment &MergedEnv) final; + +private: + std::function &State)> + TransferMatchSwitch; +}; + +} // namespace dataflow +} // namespace clang + +#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_UNCHECKED_OPTIONAL_USE_MODEL_H diff --git a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt --- a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt @@ -12,3 +12,5 @@ clangAST clangBasic ) + +add_subdirectory(Models) diff --git a/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt @@ -0,0 +1,10 @@ +add_clang_library(clangAnalysisFlowSensitiveModels + UncheckedOptionalUseModel.cpp + + LINK_LIBS + clangAnalysis + clangAnalysisFlowSensitive + clangAST + clangASTMatchers + clangBasic + ) diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.cpp @@ -0,0 +1,252 @@ +#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalUseModel.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/OperationKinds.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/OperatorKinds.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang { +namespace dataflow { +namespace { +using namespace ::clang::ast_matchers; + +using State = TransferState; + +// Sets the `has_value` property of an optional struct to a fresh variable, if +// unset. Returns the property. If `optional_value` is nullptr, returns nullptr. +BoolValue *maybeInitOptionalValue(Environment &Env, Value *OptionalValue) { + if (OptionalValue == nullptr) + return nullptr; + auto *Sv = clang::cast(OptionalValue); + + if (auto *P = Sv->getProperty("has_value")) + return clang::cast(P); + + auto &V = Env.makeAtomicBoolValue(); + Sv->setProperty("has_value", V); + return &V; +} + +// May return nullptr if `optional_value` is nullptr. Otherwise, returns the +// value of the `has_value` property. +BoolValue *getHasValue(Value *OptionalValue) { + if (auto *Sv = clang::cast_or_null(OptionalValue)) { + return clang::cast(Sv->getProperty("has_value")); + } + return nullptr; +} + +auto optionalClass() { + return classTemplateSpecializationDecl( + anyOf(hasName("std::optional"), hasName("std::__optional_storage_base"), + hasName("__optional_destruct_base"), hasName("absl::optional"), + hasName("base::Optional")), + hasTemplateArgument(0, refersToType(type().bind("T")))); +} + +auto hasOptionalType() { return hasType(optionalClass()); } + +auto isOptionalMemberCallWithName(llvm::StringRef MemberName) { + return cxxMemberCallExpr( + on(expr(unless(cxxThisExpr()))), + callee(cxxMethodDecl(hasName(MemberName), ofClass(optionalClass())))); +} + +auto isOptionalOperatorCallWithName(llvm::StringRef OperatorName) { + return cxxOperatorCallExpr(hasOverloadedOperatorName(OperatorName), + callee(cxxMethodDecl(ofClass(optionalClass())))); +} + +bool isOptionalType(clang::QualType Type) { + if (!Type->isRecordType()) + return false; + auto TypeName = Type->getAsCXXRecordDecl()->getQualifiedNameAsString(); + return TypeName == "std::optional" || TypeName == "absl::optional" || + TypeName == "base::Optional"; +} + +clang::QualType stripReference(clang::QualType Type) { + return Type->isReferenceType() ? Type->getPointeeType() : Type; +} + +// FIXME: This should be done at the same time as `has_value` is initialized. +void initializeUnwrappedValue(const clang::Expr *Expr, + StructValue *OptionalValue, Environment &Env) { + assert(OptionalValue); + if (Env.getStorageLocation(*Expr, SkipPast::None) != nullptr) + return; + + // Use the value if the optional already has it. + if (auto *Value = OptionalValue->getProperty("value")) { + auto *ValueRef = clang::cast(Value); + Env.setStorageLocation(*Expr, ValueRef->getPointeeLoc()); + } else { + // Otherwise, initialize the value. + auto Type = stripReference(Expr->getType()); + Value = Env.createValue(Type); + if (Value == nullptr) + return; + + OptionalValue->setProperty("value", *Value); + auto &ValueLoc = Env.createStorageLocation(Type); + auto ValueRef = std::make_unique(ValueLoc); + OptionalValue->setProperty("value", Env.takeOwnership(std::move(ValueRef))); + Env.setStorageLocation(*Expr, ValueLoc); + } +} + +template T &assertValue(T *V) { + assert(V); + return *V; +} + +// Ensures initialization of the "has_value" property of an `optional` object. +// Assumes: `e` is of (some) type `optional`. +void transferGenericOptional(const clang::Expr *OptionalExpr, State &State) { + // TODO(sgatev): Add support for pointer members. + if (auto *OptionalValue = clang::cast_or_null( + State.Env.getValue(*OptionalExpr, SkipPast::Reference))) { + if (OptionalValue->getProperty("has_value") == nullptr) { + OptionalValue->setProperty("has_value", State.Env.makeAtomicBoolValue()); + } + } +} + +void transferUnwrapCall(const clang::Expr *UnwrapExpr, + const clang::Expr *ObjectArgumentExpr, State &State) { + // TODO(sgatev): Add tests with pointer receivers. + if (auto *OptionalValue = clang::cast_or_null(State.Env.getValue( + *ObjectArgumentExpr, SkipPast::ReferenceThenPointer))) { + initializeUnwrappedValue(UnwrapExpr, OptionalValue, State.Env); + llvm::errs() << "Checking flow condition\n"; + if (State.Env.flowConditionImplies(assertValue(getHasValue(OptionalValue)))) + return; + } + // Record that this unwrap is not provably safe. + State.Lattice.getSourceLocations().insert(ObjectArgumentExpr->getBeginLoc()); +} + +void transferOptionalHasValueCall( + const clang::CXXMemberCallExpr *HasValueCallExpr, State &State) { + // Create a symbolic value for has_value_call_expr exactly once. + if (State.Env.getStorageLocation(*HasValueCallExpr, SkipPast::None) != + nullptr) { + return; + } + + // TODO(sgatev): Add tests with pointer receivers. + if (auto *ObjValue = maybeInitOptionalValue( + State.Env, + State.Env.getValue(*HasValueCallExpr->getImplicitObjectArgument(), + SkipPast::ReferenceThenPointer))) { + auto &HasValueCallExprLoc = + State.Env.createStorageLocation(*HasValueCallExpr); + State.Env.setValue(HasValueCallExprLoc, *ObjValue); + State.Env.setStorageLocation(*HasValueCallExpr, HasValueCallExprLoc); + } +} + +bool isEngagedOptionalValue(const Value &Value, const Environment &Env) { + auto *OptionalValue = clang::dyn_cast(&Value); + if (OptionalValue == nullptr) + return false; + + auto *HasValueValue = + clang::cast_or_null(OptionalValue->getProperty("has_value")); + if (HasValueValue == nullptr) + return false; + + return Env.flowConditionImplies(*HasValueValue); +} + +auto buildTransferMatchSwitch() { + return clang::dataflow::MatchSwitchBuilder< + TransferState>() + // Attach a symbolic "has_value" state to absl::optional values that we + // see for the first time (including standard constructors, not covered + // above). + .CaseOf(expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()), + transferGenericOptional) + + // std::optional.value(): + .CaseOf( + isOptionalMemberCallWithName("value"), + +[](const clang::CXXMemberCallExpr *CallExpr, State &State) { + transferUnwrapCall(CallExpr, CallExpr->getImplicitObjectArgument(), + State); + }) + // *std::optional OR std::optional->: + .CaseOf( + expr(anyOf(isOptionalOperatorCallWithName("*"), + isOptionalOperatorCallWithName("->"))), + +[](const clang::CallExpr *Call, State &State) { + transferUnwrapCall(Call, Call->getArg(0), State); + }) + + // std::optional.has_value(): + .CaseOf(isOptionalMemberCallWithName("has_value"), + transferOptionalHasValueCall) + // operator bool: + .CaseOf(isOptionalMemberCallWithName("operator bool"), + transferOptionalHasValueCall) + .Build(); +} +} // namespace + +UncheckedOptionalUseModel::UncheckedOptionalUseModel(clang::ASTContext &Context) + : DataflowAnalysis( + Context), + TransferMatchSwitch(buildTransferMatchSwitch()) {} + +void UncheckedOptionalUseModel::transfer(const clang::Stmt *Stmt, + SourceLocationsLattice &InputState, + Environment &Env) { + State State(InputState, Env); + TransferMatchSwitch(*Stmt, getASTContext(), State); +} + +bool UncheckedOptionalUseModel::compareEquivalent( + clang::QualType Type, const clang::dataflow::Value &Val1, + const clang::dataflow::Environment &Env1, + const clang::dataflow::Value &Val2, + const clang::dataflow::Environment &Env2) { + return isEngagedOptionalValue(Val1, Env1) == + isEngagedOptionalValue(Val2, Env2); +} + +bool UncheckedOptionalUseModel::merge(clang::QualType Type, + const clang::dataflow::Value &Val1, + const Environment &Env1, + const clang::dataflow::Value &Val2, + const Environment &Env2, + clang::dataflow::Value &MergedVal, + clang::dataflow::Environment &MergedEnv) { + if (!isOptionalType(Type)) + return false; + + auto has_value_value = MergedEnv.makeAtomicBoolValue(); + if (isEngagedOptionalValue(Val1, MergedEnv) && + isEngagedOptionalValue(Val2, MergedEnv)) { + MergedEnv.addToFlowCondition(has_value_value); + } + + clang::cast(MergedVal).setProperty("has_value", has_value_value); + return true; +} +} // namespace dataflow +} // namespace clang diff --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt --- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt @@ -10,18 +10,20 @@ MatchSwitchTest.cpp MultiVarConstantPropagationTest.cpp SingleVarConstantPropagationTest.cpp + SolverTest.cpp SourceLocationsLatticeTest.cpp TestingSupport.cpp TestingSupportTest.cpp TransferTest.cpp TypeErasedDataflowAnalysisTest.cpp - SolverTest.cpp + UncheckedOptionalUseModelTest.cpp ) clang_target_link_libraries(ClangAnalysisFlowSensitiveTests PRIVATE clangAnalysis clangAnalysisFlowSensitive + clangAnalysisFlowSensitiveModels clangAST clangASTMatchers clangBasic diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalUseModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalUseModelTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalUseModelTest.cpp @@ -0,0 +1,1851 @@ +//===- unittests/Analysis/FlowSensitive/UncheckedOptionalUseModelTest.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/UncheckedOptionalUseModel.h" +#include "TestingSupport.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Stmt.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/DataflowLattice.h" +#include "clang/Analysis/FlowSensitive/MapLattice.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Testing/Support/Annotations.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include + +namespace clang { +namespace dataflow { +namespace { + +using ::testing::Pair; +using ::testing::UnorderedElementsAre; + +static constexpr char BaseDefsHeader[] = R"( +namespace std { + +// clang-format off +template struct remove_reference { using type = T; }; +template struct remove_reference { using type = T; }; +template struct remove_reference { using type = T; }; +// clang-format on + +template +using remove_reference_t = typename remove_reference::type; + +template +constexpr T&& forward(remove_reference_t& t) noexcept; + +template +constexpr T&& forward(remove_reference_t&& t) noexcept; + +template +constexpr std::remove_reference_t&& move(T&& x); + +struct nullopt_t { + constexpr explicit nullopt_t() {} +}; + +constexpr nullopt_t nullopt; + +template +class optional { + public: + constexpr optional() noexcept; + + constexpr optional(nullopt_t) noexcept; + + optional(const optional&) = default; + + optional(optional&&) = default; + + const T& operator*() const&; + T& operator*() &; + const T&& operator*() const&&; + T&& operator*() &&; + + const T* operator->() const; + T* operator->(); + + const T& value() const&; + T& value() &; + const T&& value() const&&; + T&& value() &&; + + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + + template + constexpr T value_or(U&& v) const&; + template + T value_or(U&& v) &&; + + template + T& emplace(Args&&... args); + + void reset() noexcept; + + void swap(optional& rhs) noexcept; +}; +} + +namespace absl { +struct nullopt_t { + constexpr explicit nullopt_t() {} +}; + +constexpr nullopt_t nullopt; + +using std::optional; +} + +namespace base { +struct nullopt_t { + constexpr explicit nullopt_t() {} +}; + +constexpr nullopt_t nullopt; + +template +class Optional { + public: + constexpr Optional() noexcept; + + constexpr Optional(nullopt_t) noexcept; + + Optional(const Optional&) = default; + + Optional(Optional&&) = default; + + const T& operator*() const&; + T& operator*() &; + const T&& operator*() const&&; + T&& operator*() &&; + + const T* operator->() const; + T* operator->(); + + const T& value() const&; + T& value() &; + const T&& value() const&&; + T&& value() &&; + + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + + template + constexpr T value_or(U&& v) const&; + template + T value_or(U&& v) &&; + + template + T& emplace(Args&&... args); + + void reset() noexcept; + + void swap(Optional& rhs) noexcept; +}; +} +)"; + +// Replaces all occurrences of |pattern| in |s| with |replacement|. +void ReplaceAllOccurrences(std::string &s, const std::string &pattern, + const std::string &replacement) { + size_t pos = 0; + while (true) { + pos = s.find(pattern, pos); + if (pos == std::string::npos) + break; + s.replace(pos, pattern.size(), replacement); + } +} + +std::string ConvertToString(const SourceLocationsLattice &element, + const clang::ASTContext &ast_context) { + if (element.getSourceLocations().empty()) { + return "safe"; + } + return "unsafe: " + DebugString(element, ast_context); +} + +struct OptionalTypeIdentifier { + std::string namespace_name; + std::string type_name; +}; + +class UncheckedOptionalUseTest + : public ::testing::TestWithParam { +protected: + template + void ExpectLatticeChecksFor(std::string source_code, + LatticeChecksMatcher matches_lattice_checks) { + ExpectLatticeChecksFor(source_code, ast_matchers::hasName("target"), + matches_lattice_checks); + } + +private: + template + void ExpectLatticeChecksFor(std::string source_code, + FuncDeclMatcher func_matcher, + LatticeChecksMatcher matches_lattice_checks) { + ReplaceAllOccurrences(source_code, "$ns", GetParam().namespace_name); + ReplaceAllOccurrences(source_code, "$optional", GetParam().type_name); + + std::vector> headers; + headers.emplace_back("base_defs.h", BaseDefsHeader); + headers.emplace_back("unchecked_optional_use_test_defs.h", R"( + #include "base_defs.h" + + template + T Make(); + + class Fatal { + public: + ~Fatal() __attribute__((noreturn)); + int value(); + }; + )"); + const clang::tooling::FileContentMappings file_contents(headers.begin(), + headers.end()); + llvm::Error error = + clang::dataflow::test::checkDataflow( + source_code, func_matcher, + [](clang::ASTContext &context, clang::dataflow::Environment &) { + return UncheckedOptionalUseModel(context); + }, + [&matches_lattice_checks]( + llvm::ArrayRef>> + check_to_lattice_map, + clang::ASTContext &context) { + // FIXME: Consider using a matcher instead of translating + // check_to_lattice_map to check_to_stringified_lattice_map. + std::vector> + check_to_stringified_lattice_map; + for (const auto &e : check_to_lattice_map) { + check_to_stringified_lattice_map.emplace_back( + e.first, ConvertToString(e.second.Lattice, context)); + } + EXPECT_THAT(check_to_stringified_lattice_map, + matches_lattice_checks); + }, + {"-fsyntax-only", "-std=c++17"}, file_contents); + if (error) + FAIL() << llvm::toString(std::move(error)); + } +}; + +INSTANTIATE_TEST_SUITE_P( + UncheckedOptionalUseTestInst, UncheckedOptionalUseTest, + ::testing::Values(OptionalTypeIdentifier{"std", "optional"} + // , + // OptionalTypeIdentifier{"absl", "optional"}, + // OptionalTypeIdentifier{"base", "Optional"} + ), + [](const ::testing::TestParamInfo &info) { + return info.param.namespace_name; + }); + +TEST_P(UncheckedOptionalUseTest, EmptyFunctionBody) { + ExpectLatticeChecksFor(R"( + void target() { + "nop"; + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, UnwrapWithoutCheck_Lvalue_CallToValue) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7"))); +} + +TEST_P(UncheckedOptionalUseTest, UnwrapWithoutCheck_Rvalue_CallToValue) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + std::move(opt).value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7"))); +} + +TEST_P(UncheckedOptionalUseTest, UnwrapWithoutCheck_Lvalue_CallToOperatorStar) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + *opt; + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:8"))); +} + +TEST_P(UncheckedOptionalUseTest, UnwrapWithoutCheck_Rvalue_CallToOperatorStar) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + *std::move(opt); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:8"))); +} + +TEST_P(UncheckedOptionalUseTest, + UnwrapWithoutCheck_Lvalue_CallToOperatorArrow) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + struct Foo { + void foo(); + }; + + void target($ns::$optional opt) { + opt->foo(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7"))); +} + +TEST_P(UncheckedOptionalUseTest, + UnwrapWithoutCheck_Rvalue_CallToOperatorArrow) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + struct Foo { + void foo(); + }; + + void target($ns::$optional opt) { + std::move(opt)->foo(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7"))); +} + +TEST_P(UncheckedOptionalUseTest, UnwrapLvalueWithCheck) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} + +#if 0 +TEST_P(UncheckedOptionalUseTest, UnwrapRvalueWithCheck) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (opt.has_value()) { + std::move(opt).value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} +#endif + +TEST_P(UncheckedOptionalUseTest, UnwrapWithBoolCheck) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (opt) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} + +#if 0 +TEST_P(UncheckedOptionalUseTest, ParensInDeclInitExpr) { + ExpectLatticeChecksFor(R"code( + #include "unchecked_optional_use_test_defs.h" + + void target() { + auto opt = (Make<$ns::$optional>()); + if (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )code", + UnorderedElementsAre(Pair("check", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, ReferenceInDeclInitExpr) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + struct Foo { + const $ns::$optional& GetOptionalInt() const; + }; + + void target(Foo foo) { + auto opt = foo.GetOptionalInt(); + if (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + struct Foo { + $ns::$optional& GetOptionalInt(); + }; + + void target(Foo foo) { + auto opt = foo.GetOptionalInt(); + if (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + struct Foo { + $ns::$optional&& GetOptionalInt() &&; + }; + + void target(Foo foo) { + auto opt = std::move(foo).GetOptionalInt(); + if (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor(R"code( + #include "unchecked_optional_use_test_defs.h" + + template + using AliasedOptional = $ns::$optional; + + struct Foo { + const AliasedOptional& GetOptionalInt() const; + }; + + void target(Foo foo) { + auto opt = foo.GetOptionalInt(); + if (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )code", + UnorderedElementsAre(Pair("check", "safe"))); +} +#endif + +TEST_P(UncheckedOptionalUseTest, IfThenElse) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (opt.has_value()) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + + "nop"; + /*[[check-3]]*/ + + opt.value(); + /*[[check-4]]*/ + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "unsafe: input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:9:9"), + Pair("check-4", "unsafe: input.cc:16:7, input.cc:9:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target() { + if (auto opt = Make<$ns::$optional>(); opt.has_value()) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:9:9"))); +} + +#if 0 +TEST_P(UncheckedOptionalUseTest, JoinSafeSafe) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt, bool b) { + if (opt.has_value()) { + if (b) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + } + + "nop"; + /*[[check-3]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), Pair("check-2", "safe"), + Pair("check-3", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, JoinUnsafeUnsafe) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt, bool b) { + if (b) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + + "nop"; + /*[[check-3]]*/ + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:6:9, input.cc:9:9"))); +} + +TEST_P(UncheckedOptionalUseTest, InversedIfThenElse) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (!opt.has_value()) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + + "nop"; + /*[[check-3]]*/ + + opt.value(); + /*[[check-4]]*/ + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), Pair("check-2", "safe"), + Pair("check-3", "unsafe: input.cc:6:9"), + Pair("check-4", "unsafe: input.cc:16:7, input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, DoubleInversedIfThenElse) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (!!opt.has_value()) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:9:9"))); +} + +TEST_P(UncheckedOptionalUseTest, TripleInversedIfThenElse) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (!!!opt.has_value()) { + opt.value(); + /*[[check-1]]*/ + } else { + opt.value(); + /*[[check-2]]*/ + } + } + )", + UnorderedElementsAre(Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_LhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (x.has_value() && y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "safe"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_NotLhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!x.has_value() && y.has_value()) { + y.value(); + /*[[check-1]]*/ + + x.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "unsafe: input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_LhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (x.has_value() && !y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "unsafe: input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_NotLhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!x.has_value() && !y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_LhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(x.has_value() && y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "safe"), Pair("check-4", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_NotLhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(!x.has_value() && y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + y.value(); + /*[[check-3]]*/ + + x.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "safe"), Pair("check-4", "unsafe: input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_LhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(x.has_value() && !y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "safe"), Pair("check-4", "unsafe: input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_NotLhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(!x.has_value() && !y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_LhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (x.has_value() || y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_NotLhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!x.has_value() || y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "safe"), Pair("check-4", "unsafe: input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_LhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (x.has_value() || !y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + y.value(); + /*[[check-3]]*/ + + x.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "safe"), Pair("check-4", "unsafe: input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_NotLhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!x.has_value() || !y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "safe"), Pair("check-4", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_LhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(x.has_value() || y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "unsafe: input.cc:6:9"), + Pair("check-2", "unsafe: input.cc:6:9, input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_NotLhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(!x.has_value() || y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "unsafe: input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_LhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(x.has_value() || !y.has_value())) { + y.value(); + /*[[check-1]]*/ + + x.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "unsafe: input.cc:9:9"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, IfThenElse_Not_NotLhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!(!x.has_value() || !y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } else { + x.value(); + /*[[check-3]]*/ + + y.value(); + /*[[check-4]]*/ + } + } + )", + UnorderedElementsAre( + Pair("check-1", "safe"), Pair("check-2", "safe"), + Pair("check-3", "unsafe: input.cc:12:9"), + Pair("check-4", "unsafe: input.cc:12:9, input.cc:15:9"))); +} + +TEST_P(UncheckedOptionalUseTest, TerminatingIfThenBranch) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (!opt.has_value()) { + return; + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (opt.has_value()) { + return; + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + if (!x.has_value() || !y.has_value()) { + return; + } + + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), Pair("check-2", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, TerminatingIfElseBranch) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (opt.has_value()) { + } else { + return; + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + if (!opt.has_value()) { + } else { + return; + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:10:7"))); +} + +TEST_P(UncheckedOptionalUseTest, TerminatingIfThenBranchInLoop) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (Make()) { + if (!opt.has_value()) { + continue; + } + + opt.value(); + /*[[check-1]]*/ + } + + opt.value(); + /*[[check-2]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:14:7"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (Make()) { + if (!opt.has_value()) { + break; + } + + opt.value(); + /*[[check-1]]*/ + } + + opt.value(); + /*[[check-2]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:14:7"))); +} + +TEST_P(UncheckedOptionalUseTest, TernaryConditionalOperator) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + opt.has_value() ? opt.value() : 21; + /*[[check-1]]*/ + + opt.has_value() ? 21 : opt.value(); + /*[[check-2]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:8:30"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + !opt.has_value() ? 21 : opt.value(); + /*[[check-1]]*/ + + !opt.has_value() ? opt.value() : 21; + /*[[check-2]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:8:26"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt1, $ns::$optional opt2) { + !((__builtin_expect(false || + (!(opt1.has_value() && opt2.has_value())), false))) + ? (void)0 : (void)1; + do { + opt1.value(); + opt2.value(); + /*[[check]]*/ + } while (true); + } + )", + UnorderedElementsAre(Pair( + "check", "unsafe: input.cc:10:9, input.cc:9:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (Make()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (!opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (!!opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (!!!opt.has_value()) { + opt.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_LhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() && y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), Pair("check-2", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, While_NotLhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() && y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() && y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, While_LhsAndNotRhs) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() && !y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() && !y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_NotLhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() && !y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() && !y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_LhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() && y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() && y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_NotLhsAndRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() && y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() && y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_LhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() && !y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() && !y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_NotLhsAndNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() && !y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() && !y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_LhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() || y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() || y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_NotLhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() || y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() || y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_LhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() || !y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (x.has_value() || !y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_NotLhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() || !y.has_value()) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!x.has_value() || !y.has_value()) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_LhsOrRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() || y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() || y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_NotLhsOrRhs) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() || y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() || y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_LhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() || !y.has_value())) { + x.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:9"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(x.has_value() || !y.has_value())) { + y.value(); + /*[[check]]*/ + } + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, While_Not_NotLhsOrNotRhs) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (!(!x.has_value() || !y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), Pair("check-2", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, While_AccessAfterStmt) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (opt.has_value()) { + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:8:7"))); + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (!opt.has_value()) { + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "safe"))); +} + +TEST_P(UncheckedOptionalUseTest, While_TerminatingBranch_Return) { + ExpectLatticeChecksFor(R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (!opt.has_value()) { + return; + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (opt.has_value()) { + return; + } + + opt.value(); + /*[[check]]*/ + } + )", + UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7"))); +} + +TEST_P(UncheckedOptionalUseTest, While_TerminatingBranch_Continue) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional opt) { + while (Make()) { + if (!opt.has_value()) { + continue; + } + + opt.value(); + /*[[check-1]]*/ + } + + opt.value(); + /*[[check-2]]*/ + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), + Pair("check-2", "unsafe: input.cc:14:7"))); +} + +TEST_P(UncheckedOptionalUseTest, While_NestedIfWithBinaryCondition) { + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (Make()) { + if (x.has_value() && y.has_value()) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } + } + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), Pair("check-2", "safe"))); + ExpectLatticeChecksFor( + R"( + #include "unchecked_optional_use_test_defs.h" + + void target($ns::$optional x, $ns::$optional y) { + while (Make()) { + if (!(!x.has_value() || !y.has_value())) { + x.value(); + /*[[check-1]]*/ + + y.value(); + /*[[check-2]]*/ + } + } + } + )", + UnorderedElementsAre(Pair("check-1", "safe"), Pair("check-2", "safe"))); +} +#endif + +} // namespace +} // namespace dataflow +} // namespace clang