[clang] 390029b - [clang][dataflow] Support (in)equality operators in `optional` model.

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Wed Dec 7 08:25:47 PST 2022


Author: Yitzhak Mandelbaum
Date: 2022-12-07T16:24:49Z
New Revision: 390029be8946cac807e8fc978b9cf3790e7456cc

URL: https://github.com/llvm/llvm-project/commit/390029be8946cac807e8fc978b9cf3790e7456cc
DIFF: https://github.com/llvm/llvm-project/commit/390029be8946cac807e8fc978b9cf3790e7456cc.diff

LOG: [clang][dataflow] Support (in)equality operators in `optional` model.

This patch adds interpretation of the overloaded equality and inequality
operators available for the optional types.

Fixes issue #57253.

Differential Revision: https://reviews.llvm.org/D139360

Added: 
    

Modified: 
    clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
    clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 2ae6cd16f2d11..44a6f46c0f447 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -80,11 +80,20 @@ auto isMakeOptionalCall() {
       hasOptionalType());
 }
 
-auto hasNulloptType() {
-  return hasType(namedDecl(
-      hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t")));
+auto nulloptTypeDecl() {
+  return namedDecl(
+      hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t"));
 }
 
+auto hasNulloptType() { return hasType(nulloptTypeDecl()); }
+
+// `optional` or `nullopt_t`
+auto hasAnyOptionalType() {
+  return hasType(hasUnqualifiedDesugaredType(
+      recordType(hasDeclaration(anyOf(nulloptTypeDecl(), optionalClass())))));
+}
+
+
 auto inPlaceClass() {
   return recordDecl(
       hasAnyName("std::in_place_t", "absl::in_place_t", "base::in_place_t"));
@@ -117,6 +126,11 @@ auto isOptionalValueOrConversionAssignment() {
       argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
 }
 
+auto isNulloptConstructor() {
+  return cxxConstructExpr(hasNulloptType(), argumentCountIs(1),
+                          hasArgument(0, hasNulloptType()));
+}
+
 auto isOptionalNulloptAssignment() {
   return cxxOperatorCallExpr(hasOverloadedOperatorName("="),
                              callee(cxxMethodDecl(ofClass(optionalClass()))),
@@ -172,6 +186,27 @@ auto isCallReturningOptional() {
       optionalOrAliasType(), referenceType(pointee(optionalOrAliasType()))))));
 }
 
+template <typename L, typename R>
+auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) {
+  return cxxOperatorCallExpr(
+      anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")),
+      argumentCountIs(2), hasArgument(0, lhs_arg_matcher),
+      hasArgument(1, rhs_arg_matcher));
+}
+
+// Ensures that `Expr` is mapped to a `BoolValue` and returns it.
+BoolValue &forceBoolValue(Environment &Env, const Expr &Expr) {
+  auto *Value = cast_or_null<BoolValue>(Env.getValue(Expr, SkipPast::None));
+  if (Value != nullptr)
+    return *Value;
+
+  auto &Loc = Env.createStorageLocation(Expr);
+  Value = &Env.makeAtomicBoolValue();
+  Env.setValue(Loc, *Value);
+  Env.setStorageLocation(Expr, Loc);
+  return *Value;
+}
+
 /// Sets `HasValueVal` as the symbolic value that represents the "has_value"
 /// property of the optional value `OptionalVal`.
 void setHasValue(Value &OptionalVal, BoolValue &HasValueVal) {
@@ -357,16 +392,8 @@ void transferValueOrImpl(const clang::Expr *ValueOrPredExpr,
   if (HasValueVal == nullptr)
     return;
 
-  auto *ExprValue = cast_or_null<BoolValue>(
-      State.Env.getValue(*ValueOrPredExpr, SkipPast::None));
-  if (ExprValue == nullptr) {
-    auto &ExprLoc = State.Env.createStorageLocation(*ValueOrPredExpr);
-    ExprValue = &State.Env.makeAtomicBoolValue();
-    State.Env.setValue(ExprLoc, *ExprValue);
-    State.Env.setStorageLocation(*ValueOrPredExpr, ExprLoc);
-  }
-
-  Env.addToFlowCondition(ModelPred(Env, *ExprValue, *HasValueVal));
+  Env.addToFlowCondition(
+      ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr), *HasValueVal));
 }
 
 void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,
@@ -423,9 +450,9 @@ void assignOptionalValue(const Expr &E, LatticeTransferState &State,
 /// Returns a symbolic value for the "has_value" property of an `optional<T>`
 /// value that is constructed/assigned from a value of type `U` or `optional<U>`
 /// where `T` is constructible from `U`.
-BoolValue &value_orConversionHasValue(const FunctionDecl &F, const Expr &E,
-                                      const MatchFinder::MatchResult &MatchRes,
-                                      LatticeTransferState &State) {
+BoolValue &valueOrConversionHasValue(const FunctionDecl &F, const Expr &E,
+                                     const MatchFinder::MatchResult &MatchRes,
+                                     LatticeTransferState &State) {
   assert(F.getTemplateSpecializationArgs()->size() > 0);
 
   const int TemplateParamOptionalWrappersCount = countOptionalWrappers(
@@ -453,9 +480,9 @@ void transferValueOrConversionConstructor(
   assert(E->getNumArgs() > 0);
 
   assignOptionalValue(*E, State,
-                      value_orConversionHasValue(*E->getConstructor(),
-                                                 *E->getArg(0), MatchRes,
-                                                 State));
+                      valueOrConversionHasValue(*E->getConstructor(),
+                                                *E->getArg(0), MatchRes,
+                                                State));
 }
 
 void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,
@@ -478,8 +505,8 @@ void transferValueOrConversionAssignment(
     LatticeTransferState &State) {
   assert(E->getNumArgs() > 1);
   transferAssignment(E,
-                     value_orConversionHasValue(*E->getDirectCallee(),
-                                                *E->getArg(1), MatchRes, State),
+                     valueOrConversionHasValue(*E->getDirectCallee(),
+                                               *E->getArg(1), MatchRes, State),
                      State);
 }
 
@@ -533,6 +560,56 @@ void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,
   transferSwap(*OptionalLoc1, *OptionalLoc2, State);
 }
 
+BoolValue &evaluateEquality(Environment &Env, BoolValue &EqVal, BoolValue &LHS,
+                            BoolValue &RHS) {
+  // Logically, an optional<T> object is composed of two values - a `has_value`
+  // bit and a value of type T. Equality of optional objects compares both
+  // values. Therefore, merely comparing the `has_value` bits isn't sufficient:
+  // when two optional objects are engaged, the equality of their respective
+  // values of type T matters. Since we only track the `has_value` bits, we
+  // can't make any conclusions about equality when we know that two optional
+  // objects are engaged.
+  //
+  // We express this as two facts about the equality:
+  // a) EqVal => (LHS & RHS) v (!RHS & !LHS)
+  //    If they are equal, then either both are set or both are unset.
+  // b) (!LHS & !RHS) => EqVal
+  //    If neither is set, then they are equal.
+  // We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.
+  return Env.makeAnd(
+      Env.makeImplication(
+          EqVal, Env.makeOr(Env.makeAnd(LHS, RHS),
+                            Env.makeAnd(Env.makeNot(LHS), Env.makeNot(RHS)))),
+      Env.makeImplication(Env.makeNot(EqVal), Env.makeOr(LHS, RHS)));
+}
+
+void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr,
+                                    const MatchFinder::MatchResult &,
+                                    LatticeTransferState &State) {
+  Environment &Env = State.Env;
+  auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
+  if (auto *LHasVal = getHasValue(
+          Env, Env.getValue(*CmpExpr->getArg(0), SkipPast::Reference)))
+    if (auto *RHasVal = getHasValue(
+            Env, Env.getValue(*CmpExpr->getArg(1), SkipPast::Reference))) {
+      if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
+        CmpValue = &State.Env.makeNot(*CmpValue);
+      Env.addToFlowCondition(
+          evaluateEquality(Env, *CmpValue, *LHasVal, *RHasVal));
+    }
+}
+
+void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr,
+                                 const clang::Expr *E, Environment &Env) {
+  auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
+  if (auto *HasVal = getHasValue(Env, Env.getValue(*E, SkipPast::Reference))) {
+    if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
+      CmpValue = &Env.makeNot(*CmpValue);
+    Env.addToFlowCondition(evaluateEquality(Env, *CmpValue, *HasVal,
+                                            Env.getBoolLiteralValue(true)));
+  }
+}
+
 llvm::Optional<StatementMatcher>
 ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {
   if (Options.IgnoreSmartPointerDereference)
@@ -578,12 +655,24 @@ auto buildTransferMatchSwitch(
             assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true));
           })
       .CaseOfCFGStmt<CXXConstructExpr>(
-          isOptionalNulloptConstructor(),
+          isNulloptConstructor(),
           [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             assignOptionalValue(*E, State,
                                 State.Env.getBoolLiteralValue(false));
           })
