[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