diff --git a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h --- a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h +++ b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h @@ -62,6 +62,9 @@ const Value &Val2, const Environment &Env2, Value &MergedVal, Environment &MergedEnv) override; + Value *widen(QualType Type, Value &Prev, const Environment &PrevEnv, + Value &Current, Environment &CurrentEnv) override; + private: CFGMatchSwitch> TransferMatchSwitch; }; diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -27,6 +27,7 @@ #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" #include #include #include @@ -835,7 +836,14 @@ const Value &Val2, const Environment &Env2) { if (!isOptionalType(Type)) return ComparisonResult::Unknown; - return isNonEmptyOptional(Val1, Env1) == isNonEmptyOptional(Val2, Env2) + bool MustNonEmpty1 = isNonEmptyOptional(Val1, Env1); + bool MustNonEmpty2 = isNonEmptyOptional(Val2, Env2); + if (MustNonEmpty1 && MustNonEmpty2) return ComparisonResult::Same; + // If exactly one is true, then they're different, no reason to check whether + // they're definitely empty. + if (MustNonEmpty1 || MustNonEmpty2) return ComparisonResult::Different; + // Check if they're both definitely empty. + return (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2)) ? ComparisonResult::Same : ComparisonResult::Different; } @@ -848,16 +856,48 @@ Environment &MergedEnv) { if (!isOptionalType(Type)) return true; - + // FIXME: uses same approach as join for `BoolValues`. Requires non-const + // values, though, so will require updating the interface. auto &HasValueVal = MergedEnv.makeAtomicBoolValue(); - if (isNonEmptyOptional(Val1, Env1) && isNonEmptyOptional(Val2, Env2)) + bool MustNonEmpty1 = isNonEmptyOptional(Val1, Env1); + bool MustNonEmpty2 = isNonEmptyOptional(Val2, Env2); + if (MustNonEmpty1 && MustNonEmpty2) MergedEnv.addToFlowCondition(HasValueVal); - else if (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2)) + else if ( + // Only make the costly calls to `isEmptyOptional` if we got "unknown" + // (false) for both calls to `isNonEmptyOptional`. + !MustNonEmpty1 && !MustNonEmpty2 && isEmptyOptional(Val1, Env1) && + isEmptyOptional(Val2, Env2)) MergedEnv.addToFlowCondition(MergedEnv.makeNot(HasValueVal)); setHasValue(MergedVal, HasValueVal); return true; } +Value *UncheckedOptionalAccessModel::widen(QualType Type, Value &Prev, + const Environment &PrevEnv, + Value &Current, + Environment &CurrentEnv) { + switch (compare(Type, Prev, PrevEnv, Current, CurrentEnv)) { + case ComparisonResult::Same: + return &Prev; + case ComparisonResult::Different: + if (auto *PrevHasVal = + cast_or_null(Prev.getProperty("has_value"))) { + if (isa(PrevHasVal)) + return &Prev; + } + if (auto *CurrentHasVal = + cast_or_null(Current.getProperty("has_value"))) { + if (isa(CurrentHasVal)) + return &Current; + } + return &createOptionalValue(CurrentEnv, CurrentEnv.makeTopBoolValue()); + case ComparisonResult::Unknown: + return nullptr; + } + llvm_unreachable("all cases covered in switch"); +} + UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser( UncheckedOptionalAccessModelOptions Options) : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {} diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -2748,19 +2748,6 @@ } } )"); - - // FIXME: Add support for operator==. - // ExpectDiagnosticsFor(R"( - // #include "unchecked_optional_access_test.h" - // - // void target($ns::$optional opt1, $ns::$optional opt2) { - // if (opt1 == opt2) { - // if (opt1.has_value()) { - // opt2.value(); - // } - // } - // } - // )"); } TEST_P(UncheckedOptionalAccessTest, JoinDistinctValues) { @@ -2846,7 +2833,7 @@ )code"); } -TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoop) { +TEST_P(UncheckedOptionalAccessTest, AccessValueInLoop) { ExpectDiagnosticsFor(R"( #include "unchecked_optional_access_test.h" @@ -2857,7 +2844,9 @@ } } )"); +} +TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopWithCheckSafe) { ExpectDiagnosticsFor(R"( #include "unchecked_optional_access_test.h" @@ -2871,7 +2860,9 @@ } } )"); +} +TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopNoCheckUnsafe) { ExpectDiagnosticsFor( R"( #include "unchecked_optional_access_test.h" @@ -2885,7 +2876,60 @@ } } )"); +} + +TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnsetUnsafe) { + ExpectDiagnosticsFor( + R"( + #include "unchecked_optional_access_test.h" + + void target() { + $ns::$optional opt = 3; + while (Make()) + opt = $ns::nullopt; + $ns::$optional opt2 = $ns::nullopt; + if (opt.has_value()) + opt2 = $ns::$optional(3); + opt2.value(); // [[unsafe]] + } + )"); +} + +TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToSetUnsafe) { + ExpectDiagnosticsFor( + R"( + #include "unchecked_optional_access_test.h" + + void target() { + $ns::$optional opt = $ns::nullopt; + while (Make()) + opt = $ns::$optional(3); + $ns::$optional opt2 = $ns::nullopt; + if (!opt.has_value()) + opt2 = $ns::$optional(3); + opt2.value(); // [[unsafe]] + } + )"); +} + +TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnknownUnsafe) { + ExpectDiagnosticsFor( + R"( + #include "unchecked_optional_access_test.h" + + void target() { + $ns::$optional opt = $ns::nullopt; + while (Make()) + opt = Make<$ns::$optional>(); + $ns::$optional opt2 = $ns::nullopt; + if (!opt.has_value()) + opt2 = $ns::$optional(3); + opt2.value(); // [[unsafe]] + } + )"); +} +TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopBadConditionUnsafe) { ExpectDiagnosticsFor( R"( #include "unchecked_optional_access_test.h"