+      .CaseOfCFGStmt<CXXConstructExpr>(
+          isOptionalNulloptConstructor(),
+          [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            // Shares a temporary with the underlying `nullopt_t` instance.
+            if (auto *OptionalLoc =
+                    State.Env.getStorageLocation(*E, SkipPast::None)) {
+              State.Env.setValue(
+                  *OptionalLoc,
+                  *State.Env.getValue(*E->getArg(0), SkipPast::None));
+            }
+          })
       .CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
                                        transferValueOrConversionConstructor)
 
@@ -652,6 +741,25 @@ auto buildTransferMatchSwitch(
       // opt.value_or(X) != X
       .CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)
 
+      // Comparisons (==, !=):
+      .CaseOfCFGStmt<CXXOperatorCallExpr>(
+          isComparisonOperatorCall(hasAnyOptionalType(), hasAnyOptionalType()),
+          transferOptionalAndOptionalCmp)
+      .CaseOfCFGStmt<CXXOperatorCallExpr>(
+          isComparisonOperatorCall(hasOptionalType(),
+                                   unless(hasAnyOptionalType())),
+          [](const clang::CXXOperatorCallExpr *Cmp,
+             const MatchFinder::MatchResult &, LatticeTransferState &State) {
+            transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);
+          })
+      .CaseOfCFGStmt<CXXOperatorCallExpr>(
+          isComparisonOperatorCall(unless(hasAnyOptionalType()),
+                                   hasOptionalType()),
+          [](const clang::CXXOperatorCallExpr *Cmp,
+             const MatchFinder::MatchResult &, LatticeTransferState &State) {
+            transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);
+          })
+
       // returns optional
       .CaseOfCFGStmt<CallExpr>(isCallReturningOptional(),
                                transferCallReturningOptional)

diff  --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index 845209b90004d..b1a1282edabb6 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -762,6 +762,29 @@ template <typename T, typename U, typename... Args>
 constexpr optional<T> make_optional(std::initializer_list<U> il,
                                     Args&&... args);
 
+template <typename T, typename U>
+constexpr bool operator==(const optional<T> &lhs, const optional<U> &rhs);
+template <typename T, typename U>
+constexpr bool operator!=(const optional<T> &lhs, const optional<U> &rhs);
+
+template <typename T>
+constexpr bool operator==(const optional<T> &opt, nullopt_t);
+template <typename T>
+constexpr bool operator==(nullopt_t, const optional<T> &opt);
+template <typename T>
+constexpr bool operator!=(const optional<T> &opt, nullopt_t);
+template <typename T>
+constexpr bool operator!=(nullopt_t, const optional<T> &opt);
+
+template <typename T, typename U>
+constexpr bool operator==(const optional<T> &opt, const U &value);
+template <typename T, typename U>
+constexpr bool operator==(const T &value, const optional<U> &opt);
+template <typename T, typename U>
+constexpr bool operator!=(const optional<T> &opt, const U &value);
+template <typename T, typename U>
+constexpr bool operator!=(const T &value, const optional<U> &opt);
+
 } // namespace std
 )";
 
@@ -984,6 +1007,29 @@ template <typename T, typename U, typename... Args>
 constexpr optional<T> make_optional(std::initializer_list<U> il,
                                     Args&&... args);
 
+template <typename T, typename U>
+constexpr bool operator==(const optional<T> &lhs, const optional<U> &rhs);
+template <typename T, typename U>
+constexpr bool operator!=(const optional<T> &lhs, const optional<U> &rhs);
+
+template <typename T>
+constexpr bool operator==(const optional<T> &opt, nullopt_t);
+template <typename T>
+constexpr bool operator==(nullopt_t, const optional<T> &opt);
+template <typename T>
+constexpr bool operator!=(const optional<T> &opt, nullopt_t);
+template <typename T>
+constexpr bool operator!=(nullopt_t, const optional<T> &opt);
+
+template <typename T, typename U>
+constexpr bool operator==(const optional<T> &opt, const U &value);
+template <typename T, typename U>
+constexpr bool operator==(const T &value, const optional<U> &opt);
+template <typename T, typename U>
+constexpr bool operator!=(const optional<T> &opt, const U &value);
+template <typename T, typename U>
+constexpr bool operator!=(const T &value, const optional<U> &opt);
+
 } // namespace absl
 )";
 
@@ -1177,6 +1223,29 @@ template <typename T, typename U, typename... Args>
 constexpr Optional<T> make_optional(std::initializer_list<U> il,
                                     Args&&... args);
 
+template <typename T, typename U>
+constexpr bool operator==(const Optional<T> &lhs, const Optional<U> &rhs);
+template <typename T, typename U>
+constexpr bool operator!=(const Optional<T> &lhs, const Optional<U> &rhs);
+
+template <typename T>
+constexpr bool operator==(const Optional<T> &opt, nullopt_t);
+template <typename T>
+constexpr bool operator==(nullopt_t, const Optional<T> &opt);
+template <typename T>
+constexpr bool operator!=(const Optional<T> &opt, nullopt_t);
+template <typename T>
+constexpr bool operator!=(nullopt_t, const Optional<T> &opt);
+
+template <typename T, typename U>
+constexpr bool operator==(const Optional<T> &opt, const U &value);
+template <typename T, typename U>
+constexpr bool operator==(const T &value, const Optional<U> &opt);
+template <typename T, typename U>
+constexpr bool operator!=(const Optional<T> &opt, const U &value);
+template <typename T, typename U>
+constexpr bool operator!=(const T &value, const Optional<U> &opt);
+
 } // namespace base
 )";
 
@@ -2117,6 +2186,325 @@ TEST_P(UncheckedOptionalAccessTest, CallReturningOptional) {
   )");
 }
 
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftSet) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = 3;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt1 == opt2) {
+        opt2.value();
+      } else {
+        opt2.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightSet) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = 3;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt2 == opt1) {
+        opt2.value();
+      } else {
+        opt2.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckVerifySetAfterEq) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = Make<$ns::$optional<int>>();
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt1 == opt2) {
+        if (opt1.has_value())
+          opt2.value();
+        if (opt2.has_value())
+          opt1.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftUnset) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = $ns::nullopt;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt1 == opt2) {
+        opt2.value(); // [[unsafe]]
+      } else {
+        opt2.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightUnset) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = $ns::nullopt;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt2 == opt1) {
+        opt2.value(); // [[unsafe]]
+      } else {
+        opt2.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightNullopt) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if (opt == $ns::nullopt) {
+        opt.value(); // [[unsafe]]
+      } else {
+        opt.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftNullopt) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if ($ns::nullopt == opt) {
+        opt.value(); // [[unsafe]]
+      } else {
+        opt.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightValue) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if (opt == 3) {
+        opt.value();
+      } else {
+        opt.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftValue) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if (3 == opt) {
+        opt.value();
+      } else {
+        opt.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftSet) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = 3;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt1 != opt2) {
+        opt2.value(); // [[unsafe]]
+      } else {
+        opt2.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightSet) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = 3;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt2 != opt1) {
+        opt2.value(); // [[unsafe]]
+      } else {
+        opt2.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckVerifySetAfterEq) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = Make<$ns::$optional<int>>();
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt1 != opt2) {
+        if (opt1.has_value())
+          opt2.value(); // [[unsafe]]
+        if (opt2.has_value())
+          opt1.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftUnset) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = $ns::nullopt;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt1 != opt2) {
+        opt2.value();
+      } else {
+        opt2.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightUnset) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt1 = $ns::nullopt;
+      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
+
+      if (opt2 != opt1) {
+        opt2.value();
+      } else {
+        opt2.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightNullopt) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if (opt != $ns::nullopt) {
+        opt.value();
+      } else {
+        opt.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftNullopt) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if ($ns::nullopt != opt) {
+        opt.value();
+      } else {
+        opt.value(); // [[unsafe]]
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightValue) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if (opt != 3) {
+        opt.value(); // [[unsafe]]
+      } else {
+        opt.value();
+      }
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftValue) {
+  ExpectDiagnosticsFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = Make<$ns::$optional<int>>();
+
+      if (3 != opt) {
+        opt.value(); // [[unsafe]]
+      } else {
+        opt.value();
+      }
+    }
+  )");
+}
+
 // Verifies that the model sees through aliases.
 TEST_P(UncheckedOptionalAccessTest, WithAlias) {
   ExpectDiagnosticsFor(


        


More information about the cfe-commits mailing list